From 673689ee3d4022e21a5be5e08c31fcc30bdca1ec Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Fri, 13 Jun 2025 15:53:06 -0700 Subject: [PATCH 001/141] [JExtract] Introduce SwiftKnownTypes to construct stdlib types easily. Rename `SwiftStandardLibraryTypes` to `SwiftStandardLibraryTypeDecls` Rename `KnownStandardLibraryType` to `SwiftStandardLibraryTypeKind` --- .../FFM/CDeclLowering/CRepresentation.swift | 2 +- ...Swift2JavaGenerator+FunctionLowering.swift | 63 +++++++--------- ...MSwift2JavaGenerator+JavaTranslation.swift | 2 +- .../FFM/FFMSwift2JavaGenerator.swift | 4 +- .../Swift2JavaTranslator.swift | 4 +- .../SwiftTypes/SwiftKnownTypes.swift | 73 +++++++++++++++++++ .../SwiftNominalTypeDeclaration.swift | 6 +- ...ft => SwiftStandardLibraryTypeDecls.swift} | 30 ++++---- .../Asserts/LoweringAssertions.swift | 4 +- 9 files changed, 124 insertions(+), 64 deletions(-) create mode 100644 Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift rename Sources/JExtractSwiftLib/SwiftTypes/{SwiftStandardLibraryTypes.swift => SwiftStandardLibraryTypeDecls.swift} (85%) diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift index e1a69d12c..c52bf7db0 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift @@ -99,7 +99,7 @@ enum CDeclToCLoweringError: Error { case invalidFunctionConvention(SwiftFunctionType) } -extension KnownStandardLibraryType { +extension SwiftStandardLibraryTypeKind { /// Determine the primitive C type that corresponds to this C standard /// library type, if there is one. var primitiveCType: CType? { diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index 02501dc53..061dc9973 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -75,7 +75,11 @@ extension FFMSwift2JavaGenerator { /// Responsible for lowering Swift API to C API. struct CdeclLowering { - var swiftStdlibTypes: SwiftStandardLibraryTypes + var knownTypes: SwiftKnownTypes + + init(swiftStdlibTypes: SwiftStandardLibraryTypeDecls) { + self.knownTypes = SwiftKnownTypes(decls: swiftStdlibTypes) + } /// Lower the given Swift function signature to a Swift @_cdecl function signature, /// which is C compatible, and the corresponding Java method signature. @@ -149,11 +153,7 @@ struct CdeclLowering { SwiftParameter( convention: .byValue, parameterName: parameterName, - type: .nominal( - SwiftNominalType( - nominalTypeDecl: swiftStdlibTypes[.unsafeRawPointer] - ) - ) + type: knownTypes.unsafeRawPointer ) ], conversion: .unsafeCastPointer(.placeholder, swiftType: instanceType) @@ -173,10 +173,14 @@ struct CdeclLowering { // Typed pointers are mapped down to their raw forms in cdecl entry // points. These can be passed through directly. let isMutable = knownType == .unsafeMutablePointer - let rawPointerNominal = swiftStdlibTypes[isMutable ? .unsafeMutableRawPointer : .unsafeRawPointer] - let paramType: SwiftType = .nominal(SwiftNominalType(nominalTypeDecl: rawPointerNominal)) return LoweredParameter( - cdeclParameters: [SwiftParameter(convention: .byValue, parameterName: parameterName, type: paramType)], + cdeclParameters: [ + SwiftParameter( + convention: .byValue, + parameterName: parameterName, + type: isMutable ? knownTypes.unsafeMutableRawPointer : knownTypes.unsafeRawPointer + ) + ], conversion: .typedPointer(.placeholder, swiftType: genericArgs[0]) ) @@ -186,17 +190,15 @@ struct CdeclLowering { } // Typed pointers are lowered to (raw-pointer, count) pair. let isMutable = knownType == .unsafeMutableBufferPointer - let rawPointerNominal = swiftStdlibTypes[isMutable ? .unsafeMutableRawPointer : .unsafeRawPointer] - return LoweredParameter( cdeclParameters: [ SwiftParameter( convention: .byValue, parameterName: "\(parameterName)_pointer", - type: .nominal(SwiftNominalType(nominalTypeDecl: rawPointerNominal)) + type: isMutable ? knownTypes.unsafeMutableRawPointer : knownTypes.unsafeRawPointer ), SwiftParameter( convention: .byValue, parameterName: "\(parameterName)_count", - type: .nominal(SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.int])) + type: knownTypes.int ), ], conversion: .initialize( type, @@ -221,12 +223,7 @@ struct CdeclLowering { SwiftParameter( convention: .byValue, parameterName: parameterName, - type: .nominal(SwiftNominalType( - nominalTypeDecl: swiftStdlibTypes.unsafePointerDecl, - genericArguments: [ - .nominal(SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.int8])) - ] - )) + type: knownTypes.unsafePointer(knownTypes.int8) ) ], conversion: .initialize(type, arguments: [ @@ -243,15 +240,12 @@ struct CdeclLowering { // Arbitrary nominal types are passed-in as an pointer. let isMutable = (convention == .inout) - let rawPointerNominal = swiftStdlibTypes[isMutable ? .unsafeMutableRawPointer : .unsafeRawPointer] - let parameterType: SwiftType = .nominal(SwiftNominalType(nominalTypeDecl: rawPointerNominal)) - return LoweredParameter( cdeclParameters: [ SwiftParameter( convention: .byValue, parameterName: parameterName, - type: parameterType + type: isMutable ? knownTypes.unsafeMutableRawPointer : knownTypes.unsafeRawPointer ), ], conversion: .pointee(.typedPointer(.placeholder, swiftType: type)) @@ -352,12 +346,10 @@ struct CdeclLowering { switch type { case .metatype: // 'unsafeBitcast(, to: UnsafeRawPointer.self)' as 'UnsafeRawPointer' - - let resultType: SwiftType = .nominal(SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.unsafeRawPointer])) return LoweredResult( - cdeclResultType: resultType, + cdeclResultType: knownTypes.unsafeRawPointer, cdeclOutParameters: [], - conversion: .unsafeCastPointer(.placeholder, swiftType: resultType) + conversion: .unsafeCastPointer(.placeholder, swiftType: knownTypes.unsafeRawPointer) ) case .nominal(let nominal): @@ -367,8 +359,7 @@ struct CdeclLowering { case .unsafePointer, .unsafeMutablePointer: // Typed pointers are lowered to corresponding raw forms. let isMutable = knownType == .unsafeMutablePointer - let rawPointerNominal = swiftStdlibTypes[isMutable ? .unsafeMutableRawPointer : .unsafeRawPointer] - let resultType: SwiftType = .nominal(SwiftNominalType(nominalTypeDecl: rawPointerNominal)) + let resultType: SwiftType = isMutable ? knownTypes.unsafeMutableRawPointer : knownTypes.unsafeRawPointer return LoweredResult( cdeclResultType: resultType, cdeclOutParameters: [], @@ -378,11 +369,10 @@ struct CdeclLowering { case .unsafeBufferPointer, .unsafeMutableBufferPointer: // Typed pointers are lowered to (raw-pointer, count) pair. let isMutable = knownType == .unsafeMutableBufferPointer - let rawPointerType = swiftStdlibTypes[isMutable ? .unsafeMutableRawPointer : .unsafeRawPointer] return try lowerResult( .tuple([ - .nominal(SwiftNominalType(nominalTypeDecl: rawPointerType)), - .nominal(SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.int])) + isMutable ? knownTypes.unsafeMutableRawPointer : knownTypes.unsafeRawPointer, + knownTypes.int ]), outParameterName: outParameterName ) @@ -407,7 +397,7 @@ struct CdeclLowering { SwiftParameter( convention: .byValue, parameterName: outParameterName, - type: .nominal(SwiftNominalType(nominalTypeDecl: swiftStdlibTypes[.unsafeMutableRawPointer])) + type: knownTypes.unsafeMutableRawPointer ) ], conversion: .populatePointer(name: outParameterName, assumingType: type, to: .placeholder) @@ -431,10 +421,7 @@ struct CdeclLowering { let parameter = SwiftParameter( convention: .byValue, parameterName: parameterName, - type: .nominal(SwiftNominalType( - nominalTypeDecl: swiftStdlibTypes.unsafeMutablePointerDecl, - genericArguments: [lowered.cdeclResultType] - )) + type: knownTypes.unsafeMutablePointer(lowered.cdeclResultType) ) parameters.append(parameter) conversions.append(.populatePointer( @@ -551,7 +538,7 @@ extension LoweredFunctionSignature { cName: String, swiftAPIName: String, as apiKind: SwiftAPIKind, - stdlibTypes: SwiftStandardLibraryTypes + stdlibTypes: SwiftStandardLibraryTypeDecls ) -> FunctionDeclSyntax { let cdeclParams = allLoweredParameters.map(\.description).joined(separator: ", ") diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index c7e2338de..476160303 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -144,7 +144,7 @@ extension TranslatedFunctionSignature { } struct JavaTranslation { - var swiftStdlibTypes: SwiftStandardLibraryTypes + var swiftStdlibTypes: SwiftStandardLibraryTypeDecls func translate( _ decl: ImportedFunc diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index 7cd0075b6..036ee31e7 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -23,7 +23,7 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { let javaPackage: String let swiftOutputDirectory: String let javaOutputDirectory: String - let swiftStdlibTypes: SwiftStandardLibraryTypes + let swiftStdlibTypes: SwiftStandardLibraryTypeDecls let symbolTable: SwiftSymbolTable var javaPackagePath: String { @@ -49,7 +49,7 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { self.swiftOutputDirectory = swiftOutputDirectory self.javaOutputDirectory = javaOutputDirectory self.symbolTable = translator.symbolTable - self.swiftStdlibTypes = translator.swiftStdlibTypes + self.swiftStdlibTypes = translator.swiftStdlibTypeDecls } func generate() throws { diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index ace5f4444..857a053a6 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -43,7 +43,7 @@ public final class Swift2JavaTranslator { /// type representation. package var importedTypes: [String: ImportedNominalType] = [:] - package var swiftStdlibTypes: SwiftStandardLibraryTypes + package var swiftStdlibTypeDecls: SwiftStandardLibraryTypeDecls package let symbolTable: SwiftSymbolTable @@ -59,7 +59,7 @@ public final class Swift2JavaTranslator { // Create a mock of the Swift standard library. var parsedSwiftModule = SwiftParsedModuleSymbolTable(moduleName: "Swift") - self.swiftStdlibTypes = SwiftStandardLibraryTypes(into: &parsedSwiftModule) + self.swiftStdlibTypeDecls = SwiftStandardLibraryTypeDecls(into: &parsedSwiftModule) self.symbolTable.importedModules.append(parsedSwiftModule.symbolTable) } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift new file mode 100644 index 000000000..eaae18cd4 --- /dev/null +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift @@ -0,0 +1,73 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +struct SwiftKnownTypes { + private let decls: SwiftStandardLibraryTypeDecls + + init(decls: SwiftStandardLibraryTypeDecls) { + self.decls = decls + } + + var bool: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.bool])) } + var int: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.int])) } + var uint: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.uint])) } + var int8: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.int8])) } + var uint8: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.uint8])) } + var int16: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.int16])) } + var uint16: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.uint16])) } + var int32: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.int32])) } + var uint32: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.uint32])) } + var int64: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.int64])) } + var uint64: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.uint64])) } + var float: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.float])) } + var double: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.double])) } + var unsafeRawPointer: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.unsafeRawPointer])) } + var unsafeMutableRawPointer: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: decls[.unsafeMutableRawPointer])) } + + func unsafePointer(_ pointeeType: SwiftType) -> SwiftType { + .nominal( + SwiftNominalType( + nominalTypeDecl: decls.unsafePointerDecl, + genericArguments: [pointeeType] + ) + ) + } + + func unsafeMutablePointer(_ pointeeType: SwiftType) -> SwiftType { + .nominal( + SwiftNominalType( + nominalTypeDecl: decls.unsafeMutablePointerDecl, + genericArguments: [pointeeType] + ) + ) + } + + func unsafeBufferPointer(_ elementType: SwiftType) -> SwiftType { + .nominal( + SwiftNominalType( + nominalTypeDecl: decls.unsafeBufferPointerDecl, + genericArguments: [elementType] + ) + ) + } + + func unsafeMutableBufferPointer(_ elementType: SwiftType) -> SwiftType { + .nominal( + SwiftNominalType( + nominalTypeDecl: decls.unsafeMutableBufferPointerDecl, + genericArguments: [elementType] + ) + ) + } +} diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift index c83301266..29a287fec 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -52,7 +52,7 @@ package class SwiftNominalTypeDeclaration { /// Identify this nominal declaration as one of the known standard library /// types, like 'Swift.Int[. - lazy var knownStandardLibraryType: KnownStandardLibraryType? = { + lazy var knownStandardLibraryType: SwiftStandardLibraryTypeKind? = { self.computeKnownStandardLibraryType() }() @@ -80,12 +80,12 @@ package class SwiftNominalTypeDeclaration { /// Determine the known standard library type for this nominal type /// declaration. - private func computeKnownStandardLibraryType() -> KnownStandardLibraryType? { + private func computeKnownStandardLibraryType() -> SwiftStandardLibraryTypeKind? { if parent != nil || moduleName != "Swift" { return nil } - return KnownStandardLibraryType(typeNameInSwiftModule: name) + return SwiftStandardLibraryTypeKind(typeNameInSwiftModule: name) } package var qualifiedName: String { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypes.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypeDecls.swift similarity index 85% rename from Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypes.swift rename to Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypeDecls.swift index 4b07c5751..51e1adcf7 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypes.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypeDecls.swift @@ -14,7 +14,7 @@ import SwiftSyntax -enum KnownStandardLibraryType: String, Hashable, CaseIterable { +enum SwiftStandardLibraryTypeKind: String, Hashable, CaseIterable { case bool = "Bool" case int = "Int" case uint = "UInt" @@ -60,7 +60,7 @@ enum KnownStandardLibraryType: String, Hashable, CaseIterable { /// Captures many types from the Swift standard library in their most basic /// forms, so that the translator can reason about them in source code. -public struct SwiftStandardLibraryTypes { +public struct SwiftStandardLibraryTypeDecls { // Swift.UnsafePointer let unsafePointerDecl: SwiftNominalTypeDeclaration @@ -74,16 +74,16 @@ public struct SwiftStandardLibraryTypes { let unsafeMutableBufferPointerDecl: SwiftNominalTypeDeclaration /// Mapping from known standard library types to their nominal type declaration. - let knownTypeToNominal: [KnownStandardLibraryType: SwiftNominalTypeDeclaration] + let knownTypeToNominal: [SwiftStandardLibraryTypeKind: SwiftNominalTypeDeclaration] /// Mapping from nominal type declarations to known types. - let nominalTypeDeclToKnownType: [SwiftNominalTypeDeclaration: KnownStandardLibraryType] + let nominalTypeDeclToKnownType: [SwiftNominalTypeDeclaration: SwiftStandardLibraryTypeKind] private static func recordKnownType( - _ type: KnownStandardLibraryType, + _ type: SwiftStandardLibraryTypeKind, _ syntax: NominalTypeDeclSyntaxNode, - knownTypeToNominal: inout [KnownStandardLibraryType: SwiftNominalTypeDeclaration], - nominalTypeDeclToKnownType: inout [SwiftNominalTypeDeclaration: KnownStandardLibraryType], + knownTypeToNominal: inout [SwiftStandardLibraryTypeKind: SwiftNominalTypeDeclaration], + nominalTypeDeclToKnownType: inout [SwiftNominalTypeDeclaration: SwiftStandardLibraryTypeKind], parsedModule: inout SwiftParsedModuleSymbolTable ) { let nominalDecl = parsedModule.addNominalTypeDeclaration(syntax, parent: nil) @@ -92,9 +92,9 @@ public struct SwiftStandardLibraryTypes { } private static func recordKnownNonGenericStruct( - _ type: KnownStandardLibraryType, - knownTypeToNominal: inout [KnownStandardLibraryType: SwiftNominalTypeDeclaration], - nominalTypeDeclToKnownType: inout [SwiftNominalTypeDeclaration: KnownStandardLibraryType], + _ type: SwiftStandardLibraryTypeKind, + knownTypeToNominal: inout [SwiftStandardLibraryTypeKind: SwiftNominalTypeDeclaration], + nominalTypeDeclToKnownType: inout [SwiftNominalTypeDeclaration: SwiftStandardLibraryTypeKind], parsedModule: inout SwiftParsedModuleSymbolTable ) { recordKnownType( @@ -155,11 +155,11 @@ public struct SwiftStandardLibraryTypes { parent: nil ) - var knownTypeToNominal: [KnownStandardLibraryType: SwiftNominalTypeDeclaration] = [:] - var nominalTypeDeclToKnownType: [SwiftNominalTypeDeclaration: KnownStandardLibraryType] = [:] + var knownTypeToNominal: [SwiftStandardLibraryTypeKind: SwiftNominalTypeDeclaration] = [:] + var nominalTypeDeclToKnownType: [SwiftNominalTypeDeclaration: SwiftStandardLibraryTypeKind] = [:] // Handle all of the non-generic types at once. - for knownType in KnownStandardLibraryType.allCases { + for knownType in SwiftStandardLibraryTypeKind.allCases { guard !knownType.isGeneric else { continue } @@ -176,11 +176,11 @@ public struct SwiftStandardLibraryTypes { self.nominalTypeDeclToKnownType = nominalTypeDeclToKnownType } - subscript(knownType: KnownStandardLibraryType) -> SwiftNominalTypeDeclaration { + subscript(knownType: SwiftStandardLibraryTypeKind) -> SwiftNominalTypeDeclaration { knownTypeToNominal[knownType]! } - subscript(nominalType: SwiftNominalTypeDeclaration) -> KnownStandardLibraryType? { + subscript(nominalType: SwiftNominalTypeDeclaration) -> SwiftStandardLibraryTypeKind? { nominalTypeDeclToKnownType[nominalType] } } diff --git a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift index 5dd28cc8c..f017bc494 100644 --- a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift @@ -73,7 +73,7 @@ func assertLoweredFunction( cName: "c_\(swiftFunctionName)", swiftAPIName: swiftFunctionName, as: apiKind, - stdlibTypes: translator.swiftStdlibTypes + stdlibTypes: translator.swiftStdlibTypeDecls ) #expect( @@ -141,7 +141,7 @@ func assertLoweredVariableAccessor( cName: "c_\(swiftVariableName)", swiftAPIName: swiftVariableName, as: isSet ? .setter : .getter, - stdlibTypes: translator.swiftStdlibTypes + stdlibTypes: translator.swiftStdlibTypeDecls ) #expect( From 660acb7c145b850036d9d7181b4e81eed156eb7f Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sun, 15 Jun 2025 16:10:12 +0200 Subject: [PATCH 002/141] [jextract] Generate code for free functions with primitive types in JNI mode (#269) * generate java code for primitive types and global functions * output swift thunk for JNI * add tests * remove unsigned integer support for now --- Sources/JExtractSwiftLib/ImportedDecls.swift | 1 - .../JNI/JNISwift2JavaGenerator.swift | 253 ++++++++++++++++++ Sources/JExtractSwiftLib/Swift2Java.swift | 10 + .../GenerationMode.swift | 3 + Sources/JavaTypes/JavaType+JNI.swift | 2 +- .../Asserts/TextAssertions.swift | 48 +++- .../ClassPrintingTests.swift | 6 +- .../JNI/JNIModuleTests.swift | 110 ++++++++ .../JExtractSwiftTests/MethodThunkTests.swift | 8 +- .../StringPassingTests.swift | 8 +- .../VariableImportTests.swift | 8 +- 11 files changed, 419 insertions(+), 38 deletions(-) create mode 100644 Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift create mode 100644 Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index e98d29b30..3619890bd 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -60,7 +60,6 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible { self.swiftDecl.signatureString } - var parentType: SwiftType? { guard let selfParameter = functionSignature.selfParameter else { return nil diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift new file mode 100644 index 000000000..56d75218e --- /dev/null +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -0,0 +1,253 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JavaTypes + +package class JNISwift2JavaGenerator: Swift2JavaGenerator { + let analysis: AnalysisResult + let swiftModuleName: String + let javaPackage: String + let logger: Logger + let swiftOutputDirectory: String + let javaOutputDirectory: String + + var javaPackagePath: String { + javaPackage.replacingOccurrences(of: ".", with: "/") + } + + var thunkNameRegistry = ThunkNameRegistry() + + package init( + translator: Swift2JavaTranslator, + javaPackage: String, + swiftOutputDirectory: String, + javaOutputDirectory: String + ) { + self.logger = Logger(label: "jni-generator", logLevel: translator.log.logLevel) + self.analysis = translator.result + self.swiftModuleName = translator.swiftModuleName + self.javaPackage = javaPackage + self.swiftOutputDirectory = swiftOutputDirectory + self.javaOutputDirectory = javaOutputDirectory + } + + func generate() throws { + try writeSwiftThunkSources() + try writeExportedJavaSources() + } +} + +extension JNISwift2JavaGenerator { + func writeExportedJavaSources() throws { + var printer = CodePrinter() + try writeExportedJavaSources(&printer) + } + + package func writeExportedJavaSources(_ printer: inout CodePrinter) throws { + let filename = "\(self.swiftModuleName).java" + logger.trace("Printing module class: \(filename)") + printModule(&printer) + + if let outputFile = try printer.writeContents( + outputDirectory: javaOutputDirectory, + javaPackagePath: javaPackagePath, + filename: filename + ) { + logger.info("[swift-java] Generated: \(self.swiftModuleName).java (at \(outputFile))") + } + } + + func writeSwiftThunkSources() throws { + var printer = CodePrinter() + try writeSwiftThunkSources(&printer) + } + + package func writeSwiftThunkSources(_ printer: inout CodePrinter) throws { + let moduleFilenameBase = "\(self.swiftModuleName)Module+SwiftJava" + let moduleFilename = "\(moduleFilenameBase).swift" + + do { + logger.trace("Printing swift module class: \(moduleFilename)") + + try printGlobalSwiftThunkSources(&printer) + + if let outputFile = try printer.writeContents( + outputDirectory: self.swiftOutputDirectory, + javaPackagePath: javaPackagePath, + filename: moduleFilename + ) { + print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile)") + } + } catch { + logger.warning("Failed to write to Swift thunks: \(moduleFilename)") + } + } +} + +extension JNISwift2JavaGenerator { + private func printGlobalSwiftThunkSources(_ printer: inout CodePrinter) throws { + printer.print( + """ + // Generated by swift-java + + import JavaKit + + """) + + for decl in analysis.importedGlobalFuncs { + printSwiftFunctionThunk(&printer, decl) + printer.println() + } + } + + private func printSwiftFunctionThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + // TODO: Replace swiftModuleName with class name if non-global + let cName = "Java_" + self.javaPackage.replacingOccurrences(of: ".", with: "_") + "_\(swiftModuleName)_" + decl.name + let thunkName = thunkNameRegistry.functionThunkName(decl: decl) + let translatedParameters = decl.functionSignature.parameters.enumerated().map { idx, param in + (param.parameterName ?? "arg\(idx)", param.type.javaType) + } + + let thunkParameters = [ + "environment: UnsafeMutablePointer!", + "thisClass: jclass" + ] + translatedParameters.map { "\($0.0): \($0.1.jniTypeName)"} + let swiftReturnType = decl.functionSignature.result.type + + let thunkReturnType = !swiftReturnType.isVoid ? " -> \(swiftReturnType.javaType.jniTypeName)" : "" + + printer.printBraceBlock( + """ + @_cdecl("\(cName)") + func \(thunkName)(\(thunkParameters.joined(separator: ", ")))\(thunkReturnType) + """ + ) { printer in + let downcallParameters = zip(decl.functionSignature.parameters, translatedParameters).map { originalParam, translatedParam in + let label = originalParam.argumentLabel ?? originalParam.parameterName ?? "" + return "\(label)\(!label.isEmpty ? ": " : "")\(originalParam.type)(fromJNI: \(translatedParam.0), in: environment!)" + } + let functionDowncall = "\(swiftModuleName).\(decl.name)(\(downcallParameters.joined(separator: ", ")))" + + if swiftReturnType.isVoid { + printer.print(functionDowncall) + } else { + printer.print( + """ + let result = \(functionDowncall) + return result.getJNIValue(in: environment)") + """ + ) + } + } + } +} + +extension JNISwift2JavaGenerator { + private func printModule(_ printer: inout CodePrinter) { + printHeader(&printer) + printPackage(&printer) + + printModuleClass(&printer) { printer in + for decl in analysis.importedGlobalFuncs { + self.logger.trace("Print global function: \(decl)") + printFunctionBinding(&printer, decl) + printer.println() + } + } + } + + private func printHeader(_ printer: inout CodePrinter) { + printer.print( + """ + // Generated by jextract-swift + // Swift module: \(swiftModuleName) + + """ + ) + } + + private func printPackage(_ printer: inout CodePrinter) { + printer.print( + """ + package \(javaPackage); + + """ + ) + } + + private func printModuleClass(_ printer: inout CodePrinter, body: (inout CodePrinter) -> Void) { + printer.printBraceBlock("public final class \(swiftModuleName)") { printer in + body(&printer) + } + } + + private func printFunctionBinding(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + let returnType = decl.functionSignature.result.type.javaType + let params = decl.functionSignature.parameters.enumerated().map { idx, param in + "\(param.type.javaType) \(param.parameterName ?? "arg\(idx))")" + } + + printer.print( + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * \(decl.signatureString) + * } + */ + """ + ) + printer.print("public static native \(returnType) \(decl.name)(\(params.joined(separator: ", ")));") + } +} + +extension SwiftType { + var javaType: JavaType { + switch self { + case .nominal(let nominalType): + if let knownType = nominalType.nominalTypeDecl.knownStandardLibraryType { + guard let javaType = knownType.javaType else { + fatalError("unsupported known type: \(knownType)") + } + return javaType + } + + fatalError("unsupported nominal type: \(nominalType)") + + case .tuple([]): + return .void + + case .metatype, .optional, .tuple, .function: + fatalError("unsupported type: \(self)") + } + } +} + +extension SwiftStandardLibraryTypeKind { + var javaType: JavaType? { + switch self { + case .bool: .boolean + case .int: .long // TODO: Handle 32-bit or 64-bit + case .int8: .byte + case .uint16: .char + case .int16: .short + case .int32: .int + case .int64: .long + case .float: .float + case .double: .double + case .void: .void + case .uint, .uint8, .uint32, .uint64, .unsafeRawPointer, .unsafeMutableRawPointer, .unsafePointer, .unsafeMutablePointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string: nil + } + } +} diff --git a/Sources/JExtractSwiftLib/Swift2Java.swift b/Sources/JExtractSwiftLib/Swift2Java.swift index 8adb76701..9b1b06bcd 100644 --- a/Sources/JExtractSwiftLib/Swift2Java.swift +++ b/Sources/JExtractSwiftLib/Swift2Java.swift @@ -97,6 +97,16 @@ public struct SwiftToJava { javaOutputDirectory: outputJavaDirectory ) + try generator.generate() + + case .jni: + let generator = JNISwift2JavaGenerator( + translator: translator, + javaPackage: config.javaPackage ?? "", + swiftOutputDirectory: outputSwiftDirectory, + javaOutputDirectory: outputJavaDirectory + ) + try generator.generate() } diff --git a/Sources/JavaKitConfigurationShared/GenerationMode.swift b/Sources/JavaKitConfigurationShared/GenerationMode.swift index ff1d081da..ea30a4364 100644 --- a/Sources/JavaKitConfigurationShared/GenerationMode.swift +++ b/Sources/JavaKitConfigurationShared/GenerationMode.swift @@ -15,4 +15,7 @@ public enum GenerationMode: String, Codable { /// Foreign Value and Memory API case ffm + + /// Java Native Interface + case jni } diff --git a/Sources/JavaTypes/JavaType+JNI.swift b/Sources/JavaTypes/JavaType+JNI.swift index 41a93d255..08361bac7 100644 --- a/Sources/JavaTypes/JavaType+JNI.swift +++ b/Sources/JavaTypes/JavaType+JNI.swift @@ -14,7 +14,7 @@ extension JavaType { /// Map this Java type to the appropriate JNI type name. - var jniTypeName: String { + package var jniTypeName: String { switch self { case .boolean: "jboolean" case .float: "jfloat" diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index 3ea03d981..b3a09a22b 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -14,6 +14,7 @@ import JExtractSwiftLib import Testing +import JavaKitConfigurationShared import struct Foundation.CharacterSet enum RenderKind { @@ -23,9 +24,10 @@ enum RenderKind { func assertOutput( dump: Bool = false, - _ translator: Swift2JavaTranslator, input: String, + _ mode: GenerationMode, _ renderKind: RenderKind, + swiftModuleName: String = "SwiftModule", detectChunkByInitialLines: Int = 4, expectedChunks: [String], fileID: String = #fileID, @@ -33,22 +35,42 @@ func assertOutput( line: Int = #line, column: Int = #column ) throws { - try! translator.analyze(file: "/fake/Fake.swiftinterface", text: input) + let translator = Swift2JavaTranslator(swiftModuleName: swiftModuleName) - let generator = FFMSwift2JavaGenerator( - translator: translator, - javaPackage: "com.example.swift", - swiftOutputDirectory: "/fake", - javaOutputDirectory: "/fake" - ) + try! translator.analyze(file: "/fake/Fake.swiftinterface", text: input) let output: String var printer: CodePrinter = CodePrinter(mode: .accumulateAll) - switch renderKind { - case .swift: - try generator.writeSwiftThunkSources(printer: &printer) - case .java: - try generator.writeExportedJavaSources(printer: &printer) + switch mode { + case .ffm: + let generator = FFMSwift2JavaGenerator( + translator: translator, + javaPackage: "com.example.swift", + swiftOutputDirectory: "/fake", + javaOutputDirectory: "/fake" + ) + + switch renderKind { + case .swift: + try generator.writeSwiftThunkSources(printer: &printer) + case .java: + try generator.writeExportedJavaSources(printer: &printer) + } + + case .jni: + let generator = JNISwift2JavaGenerator( + translator: translator, + javaPackage: "com.example.swift", + swiftOutputDirectory: "/fake", + javaOutputDirectory: "/fake" + ) + + switch renderKind { + case .swift: + try generator.writeSwiftThunkSources(&printer) + case .java: + try generator.writeExportedJavaSources(&printer) + } } output = printer.finalize() diff --git a/Tests/JExtractSwiftTests/ClassPrintingTests.swift b/Tests/JExtractSwiftTests/ClassPrintingTests.swift index f0c594562..6c0d8d0a1 100644 --- a/Tests/JExtractSwiftTests/ClassPrintingTests.swift +++ b/Tests/JExtractSwiftTests/ClassPrintingTests.swift @@ -41,11 +41,7 @@ struct ClassPrintingTests { @Test("Import: class layout") func class_layout() throws { - let st = Swift2JavaTranslator( - swiftModuleName: "__FakeModule" - ) - - try assertOutput(st, input: class_interfaceFile, .java, expectedChunks: [ + try assertOutput(input: class_interfaceFile, .ffm, .java, swiftModuleName: "__FakeModule", expectedChunks: [ """ public static final SwiftAnyType TYPE_METADATA = new SwiftAnyType(SwiftKit.swiftjava.getType("__FakeModule", "MySwiftClass")); diff --git a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift new file mode 100644 index 000000000..c5df98a13 --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift @@ -0,0 +1,110 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import Testing + +@Suite +struct JNIModuleTests { + let globalMethodWithPrimitives = """ + public func helloWorld() + public func takeIntegers(i1: Int8, i2: Int16, i3: Int32, i4: Int) -> UInt16 + public func otherPrimitives(b: Bool, f: Float, d: Double) + """ + + @Test + func generatesModuleJavaClass() throws { + let input = "public func helloWorld()" + + try assertOutput(input: input, .jni, .java, expectedChunks: [ + """ + // Generated by jextract-swift + // Swift module: SwiftModule + + package com.example.swift; + + public final class SwiftModule { + """ + ]) + } + + @Test + func globalMethodWithPrimitives_javaBindings() throws { + try assertOutput( + input: globalMethodWithPrimitives, + .jni, + .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func helloWorld() + * } + */ + public static native void helloWorld(); + """, + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func takeIntegers(i1: Int8, i2: Int16, i3: Int32, i4: Int) -> UInt16 + * } + */ + public static native char takeIntegers(byte i1, short i2, int i3, long i4); + """, + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func otherPrimitives(b: Bool, f: Float, d: Double) + * } + */ + public static native void otherPrimitives(boolean b, float f, double d); + """ + ] + ) + } + + @Test + func globalMethodWithPrimitives_swiftThunks() throws { + try assertOutput( + input: globalMethodWithPrimitives, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule_helloWorld") + func swiftjava_SwiftModule_helloWorld(environment: UnsafeMutablePointer!, thisClass: jclass) { + SwiftModule.helloWorld() + } + """, + """ + @_cdecl("Java_com_example_swift_SwiftModule_takeIntegers") + func swiftjava_SwiftModule_takeIntegers_i1_i2_i3_i4(environment: UnsafeMutablePointer!, thisClass: jclass, i1: jbyte, i2: jshort, i3: jint, i4: jlong) -> jchar { + let result = SwiftModule.takeIntegers(i1: Int8(fromJNI: i1, in: environment!), i2: Int16(fromJNI: i2, in: environment!), i3: Int32(fromJNI: i3, in: environment!), i4: Int(fromJNI: i4, in: environment!)) + return result.getJNIValue(in: environment)") + } + """, + """ + @_cdecl("Java_com_example_swift_SwiftModule_otherPrimitives") + func swiftjava_SwiftModule_otherPrimitives_b_f_d(environment: UnsafeMutablePointer!, thisClass: jclass, b: jboolean, f: jfloat, d: jdouble) { + SwiftModule.otherPrimitives(b: Bool(fromJNI: b, in: environment!), f: Float(fromJNI: f, in: environment!), d: Double(fromJNI: d, in: environment!)) + } + """ + ] + ) + } +} diff --git a/Tests/JExtractSwiftTests/MethodThunkTests.swift b/Tests/JExtractSwiftTests/MethodThunkTests.swift index b91c0d8fb..dab550b3e 100644 --- a/Tests/JExtractSwiftTests/MethodThunkTests.swift +++ b/Tests/JExtractSwiftTests/MethodThunkTests.swift @@ -32,13 +32,9 @@ final class MethodThunkTests { @Test("Thunk overloads: globalFunc(a: Int32, b: Int64) & globalFunc(i32: Int32, l: Int64)") func thunk_overloads() throws { - let st = Swift2JavaTranslator( - swiftModuleName: "FakeModule" - ) - st.log.logLevel = .error - try assertOutput( - st, input: input, .swift, + input: input, .ffm, .swift, + swiftModuleName: "FakeModule", detectChunkByInitialLines: 1, expectedChunks: [ diff --git a/Tests/JExtractSwiftTests/StringPassingTests.swift b/Tests/JExtractSwiftTests/StringPassingTests.swift index 92ce8ea6b..99dfab5c1 100644 --- a/Tests/JExtractSwiftTests/StringPassingTests.swift +++ b/Tests/JExtractSwiftTests/StringPassingTests.swift @@ -25,13 +25,9 @@ final class StringPassingTests { @Test("Import: public func writeString(string: String) -> Int") func method_helloWorld() throws { - let st = Swift2JavaTranslator( - swiftModuleName: "__FakeModule" - ) - st.log.logLevel = .trace - try assertOutput( - st, input: class_interfaceFile, .java, + input: class_interfaceFile, .ffm, .java, + swiftModuleName: "__FakeModule", expectedChunks: [ """ /** diff --git a/Tests/JExtractSwiftTests/VariableImportTests.swift b/Tests/JExtractSwiftTests/VariableImportTests.swift index 562b92ae4..33f41e209 100644 --- a/Tests/JExtractSwiftTests/VariableImportTests.swift +++ b/Tests/JExtractSwiftTests/VariableImportTests.swift @@ -35,13 +35,9 @@ final class VariableImportTests { @Test("Import: var counter: Int") func variable_int() throws { - let st = Swift2JavaTranslator( - swiftModuleName: "FakeModule" - ) - st.log.logLevel = .error - try assertOutput( - st, input: class_interfaceFile, .java, + input: class_interfaceFile, .ffm, .java, + swiftModuleName: "FakeModule", detectChunkByInitialLines: 8, expectedChunks: [ """ From ccc87db5e3c63f861fb2fa1d433843cef4fcba73 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Mon, 16 Jun 2025 04:03:05 +0200 Subject: [PATCH 003/141] add support for strings (#273) --- .../JNI/JNISwift2JavaGenerator.swift | 7 +-- .../JNI/JNIModuleTests.swift | 43 +++++++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index 56d75218e..946223aef 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -134,8 +134,8 @@ extension JNISwift2JavaGenerator { """ ) { printer in let downcallParameters = zip(decl.functionSignature.parameters, translatedParameters).map { originalParam, translatedParam in - let label = originalParam.argumentLabel ?? originalParam.parameterName ?? "" - return "\(label)\(!label.isEmpty ? ": " : "")\(originalParam.type)(fromJNI: \(translatedParam.0), in: environment!)" + let label = originalParam.argumentLabel.map { "\($0): "} ?? "" + return "\(label)\(originalParam.type)(fromJNI: \(translatedParam.0), in: environment!)" } let functionDowncall = "\(swiftModuleName).\(decl.name)(\(downcallParameters.joined(separator: ", ")))" @@ -247,7 +247,8 @@ extension SwiftStandardLibraryTypeKind { case .float: .float case .double: .double case .void: .void - case .uint, .uint8, .uint32, .uint64, .unsafeRawPointer, .unsafeMutableRawPointer, .unsafePointer, .unsafeMutablePointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string: nil + case .string: .javaLangString + case .uint, .uint8, .uint32, .uint64, .unsafeRawPointer, .unsafeMutableRawPointer, .unsafePointer, .unsafeMutablePointer, .unsafeBufferPointer, .unsafeMutableBufferPointer: nil } } } diff --git a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift index c5df98a13..ba6258a13 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift @@ -23,6 +23,10 @@ struct JNIModuleTests { public func otherPrimitives(b: Bool, f: Float, d: Double) """ + let globalMethodWithString = """ + public func copy(_ string: String) -> String + """ + @Test func generatesModuleJavaClass() throws { let input = "public func helloWorld()" @@ -107,4 +111,43 @@ struct JNIModuleTests { ] ) } + + @Test + func globalMethodWithString_javaBindings() throws { + try assertOutput( + input: globalMethodWithString, + .jni, + .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func copy(_ string: String) -> String + * } + */ + public static native java.lang.String copy(java.lang.String string); + """, + ] + ) + } + + @Test + func globalMethodWithString_swiftThunks() throws { + try assertOutput( + input: globalMethodWithString, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule_copy") + func swiftjava_SwiftModule_copy__(environment: UnsafeMutablePointer!, thisClass: jclass, string: jstring?) -> jstring? { + let result = SwiftModule.copy(String(fromJNI: string, in: environment!)) + return result.getJNIValue(in: environment) + } + """, + ] + ) + } } From 2fb33d8fa376865c81cab17d812ff0fd26d9b15a Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Mon, 16 Jun 2025 11:13:25 +0900 Subject: [PATCH 004/141] Extract `configure` into its own subcommand (first of such extractions) (#268) --- .../JExtractSwiftCommandPlugin.swift | 2 +- .../JExtractSwiftPlugin.swift | 2 +- Plugins/PluginsShared/PluginUtils.swift | 4 +- Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift | 15 +- .../Sources/Test/swift-java.config | 429 ++++++++++++++ Samples/JavaSieve/Package.swift | 2 + .../JavaSieve/Sources/JavaSieve/main.swift | 7 +- .../Sources/JavaSieve/swift-java.config | 1 + .../Configuration.swift | 4 +- .../Commands/ConfigureCommand.swift | 238 ++++++++ .../ResolveCommand.swift} | 40 +- .../SwiftJava+GenerateWrappers.swift | 7 +- .../{ => Commands}/SwiftJava+JExtract.swift | 0 Sources/SwiftJavaTool/CommonOptions.swift | 59 ++ .../SwiftJavaTool/Java/JavaClassLoader.swift | 29 + .../SwiftJava+EmitConfiguration.swift | 120 ---- Sources/SwiftJavaTool/SwiftJava.swift | 525 ++++++------------ .../SwiftJavaBaseAsyncParsableCommand.swift | 181 ++++++ USER_GUIDE.md | 97 ++-- 19 files changed, 1213 insertions(+), 549 deletions(-) create mode 100644 Samples/JavaDependencySampleApp/Sources/Test/swift-java.config create mode 100644 Sources/SwiftJavaTool/Commands/ConfigureCommand.swift rename Sources/SwiftJavaTool/{SwiftJava+FetchDependencies.swift => Commands/ResolveCommand.swift} (86%) rename Sources/SwiftJavaTool/{ => Commands}/SwiftJava+GenerateWrappers.swift (95%) rename Sources/SwiftJavaTool/{ => Commands}/SwiftJava+JExtract.swift (100%) create mode 100644 Sources/SwiftJavaTool/CommonOptions.swift create mode 100644 Sources/SwiftJavaTool/Java/JavaClassLoader.swift delete mode 100644 Sources/SwiftJavaTool/SwiftJava+EmitConfiguration.swift create mode 100644 Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift diff --git a/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift b/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift index 3ea898862..f97feab68 100644 --- a/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift +++ b/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift @@ -72,7 +72,7 @@ final class JExtractSwiftCommandPlugin: SwiftJavaPluginProtocol, BuildToolPlugin var arguments: [String] = [ "--input-swift", sourceDir, - "--module-name", sourceModule.name, + "--swift-module", sourceModule.name, "--output-java", context.outputJavaDirectory.path(percentEncoded: false), "--output-swift", context.outputSwiftDirectory.path(percentEncoded: false), // TODO: "--build-cache-directory", ... diff --git a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift index a2cda3521..4b52df26d 100644 --- a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift +++ b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift @@ -55,7 +55,7 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { var arguments: [String] = [ "--input-swift", sourceDir, - "--module-name", sourceModule.name, + "--swift-module", sourceModule.name, "--output-java", outputJavaDirectory.path(percentEncoded: false), "--output-swift", outputSwiftDirectory.path(percentEncoded: false), // TODO: "--build-cache-directory", ... diff --git a/Plugins/PluginsShared/PluginUtils.swift b/Plugins/PluginsShared/PluginUtils.swift index 691d33759..863cf99c4 100644 --- a/Plugins/PluginsShared/PluginUtils.swift +++ b/Plugins/PluginsShared/PluginUtils.swift @@ -72,8 +72,8 @@ extension PluginContext { .appending(path: "Sources") } - func cachedClasspathFile(moduleName: String) -> URL { + func cachedClasspathFile(swiftModule: String) -> URL { self.pluginWorkDirectoryURL - .appending(path: "\(moduleName)", directoryHint: .notDirectory) + .appending(path: "\(swiftModule)", directoryHint: .notDirectory) } } diff --git a/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift index 77d7058dd..effdcc533 100644 --- a/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift +++ b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift @@ -166,8 +166,9 @@ struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { displayName: displayName, executable: executable, arguments: [ + // FIXME: change to 'resolve' subcommand "--fetch", configFile.path(percentEncoded: false), - "--module-name", sourceModule.name, + "--swift-module", sourceModule.name, "--output-directory", outputDirectory(context: context, generated: false).path(percentEncoded: false) ], environment: [:], @@ -180,21 +181,21 @@ struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { } if !outputSwiftFiles.isEmpty { + let displayName = "Wrapping \(classes.count) Java classes in Swift target '\(sourceModule.name)'" + log("Prepared: \(displayName)") commands += [ .buildCommand( - displayName: "Wrapping \(classes.count) Java classes in Swift target '\(sourceModule.name)'", + displayName: displayName, executable: executable, arguments: arguments, - inputFiles: compiledClassFiles + fetchDependenciesOutputFiles + [ - configFile - ], + inputFiles: compiledClassFiles + fetchDependenciesOutputFiles + [ configFile ], outputFiles: outputSwiftFiles ) ] } else { log("No Swift output files, skip wrapping") } - + return commands } } @@ -202,7 +203,7 @@ struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { extension SwiftJavaBuildToolPlugin { func argumentsModuleName(sourceModule: Target) -> [String] { return [ - "--module-name", sourceModule.name + "--swift-module", sourceModule.name ] } diff --git a/Samples/JavaDependencySampleApp/Sources/Test/swift-java.config b/Samples/JavaDependencySampleApp/Sources/Test/swift-java.config new file mode 100644 index 000000000..50fd73372 --- /dev/null +++ b/Samples/JavaDependencySampleApp/Sources/Test/swift-java.config @@ -0,0 +1,429 @@ +{ + "classes" : { + "org.apache.commons.codec.BinaryDecoder" : "BinaryDecoder", + "org.apache.commons.codec.BinaryEncoder" : "BinaryEncoder", + "org.apache.commons.codec.CharEncoding" : "CharEncoding", + "org.apache.commons.codec.Charsets" : "Charsets", + "org.apache.commons.codec.CodecPolicy" : "CodecPolicy", + "org.apache.commons.codec.Decoder" : "Decoder", + "org.apache.commons.codec.DecoderException" : "DecoderException", + "org.apache.commons.codec.Encoder" : "Encoder", + "org.apache.commons.codec.EncoderException" : "EncoderException", + "org.apache.commons.codec.Resources" : "Resources", + "org.apache.commons.codec.StringDecoder" : "StringDecoder", + "org.apache.commons.codec.StringEncoder" : "StringEncoder", + "org.apache.commons.codec.StringEncoderComparator" : "StringEncoderComparator", + "org.apache.commons.codec.binary.Base16" : "Base16", + "org.apache.commons.codec.binary.Base16InputStream" : "Base16InputStream", + "org.apache.commons.codec.binary.Base16OutputStream" : "Base16OutputStream", + "org.apache.commons.codec.binary.Base32" : "Base32", + "org.apache.commons.codec.binary.Base32$Builder" : "Base32.Builder", + "org.apache.commons.codec.binary.Base32InputStream" : "Base32InputStream", + "org.apache.commons.codec.binary.Base32OutputStream" : "Base32OutputStream", + "org.apache.commons.codec.binary.Base64" : "Base64", + "org.apache.commons.codec.binary.Base64$Builder" : "Base64.Builder", + "org.apache.commons.codec.binary.Base64InputStream" : "Base64InputStream", + "org.apache.commons.codec.binary.Base64OutputStream" : "Base64OutputStream", + "org.apache.commons.codec.binary.BaseNCodec" : "BaseNCodec", + "org.apache.commons.codec.binary.BaseNCodec$AbstractBuilder" : "BaseNCodec.AbstractBuilder", + "org.apache.commons.codec.binary.BaseNCodec$Context" : "BaseNCodec.Context", + "org.apache.commons.codec.binary.BaseNCodecInputStream" : "BaseNCodecInputStream", + "org.apache.commons.codec.binary.BaseNCodecOutputStream" : "BaseNCodecOutputStream", + "org.apache.commons.codec.binary.BinaryCodec" : "BinaryCodec", + "org.apache.commons.codec.binary.CharSequenceUtils" : "CharSequenceUtils", + "org.apache.commons.codec.binary.Hex" : "Hex", + "org.apache.commons.codec.binary.StringUtils" : "StringUtils", + "org.apache.commons.codec.cli.Digest" : "Digest", + "org.apache.commons.codec.digest.B64" : "B64", + "org.apache.commons.codec.digest.Blake3" : "Blake3", + "org.apache.commons.codec.digest.Blake3$ChunkState" : "Blake3.ChunkState", + "org.apache.commons.codec.digest.Blake3$EngineState" : "Blake3.EngineState", + "org.apache.commons.codec.digest.Blake3$Output" : "Blake3.Output", + "org.apache.commons.codec.digest.Crypt" : "Crypt", + "org.apache.commons.codec.digest.DigestUtils" : "DigestUtils", + "org.apache.commons.codec.digest.HmacAlgorithms" : "HmacAlgorithms", + "org.apache.commons.codec.digest.HmacUtils" : "HmacUtils", + "org.apache.commons.codec.digest.Md5Crypt" : "Md5Crypt", + "org.apache.commons.codec.digest.MessageDigestAlgorithms" : "MessageDigestAlgorithms", + "org.apache.commons.codec.digest.MurmurHash2" : "MurmurHash2", + "org.apache.commons.codec.digest.MurmurHash3" : "MurmurHash3", + "org.apache.commons.codec.digest.MurmurHash3$IncrementalHash32" : "MurmurHash3.IncrementalHash32", + "org.apache.commons.codec.digest.MurmurHash3$IncrementalHash32x86" : "MurmurHash3.IncrementalHash32x86", + "org.apache.commons.codec.digest.PureJavaCrc32" : "PureJavaCrc32", + "org.apache.commons.codec.digest.PureJavaCrc32C" : "PureJavaCrc32C", + "org.apache.commons.codec.digest.Sha2Crypt" : "Sha2Crypt", + "org.apache.commons.codec.digest.UnixCrypt" : "UnixCrypt", + "org.apache.commons.codec.digest.XXHash32" : "XXHash32", + "org.apache.commons.codec.language.AbstractCaverphone" : "AbstractCaverphone", + "org.apache.commons.codec.language.Caverphone" : "Caverphone", + "org.apache.commons.codec.language.Caverphone1" : "Caverphone1", + "org.apache.commons.codec.language.Caverphone2" : "Caverphone2", + "org.apache.commons.codec.language.ColognePhonetic" : "ColognePhonetic", + "org.apache.commons.codec.language.ColognePhonetic$CologneBuffer" : "ColognePhonetic.CologneBuffer", + "org.apache.commons.codec.language.ColognePhonetic$CologneInputBuffer" : "ColognePhonetic.CologneInputBuffer", + "org.apache.commons.codec.language.ColognePhonetic$CologneOutputBuffer" : "ColognePhonetic.CologneOutputBuffer", + "org.apache.commons.codec.language.DaitchMokotoffSoundex" : "DaitchMokotoffSoundex", + "org.apache.commons.codec.language.DaitchMokotoffSoundex$Branch" : "DaitchMokotoffSoundex.Branch", + "org.apache.commons.codec.language.DaitchMokotoffSoundex$Rule" : "DaitchMokotoffSoundex.Rule", + "org.apache.commons.codec.language.DoubleMetaphone" : "DoubleMetaphone", + "org.apache.commons.codec.language.DoubleMetaphone$DoubleMetaphoneResult" : "DoubleMetaphone.DoubleMetaphoneResult", + "org.apache.commons.codec.language.MatchRatingApproachEncoder" : "MatchRatingApproachEncoder", + "org.apache.commons.codec.language.Metaphone" : "Metaphone", + "org.apache.commons.codec.language.Nysiis" : "Nysiis", + "org.apache.commons.codec.language.RefinedSoundex" : "RefinedSoundex", + "org.apache.commons.codec.language.Soundex" : "Soundex", + "org.apache.commons.codec.language.SoundexUtils" : "SoundexUtils", + "org.apache.commons.codec.language.bm.BeiderMorseEncoder" : "BeiderMorseEncoder", + "org.apache.commons.codec.language.bm.Lang" : "Lang", + "org.apache.commons.codec.language.bm.Lang$LangRule" : "Lang.LangRule", + "org.apache.commons.codec.language.bm.Languages" : "Languages", + "org.apache.commons.codec.language.bm.Languages$LanguageSet" : "Languages.LanguageSet", + "org.apache.commons.codec.language.bm.Languages$SomeLanguages" : "Languages.SomeLanguages", + "org.apache.commons.codec.language.bm.NameType" : "NameType", + "org.apache.commons.codec.language.bm.PhoneticEngine" : "PhoneticEngine", + "org.apache.commons.codec.language.bm.PhoneticEngine$PhonemeBuilder" : "PhoneticEngine.PhonemeBuilder", + "org.apache.commons.codec.language.bm.PhoneticEngine$RulesApplication" : "PhoneticEngine.RulesApplication", + "org.apache.commons.codec.language.bm.ResourceConstants" : "ResourceConstants", + "org.apache.commons.codec.language.bm.Rule" : "Rule", + "org.apache.commons.codec.language.bm.Rule$Phoneme" : "Rule.Phoneme", + "org.apache.commons.codec.language.bm.Rule$PhonemeExpr" : "Rule.PhonemeExpr", + "org.apache.commons.codec.language.bm.Rule$PhonemeList" : "Rule.PhonemeList", + "org.apache.commons.codec.language.bm.Rule$RPattern" : "Rule.RPattern", + "org.apache.commons.codec.language.bm.RuleType" : "RuleType", + "org.apache.commons.codec.net.BCodec" : "BCodec", + "org.apache.commons.codec.net.PercentCodec" : "PercentCodec", + "org.apache.commons.codec.net.QCodec" : "QCodec", + "org.apache.commons.codec.net.QuotedPrintableCodec" : "QuotedPrintableCodec", + "org.apache.commons.codec.net.RFC1522Codec" : "RFC1522Codec", + "org.apache.commons.codec.net.URLCodec" : "URLCodec", + "org.apache.commons.codec.net.Utils" : "Utils", + "org.apache.commons.csv.CSVException" : "CSVException", + "org.apache.commons.csv.CSVFormat" : "CSVFormat", + "org.apache.commons.csv.CSVFormat$Builder" : "CSVFormat.Builder", + "org.apache.commons.csv.CSVFormat$Predefined" : "CSVFormat.Predefined", + "org.apache.commons.csv.CSVParser" : "CSVParser", + "org.apache.commons.csv.CSVParser$CSVRecordIterator" : "CSVParser.CSVRecordIterator", + "org.apache.commons.csv.CSVParser$Headers" : "CSVParser.Headers", + "org.apache.commons.csv.CSVPrinter" : "CSVPrinter", + "org.apache.commons.csv.CSVRecord" : "CSVRecord", + "org.apache.commons.csv.Constants" : "Constants", + "org.apache.commons.csv.DuplicateHeaderMode" : "DuplicateHeaderMode", + "org.apache.commons.csv.ExtendedBufferedReader" : "ExtendedBufferedReader", + "org.apache.commons.csv.Lexer" : "Lexer", + "org.apache.commons.csv.QuoteMode" : "QuoteMode", + "org.apache.commons.csv.Token" : "Token", + "org.apache.commons.csv.Token$Type" : "Token.Type", + "org.apache.commons.io.ByteOrderMark" : "ByteOrderMark", + "org.apache.commons.io.ByteOrderParser" : "ByteOrderParser", + "org.apache.commons.io.Charsets" : "Charsets", + "org.apache.commons.io.CloseableURLConnection" : "CloseableURLConnection", + "org.apache.commons.io.CopyUtils" : "CopyUtils", + "org.apache.commons.io.DirectoryWalker" : "DirectoryWalker", + "org.apache.commons.io.DirectoryWalker$CancelException" : "DirectoryWalker.CancelException", + "org.apache.commons.io.EndianUtils" : "EndianUtils", + "org.apache.commons.io.FileCleaner" : "FileCleaner", + "org.apache.commons.io.FileCleaningTracker" : "FileCleaningTracker", + "org.apache.commons.io.FileCleaningTracker$Reaper" : "FileCleaningTracker.Reaper", + "org.apache.commons.io.FileCleaningTracker$Tracker" : "FileCleaningTracker.Tracker", + "org.apache.commons.io.FileDeleteStrategy" : "FileDeleteStrategy", + "org.apache.commons.io.FileDeleteStrategy$ForceFileDeleteStrategy" : "FileDeleteStrategy.ForceFileDeleteStrategy", + "org.apache.commons.io.FileExistsException" : "FileExistsException", + "org.apache.commons.io.FileSystem" : "FileSystem", + "org.apache.commons.io.FileSystemUtils" : "FileSystemUtils", + "org.apache.commons.io.FileUtils" : "FileUtils", + "org.apache.commons.io.FilenameUtils" : "FilenameUtils", + "org.apache.commons.io.HexDump" : "HexDump", + "org.apache.commons.io.IO" : "IO", + "org.apache.commons.io.IOCase" : "IOCase", + "org.apache.commons.io.IOExceptionList" : "IOExceptionList", + "org.apache.commons.io.IOExceptionWithCause" : "IOExceptionWithCause", + "org.apache.commons.io.IOIndexedException" : "IOIndexedException", + "org.apache.commons.io.IOUtils" : "IOUtils", + "org.apache.commons.io.LineIterator" : "LineIterator", + "org.apache.commons.io.RandomAccessFileMode" : "RandomAccessFileMode", + "org.apache.commons.io.RandomAccessFiles" : "RandomAccessFiles", + "org.apache.commons.io.StandardLineSeparator" : "StandardLineSeparator", + "org.apache.commons.io.StreamIterator" : "StreamIterator", + "org.apache.commons.io.TaggedIOException" : "TaggedIOException", + "org.apache.commons.io.ThreadMonitor" : "ThreadMonitor", + "org.apache.commons.io.ThreadUtils" : "ThreadUtils", + "org.apache.commons.io.UncheckedIOExceptions" : "UncheckedIOExceptions", + "org.apache.commons.io.build.AbstractOrigin" : "AbstractOrigin", + "org.apache.commons.io.build.AbstractOrigin$ByteArrayOrigin" : "AbstractOrigin.ByteArrayOrigin", + "org.apache.commons.io.build.AbstractOrigin$CharSequenceOrigin" : "AbstractOrigin.CharSequenceOrigin", + "org.apache.commons.io.build.AbstractOrigin$FileOrigin" : "AbstractOrigin.FileOrigin", + "org.apache.commons.io.build.AbstractOrigin$InputStreamOrigin" : "AbstractOrigin.InputStreamOrigin", + "org.apache.commons.io.build.AbstractOrigin$OutputStreamOrigin" : "AbstractOrigin.OutputStreamOrigin", + "org.apache.commons.io.build.AbstractOrigin$PathOrigin" : "AbstractOrigin.PathOrigin", + "org.apache.commons.io.build.AbstractOrigin$ReaderOrigin" : "AbstractOrigin.ReaderOrigin", + "org.apache.commons.io.build.AbstractOrigin$URIOrigin" : "AbstractOrigin.URIOrigin", + "org.apache.commons.io.build.AbstractOrigin$WriterOrigin" : "AbstractOrigin.WriterOrigin", + "org.apache.commons.io.build.AbstractOriginSupplier" : "AbstractOriginSupplier", + "org.apache.commons.io.build.AbstractStreamBuilder" : "AbstractStreamBuilder", + "org.apache.commons.io.build.AbstractSupplier" : "AbstractSupplier", + "org.apache.commons.io.channels.FileChannels" : "FileChannels", + "org.apache.commons.io.charset.CharsetDecoders" : "CharsetDecoders", + "org.apache.commons.io.charset.CharsetEncoders" : "CharsetEncoders", + "org.apache.commons.io.comparator.AbstractFileComparator" : "AbstractFileComparator", + "org.apache.commons.io.comparator.CompositeFileComparator" : "CompositeFileComparator", + "org.apache.commons.io.comparator.DefaultFileComparator" : "DefaultFileComparator", + "org.apache.commons.io.comparator.DirectoryFileComparator" : "DirectoryFileComparator", + "org.apache.commons.io.comparator.ExtensionFileComparator" : "ExtensionFileComparator", + "org.apache.commons.io.comparator.LastModifiedFileComparator" : "LastModifiedFileComparator", + "org.apache.commons.io.comparator.NameFileComparator" : "NameFileComparator", + "org.apache.commons.io.comparator.PathFileComparator" : "PathFileComparator", + "org.apache.commons.io.comparator.ReverseFileComparator" : "ReverseFileComparator", + "org.apache.commons.io.comparator.SizeFileComparator" : "SizeFileComparator", + "org.apache.commons.io.file.AccumulatorPathVisitor" : "AccumulatorPathVisitor", + "org.apache.commons.io.file.CleaningPathVisitor" : "CleaningPathVisitor", + "org.apache.commons.io.file.CopyDirectoryVisitor" : "CopyDirectoryVisitor", + "org.apache.commons.io.file.Counters" : "Counters", + "org.apache.commons.io.file.Counters$AbstractPathCounters" : "Counters.AbstractPathCounters", + "org.apache.commons.io.file.Counters$BigIntegerCounter" : "Counters.BigIntegerCounter", + "org.apache.commons.io.file.Counters$BigIntegerPathCounters" : "Counters.BigIntegerPathCounters", + "org.apache.commons.io.file.Counters$Counter" : "Counters.Counter", + "org.apache.commons.io.file.Counters$LongCounter" : "Counters.LongCounter", + "org.apache.commons.io.file.Counters$LongPathCounters" : "Counters.LongPathCounters", + "org.apache.commons.io.file.Counters$NoopCounter" : "Counters.NoopCounter", + "org.apache.commons.io.file.Counters$NoopPathCounters" : "Counters.NoopPathCounters", + "org.apache.commons.io.file.Counters$PathCounters" : "Counters.PathCounters", + "org.apache.commons.io.file.CountingPathVisitor" : "CountingPathVisitor", + "org.apache.commons.io.file.DeleteOption" : "DeleteOption", + "org.apache.commons.io.file.DeletingPathVisitor" : "DeletingPathVisitor", + "org.apache.commons.io.file.DirectoryStreamFilter" : "DirectoryStreamFilter", + "org.apache.commons.io.file.FilesUncheck" : "FilesUncheck", + "org.apache.commons.io.file.NoopPathVisitor" : "NoopPathVisitor", + "org.apache.commons.io.file.PathFilter" : "PathFilter", + "org.apache.commons.io.file.PathUtils" : "PathUtils", + "org.apache.commons.io.file.PathUtils$RelativeSortedPaths" : "PathUtils.RelativeSortedPaths", + "org.apache.commons.io.file.PathVisitor" : "PathVisitor", + "org.apache.commons.io.file.SimplePathVisitor" : "SimplePathVisitor", + "org.apache.commons.io.file.StandardDeleteOption" : "StandardDeleteOption", + "org.apache.commons.io.file.attribute.FileTimes" : "FileTimes", + "org.apache.commons.io.file.spi.FileSystemProviders" : "FileSystemProviders", + "org.apache.commons.io.filefilter.AbstractFileFilter" : "AbstractFileFilter", + "org.apache.commons.io.filefilter.AgeFileFilter" : "AgeFileFilter", + "org.apache.commons.io.filefilter.AndFileFilter" : "AndFileFilter", + "org.apache.commons.io.filefilter.CanExecuteFileFilter" : "CanExecuteFileFilter", + "org.apache.commons.io.filefilter.CanReadFileFilter" : "CanReadFileFilter", + "org.apache.commons.io.filefilter.CanWriteFileFilter" : "CanWriteFileFilter", + "org.apache.commons.io.filefilter.ConditionalFileFilter" : "ConditionalFileFilter", + "org.apache.commons.io.filefilter.DelegateFileFilter" : "DelegateFileFilter", + "org.apache.commons.io.filefilter.DirectoryFileFilter" : "DirectoryFileFilter", + "org.apache.commons.io.filefilter.EmptyFileFilter" : "EmptyFileFilter", + "org.apache.commons.io.filefilter.FalseFileFilter" : "FalseFileFilter", + "org.apache.commons.io.filefilter.FileEqualsFileFilter" : "FileEqualsFileFilter", + "org.apache.commons.io.filefilter.FileFileFilter" : "FileFileFilter", + "org.apache.commons.io.filefilter.FileFilterUtils" : "FileFilterUtils", + "org.apache.commons.io.filefilter.HiddenFileFilter" : "HiddenFileFilter", + "org.apache.commons.io.filefilter.IOFileFilter" : "IOFileFilter", + "org.apache.commons.io.filefilter.MagicNumberFileFilter" : "MagicNumberFileFilter", + "org.apache.commons.io.filefilter.NameFileFilter" : "NameFileFilter", + "org.apache.commons.io.filefilter.NotFileFilter" : "NotFileFilter", + "org.apache.commons.io.filefilter.OrFileFilter" : "OrFileFilter", + "org.apache.commons.io.filefilter.PathEqualsFileFilter" : "PathEqualsFileFilter", + "org.apache.commons.io.filefilter.PathMatcherFileFilter" : "PathMatcherFileFilter", + "org.apache.commons.io.filefilter.PathVisitorFileFilter" : "PathVisitorFileFilter", + "org.apache.commons.io.filefilter.PrefixFileFilter" : "PrefixFileFilter", + "org.apache.commons.io.filefilter.RegexFileFilter" : "RegexFileFilter", + "org.apache.commons.io.filefilter.SizeFileFilter" : "SizeFileFilter", + "org.apache.commons.io.filefilter.SuffixFileFilter" : "SuffixFileFilter", + "org.apache.commons.io.filefilter.SymbolicLinkFileFilter" : "SymbolicLinkFileFilter", + "org.apache.commons.io.filefilter.TrueFileFilter" : "TrueFileFilter", + "org.apache.commons.io.filefilter.WildcardFileFilter" : "WildcardFileFilter", + "org.apache.commons.io.filefilter.WildcardFileFilter$Builder" : "WildcardFileFilter.Builder", + "org.apache.commons.io.filefilter.WildcardFilter" : "WildcardFilter", + "org.apache.commons.io.function.Constants" : "Constants", + "org.apache.commons.io.function.Erase" : "Erase", + "org.apache.commons.io.function.IOBaseStream" : "IOBaseStream", + "org.apache.commons.io.function.IOBaseStreamAdapter" : "IOBaseStreamAdapter", + "org.apache.commons.io.function.IOBiConsumer" : "IOBiConsumer", + "org.apache.commons.io.function.IOBiFunction" : "IOBiFunction", + "org.apache.commons.io.function.IOBinaryOperator" : "IOBinaryOperator", + "org.apache.commons.io.function.IOComparator" : "IOComparator", + "org.apache.commons.io.function.IOConsumer" : "IOConsumer", + "org.apache.commons.io.function.IOFunction" : "IOFunction", + "org.apache.commons.io.function.IOIntSupplier" : "IOIntSupplier", + "org.apache.commons.io.function.IOIterator" : "IOIterator", + "org.apache.commons.io.function.IOIteratorAdapter" : "IOIteratorAdapter", + "org.apache.commons.io.function.IOLongSupplier" : "IOLongSupplier", + "org.apache.commons.io.function.IOPredicate" : "IOPredicate", + "org.apache.commons.io.function.IOQuadFunction" : "IOQuadFunction", + "org.apache.commons.io.function.IORunnable" : "IORunnable", + "org.apache.commons.io.function.IOSpliterator" : "IOSpliterator", + "org.apache.commons.io.function.IOSpliteratorAdapter" : "IOSpliteratorAdapter", + "org.apache.commons.io.function.IOStream" : "IOStream", + "org.apache.commons.io.function.IOStreamAdapter" : "IOStreamAdapter", + "org.apache.commons.io.function.IOStreams" : "IOStreams", + "org.apache.commons.io.function.IOSupplier" : "IOSupplier", + "org.apache.commons.io.function.IOTriConsumer" : "IOTriConsumer", + "org.apache.commons.io.function.IOTriFunction" : "IOTriFunction", + "org.apache.commons.io.function.IOUnaryOperator" : "IOUnaryOperator", + "org.apache.commons.io.function.Uncheck" : "Uncheck", + "org.apache.commons.io.function.UncheckedIOBaseStream" : "UncheckedIOBaseStream", + "org.apache.commons.io.function.UncheckedIOIterator" : "UncheckedIOIterator", + "org.apache.commons.io.function.UncheckedIOSpliterator" : "UncheckedIOSpliterator", + "org.apache.commons.io.input.AbstractCharacterFilterReader" : "AbstractCharacterFilterReader", + "org.apache.commons.io.input.AbstractInputStream" : "AbstractInputStream", + "org.apache.commons.io.input.AutoCloseInputStream" : "AutoCloseInputStream", + "org.apache.commons.io.input.AutoCloseInputStream$Builder" : "AutoCloseInputStream.Builder", + "org.apache.commons.io.input.BOMInputStream" : "BOMInputStream", + "org.apache.commons.io.input.BOMInputStream$Builder" : "BOMInputStream.Builder", + "org.apache.commons.io.input.BoundedInputStream" : "BoundedInputStream", + "org.apache.commons.io.input.BoundedInputStream$AbstractBuilder" : "BoundedInputStream.AbstractBuilder", + "org.apache.commons.io.input.BoundedInputStream$Builder" : "BoundedInputStream.Builder", + "org.apache.commons.io.input.BoundedReader" : "BoundedReader", + "org.apache.commons.io.input.BrokenInputStream" : "BrokenInputStream", + "org.apache.commons.io.input.BrokenReader" : "BrokenReader", + "org.apache.commons.io.input.BufferedFileChannelInputStream" : "BufferedFileChannelInputStream", + "org.apache.commons.io.input.BufferedFileChannelInputStream$Builder" : "BufferedFileChannelInputStream.Builder", + "org.apache.commons.io.input.ByteBufferCleaner" : "ByteBufferCleaner", + "org.apache.commons.io.input.ByteBufferCleaner$Cleaner" : "ByteBufferCleaner.Cleaner", + "org.apache.commons.io.input.ByteBufferCleaner$Java8Cleaner" : "ByteBufferCleaner.Java8Cleaner", + "org.apache.commons.io.input.ByteBufferCleaner$Java9Cleaner" : "ByteBufferCleaner.Java9Cleaner", + "org.apache.commons.io.input.CharSequenceInputStream" : "CharSequenceInputStream", + "org.apache.commons.io.input.CharSequenceInputStream$Builder" : "CharSequenceInputStream.Builder", + "org.apache.commons.io.input.CharSequenceReader" : "CharSequenceReader", + "org.apache.commons.io.input.CharacterFilterReader" : "CharacterFilterReader", + "org.apache.commons.io.input.CharacterSetFilterReader" : "CharacterSetFilterReader", + "org.apache.commons.io.input.ChecksumInputStream" : "ChecksumInputStream", + "org.apache.commons.io.input.ChecksumInputStream$Builder" : "ChecksumInputStream.Builder", + "org.apache.commons.io.input.CircularInputStream" : "CircularInputStream", + "org.apache.commons.io.input.ClassLoaderObjectInputStream" : "ClassLoaderObjectInputStream", + "org.apache.commons.io.input.CloseShieldInputStream" : "CloseShieldInputStream", + "org.apache.commons.io.input.CloseShieldReader" : "CloseShieldReader", + "org.apache.commons.io.input.ClosedInputStream" : "ClosedInputStream", + "org.apache.commons.io.input.ClosedReader" : "ClosedReader", + "org.apache.commons.io.input.CountingInputStream" : "CountingInputStream", + "org.apache.commons.io.input.DemuxInputStream" : "DemuxInputStream", + "org.apache.commons.io.input.InfiniteCircularInputStream" : "InfiniteCircularInputStream", + "org.apache.commons.io.input.Input" : "Input", + "org.apache.commons.io.input.MarkShieldInputStream" : "MarkShieldInputStream", + "org.apache.commons.io.input.MemoryMappedFileInputStream" : "MemoryMappedFileInputStream", + "org.apache.commons.io.input.MemoryMappedFileInputStream$Builder" : "MemoryMappedFileInputStream.Builder", + "org.apache.commons.io.input.MessageDigestCalculatingInputStream" : "MessageDigestCalculatingInputStream", + "org.apache.commons.io.input.MessageDigestCalculatingInputStream$Builder" : "MessageDigestCalculatingInputStream.Builder", + "org.apache.commons.io.input.MessageDigestCalculatingInputStream$MessageDigestMaintainingObserver" : "MessageDigestCalculatingInputStream.MessageDigestMaintainingObserver", + "org.apache.commons.io.input.MessageDigestInputStream" : "MessageDigestInputStream", + "org.apache.commons.io.input.MessageDigestInputStream$Builder" : "MessageDigestInputStream.Builder", + "org.apache.commons.io.input.MessageDigestInputStream$MessageDigestMaintainingObserver" : "MessageDigestInputStream.MessageDigestMaintainingObserver", + "org.apache.commons.io.input.NullInputStream" : "NullInputStream", + "org.apache.commons.io.input.NullReader" : "NullReader", + "org.apache.commons.io.input.ObservableInputStream" : "ObservableInputStream", + "org.apache.commons.io.input.ObservableInputStream$Observer" : "ObservableInputStream.Observer", + "org.apache.commons.io.input.ProxyInputStream" : "ProxyInputStream", + "org.apache.commons.io.input.ProxyReader" : "ProxyReader", + "org.apache.commons.io.input.QueueInputStream" : "QueueInputStream", + "org.apache.commons.io.input.QueueInputStream$Builder" : "QueueInputStream.Builder", + "org.apache.commons.io.input.RandomAccessFileInputStream" : "RandomAccessFileInputStream", + "org.apache.commons.io.input.RandomAccessFileInputStream$Builder" : "RandomAccessFileInputStream.Builder", + "org.apache.commons.io.input.ReadAheadInputStream" : "ReadAheadInputStream", + "org.apache.commons.io.input.ReadAheadInputStream$Builder" : "ReadAheadInputStream.Builder", + "org.apache.commons.io.input.ReaderInputStream" : "ReaderInputStream", + "org.apache.commons.io.input.ReaderInputStream$Builder" : "ReaderInputStream.Builder", + "org.apache.commons.io.input.ReversedLinesFileReader" : "ReversedLinesFileReader", + "org.apache.commons.io.input.ReversedLinesFileReader$Builder" : "ReversedLinesFileReader.Builder", + "org.apache.commons.io.input.ReversedLinesFileReader$FilePart" : "ReversedLinesFileReader.FilePart", + "org.apache.commons.io.input.SequenceReader" : "SequenceReader", + "org.apache.commons.io.input.SwappedDataInputStream" : "SwappedDataInputStream", + "org.apache.commons.io.input.TaggedInputStream" : "TaggedInputStream", + "org.apache.commons.io.input.TaggedReader" : "TaggedReader", + "org.apache.commons.io.input.Tailer" : "Tailer", + "org.apache.commons.io.input.Tailer$Builder" : "Tailer.Builder", + "org.apache.commons.io.input.Tailer$RandomAccessFileBridge" : "Tailer.RandomAccessFileBridge", + "org.apache.commons.io.input.Tailer$RandomAccessResourceBridge" : "Tailer.RandomAccessResourceBridge", + "org.apache.commons.io.input.Tailer$Tailable" : "Tailer.Tailable", + "org.apache.commons.io.input.Tailer$TailablePath" : "Tailer.TailablePath", + "org.apache.commons.io.input.TailerListener" : "TailerListener", + "org.apache.commons.io.input.TailerListenerAdapter" : "TailerListenerAdapter", + "org.apache.commons.io.input.TeeInputStream" : "TeeInputStream", + "org.apache.commons.io.input.TeeReader" : "TeeReader", + "org.apache.commons.io.input.ThrottledInputStream" : "ThrottledInputStream", + "org.apache.commons.io.input.ThrottledInputStream$Builder" : "ThrottledInputStream.Builder", + "org.apache.commons.io.input.TimestampedObserver" : "TimestampedObserver", + "org.apache.commons.io.input.UncheckedBufferedReader" : "UncheckedBufferedReader", + "org.apache.commons.io.input.UncheckedBufferedReader$Builder" : "UncheckedBufferedReader.Builder", + "org.apache.commons.io.input.UncheckedFilterInputStream" : "UncheckedFilterInputStream", + "org.apache.commons.io.input.UncheckedFilterInputStream$Builder" : "UncheckedFilterInputStream.Builder", + "org.apache.commons.io.input.UncheckedFilterReader" : "UncheckedFilterReader", + "org.apache.commons.io.input.UncheckedFilterReader$Builder" : "UncheckedFilterReader.Builder", + "org.apache.commons.io.input.UnixLineEndingInputStream" : "UnixLineEndingInputStream", + "org.apache.commons.io.input.UnsupportedOperationExceptions" : "UnsupportedOperationExceptions", + "org.apache.commons.io.input.UnsynchronizedBufferedInputStream" : "UnsynchronizedBufferedInputStream", + "org.apache.commons.io.input.UnsynchronizedBufferedInputStream$Builder" : "UnsynchronizedBufferedInputStream.Builder", + "org.apache.commons.io.input.UnsynchronizedBufferedReader" : "UnsynchronizedBufferedReader", + "org.apache.commons.io.input.UnsynchronizedByteArrayInputStream" : "UnsynchronizedByteArrayInputStream", + "org.apache.commons.io.input.UnsynchronizedByteArrayInputStream$Builder" : "UnsynchronizedByteArrayInputStream.Builder", + "org.apache.commons.io.input.UnsynchronizedFilterInputStream" : "UnsynchronizedFilterInputStream", + "org.apache.commons.io.input.UnsynchronizedFilterInputStream$Builder" : "UnsynchronizedFilterInputStream.Builder", + "org.apache.commons.io.input.UnsynchronizedReader" : "UnsynchronizedReader", + "org.apache.commons.io.input.WindowsLineEndingInputStream" : "WindowsLineEndingInputStream", + "org.apache.commons.io.input.XmlStreamReader" : "XmlStreamReader", + "org.apache.commons.io.input.XmlStreamReader$Builder" : "XmlStreamReader.Builder", + "org.apache.commons.io.input.XmlStreamReaderException" : "XmlStreamReaderException", + "org.apache.commons.io.input.buffer.CircularBufferInputStream" : "CircularBufferInputStream", + "org.apache.commons.io.input.buffer.CircularByteBuffer" : "CircularByteBuffer", + "org.apache.commons.io.input.buffer.PeekableInputStream" : "PeekableInputStream", + "org.apache.commons.io.monitor.FileAlterationListener" : "FileAlterationListener", + "org.apache.commons.io.monitor.FileAlterationListenerAdaptor" : "FileAlterationListenerAdaptor", + "org.apache.commons.io.monitor.FileAlterationMonitor" : "FileAlterationMonitor", + "org.apache.commons.io.monitor.FileAlterationObserver" : "FileAlterationObserver", + "org.apache.commons.io.monitor.FileEntry" : "FileEntry", + "org.apache.commons.io.monitor.SerializableFileTime" : "SerializableFileTime", + "org.apache.commons.io.output.AbstractByteArrayOutputStream" : "AbstractByteArrayOutputStream", + "org.apache.commons.io.output.AbstractByteArrayOutputStream$InputStreamConstructor" : "AbstractByteArrayOutputStream.InputStreamConstructor", + "org.apache.commons.io.output.AppendableOutputStream" : "AppendableOutputStream", + "org.apache.commons.io.output.AppendableWriter" : "AppendableWriter", + "org.apache.commons.io.output.BrokenOutputStream" : "BrokenOutputStream", + "org.apache.commons.io.output.BrokenWriter" : "BrokenWriter", + "org.apache.commons.io.output.ByteArrayOutputStream" : "ByteArrayOutputStream", + "org.apache.commons.io.output.ChunkedOutputStream" : "ChunkedOutputStream", + "org.apache.commons.io.output.ChunkedOutputStream$Builder" : "ChunkedOutputStream.Builder", + "org.apache.commons.io.output.ChunkedWriter" : "ChunkedWriter", + "org.apache.commons.io.output.CloseShieldOutputStream" : "CloseShieldOutputStream", + "org.apache.commons.io.output.CloseShieldWriter" : "CloseShieldWriter", + "org.apache.commons.io.output.ClosedOutputStream" : "ClosedOutputStream", + "org.apache.commons.io.output.ClosedWriter" : "ClosedWriter", + "org.apache.commons.io.output.CountingOutputStream" : "CountingOutputStream", + "org.apache.commons.io.output.DeferredFileOutputStream" : "DeferredFileOutputStream", + "org.apache.commons.io.output.DeferredFileOutputStream$Builder" : "DeferredFileOutputStream.Builder", + "org.apache.commons.io.output.DemuxOutputStream" : "DemuxOutputStream", + "org.apache.commons.io.output.FileWriterWithEncoding" : "FileWriterWithEncoding", + "org.apache.commons.io.output.FileWriterWithEncoding$Builder" : "FileWriterWithEncoding.Builder", + "org.apache.commons.io.output.FilterCollectionWriter" : "FilterCollectionWriter", + "org.apache.commons.io.output.LockableFileWriter" : "LockableFileWriter", + "org.apache.commons.io.output.LockableFileWriter$Builder" : "LockableFileWriter.Builder", + "org.apache.commons.io.output.NullAppendable" : "NullAppendable", + "org.apache.commons.io.output.NullOutputStream" : "NullOutputStream", + "org.apache.commons.io.output.NullPrintStream" : "NullPrintStream", + "org.apache.commons.io.output.NullWriter" : "NullWriter", + "org.apache.commons.io.output.ProxyCollectionWriter" : "ProxyCollectionWriter", + "org.apache.commons.io.output.ProxyOutputStream" : "ProxyOutputStream", + "org.apache.commons.io.output.ProxyWriter" : "ProxyWriter", + "org.apache.commons.io.output.QueueOutputStream" : "QueueOutputStream", + "org.apache.commons.io.output.StringBuilderWriter" : "StringBuilderWriter", + "org.apache.commons.io.output.TaggedOutputStream" : "TaggedOutputStream", + "org.apache.commons.io.output.TaggedWriter" : "TaggedWriter", + "org.apache.commons.io.output.TeeOutputStream" : "TeeOutputStream", + "org.apache.commons.io.output.TeeWriter" : "TeeWriter", + "org.apache.commons.io.output.ThresholdingOutputStream" : "ThresholdingOutputStream", + "org.apache.commons.io.output.UncheckedAppendable" : "UncheckedAppendable", + "org.apache.commons.io.output.UncheckedAppendableImpl" : "UncheckedAppendableImpl", + "org.apache.commons.io.output.UncheckedFilterOutputStream" : "UncheckedFilterOutputStream", + "org.apache.commons.io.output.UncheckedFilterOutputStream$Builder" : "UncheckedFilterOutputStream.Builder", + "org.apache.commons.io.output.UncheckedFilterWriter" : "UncheckedFilterWriter", + "org.apache.commons.io.output.UncheckedFilterWriter$Builder" : "UncheckedFilterWriter.Builder", + "org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream" : "UnsynchronizedByteArrayOutputStream", + "org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream$Builder" : "UnsynchronizedByteArrayOutputStream.Builder", + "org.apache.commons.io.output.WriterOutputStream" : "WriterOutputStream", + "org.apache.commons.io.output.WriterOutputStream$Builder" : "WriterOutputStream.Builder", + "org.apache.commons.io.output.XmlStreamWriter" : "XmlStreamWriter", + "org.apache.commons.io.output.XmlStreamWriter$Builder" : "XmlStreamWriter.Builder", + "org.apache.commons.io.serialization.ClassNameMatcher" : "ClassNameMatcher", + "org.apache.commons.io.serialization.FullClassNameMatcher" : "FullClassNameMatcher", + "org.apache.commons.io.serialization.RegexpClassNameMatcher" : "RegexpClassNameMatcher", + "org.apache.commons.io.serialization.ValidatingObjectInputStream" : "ValidatingObjectInputStream", + "org.apache.commons.io.serialization.WildcardClassNameMatcher" : "WildcardClassNameMatcher" + }, + "classpath" : "\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/classes\/java\/main:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/resources\/main:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/org.apache.commons\/commons-csv\/1.12.0\/c77e053d7189bc0857f8d323ab61cb949965fbd1\/commons-csv-1.12.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-io\/commons-io\/2.17.0\/ddcc8433eb019fb48fe25207c0278143f3e1d7e2\/commons-io-2.17.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-codec\/commons-codec\/1.17.1\/973638b7149d333563584137ebf13a691bb60579\/commons-codec-1.17.1.jar:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/classes\/java\/main:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/resources\/main:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/org.apache.commons\/commons-csv\/1.12.0\/c77e053d7189bc0857f8d323ab61cb949965fbd1\/commons-csv-1.12.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-io\/commons-io\/2.17.0\/ddcc8433eb019fb48fe25207c0278143f3e1d7e2\/commons-io-2.17.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-codec\/commons-codec\/1.17.1\/973638b7149d333563584137ebf13a691bb60579\/commons-codec-1.17.1.jar:test.jar:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/classes\/java\/main:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/resources\/main:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/org.apache.commons\/commons-csv\/1.12.0\/c77e053d7189bc0857f8d323ab61cb949965fbd1\/commons-csv-1.12.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-io\/commons-io\/2.17.0\/ddcc8433eb019fb48fe25207c0278143f3e1d7e2\/commons-io-2.17.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-codec\/commons-codec\/1.17.1\/973638b7149d333563584137ebf13a691bb60579\/commons-codec-1.17.1.jar:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/classes\/java\/main:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/resources\/main:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/org.apache.commons\/commons-csv\/1.12.0\/c77e053d7189bc0857f8d323ab61cb949965fbd1\/commons-csv-1.12.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-io\/commons-io\/2.17.0\/ddcc8433eb019fb48fe25207c0278143f3e1d7e2\/commons-io-2.17.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-codec\/commons-codec\/1.17.1\/973638b7149d333563584137ebf13a691bb60579\/commons-codec-1.17.1.jar:test.jar:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/classes\/java\/main:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/resources\/main:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/org.apache.commons\/commons-csv\/1.12.0\/c77e053d7189bc0857f8d323ab61cb949965fbd1\/commons-csv-1.12.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-io\/commons-io\/2.17.0\/ddcc8433eb019fb48fe25207c0278143f3e1d7e2\/commons-io-2.17.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-codec\/commons-codec\/1.17.1\/973638b7149d333563584137ebf13a691bb60579\/commons-codec-1.17.1.jar:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/classes\/java\/main:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/resources\/main:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/org.apache.commons\/commons-csv\/1.12.0\/c77e053d7189bc0857f8d323ab61cb949965fbd1\/commons-csv-1.12.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-io\/commons-io\/2.17.0\/ddcc8433eb019fb48fe25207c0278143f3e1d7e2\/commons-io-2.17.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-codec\/commons-codec\/1.17.1\/973638b7149d333563584137ebf13a691bb60579\/commons-codec-1.17.1.jar:test.jar:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/classes\/java\/main:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/resources\/main:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/org.apache.commons\/commons-csv\/1.12.0\/c77e053d7189bc0857f8d323ab61cb949965fbd1\/commons-csv-1.12.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-io\/commons-io\/2.17.0\/ddcc8433eb019fb48fe25207c0278143f3e1d7e2\/commons-io-2.17.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-codec\/commons-codec\/1.17.1\/973638b7149d333563584137ebf13a691bb60579\/commons-codec-1.17.1.jar:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/classes\/java\/main:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/resources\/main:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/org.apache.commons\/commons-csv\/1.12.0\/c77e053d7189bc0857f8d323ab61cb949965fbd1\/commons-csv-1.12.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-io\/commons-io\/2.17.0\/ddcc8433eb019fb48fe25207c0278143f3e1d7e2\/commons-io-2.17.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-codec\/commons-codec\/1.17.1\/973638b7149d333563584137ebf13a691bb60579\/commons-codec-1.17.1.jar:test.jar:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/classes\/java\/main:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/resources\/main:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/org.apache.commons\/commons-csv\/1.12.0\/c77e053d7189bc0857f8d323ab61cb949965fbd1\/commons-csv-1.12.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-io\/commons-io\/2.17.0\/ddcc8433eb019fb48fe25207c0278143f3e1d7e2\/commons-io-2.17.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-codec\/commons-codec\/1.17.1\/973638b7149d333563584137ebf13a691bb60579\/commons-codec-1.17.1.jar:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/classes\/java\/main:\/Users\/ktoso\/code\/swift-java\/Samples\/JavaDependencySampleApp\/.build\/swift-java-dependencies-C358EF3D-93BF-4D44-9523-80865DF0B59D\/build\/resources\/main:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/org.apache.commons\/commons-csv\/1.12.0\/c77e053d7189bc0857f8d323ab61cb949965fbd1\/commons-csv-1.12.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-io\/commons-io\/2.17.0\/ddcc8433eb019fb48fe25207c0278143f3e1d7e2\/commons-io-2.17.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-codec\/commons-codec\/1.17.1\/973638b7149d333563584137ebf13a691bb60579\/commons-codec-1.17.1.jar:test.jar:test.jar:test.jar:test.jar:test.jar:test.jar:configure:test.jar:configure" +} diff --git a/Samples/JavaSieve/Package.swift b/Samples/JavaSieve/Package.swift index cd65f82ef..65c10481e 100644 --- a/Samples/JavaSieve/Package.swift +++ b/Samples/JavaSieve/Package.swift @@ -54,6 +54,7 @@ let package = Package( .product(name: "JavaKit", package: "swift-java"), .product(name: "JavaKitJar", package: "swift-java"), ], + exclude: ["swift-java.config"], swiftSettings: [ .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) ], @@ -71,6 +72,7 @@ let package = Package( .product(name: "JavaKit", package: "swift-java"), .product(name: "JavaKitCollection", package: "swift-java"), ], + exclude: ["swift-java.config"], swiftSettings: [ .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) ], diff --git a/Samples/JavaSieve/Sources/JavaSieve/main.swift b/Samples/JavaSieve/Sources/JavaSieve/main.swift index e20477134..feec792d1 100644 --- a/Samples/JavaSieve/Sources/JavaSieve/main.swift +++ b/Samples/JavaSieve/Sources/JavaSieve/main.swift @@ -15,10 +15,7 @@ import JavaKit import JavaMath -let jvm = try JavaVirtualMachine.shared(classpath: [ - "quadratic-sieve-Java/build/libs/QuadraticSieve-1.0.jar", - ".", -]) +let jvm = try JavaVirtualMachine.shared() do { let sieveClass = try JavaClass(environment: jvm.environment()) @@ -26,7 +23,7 @@ do { print("Found prime: \(prime.intValue())") } - try JavaClass().HALF_UP + _ = try JavaClass().HALF_UP // can import a Java enum value } catch { print("Failure: \(error)") } diff --git a/Samples/JavaSieve/Sources/JavaSieve/swift-java.config b/Samples/JavaSieve/Sources/JavaSieve/swift-java.config index 7e055d1c9..40d01d4b9 100644 --- a/Samples/JavaSieve/Sources/JavaSieve/swift-java.config +++ b/Samples/JavaSieve/Sources/JavaSieve/swift-java.config @@ -29,3 +29,4 @@ "com.gazman.quadratic_sieve.wheel.Wheel" : "Wheel" } } + diff --git a/Sources/JavaKitConfigurationShared/Configuration.swift b/Sources/JavaKitConfigurationShared/Configuration.swift index 2314a1b8e..2298b9fa0 100644 --- a/Sources/JavaKitConfigurationShared/Configuration.swift +++ b/Sources/JavaKitConfigurationShared/Configuration.swift @@ -137,13 +137,13 @@ public func readConfiguration(configPath: URL, file: String = #fileID, line: UIn } } -public func findSwiftJavaClasspaths(moduleName: String) -> [String] { +public func findSwiftJavaClasspaths(swiftModule: String) -> [String] { let basePath: String = FileManager.default.currentDirectoryPath let pluginOutputsDir = URL(fileURLWithPath: basePath) .appendingPathComponent(".build", isDirectory: true) .appendingPathComponent("plugins", isDirectory: true) .appendingPathComponent("outputs", isDirectory: true) - .appendingPathComponent(moduleName, isDirectory: true) + .appendingPathComponent(swiftModule, isDirectory: true) return findSwiftJavaClasspaths(in: pluginOutputsDir.path) } diff --git a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift new file mode 100644 index 000000000..8574888d4 --- /dev/null +++ b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift @@ -0,0 +1,238 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import ArgumentParser +import Foundation +import SwiftJavaLib +import JExtractSwiftLib +import JavaKit +import JavaKitJar +import JavaKitNetwork +import JavaKitReflection +import SwiftSyntax +import SwiftSyntaxBuilder +import JavaKitConfigurationShared +import JavaKitShared + +extension SwiftJava { + struct ConfigureCommand: SwiftJavaBaseAsyncParsableCommand, HasCommonOptions, HasCommonJVMOptions { + static let configuration = CommandConfiguration( + commandName: "configure", + abstract: "Configure and emit a swift-java.config file based on an input dependency or jar file") + + @OptionGroup var commonOptions: SwiftJava.CommonOptions + @OptionGroup var commonJVMOptions: SwiftJava.CommonJVMOptions + + // TODO: This should be a "make wrappers" option that just detects when we give it a jar + @Flag( + help: "Specifies that the input is a *.jar file whose public classes will be loaded. The output of swift-java will be a configuration file (swift-java.config) that can be used as input to a subsequent swift-java invocation to generate wrappers for those public classes." + ) + var jar: Bool = false + + @Option( + name: .long, + help: "How to handle an existing swift-java.config; by default 'overwrite' by can be changed to amending a configuration" + ) + var existingConfigFile: ExistingConfigFileMode = .overwrite + enum ExistingConfigFileMode: String, ExpressibleByArgument, Codable { + case overwrite + case amend + } + + @Option(help: "The name of the Swift module into which the resulting Swift types will be generated.") + var swiftModule: String + + var effectiveSwiftModule: String { + swiftModule + } + + @Argument( + help: "The input file, which is either a swift-java configuration file or (if '-jar' was specified) a Jar file." + ) + var input: String? + } +} + +extension SwiftJava.ConfigureCommand { + mutating func runSwiftJavaCommand(config: inout Configuration) async throws { + // Form a class path from all of our input sources: + // * Command-line option --classpath + let classpathOptionEntries: [String] = self.commonJVMOptions.classpath.flatMap { $0.split(separator: ":").map(String.init) } + let classpathFromEnv = ProcessInfo.processInfo.environment["CLASSPATH"]?.split(separator: ":").map(String.init) ?? [] + let classpathFromConfig: [String] = config.classpath?.split(separator: ":").map(String.init) ?? [] + print("[debug][swift-java] Base classpath from config: \(classpathFromConfig)") + + var classpathEntries: [String] = classpathFromConfig + + let swiftJavaCachedModuleClasspath = findSwiftJavaClasspaths(in: + // self.effectiveCacheDirectory ?? + FileManager.default.currentDirectoryPath) + print("[debug][swift-java] Classpath from *.swift-java.classpath files: \(swiftJavaCachedModuleClasspath)") + classpathEntries += swiftJavaCachedModuleClasspath + + if !classpathOptionEntries.isEmpty { + print("[debug][swift-java] Classpath from options: \(classpathOptionEntries)") + classpathEntries += classpathOptionEntries + } else { + // * Base classpath from CLASSPATH env variable + print("[debug][swift-java] Classpath from environment: \(classpathFromEnv)") + classpathEntries += classpathFromEnv + } + + let extraClasspath = input ?? "" // FIXME: just use the -cp as usual + let extraClasspathEntries = extraClasspath.split(separator: ":").map(String.init) + print("[debug][swift-java] Extra classpath: \(extraClasspathEntries)") + classpathEntries += extraClasspathEntries + + // Bring up the Java VM when necessary + + if logLevel >= .debug { + let classpathString = classpathEntries.joined(separator: ":") + print("[debug][swift-java] Initialize JVM with classpath: \(classpathString)") + } + let jvm = try JavaVirtualMachine.shared(classpath: classpathEntries) + + try emitConfiguration(classpath: self.commonJVMOptions.classpath, environment: jvm.environment()) + } + + /// Get base configuration, depending on if we are to 'amend' or 'overwrite' the existing configuration. + func getBaseConfigurationForWrite() throws -> (Bool, Configuration) { + guard let actualOutputDirectory = self.actualOutputDirectory else { + // If output has no path there's nothing to amend + return (false, .init()) + } + + switch self.existingConfigFile { + case .overwrite: + // always make up a fresh instance if we're overwriting + return (false, .init()) + case .amend: + let configPath = actualOutputDirectory + guard let config = try readConfiguration(sourceDir: configPath.path) else { + return (false, .init()) + } + return (true, config) + } + } + + // TODO: make this perhaps "emit type mappings" + mutating func emitConfiguration( + classpath: [String], + environment: JNIEnvironment + ) throws { + if let filterJavaPackage = self.commonJVMOptions.filterJavaPackage { + print("[java-swift][debug] Generate Java->Swift type mappings. Active filter: \(filterJavaPackage)") + } + print("[java-swift][debug] Classpath: \(classpath)") + + if classpath.isEmpty { + print("[java-swift][warning] Classpath is empty!") + } + + // Get a fresh or existing configuration we'll amend + var (amendExistingConfig, configuration) = try getBaseConfigurationForWrite() + if amendExistingConfig { + print("[swift-java] Amend existing swift-java.config file...") + } + configuration.classpath = classpath.joined(separator: ":") // TODO: is this correct? + + // Import types from all the classpath entries; + // Note that we use the package level filtering, so users have some control over what gets imported. + let classpathEntries = classpath.split(separator: ":").map(String.init) + for entry in classpathEntries { + guard fileOrDirectoryExists(at: entry) else { + // We only log specific jars missing, as paths may be empty directories that won't hurt not existing. + print("[debug][swift-java] Classpath entry does not exist: \(entry)") + continue + } + + print("[debug][swift-java] Importing classpath entry: \(entry)") + if entry.hasSuffix(".jar") { + let jarFile = try JarFile(entry, false, environment: environment) + try addJavaToSwiftMappings( + to: &configuration, + forJar: jarFile, + environment: environment + ) + } else if FileManager.default.fileExists(atPath: entry) { + print("[warning][swift-java] Currently unable handle directory classpath entries for config generation! Skipping: \(entry)") + } else { + print("[warning][swift-java] Classpath entry does not exist, skipping: \(entry)") + } + } + + // Encode the configuration. + let contents = try configuration.renderJSON() + + // Write the file. + try writeContents( + contents, + to: "swift-java.config", + description: "swift-java configuration file" + ) + } + + mutating func addJavaToSwiftMappings( + to configuration: inout Configuration, + forJar jarFile: JarFile, + environment: JNIEnvironment + ) throws { + for entry in jarFile.entries()! { + // We only look at class files in the Jar file. + guard entry.getName().hasSuffix(".class") else { + continue + } + + // Skip some "common" files we know that would be duplicated in every jar + guard !entry.getName().hasPrefix("META-INF") else { + continue + } + guard !entry.getName().hasSuffix("package-info") else { + continue + } + guard !entry.getName().hasSuffix("package-info.class") else { + continue + } + + // If this is a local class, it cannot be mapped into Swift. + if entry.getName().isLocalJavaClass { + continue + } + + let javaCanonicalName = String(entry.getName().replacing("/", with: ".") + .dropLast(".class".count)) + + if let filterJavaPackage = self.commonJVMOptions.filterJavaPackage, + !javaCanonicalName.hasPrefix(filterJavaPackage) { + // Skip classes which don't match our expected prefix + continue + } + + if configuration.classes?[javaCanonicalName] != nil { + // We never overwrite an existing class mapping configuration. + // E.g. the user may have configured a custom name for a type. + continue + } + + configuration.classes?[javaCanonicalName] = + javaCanonicalName.defaultSwiftNameForJavaClass + } + } + +} + +package func fileOrDirectoryExists(at path: String) -> Bool { + var isDirectory: ObjCBool = false + return FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory) +} \ No newline at end of file diff --git a/Sources/SwiftJavaTool/SwiftJava+FetchDependencies.swift b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift similarity index 86% rename from Sources/SwiftJavaTool/SwiftJava+FetchDependencies.swift rename to Sources/SwiftJavaTool/Commands/ResolveCommand.swift index 47570b199..eb96e4904 100644 --- a/Sources/SwiftJavaTool/SwiftJava+FetchDependencies.swift +++ b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Copyright (c) 2024-2025 Apple Inc. and the Swift.org project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +import ArgumentParser import Foundation import SwiftJavaLib import JavaKit @@ -23,12 +24,37 @@ import JavaKitShared import _Subprocess extension SwiftJava { + struct ResolveCommand: SwiftJavaBaseAsyncParsableCommand { + static let configuration = CommandConfiguration( + commandName: "resolve", + abstract: "Resolve dependencies and write the resulting swift-java.classpath file") + @OptionGroup var commonOptions: SwiftJava.CommonOptions + + @Option(help: "The name of the Swift module into which the resulting Swift types will be generated.") + var swiftModule: String + + var effectiveSwiftModule: String { + swiftModule + } + + } +} + +extension SwiftJava.ResolveCommand { + mutating func runSwiftJavaCommand(config: inout Configuration) async throws { + fatalError("NOT IMPLEMENTED: resolve") + } +} + + + +extension SwiftJava { var SwiftJavaClasspathPrefix: String { "SWIFT_JAVA_CLASSPATH:" } var printRuntimeClasspathTaskName: String { "printRuntimeClasspath" } - func fetchDependencies(moduleName: String, + func fetchDependencies(swiftModule: String, dependencies: [JavaDependencyDescriptor]) async throws -> ResolvedDependencyClasspath { let deps = dependencies.map { $0.descriptionGradleStyle } print("[debug][swift-java] Resolve and fetch dependencies for: \(deps)") @@ -37,7 +63,7 @@ extension SwiftJava { let classpathEntries = dependenciesClasspath.split(separator: ":") - print("[info][swift-java] Resolved classpath for \(deps.count) dependencies of '\(moduleName)', classpath entries: \(classpathEntries.count), ", terminator: "") + print("[info][swift-java] Resolved classpath for \(deps.count) dependencies of '\(swiftModule)', classpath entries: \(classpathEntries.count), ", terminator: "") print("done.".green) for entry in classpathEntries { @@ -128,7 +154,7 @@ extension SwiftJava { } mutating func writeFetchedDependenciesClasspath( - moduleName: String, + swiftModule: String, cacheDir: String, resolvedClasspath: ResolvedDependencyClasspath) throws { // Convert the artifact name to a module name @@ -137,14 +163,14 @@ extension SwiftJava { // The file contents are just plain let contents = resolvedClasspath.classpath - print("[debug][swift-java] Resolved dependency: \(classpath)") + print("[debug][swift-java] Resolved dependency: \(commonJVMOptions.classpath)") // Write the file try writeContents( contents, outputDirectoryOverride: URL(fileURLWithPath: cacheDir), - to: "\(moduleName).swift-java.classpath", - description: "swift-java.classpath file for module \(moduleName)" + to: "\(swiftModule).swift-java.classpath", + description: "swift-java.classpath file for module \(swiftModule)" ) } diff --git a/Sources/SwiftJavaTool/SwiftJava+GenerateWrappers.swift b/Sources/SwiftJavaTool/Commands/SwiftJava+GenerateWrappers.swift similarity index 95% rename from Sources/SwiftJavaTool/SwiftJava+GenerateWrappers.swift rename to Sources/SwiftJavaTool/Commands/SwiftJava+GenerateWrappers.swift index a57644dea..676b278d9 100644 --- a/Sources/SwiftJavaTool/SwiftJava+GenerateWrappers.swift +++ b/Sources/SwiftJavaTool/Commands/SwiftJava+GenerateWrappers.swift @@ -27,11 +27,8 @@ extension SwiftJava { dependentConfigs: [(String, Configuration)], environment: JNIEnvironment ) throws { - guard let moduleName else { - fatalError("--module-name must be set in 'generate wrappers' mode!") - } let translator = JavaTranslator( - swiftModuleName: moduleName, + swiftModuleName: effectiveSwiftModule, environment: environment, translateAsClass: true ) @@ -49,7 +46,7 @@ extension SwiftJava { } // Add the configuration for this module. - translator.addConfiguration(config, forSwiftModule: moduleName) + translator.addConfiguration(config, forSwiftModule: effectiveSwiftModule) // Load all of the explicitly-requested classes. let classLoader = try JavaClass(environment: environment) diff --git a/Sources/SwiftJavaTool/SwiftJava+JExtract.swift b/Sources/SwiftJavaTool/Commands/SwiftJava+JExtract.swift similarity index 100% rename from Sources/SwiftJavaTool/SwiftJava+JExtract.swift rename to Sources/SwiftJavaTool/Commands/SwiftJava+JExtract.swift diff --git a/Sources/SwiftJavaTool/CommonOptions.swift b/Sources/SwiftJavaTool/CommonOptions.swift new file mode 100644 index 000000000..43a35a5da --- /dev/null +++ b/Sources/SwiftJavaTool/CommonOptions.swift @@ -0,0 +1,59 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import ArgumentParser +import Foundation +import SwiftJavaLib +import JExtractSwiftLib +import JavaKit +import JavaKitJar +import JavaKitNetwork +import JavaKitReflection +import SwiftSyntax +import SwiftSyntaxBuilder +import JavaKitConfigurationShared +import JavaKitShared + +protocol HasCommonOptions { + var commonOptions: SwiftJava.CommonOptions { get set } +} + +protocol HasCommonJVMOptions { + var commonJVMOptions: SwiftJava.CommonJVMOptions { get set } +} + +extension SwiftJava { + struct CommonOptions: ParsableArguments { + // TODO: clarify this vs outputSwift (history: outputSwift is jextract, and this was java2swift) + @Option(name: .shortAndLong, help: "The directory in which to output the generated Swift files or the SwiftJava configuration file.") + var outputDirectory: String? = nil + + @Option(help: "Directory containing Swift files which should be extracted into Java bindings. Also known as 'jextract' mode. Must be paired with --output-java and --output-swift.") + var inputSwift: String? = nil + + @Option(name: .shortAndLong, help: "Configure the level of logs that should be printed") + var logLevel: Logger.Level = .info + } + + struct CommonJVMOptions: ParsableArguments { + @Option( + name: [.customLong("cp"), .customLong("classpath")], + help: "Class search path of directories and zip/jar files from which Java classes can be loaded." + ) + var classpath: [String] = [] + + @Option(name: .shortAndLong, help: "While scanning a classpath, inspect only types included in this package") + var filterJavaPackage: String? = nil + } +} \ No newline at end of file diff --git a/Sources/SwiftJavaTool/Java/JavaClassLoader.swift b/Sources/SwiftJavaTool/Java/JavaClassLoader.swift new file mode 100644 index 000000000..41492d537 --- /dev/null +++ b/Sources/SwiftJavaTool/Java/JavaClassLoader.swift @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftJavaLib +import JavaKitShared +import JavaRuntime +import JavaKit + +@JavaClass("java.lang.ClassLoader") +public struct ClassLoader { + @JavaMethod + public func loadClass(_ arg0: String) throws -> JavaClass? +} + +extension JavaClass { + @JavaStaticMethod + public func getSystemClassLoader() -> ClassLoader? +} \ No newline at end of file diff --git a/Sources/SwiftJavaTool/SwiftJava+EmitConfiguration.swift b/Sources/SwiftJavaTool/SwiftJava+EmitConfiguration.swift deleted file mode 100644 index e029d2db6..000000000 --- a/Sources/SwiftJavaTool/SwiftJava+EmitConfiguration.swift +++ /dev/null @@ -1,120 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import Foundation -import ArgumentParser -import SwiftJavaLib -import JavaKit -import JavaKitJar -import JavaKitConfigurationShared - -extension SwiftJava { - - // TODO: make this perhaps "emit type mappings" - mutating func emitConfiguration( - classpath: String, - environment: JNIEnvironment - ) throws { - print("[java-swift] Generate Java->Swift type mappings. Active filter: \(javaPackageFilter)") - print("[java-swift] Classpath: \(classpath)") - - if classpath.isEmpty { - print("[warning][java-swift] Classpath is empty!") - } - - // Get a fresh or existing configuration we'll amend - var (amendExistingConfig, configuration) = try getBaseConfigurationForWrite() - if amendExistingConfig { - print("[swift-java] Amend existing swift-java.config file...") - } - configuration.classpath = classpath // TODO: is this correct? - - // Import types from all the classpath entries; - // Note that we use the package level filtering, so users have some control over what gets imported. - for entry in classpath.split(separator: ":").map(String.init) { - print("[debug][swift-java] Importing classpath entry: \(entry)") - if entry.hasSuffix(".jar") { - let jarFile = try JarFile(entry, false, environment: environment) - try addJavaToSwiftMappings( - to: &configuration, - forJar: jarFile, - environment: environment - ) - } else if FileManager.default.fileExists(atPath: entry) { - print("[warning][swift-java] Currently unable handle directory classpath entries for config generation! Skipping: \(entry)") - } else { - print("[warning][swift-java] Classpath entry does not exist, skipping: \(entry)") - } - } - - // Encode the configuration. - let contents = try configuration.renderJSON() - - // Write the file. - try writeContents( - contents, - to: "swift-java.config", - description: "swift-java configuration file" - ) - } - - mutating func addJavaToSwiftMappings( - to configuration: inout Configuration, - forJar jarFile: JarFile, - environment: JNIEnvironment - ) throws { - for entry in jarFile.entries()! { - // We only look at class files in the Jar file. - guard entry.getName().hasSuffix(".class") else { - continue - } - - // Skip some "common" files we know that would be duplicated in every jar - guard !entry.getName().hasPrefix("META-INF") else { - continue - } - guard !entry.getName().hasSuffix("package-info") else { - continue - } - guard !entry.getName().hasSuffix("package-info.class") else { - continue - } - - // If this is a local class, it cannot be mapped into Swift. - if entry.getName().isLocalJavaClass { - continue - } - - let javaCanonicalName = String(entry.getName().replacing("/", with: ".") - .dropLast(".class".count)) - - if let javaPackageFilter { - if !javaCanonicalName.hasPrefix(javaPackageFilter) { - // Skip classes which don't match our expected prefix - continue - } - } - - if configuration.classes?[javaCanonicalName] != nil { - // We never overwrite an existing class mapping configuration. - // E.g. the user may have configured a custom name for a type. - continue - } - - configuration.classes?[javaCanonicalName] = - javaCanonicalName.defaultSwiftNameForJavaClass - } - } - -} \ No newline at end of file diff --git a/Sources/SwiftJavaTool/SwiftJava.swift b/Sources/SwiftJavaTool/SwiftJava.swift index cae7a2a4c..0e6e64ae6 100644 --- a/Sources/SwiftJavaTool/SwiftJava.swift +++ b/Sources/SwiftJavaTool/SwiftJava.swift @@ -27,11 +27,22 @@ import JavaKitShared /// Command-line utility to drive the export of Java classes into Swift types. @main -struct SwiftJava: AsyncParsableCommand { +struct SwiftJava: SwiftJavaBaseAsyncParsableCommand { // FIXME: this is just a normal async command, no parsing happening here static var _commandName: String { "swift-java" } + static let configuration = CommandConfiguration( + abstract: "Generate sources and configuration for Swift and Java interoperability.", + subcommands: [ + ConfigureCommand.self, + ResolveCommand.self, + ]) + @Option(help: "The name of the Swift module into which the resulting Swift types will be generated.") - var moduleName: String? // TODO: rename to --swift-module? + var swiftModule: String? + + var effectiveSwiftModule: String { + swiftModule ?? "UnknownSwiftModule" + } @Option( help: @@ -39,30 +50,14 @@ struct SwiftJava: AsyncParsableCommand { ) var dependsOn: [String] = [] - // TODO: This should be a "make wrappers" option that just detects when we give it a jar - @Flag( - help: - "Specifies that the input is a Jar file whose public classes will be loaded. The output of Java2Swift will be a configuration file (Java2Swift.config) that can be used as input to a subsequent Java2Swift invocation to generate wrappers for those public classes." - ) - var jar: Bool = false - @Flag(help: "Fetch dependencies from given target (containing swift-java configuration) or dependency string") var fetch: Bool = false - @Option( - name: [.customLong("cp"), .customLong("classpath")], - help: "Class search path of directories and zip/jar files from which Java classes can be loaded." - ) - var classpath: [String] = [] - @Option( help: "The names of Java classes whose declared native methods will be implemented in Swift." ) var swiftNativeImplementation: [String] = [] - @Option(help: "Directory containing Swift files which should be extracted into Java bindings. Also known as 'jextract' mode. Must be paired with --output-java and --output-swift.") - var inputSwift: String? = nil - @Option(help: "The directory where generated Swift files should be written. Generally used with jextract mode.") var outputSwift: String? = nil @@ -75,107 +70,36 @@ struct SwiftJava: AsyncParsableCommand { @Option(help: "The mode of generation to use for the output files. Used with jextract mode.") var mode: GenerationMode = .ffm - // TODO: clarify this vs outputSwift (history: outputSwift is jextract, and this was java2swift) - @Option(name: .shortAndLong, help: "The directory in which to output the generated Swift files or the SwiftJava configuration file.") - var outputDirectory: String? = nil +// // TODO: clarify this vs outputSwift (history: outputSwift is jextract, and this was java2swift) +// @Option(name: .shortAndLong, help: "The directory in which to output the generated Swift files or the SwiftJava configuration file.") +// var outputDirectory: String? = nil @Option(name: .shortAndLong, help: "Directory where to write cached values (e.g. swift-java.classpath files)") var cacheDirectory: String? = nil - @Option(name: .shortAndLong, help: "Configure the level of logs that should be printed") - var logLevel: Logger.Level = .info + @OptionGroup var commonOptions: SwiftJava.CommonOptions + @OptionGroup var commonJVMOptions: SwiftJava.CommonJVMOptions var effectiveCacheDirectory: String? { if let cacheDirectory { return cacheDirectory - } else if let outputDirectory { + } else if let outputDirectory = commonOptions.outputDirectory { return outputDirectory } else { return nil } } - - @Option(name: .shortAndLong, help: "How to handle an existing swift-java.config; by default 'overwrite' by can be changed to amending a configuration") - var existingConfig: ExistingConfigFileMode = .overwrite - public enum ExistingConfigFileMode: String, ExpressibleByArgument, Codable { - case overwrite - case amend - } - - @Option(name: .shortAndLong, help: "While scanning a classpath, inspect only types included in this package") - var javaPackageFilter: String? = nil @Argument( help: "The input file, which is either a Java2Swift configuration file or (if '-jar' was specified) a Jar file." ) - var input: String? - - /// Whether we have ensured that the output directory exists. - var createdOutputDirectory: Bool = false - - var moduleBaseDir: Foundation.URL? { - if let outputDirectory { - if outputDirectory == "-" { - return nil - } - - print("[debug][swift-java] Module base directory based on outputDirectory!") - return URL(fileURLWithPath: outputDirectory) - } - - guard let moduleName else { - return nil - } - - // Put the result into Sources/\(moduleName). - let baseDir = URL(fileURLWithPath: ".") - .appendingPathComponent("Sources", isDirectory: true) - .appendingPathComponent(moduleName, isDirectory: true) - - return baseDir - } - - /// The output directory in which to place the generated files, which will - /// be the specified directory (--output-directory or -o option) if given, - /// or a default directory derived from the other command-line arguments. - /// - /// Returns `nil` only when we should emit the files to standard output. - var actualOutputDirectory: Foundation.URL? { - if let outputDirectory { - if outputDirectory == "-" { - return nil - } - - return URL(fileURLWithPath: outputDirectory) - } - - guard let moduleName else { - fatalError("--module-name must be set!") - } - - // Put the result into Sources/\(moduleName). - let baseDir = URL(fileURLWithPath: ".") - .appendingPathComponent("Sources", isDirectory: true) - .appendingPathComponent(moduleName, isDirectory: true) - - // For generated Swift sources, put them into a "generated" subdirectory. - // The configuration file goes at the top level. - let outputDir: Foundation.URL - if jar { - precondition(self.input != nil, "-jar mode requires path to jar to be specified as input path") - outputDir = baseDir - } else { - outputDir = baseDir - .appendingPathComponent("generated", isDirectory: true) - } - - return outputDir - } + var input: String? // FIXME: top level command cannot have input argument like this + // FIXME: this is subcommands /// Describes what kind of generation action is being performed by swift-java. enum ToolMode { - /// Generate a configuration file given a Jar file. - case configuration(extraClasspath: String) // FIXME: this is more like "extract" configuration from classpath + // /// Generate a configuration file given a Jar file. + // case configuration(extraClasspath: String) // FIXME: this is more like "extract" configuration from classpath /// Generate Swift wrappers for Java classes based on the given /// configuration. @@ -188,216 +112,180 @@ struct SwiftJava: AsyncParsableCommand { case jextract // TODO: carry jextract specific config here? } - mutating func run() async { - guard CommandLine.arguments.count > 1 else { + mutating func runSwiftJavaCommand(config: inout Configuration) async throws { + guard CommandLine.arguments.count > 2 else { // there's no "default" command, print USAGE when no arguments/parameters are passed. - print("Must specify run mode.\n\(Self.helpMessage())") + print("error: Must specify mode subcommand (e.g. configure, resolve, jextract, ...).\n\n\(Self.helpMessage())") return } - print("[info][swift-java] Run: \(CommandLine.arguments.joined(separator: " "))") - print("[info][swift-java] Current work directory: \(URL(fileURLWithPath: "."))") - print("[info][swift-java] Module base directory: \(moduleBaseDir)") - do { - var earlyConfig: Configuration? - if let moduleBaseDir { - print("[debug][swift-java] Load config from module base directory: \(moduleBaseDir.path)") - earlyConfig = try readConfiguration(sourceDir: moduleBaseDir.path) - } else if let inputSwift { - print("[debug][swift-java] Load config from module swift input directory: \(inputSwift)") - earlyConfig = try readConfiguration(sourceDir: inputSwift) - } - var config = earlyConfig ?? Configuration() + if let javaPackage { + config.javaPackage = javaPackage + } - config.logLevel = self.logLevel - if let javaPackage { - config.javaPackage = javaPackage + // Determine the mode in which we'll execute. + let toolMode: ToolMode + // TODO: some options are exclusive to each other so we should detect that + if let inputSwift = commonOptions.inputSwift { + guard let inputSwift = commonOptions.inputSwift else { + print("[swift-java] --input-swift enabled 'jextract' mode, however no --output-swift directory was provided!\n\(Self.helpMessage())") + return + } + guard let outputSwift else { + print("[swift-java] --output-swift enabled 'jextract' mode, however no --output-swift directory was provided!\n\(Self.helpMessage())") + return + } + guard let outputJava else { + print("[swift-java] --output-java enabled 'jextract' mode, however no --output-java directory was provided!\n\(Self.helpMessage())") + return + } + config.swiftModule = self.swiftModule ?? "UnknownModule" + config.inputSwiftDirectory = inputSwift + config.outputSwiftDirectory = outputSwift + config.outputJavaDirectory = outputJava + + toolMode = .jextract +// } else if jar { +// guard let input else { +// fatalError("Mode -jar requires path\n\(Self.helpMessage())") +// } +// toolMode = .configuration(extraClasspath: input) + } else if fetch { + guard let input else { + fatalError("Mode 'fetch' requires path\n\(Self.helpMessage())") } + config = try JavaTranslator.readConfiguration(from: URL(fileURLWithPath: input)) + guard let dependencies = config.dependencies else { + print("[swift-java] Running in 'fetch dependencies' mode but dependencies list was empty!") + print("[swift-java] Nothing to do: done.") + return + } + toolMode = .fetchDependencies + } else { + guard let input else { + fatalError("Mode -jar requires path\n\(Self.helpMessage())") + } + config = try JavaTranslator.readConfiguration(from: URL(fileURLWithPath: input)) + toolMode = .classWrappers + } - // Determine the mode in which we'll execute. - let toolMode: ToolMode - // TODO: some options are exclusive to each other so we should detect that - if let inputSwift { - guard let outputSwift else { - print("[swift-java] --input-swift enabled 'jextract' mode, however no --output-swift directory was provided!\n\(Self.helpMessage())") - return - } - guard let outputJava else { - print("[swift-java] --input-swift enabled 'jextract' mode, however no --output-java directory was provided!\n\(Self.helpMessage())") - return - } - config.swiftModule = self.moduleName // FIXME: rename the moduleName - config.inputSwiftDirectory = self.inputSwift - config.outputSwiftDirectory = self.outputSwift - config.outputJavaDirectory = self.outputJava - config.mode = self.mode - - toolMode = .jextract - } else if jar { - guard let input else { - fatalError("Mode -jar requires path\n\(Self.helpMessage())") - } - toolMode = .configuration(extraClasspath: input) - } else if fetch { - guard let input else { - fatalError("Mode 'fetch' requires path\n\(Self.helpMessage())") - } - config = try JavaTranslator.readConfiguration(from: URL(fileURLWithPath: input)) - guard let dependencies = config.dependencies else { - print("[swift-java] Running in 'fetch dependencies' mode but dependencies list was empty!") - print("[swift-java] Nothing to do: done.") - return - } - toolMode = .fetchDependencies - } else { - guard let input else { - fatalError("Mode -jar requires path\n\(Self.helpMessage())") - } - config = try JavaTranslator.readConfiguration(from: URL(fileURLWithPath: input)) - toolMode = .classWrappers + print("[debug][swift-java] Running swift-java in mode: " + "\(toolMode.prettyName)".bold) + + let swiftModule: String = + self.swiftModule ?? + self.effectiveSwiftModule.split(separator: "/").dropLast().last.map(String.init) ?? "__UnknownModule" + + // Load all of the dependent configurations and associate them with Swift + // modules. + let dependentConfigs = try dependsOn.map { dependentConfig in + guard let equalLoc = dependentConfig.firstIndex(of: "=") else { + throw JavaToSwiftError.badConfigOption(dependentConfig) } - print("[debug][swift-java] Running swift-java in mode: " + "\(toolMode.prettyName)".bold) + let afterEqual = dependentConfig.index(after: equalLoc) + let swiftModuleName = String(dependentConfig[.. (javaClassName: String, swiftName: String) { @@ -421,65 +309,6 @@ struct SwiftJava: AsyncParsableCommand { return (javaClassName, swiftName.javaClassNameToCanonicalName) } - mutating func writeContents( - _ contents: String, - to filename: String, description: String) throws { - try writeContents( - contents, - outputDirectoryOverride: self.actualOutputDirectory, - to: filename, - description: description) - } - - mutating func writeContents( - _ contents: String, - outputDirectoryOverride: Foundation.URL?, - to filename: String, - description: String) throws { - guard let outputDir = (outputDirectoryOverride ?? actualOutputDirectory) else { - print("// \(filename) - \(description)") - print(contents) - return - } - - // If we haven't tried to create the output directory yet, do so now before - // we write any files to it. - if !createdOutputDirectory { - try FileManager.default.createDirectory( - at: outputDir, - withIntermediateDirectories: true - ) - createdOutputDirectory = true - } - - // Write the file: - let file = outputDir.appendingPathComponent(filename) - print("[debug][swift-java] Writing \(description) to '\(file.path)'... ", terminator: "") - try contents.write(to: file, atomically: true, encoding: .utf8) - print("done.".green) - } -} - -extension SwiftJava { - /// Get base configuration, depending on if we are to 'amend' or 'overwrite' the existing configuration. - package func getBaseConfigurationForWrite() throws -> (Bool, Configuration) { - guard let actualOutputDirectory = self.actualOutputDirectory else { - // If output has no path there's nothing to amend - return (false, .init()) - } - - switch self.existingConfig { - case .overwrite: - // always make up a fresh instance if we're overwriting - return (false, .init()) - case .amend: - let configPath = actualOutputDirectory - guard let config = try readConfiguration(sourceDir: configPath.path) else { - return (false, .init()) - } - return (true, config) - } - } } enum JavaToSwiftError: Error { @@ -495,21 +324,9 @@ extension JavaToSwiftError: CustomStringConvertible { } } -@JavaClass("java.lang.ClassLoader") -public struct ClassLoader { - @JavaMethod - public func loadClass(_ arg0: String) throws -> JavaClass? -} - -extension JavaClass { - @JavaStaticMethod - public func getSystemClassLoader() -> ClassLoader? -} - extension SwiftJava.ToolMode { var prettyName: String { switch self { - case .configuration: "Configuration" case .fetchDependencies: "Fetch dependencies" case .classWrappers: "Wrap Java classes" case .jextract: "JExtract Swift for Java" diff --git a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift new file mode 100644 index 000000000..afe4f80c2 --- /dev/null +++ b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift @@ -0,0 +1,181 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import ArgumentParser +import Foundation +import SwiftJavaLib +import JExtractSwiftLib +import JavaKit +import JavaKitJar +import JavaKitNetwork +import JavaKitReflection +import SwiftSyntax +import SwiftSyntaxBuilder +import JavaKitConfigurationShared +import JavaKitShared + +protocol SwiftJavaBaseAsyncParsableCommand: AsyncParsableCommand { + var logLevel: Logger.Level { get set } + + var commonOptions: SwiftJava.CommonOptions { get set } + + var effectiveSwiftModule: String { get } + + mutating func runSwiftJavaCommand(config: inout Configuration) async throws + +} + +extension SwiftJavaBaseAsyncParsableCommand { + public mutating func run() async { + print("[info][swift-java] Run: \(CommandLine.arguments.joined(separator: " "))") + print("[info][swift-java] Current work directory: \(URL(fileURLWithPath: "."))") + + do { + var config = try readInitialConfiguration(command: self) + try await runSwiftJavaCommand(config: &config) + } catch { + // We fail like this since throwing out of the run often ends up hiding the failure reason when it is executed as SwiftPM plugin (!) + let message = "Failed with error: \(error)" + print("[error][java-swift] \(message)") + fatalError(message) + } + + // Just for debugging so it is clear which command has finished + print("[debug][swift-java] " + "Done: ".green + CommandLine.arguments.joined(separator: " ").green) + } +} + +extension SwiftJavaBaseAsyncParsableCommand { + mutating func writeContents( + _ contents: String, + to filename: String, description: String) throws { + try writeContents( + contents, + outputDirectoryOverride: self.actualOutputDirectory, + to: filename, + description: description) + } + + mutating func writeContents( + _ contents: String, + outputDirectoryOverride: Foundation.URL?, + to filename: String, + description: String) throws { + guard let outputDir = (outputDirectoryOverride ?? actualOutputDirectory) else { + print("// \(filename) - \(description)") + print(contents) + return + } + + // If we haven't tried to create the output directory yet, do so now before + // we write any files to it. + // if !createdOutputDirectory { + try FileManager.default.createDirectory( + at: outputDir, + withIntermediateDirectories: true + ) + // createdOutputDirectory = true + //} + + // Write the file: + let file = outputDir.appendingPathComponent(filename) + print("[debug][swift-java] Writing \(description) to '\(file.path)'... ", terminator: "") + try contents.write(to: file, atomically: true, encoding: .utf8) + print("done.".green) + } +} + + +extension SwiftJavaBaseAsyncParsableCommand { + var logLevel: Logger.Level { + get { + self.commonOptions.logLevel + } + set { + self.commonOptions.logLevel = newValue + } + } +} +extension SwiftJavaBaseAsyncParsableCommand { + + var moduleBaseDir: Foundation.URL? { +// if let outputDirectory = commonOptions.outputDirectory { +// if outputDirectory == "-" { +// return nil +// } +// +// print("[debug][swift-java] Module base directory based on outputDirectory!") +// return URL(fileURLWithPath: outputDirectory) +// } + +// guard let swiftModule else { +// return nil +// } + + // Put the result into Sources/\(swiftModule). + let baseDir = URL(fileURLWithPath: ".") + .appendingPathComponent("Sources", isDirectory: true) + .appendingPathComponent(self.effectiveSwiftModule, isDirectory: true) + + return baseDir + } + + /// The output directory in which to place the generated files, which will + /// be the specified directory (--output-directory or -o option) if given, + /// or a default directory derived from the other command-line arguments. + /// + /// Returns `nil` only when we should emit the files to standard output. + var actualOutputDirectory: Foundation.URL? { + if let outputDirectory = commonOptions.outputDirectory { + if outputDirectory == "-" { + return nil + } + + return URL(fileURLWithPath: outputDirectory) + } + + // Put the result into Sources/\(swiftModule). + let baseDir = URL(fileURLWithPath: ".") + .appendingPathComponent("Sources", isDirectory: true) + .appendingPathComponent(effectiveSwiftModule, isDirectory: true) + + // For generated Swift sources, put them into a "generated" subdirectory. + // The configuration file goes at the top level. + let outputDir: Foundation.URL + // if jar { + // precondition(self.input != nil, "-jar mode requires path to jar to be specified as input path") + outputDir = baseDir + // } else { + // outputDir = baseDir + // .appendingPathComponent("generated", isDirectory: true) + // } + + return outputDir + } + + func readInitialConfiguration(command: some SwiftJavaBaseAsyncParsableCommand) throws -> Configuration { + var earlyConfig: Configuration? + if let moduleBaseDir { + print("[debug][swift-java] Load config from module base directory: \(moduleBaseDir.path)") + earlyConfig = try readConfiguration(sourceDir: moduleBaseDir.path) + } else if let inputSwift = commonOptions.inputSwift { + print("[debug][swift-java] Load config from module swift input directory: \(inputSwift)") + earlyConfig = try readConfiguration(sourceDir: inputSwift) + } + var config = earlyConfig ?? Configuration() + // override configuration with options from command line + config.logLevel = command.logLevel + return config + } +} \ No newline at end of file diff --git a/USER_GUIDE.md b/USER_GUIDE.md index 2abe8ea0b..d7d796fd5 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -8,8 +8,8 @@ Before using this package, set the `JAVA_HOME` environment variable to point at ### Using Java libraries from Swift -Existing Java libraries can be wrapped for use in Swift with the `Java2Swift` -tool. In a Swift program, the most direct way to access a Java API is to use the SwiftPM plugin to provide Swift wrappers for the Java classes. To do so, add a configuration file `Java2Swift.config` into the source directory for the Swift target. This is a JSON file that specifies Java classes and the Swift type name that should be generated to wrap them. For example, the following file maps `java.math.BigInteger` to a Swift type named `BigInteger`: +Existing Java libraries can be wrapped for use in Swift with the `swift-java` +tool. In a Swift program, the most direct way to access a Java API is to use the SwiftPM plugin to provide Swift wrappers for the Java classes. To do so, add a configuration file `swift-java.config` into the source directory for the Swift target. This is a JSON file that specifies Java classes and the Swift type name that should be generated to wrap them. For example, the following file maps `java.math.BigInteger` to a Swift type named `BigInteger`: ```json { @@ -31,11 +31,11 @@ or, equivalently, adding the following to the package dependencies: .package(url: "https://github.com/swiftlang/swift-java", branch: "main"), ``` -Finally, update `Package.swift` so that the `Java2SwiftPlugin` plugin runs on the target in which you want to generate Swift wrappers. The plugin looks like this: +Finally, update `Package.swift` so that the `SwiftJavaPlugin` plugin runs on the target in which you want to generate Swift wrappers. The plugin looks like this: ```swift plugins: [ - .plugin(name: "Java2SwiftPlugin", package: "swift-java"), + .plugin(name: "SwiftJavaPlugin", package: "swift-java"), ] ``` @@ -101,10 +101,10 @@ let bigInt = BigInteger(veryBigNumber, environment: jniEnvironment) ### Importing a Jar file into Swift -Java libraries are often distributed as Jar files. The `Java2Swift` tool can inspect a Jar file to create a `Java2Swift.config` file that will wrap all of the public classes for use in Swift. Following the example in `swift-java/Samples/JavaSieve`, we will wrap a small [Java library for computing prime numbers](https://github.com/gazman-sdk/quadratic-sieve-Java) for use in Swift. Assuming we have a Jar file `QuadraticSieve-1.0.jar` in the package directory, run the following command: +Java libraries are often distributed as Jar files. The `swift-java` tool can inspect a Jar file to create a `swift-java.config` file that will wrap all of the public classes for use in Swift. Following the example in `swift-java/Samples/JavaSieve`, we will wrap a small [Java library for computing prime numbers](https://github.com/gazman-sdk/quadratic-sieve-Java) for use in Swift. Assuming we have a Jar file `QuadraticSieve-1.0.jar` in the package directory, run the following command: ```swift -swift run Java2Swift --module-name JavaSieve --jar QuadraticSieve-1.0.jar +swift-java generate --module-name JavaSieve --jar QuadraticSieve-1.0.jar ``` The resulting configuration file will look something like this: @@ -142,7 +142,7 @@ The resulting configuration file will look something like this: } ``` -As with the previous `JavaProbablyPrime` sample, the `JavaSieve` target in `Package.swift` should depend on the `swift-java` package modules (`JavaKit`) and apply the `Java2Swift` plugin. This makes all of the Java classes found in the Jar file available to Swift within the `JavaSieve` target. +As with the previous `JavaProbablyPrime` sample, the `JavaSieve` target in `Package.swift` should depend on the `swift-java` package modules (`JavaKit`) and apply the `swift-java` plugin. This makes all of the Java classes found in the Jar file available to Swift within the `JavaSieve` target. If you inspect the build output, there are a number of warnings that look like this: @@ -159,12 +159,12 @@ These warnings mean that some of the APIs in the Java library aren't available i .product(name: "JavaKit", package: "swift-java"), ], plugins: [ - .plugin(name: "Java2SwiftPlugin", package: "swift-java"), + .plugin(name: "SwiftJavaPlugin", package: "swift-java"), ] ), ``` -Then define a a Java2Swift configuration file in `Sources/JavaMath/Java2Swift.config` to bring in the types we need: +Then define a a swift-java configuration file in `Sources/JavaMath/swift-java.config` to bring in the types we need: ```json { @@ -255,7 +255,7 @@ public class HelloSwift { } ``` -On the Swift side, the Java class needs to be exposed to Swift through `Java2Swift.config`, e.g.,: +On the Swift side, the Java class needs to be exposed to Swift through `swift-java.config`, e.g.,: ```swift { @@ -393,7 +393,7 @@ A number of JavaKit modules provide Swift projections of Java classes and interf | `java.lang.Throwable` | `Throwable` | `JavaKit` | | `java.net.URL` | `URL` | `JavaKitNetwork` | -The `Java2Swift` tool can translate any other Java classes into Swift projections. The easiest way to use `Java2Swift` is with the SwiftPM plugin described above. More information about using this tool directly are provided later in this document +The `swift-java` tool can translate any other Java classes into Swift projections. The easiest way to use `swift-java` is with the SwiftPM plugin described above. More information about using this tool directly are provided later in this document #### Improve parameter names of imported Java methods When building Java libraries you can pass the `-parameters` option to javac @@ -438,65 +438,71 @@ public struct Enumeration { } ``` -## Translating Java classes with `Java2Swift` +## Translating Java classes with `swift-java` -The `Java2Swift` is a Swift program that uses Java's runtime reflection facilities to translate the requested Java classes into their Swift projections. The output is a number of Swift source files, each of which corresponds to a -single Java class. The `Java2Swift` can be executed like this: +The `swift-java` is a Swift program that uses Java's runtime reflection facilities to translate the requested Java classes into their Swift projections. The output is a number of Swift source files, each of which corresponds to a +single Java class. The `swift-java` can be executed like this: ``` -swift run Java2Swift +swift-java ``` to produce help output like the following: ``` -USAGE: Java2Swift --module-name [--depends-on ...] [--jar] [--cp ...] [--output-directory ] +OVERVIEW: Generate sources and configuration for Swift and Java interoperability. -ARGUMENTS: - The input file, which is either a Java2Swift - configuration file or (if '-jar' was specified) - a Jar file. +USAGE: swift-java OPTIONS: - --module-name - The name of the Swift module into which the resulting - Swift types will be generated. --depends-on - A Java2Swift configuration file for a given Swift - module name on which this module depends, e.g., - JavaKitJar=Sources/JavaKitJar/Java2Swift.config. - There should be one of these options for each Swift - module that this module depends on (transitively) - that contains wrapped Java sources. - --jar Specifies that the input is a Jar file whose public - classes will be loaded. The output of Java2Swift will - be a configuration file (Java2Swift.config) that can - be used as input to a subsequent Java2Swift - invocation to generate wrappers for those public - classes. - --cp, --classpath Class search path of directories and zip/jar files - from which Java classes can be loaded. + A Java2Swift configuration file for a given Swift module name on which this module depends, e.g., JavaKitJar=Sources/JavaKitJar/Java2Swift.config. There should be one of these options for each Swift module that this + module depends on (transitively) that contains wrapped Java sources. + --jar Specifies that the input is a Jar file whose public classes will be loaded. The output of Java2Swift will be a configuration file (Java2Swift.config) that can be used as input to a subsequent Java2Swift invocation to + generate wrappers for those public classes. + --fetch Fetch dependencies from given target (containing swift-java configuration) or dependency string + --swift-native-implementation + The names of Java classes whose declared native methods will be implemented in Swift. + --output-swift + The directory where generated Swift files should be written. Generally used with jextract mode. + --output-java + The directory where generated Java files should be written. Generally used with jextract mode. + --java-package + The Java package the generated Java code should be emitted into. + -c, --cache-directory + Directory where to write cached values (e.g. swift-java.classpath files) -o, --output-directory - The directory in which to output the generated Swift - files or the Java2Swift configuration file. (default: - .) + The directory in which to output the generated Swift files or the SwiftJava configuration file. + --input-swift + Directory containing Swift files which should be extracted into Java bindings. Also known as 'jextract' mode. Must be paired with --output-java and --output-swift. + -l, --log-level + Configure the level of logs that should be printed (values: trace, debug, info, notice, warning, error, critical; default: log level) + --cp, --classpath Class search path of directories and zip/jar files from which Java classes can be loaded. + -f, --filter-java-package + While scanning a classpath, inspect only types included in this package -h, --help Show help information. + +SUBCOMMANDS: + configure Configure and emit a swift-java.config file based on an input dependency or jar file + resolve Resolve dependencies and write the resulting swift-java.classpath file + + See 'swift-java help ' for detailed help. ``` For example, the `JavaKitJar` library is generated with this command line: ```swift -swift run Java2Swift --module-name JavaKitJar --depends-on JavaKit=Sources/JavaKit/Java2Swift.config -o Sources/JavaKitJar/generated Sources/JavaKitJar/Java2Swift.config +swift run swift-java --module-name JavaKitJar --depends-on JavaKit=Sources/JavaKit/swift-java.config -o Sources/JavaKitJar/generated Sources/JavaKitJar/swift-java.config ``` -The `--module-name JavaKitJar` parameter describes the name of the Swift module in which the code will be generated. +The `--swift-module JavaKitJar` parameter describes the name of the Swift module in which the code will be generated. -The `--depends-on` option is followed by the Java2Swift configuration files for any library on which this Swift library depends. Each `--depends-on` option is of the form `=`, and tells Java2Swift which other Java classes have already been translated to Swift. For example, if your Java class uses `java.net.URL`, then you should include +The `--depends-on` option is followed by the swift-java configuration files for any library on which this Swift library depends. Each `--depends-on` option is of the form `=`, and tells swift-java which other Java classes have already been translated to Swift. For example, if your Java class uses `java.net.URL`, then you should include `JavaKitNetwork`'s configuration file as a dependency here. The `-o` option specifies the output directory. Typically, this will be `Sources//generated` or similar to keep the generated Swift files separate from any hand-written ones. To see the output on the terminal rather than writing files to disk, pass `-` for this option. -Finally, the command line should contain the `Java2Swift.config` file containing the list of classes that should be translated into Swift and their corresponding Swift type names. The tool will output a single `.swift` file for each class, along with warnings for any public API that cannot be translated into Swift. The most common warnings are due to missing Swift projections for Java classes. For example, here we have not translated (or provided the translation manifests for) the Java classes +Finally, the command line should contain the `swift-java.config` file containing the list of classes that should be translated into Swift and their corresponding Swift type names. The tool will output a single `.swift` file for each class, along with warnings for any public API that cannot be translated into Swift. The most common warnings are due to missing Swift projections for Java classes. For example, here we have not translated (or provided the translation manifests for) the Java classes `java.util.zip.ZipOutputStream` and `java.io.OutputStream`: ``` @@ -507,7 +513,7 @@ warning: Unable to translate 'java.util.jar.JarInputStream' method 'transferTo': The result of such warnings is that certain information won't be statically available in Swift, e.g., the superclass won't be known (so we will assume it is `JavaObject`), or the specified constructors or methods won't be translated. If you don't need these APIs, the warnings can be safely ignored. The APIs can still be called dynamically via JNI. -The `--jar` option changes the operation of `Java2Swift`. Instead of wrapping Java classes in Swift, it scans the given input Jar file to find all public classes and outputs a configuration file `Java2Swift.config` mapping all of the Java classes in the Jar file to Swift types. The `--jar` mode is expected to be used to help import a Java library into Swift wholesale, after which Java2Swift should invoked again given the generated configuration file. +The `--jar` option changes the operation of `swift-java`. Instead of wrapping Java classes in Swift, it scans the given input Jar file to find all public classes and outputs a configuration file `swift-java.config` mapping all of the Java classes in the Jar file to Swift types. The `--jar` mode is expected to be used to help import a Java library into Swift wholesale, after which swift-java should invoked again given the generated configuration file. ### Under construction: Create a Java class to wrap the Swift library @@ -651,6 +657,7 @@ A Swift function may accept a closure which is used as a callback: func callMe(maybe: () -> ()) {} ``` +Minimal support for c-compatible closures is implemented, more documentation soon. ## `jextract-swift` importer behavior From d0eeaccd407d29f0503268ae3d5f881f326f82ff Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Mon, 16 Jun 2025 17:42:23 -0700 Subject: [PATCH 005/141] Bump swift-syntax version to 601.0.1 (#275) --- Package.swift | 2 +- .../JExtractSwiftLib/SwiftTypes/SwiftType.swift | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Package.swift b/Package.swift index 47312ccd7..55102874a 100644 --- a/Package.swift +++ b/Package.swift @@ -191,7 +191,7 @@ let package = Package( ], dependencies: [ - .package(url: "https://github.com/swiftlang/swift-syntax", from: "600.0.1"), + .package(url: "https://github.com/swiftlang/swift-syntax", from: "601.0.1"), .package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"), .package(url: "https://github.com/apple/swift-system", from: "1.4.0"), diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index 5b0d89612..353b9b318 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -180,7 +180,12 @@ extension SwiftType { // Translate the generic arguments. let genericArgs = try identifierType.genericArgumentClause.map { genericArgumentClause in try genericArgumentClause.arguments.map { argument in - try SwiftType(argument.argument, symbolTable: symbolTable) + switch argument.argument { + case .type(let argumentTy): + try SwiftType(argumentTy, symbolTable: symbolTable) + default: + throw TypeTranslationError.unimplementedType(type) + } } } @@ -210,7 +215,12 @@ extension SwiftType { // Translate the generic arguments. let genericArgs = try memberType.genericArgumentClause.map { genericArgumentClause in try genericArgumentClause.arguments.map { argument in - try SwiftType(argument.argument, symbolTable: symbolTable) + switch argument.argument { + case .type(let argumentTy): + try SwiftType(argumentTy, symbolTable: symbolTable) + default: + throw TypeTranslationError.unimplementedType(type) + } } } From 5c697668e93c8802ecd5230bf6473a878beed1aa Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 17 Jun 2025 13:08:42 +0200 Subject: [PATCH 006/141] [jextract] add support for throwing functions in JNI mode (#277) --- ...Swift2JavaGenerator+FunctionLowering.swift | 3 +- Sources/JExtractSwiftLib/ImportedDecls.swift | 4 ++ .../JNI/JNISwift2JavaGenerator.swift | 29 ++++++-- .../SwiftTypes/SwiftEffectSpecifier.swift | 17 +++++ .../SwiftTypes/SwiftFunctionSignature.swift | 64 +++++++++++++---- .../JNI/JNIModuleTests.swift | 68 +++++++++++++++++++ 6 files changed, 163 insertions(+), 22 deletions(-) create mode 100644 Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index 061dc9973..6938dc58e 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -526,7 +526,8 @@ public struct LoweredFunctionSignature: Equatable { SwiftFunctionSignature( selfParameter: nil, parameters: allLoweredParameters, - result: SwiftResult(convention: .direct, type: result.cdeclResultType) + result: SwiftResult(convention: .direct, type: result.cdeclResultType), + effectSpecifiers: [] ) } } diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 3619890bd..ee86fc983 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -98,6 +98,10 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible { return prefix + context + self.name } + var isThrowing: Bool { + self.functionSignature.effectSpecifiers.contains(.throws) + } + init( module: String, swiftDecl: any DeclSyntaxProtocol, diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index 946223aef..bab4c67f5 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -124,7 +124,6 @@ extension JNISwift2JavaGenerator { "thisClass: jclass" ] + translatedParameters.map { "\($0.0): \($0.1.jniTypeName)"} let swiftReturnType = decl.functionSignature.result.type - let thunkReturnType = !swiftReturnType.isVoid ? " -> \(swiftReturnType.javaType.jniTypeName)" : "" printer.printBraceBlock( @@ -137,17 +136,32 @@ extension JNISwift2JavaGenerator { let label = originalParam.argumentLabel.map { "\($0): "} ?? "" return "\(label)\(originalParam.type)(fromJNI: \(translatedParam.0), in: environment!)" } - let functionDowncall = "\(swiftModuleName).\(decl.name)(\(downcallParameters.joined(separator: ", ")))" + let tryClause: String = decl.isThrowing ? "try " : "" + let functionDowncall = "\(tryClause)\(swiftModuleName).\(decl.name)(\(downcallParameters.joined(separator: ", ")))" - if swiftReturnType.isVoid { - printer.print(functionDowncall) + let innerBody = if swiftReturnType.isVoid { + functionDowncall } else { + """ + let result = \(functionDowncall) + return result.getJNIValue(in: environment)") + """ + } + + if decl.isThrowing { + let dummyReturn = !swiftReturnType.isVoid ? "return \(swiftReturnType).jniPlaceholderValue" : "" printer.print( """ - let result = \(functionDowncall) - return result.getJNIValue(in: environment)") + do { + \(innerBody) + } catch { + environment.throwAsException(error) + \(dummyReturn) + } """ ) + } else { + printer.print(innerBody) } } } @@ -197,6 +211,7 @@ extension JNISwift2JavaGenerator { let params = decl.functionSignature.parameters.enumerated().map { idx, param in "\(param.type.javaType) \(param.parameterName ?? "arg\(idx))")" } + let throwsClause = decl.isThrowing ? " throws Exception" : "" printer.print( """ @@ -208,7 +223,7 @@ extension JNISwift2JavaGenerator { */ """ ) - printer.print("public static native \(returnType) \(decl.name)(\(params.joined(separator: ", ")));") + printer.print("public static native \(returnType) \(decl.name)(\(params.joined(separator: ", ")))\(throwsClause);") } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift new file mode 100644 index 000000000..9ba4d7ad0 --- /dev/null +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift @@ -0,0 +1,17 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +enum SwiftEffectSpecifier: Equatable { + case `throws` +} diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift index cd4dbf7aa..8f0b31283 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift @@ -21,11 +21,18 @@ public struct SwiftFunctionSignature: Equatable { var selfParameter: SwiftSelfParameter? var parameters: [SwiftParameter] var result: SwiftResult + var effectSpecifiers: [SwiftEffectSpecifier] - init(selfParameter: SwiftSelfParameter? = nil, parameters: [SwiftParameter], result: SwiftResult) { + init( + selfParameter: SwiftSelfParameter? = nil, + parameters: [SwiftParameter], + result: SwiftResult, + effectSpecifiers: [SwiftEffectSpecifier] + ) { self.selfParameter = selfParameter self.parameters = parameters self.result = result + self.effectSpecifiers = effectSpecifiers } } @@ -68,13 +75,16 @@ extension SwiftFunctionSignature { throw SwiftFunctionTranslationError.generic(generics) } + let (parameters, effectSpecifiers) = try Self.translateFunctionSignature( + node.signature, + symbolTable: symbolTable + ) + self.init( selfParameter: .initializer(enclosingType), - parameters: try Self.translateFunctionSignature( - node.signature, - symbolTable: symbolTable - ), - result: SwiftResult(convention: .direct, type: enclosingType) + parameters: parameters, + result: SwiftResult(convention: .direct, type: enclosingType), + effectSpecifiers: effectSpecifiers ) } @@ -120,7 +130,7 @@ extension SwiftFunctionSignature { } // Translate the parameters. - let parameters = try Self.translateFunctionSignature( + let (parameters, effectSpecifiers) = try Self.translateFunctionSignature( node.signature, symbolTable: symbolTable ) @@ -136,26 +146,28 @@ extension SwiftFunctionSignature { result = .void } - self.init(selfParameter: selfParameter, parameters: parameters, result: result) + self.init(selfParameter: selfParameter, parameters: parameters, result: result, effectSpecifiers: effectSpecifiers) } /// Translate the function signature, returning the list of translated - /// parameters. + /// parameters and effect specifiers. static func translateFunctionSignature( _ signature: FunctionSignatureSyntax, symbolTable: SwiftSymbolTable - ) throws -> [SwiftParameter] { - // FIXME: Prohibit effects for now. - if let throwsClause = signature.effectSpecifiers?.throwsClause { - throw SwiftFunctionTranslationError.throws(throwsClause) + ) throws -> ([SwiftParameter], [SwiftEffectSpecifier]) { + var effectSpecifiers = [SwiftEffectSpecifier]() + if signature.effectSpecifiers?.throwsClause != nil { + effectSpecifiers.append(.throws) } if let asyncSpecifier = signature.effectSpecifiers?.asyncSpecifier { throw SwiftFunctionTranslationError.async(asyncSpecifier) } - return try signature.parameterClause.parameters.map { param in + let parameters = try signature.parameterClause.parameters.map { param in try SwiftParameter(param, symbolTable: symbolTable) } + + return (parameters, effectSpecifiers) } init(_ varNode: VariableDeclSyntax, isSet: Bool, enclosingType: SwiftType?, symbolTable: SwiftSymbolTable) throws { @@ -195,6 +207,22 @@ extension SwiftFunctionSignature { } let valueType = try SwiftType(varTypeNode, symbolTable: symbolTable) + var effectSpecifiers: [SwiftEffectSpecifier]? = nil + switch binding.accessorBlock?.accessors { + case .getter(let getter): + if let getter = getter.as(AccessorDeclSyntax.self) { + effectSpecifiers = Self.effectSpecifiers(from: getter) + } + case .accessors(let accessors): + if let getter = accessors.first(where: { $0.accessorSpecifier.tokenKind == .keyword(.get) }) { + effectSpecifiers = Self.effectSpecifiers(from: getter) + } + default: + break + } + + self.effectSpecifiers = effectSpecifiers ?? [] + if isSet { self.parameters = [SwiftParameter(convention: .byValue, parameterName: "newValue", type: valueType)] self.result = .void @@ -203,6 +231,14 @@ extension SwiftFunctionSignature { self.result = .init(convention: .direct, type: valueType) } } + + private static func effectSpecifiers(from decl: AccessorDeclSyntax) -> [SwiftEffectSpecifier] { + var effectSpecifiers = [SwiftEffectSpecifier]() + if decl.effectSpecifiers?.throwsClause != nil { + effectSpecifiers.append(.throws) + } + return effectSpecifiers + } } extension VariableDeclSyntax { diff --git a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift index ba6258a13..2f73cca44 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift @@ -27,6 +27,11 @@ struct JNIModuleTests { public func copy(_ string: String) -> String """ + let globalMethodThrowing = """ + public func methodA() throws + public func methodB() throws -> Int64 + """ + @Test func generatesModuleJavaClass() throws { let input = "public func helloWorld()" @@ -150,4 +155,67 @@ struct JNIModuleTests { ] ) } + + @Test + func globalMethodThrowing_javaBindings() throws { + try assertOutput( + input: globalMethodThrowing, + .jni, + .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func methodA() throws + * } + */ + public static native void methodA() throws Exception; + """, + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func methodB() throws -> Int64 + * } + */ + public static native long methodB() throws Exception; + """, + ] + ) + } + + @Test + func globalMethodThrowing_swiftThunks() throws { + try assertOutput( + input: globalMethodThrowing, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule_methodA") + func swiftjava_SwiftModule_methodA(environment: UnsafeMutablePointer!, thisClass: jclass) { + do { + try SwiftModule.methodA() + } catch { + environment.throwAsException(error) + } + } + """, + """ + @_cdecl("Java_com_example_swift_SwiftModule_methodB") + func swiftjava_SwiftModule_methodB(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong { + do { + let result = try SwiftModule.methodB() + return result.getJNIValue(in: environment) + } catch { + environment.throwAsException(error) + return Int64.jniPlaceholderValue + } + } + """, + ] + ) + } } From d78fee1367fb98e786046bc0267db1b0f19cbd9f Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Tue, 17 Jun 2025 13:05:02 -0700 Subject: [PATCH 007/141] [JExtract] Prohibit 'throws' in FFM generator It's just not supported yet. --- .../FFMSwift2JavaGenerator+FunctionLowering.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index 6938dc58e..7609937db 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -109,6 +109,11 @@ struct CdeclLowering { ) } + for effect in signature.effectSpecifiers { + // Prohibit any effects for now. + throw LoweringError.effectNotSupported(effect) + } + // Lower the result. let loweredResult = try lowerResult(signature.result.type) @@ -642,4 +647,5 @@ extension LoweredFunctionSignature { enum LoweringError: Error { case inoutNotSupported(SwiftType) case unhandledType(SwiftType) + case effectNotSupported(SwiftEffectSpecifier) } From c9dc9b693db57301ba6e9b45164fc50337c21be1 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Tue, 17 Jun 2025 14:51:20 -0700 Subject: [PATCH 008/141] [JExtract] Bridge UnsafeRawBufferPointer (#279) --- .../MySwiftLibrary/MySwiftLibrary.swift | 6 ++ .../com/example/swift/HelloJava2Swift.java | 2 + .../ExampleSwiftLibrary/MySwiftLibrary.swift | 4 + .../FFM/CDeclLowering/CRepresentation.swift | 5 +- ...Swift2JavaGenerator+FunctionLowering.swift | 60 +++++++++++++++ .../JExtractSwiftLib/FFM/ConversionStep.swift | 31 +++++++- ...t2JavaGenerator+JavaBindingsPrinting.swift | 74 +++++++++++------- ...MSwift2JavaGenerator+JavaTranslation.swift | 77 ++++++++++++++----- .../FFM/FFMSwift2JavaGenerator.swift | 7 +- .../FFM/ForeignValueLayouts.swift | 1 + .../JNI/JNISwift2JavaGenerator.swift | 7 +- .../SwiftStandardLibraryTypeDecls.swift | 14 +++- .../SwiftTypes/SwiftType.swift | 16 +++- .../FunctionLoweringTests.swift | 18 +++++ .../MethodImportTests.swift | 49 ++++++++++++ 15 files changed, 314 insertions(+), 57 deletions(-) diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index 2bd1905c4..8b6066cf7 100644 --- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -47,6 +47,12 @@ public func globalCallMeRunnable(run: () -> ()) { run() } +public func globalReceiveRawBuffer(buf: UnsafeRawBufferPointer) -> Int { + return buf.count +} + +public var globalBuffer: UnsafeRawBufferPointer = UnsafeRawBufferPointer(UnsafeMutableRawBufferPointer.allocate(byteCount: 124, alignment: 1)) + // ==== Internal helpers func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: String = #function) { diff --git a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java index 69dfbdb34..f3073201f 100644 --- a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java +++ b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java @@ -45,6 +45,8 @@ static void examples() { SwiftKit.trace("running runnable"); }); + SwiftKit.trace("getGlobalBuffer().byteSize()=" + MySwiftLibrary.getGlobalBuffer().byteSize()); + // Example of using an arena; MyClass.deinit is run at end of scope try (var arena = SwiftArena.ofConfined()) { MySwiftClass obj = MySwiftClass.init(2222, 7777, arena); diff --git a/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift b/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift index 96afd331d..1c9477f11 100644 --- a/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift +++ b/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift @@ -41,6 +41,10 @@ public func globalCallMeRunnable(run: () -> ()) { run() } +public func globalReceiveRawBuffer(buf: UnsafeRawBufferPointer) -> Int { + return buf.count +} + public class MySwiftClass { public var len: Int diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift index c52bf7db0..eb27e5f60 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift @@ -65,6 +65,9 @@ extension CType { case .tuple([]): self = .void + case .optional(let wrapped) where wrapped.isPointer: + try self.init(cdeclType: wrapped) + case .metatype, .optional, .tuple: throw CDeclToCLoweringError.invalidCDeclType(cdeclType) } @@ -122,7 +125,7 @@ extension SwiftStandardLibraryTypeKind { .qualified(const: true, volatile: false, type: .void) ) case .void: .void - case .unsafePointer, .unsafeMutablePointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string: + case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string: nil } } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index 7609937db..2c7520ddc 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -220,6 +220,35 @@ struct CdeclLowering { ) ) + case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: + // pointer buffers are lowered to (raw-pointer, count) pair. + let isMutable = knownType == .unsafeMutableRawBufferPointer + return LoweredParameter( + cdeclParameters: [ + SwiftParameter( + convention: .byValue, + parameterName: "\(parameterName)_pointer", + type: .optional(isMutable ? knownTypes.unsafeMutableRawPointer : knownTypes.unsafeRawPointer) + ), + SwiftParameter( + convention: .byValue, parameterName: "\(parameterName)_count", + type: knownTypes.int + ) + ], + conversion: .initialize( + type, + arguments: [ + LabeledArgument( + label: "start", + argument: .explodedComponent(.placeholder, component: "pointer") + ), + LabeledArgument( + label: "count", + argument: .explodedComponent(.placeholder, component: "count") + ) + ] + )) + case .string: // 'String' is passed in by C string. i.e. 'UnsafePointer' ('const uint8_t *') if knownType == .string { @@ -382,6 +411,37 @@ struct CdeclLowering { outParameterName: outParameterName ) + case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: + // pointer buffers are lowered to (raw-pointer, count) pair. + let isMutable = knownType == .unsafeMutableRawBufferPointer + return LoweredResult( + cdeclResultType: .void, + cdeclOutParameters: [ + SwiftParameter( + convention: .byValue, + parameterName: "\(outParameterName)_pointer", + type: knownTypes.unsafeMutablePointer( + .optional(isMutable ? knownTypes.unsafeMutableRawPointer : knownTypes.unsafeRawPointer) + ) + ), + SwiftParameter( + convention: .byValue, + parameterName: "\(outParameterName)_count", + type: knownTypes.unsafeMutablePointer(knownTypes.int) + ), + ], + conversion: .aggregate([ + .populatePointer( + name: "\(outParameterName)_pointer", + to: .member(.placeholder, member: "baseAddress") + ), + .populatePointer( + name: "\(outParameterName)_count", + to: .member(.placeholder, member: "count") + ) + ], name: outParameterName) + ) + case .void: return LoweredResult(cdeclResultType: .void, cdeclOutParameters: [], conversion: .placeholder) diff --git a/Sources/JExtractSwiftLib/FFM/ConversionStep.swift b/Sources/JExtractSwiftLib/FFM/ConversionStep.swift index 31d731bc2..315acc600 100644 --- a/Sources/JExtractSwiftLib/FFM/ConversionStep.swift +++ b/Sources/JExtractSwiftLib/FFM/ConversionStep.swift @@ -52,9 +52,14 @@ enum ConversionStep: Equatable { /// Initialize mutable raw pointer with a typed value. indirect case populatePointer(name: String, assumingType: SwiftType? = nil, to: ConversionStep) - /// Perform multiple conversions, but discard the result. + /// Perform multiple conversions for each tuple input elements, but discard the result. case tupleExplode([ConversionStep], name: String?) + /// Perform multiple conversions using the same input. + case aggregate([ConversionStep], name: String?) + + indirect case member(ConversionStep, member: String) + /// Count the number of times that the placeholder occurs within this /// conversion step. var placeholderCount: Int { @@ -63,13 +68,14 @@ enum ConversionStep: Equatable { .pointee(let inner), .typedPointer(let inner, swiftType: _), .unsafeCastPointer(let inner, swiftType: _), - .populatePointer(name: _, assumingType: _, to: let inner): + .populatePointer(name: _, assumingType: _, to: let inner), + .member(let inner, member: _): inner.placeholderCount case .initialize(_, arguments: let arguments): arguments.reduce(0) { $0 + $1.argument.placeholderCount } case .placeholder, .tupleExplode: 1 - case .tuplify(let elements): + case .tuplify(let elements), .aggregate(let elements, _): elements.reduce(0) { $0 + $1.placeholderCount } } } @@ -140,6 +146,25 @@ enum ConversionStep: Equatable { } } return nil + + case .member(let step, let member): + let inner = step.asExprSyntax(placeholder: placeholder, bodyItems: &bodyItems) + return "\(inner).\(raw: member)" + + case .aggregate(let steps, let name): + let toExplode: String + if let name { + bodyItems.append("let \(raw: name) = \(raw: placeholder)") + toExplode = name + } else { + toExplode = placeholder + } + for step in steps { + if let result = step.asExprSyntax(placeholder: toExplode, bodyItems: &bodyItems) { + bodyItems.append(CodeBlockItemSyntax(item: .expr(result))) + } + } + return nil } } } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index c7c6631ac..ba88869d9 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -365,7 +365,7 @@ extension FFMSwift2JavaGenerator { "arena$" } - let varName = "_result" + outParameter.name + let varName = outParameter.name.isEmpty ? "_result" : "_result_" + outParameter.name printer.print( "MemorySegment \(varName) = \(arena).allocate(\(memoryLayout));" @@ -419,32 +419,35 @@ extension JavaConversionStep { /// Whether the conversion uses SwiftArena. var requiresSwiftArena: Bool { switch self { - case .pass, .swiftValueSelfSegment, .construct, .cast, .call, .method: + case .placeholder, .constant, .readOutParameter: return false case .constructSwiftValue: return true + + case .call(let inner, _, _), .cast(let inner, _), .construct(let inner, _), + .method(let inner, _, _, _), .swiftValueSelfSegment(let inner): + return inner.requiresSwiftArena + + case .commaSeparated(let list): + return list.contains(where: { $0.requiresSwiftArena }) } } /// Whether the conversion uses temporary Arena. var requiresTemporaryArena: Bool { switch self { - case .pass, .swiftValueSelfSegment, .construct, .constructSwiftValue, .cast: + case .placeholder, .constant: return false - case .call(_, let withArena), .method(_, _, let withArena): - return withArena - } - } - - /// Whether if the result evaluation is trivial. - /// - /// If this is false, it's advised to store it to a variable if it's used multiple times - var isTrivial: Bool { - switch self { - case .pass, .swiftValueSelfSegment: + case .readOutParameter: return true - case .cast, .construct, .constructSwiftValue, .call, .method: - return false + case .cast(let inner, _), .construct(let inner, _), .constructSwiftValue(let inner, _), .swiftValueSelfSegment(let inner): + return inner.requiresSwiftArena + case .call(let inner, _, let withArena): + return withArena || inner.requiresTemporaryArena + case .method(let inner, _, let args, let withArena): + return withArena || inner.requiresTemporaryArena || args.contains(where: { $0.requiresTemporaryArena }) + case .commaSeparated(let list): + return list.contains(where: { $0.requiresTemporaryArena }) } } @@ -453,28 +456,43 @@ extension JavaConversionStep { // NOTE: 'printer' is used if the conversion wants to cause side-effects. // E.g. storing a temporary values into a variable. switch self { - case .pass: + case .placeholder: return placeholder case .swiftValueSelfSegment: return "\(placeholder).$memorySegment()" - case .call(let function, let withArena): + case .call(let inner, let function, let withArena): + let inner = inner.render(&printer, placeholder) let arenaArg = withArena ? ", arena$" : "" - return "\(function)(\(placeholder)\(arenaArg))" + return "\(function)(\(inner)\(arenaArg))" + + case .method(let inner, let methodName, let arguments, let withArena): + let inner = inner.render(&printer, placeholder) + let args = arguments.map { $0.render(&printer, placeholder) } + let argsStr = (args + (withArena ? ["arena$"] : [])).joined(separator: " ,") + return "\(inner).\(methodName)(\(argsStr))" + + case .constructSwiftValue(let inner, let javaType): + let inner = inner.render(&printer, placeholder) + return "new \(javaType.className!)(\(inner), swiftArena$)" + + case .construct(let inner, let javaType): + let inner = inner.render(&printer, placeholder) + return "new \(javaType)(\(inner))" - case .method(let methodName, let arguments, let withArena): - let argsStr = (arguments + (withArena ? ["arena$"] : [])).joined(separator: " ,") - return "\(placeholder).\(methodName)(\(argsStr))" + case .cast(let inner, let javaType): + let inner = inner.render(&printer, placeholder) + return "(\(javaType)) \(inner)" - case .constructSwiftValue(let javaType): - return "new \(javaType.className!)(\(placeholder), swiftArena$)" + case .commaSeparated(let list): + return list.map({ $0.render(&printer, placeholder)}).joined(separator: ", ") - case .construct(let javaType): - return "new \(javaType)(\(placeholder))" + case .constant(let value): + return value - case .cast(let javaType): - return "(\(javaType)) \(placeholder)" + case .readOutParameter(let javaType, let name): + return "\(placeholder)_\(name).get(\(ForeignValueLayout(javaType: javaType)!), 0)" } } } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 476160303..6f17158b6 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -116,7 +116,7 @@ struct TranslatedFunctionType { /// Whether or not this functional interface with C ABI compatible. var isCompatibleWithC: Bool { - result.conversion.isPass && parameters.allSatisfy(\.conversion.isPass) + result.conversion.isPlaceholder && parameters.allSatisfy(\.conversion.isPlaceholder) } } @@ -200,7 +200,7 @@ struct JavaTranslation { javaParameters: [ JavaParameter(type: cType.javaType, name: paramName) ], - conversion: .pass + conversion: .placeholder ) translatedParams.append(translatedParam) continue @@ -215,7 +215,7 @@ struct JavaTranslation { let transltedResult = TranslatedResult( javaResultType: resultCType.javaType, outParameters: [], - conversion: .pass + conversion: .placeholder ) return TranslatedFunctionType( @@ -294,7 +294,7 @@ struct JavaTranslation { name: parameterName ) ], - conversion: .pass + conversion: .placeholder ) } @@ -307,7 +307,7 @@ struct JavaTranslation { type: JavaType.class(package: "org.swift.swiftkit", name: "SwiftAnyType"), name: parameterName) ], - conversion: .swiftValueSelfSegment + conversion: .swiftValueSelfSegment(.placeholder) ) case .nominal(let swiftNominalType): @@ -324,6 +324,17 @@ struct JavaTranslation { // FIXME: Implement throw JavaTranslationError.unhandledType(swiftType) + case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: + return TranslatedParameter( + javaParameters: [ + JavaParameter(type: .javaForeignMemorySegment, name: parameterName), + ], + conversion: .commaSeparated([ + .placeholder, + .method(.placeholder, methodName: "byteSize", arguments: [], withArena: false) + ]) + ) + case .string: return TranslatedParameter( javaParameters: [ @@ -332,7 +343,7 @@ struct JavaTranslation { name: parameterName ) ], - conversion: .call(function: "SwiftKit.toCString", withArena: true) + conversion: .call(.placeholder, function: "SwiftKit.toCString", withArena: true) ) default: @@ -352,7 +363,7 @@ struct JavaTranslation { name: parameterName ) ], - conversion: .swiftValueSelfSegment + conversion: .swiftValueSelfSegment(.placeholder) ) case .tuple: @@ -366,7 +377,7 @@ struct JavaTranslation { type: JavaType.class(package: nil, name: "\(methodName).\(parameterName)"), name: parameterName) ], - conversion: .call(function: "\(methodName).$toUpcallStub", withArena: true) + conversion: .call(.placeholder, function: "\(methodName).$toUpcallStub", withArena: true) ) case .optional: @@ -388,7 +399,7 @@ struct JavaTranslation { return TranslatedResult( javaResultType: javaType, outParameters: [], - conversion: .pass + conversion: .placeholder ) } @@ -399,12 +410,29 @@ struct JavaTranslation { return TranslatedResult( javaResultType: javaType, outParameters: [], - conversion: .construct(javaType) + conversion: .construct(.placeholder, javaType) ) case .nominal(let swiftNominalType): if let knownType = swiftNominalType.nominalTypeDecl.knownStandardLibraryType { switch knownType { + case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: + return TranslatedResult( + javaResultType: .javaForeignMemorySegment, + outParameters: [ + JavaParameter(type: .javaForeignMemorySegment, name: "pointer"), + JavaParameter(type: .long, name: "count"), + ], + conversion: .method( + .readOutParameter(.javaForeignMemorySegment, component: "pointer"), + methodName: "reinterpret", + arguments: [ + .readOutParameter(.long, component: "count") + ], + withArena: false + ) + ) + case .unsafePointer, .unsafeMutablePointer: // FIXME: Implement throw JavaTranslationError.unhandledType(swiftType) @@ -430,7 +458,7 @@ struct JavaTranslation { outParameters: [ JavaParameter(type: javaType, name: "") ], - conversion: .constructSwiftValue(javaType) + conversion: .constructSwiftValue(.placeholder, javaType) ) case .tuple: @@ -456,30 +484,39 @@ struct JavaTranslation { /// Describes how to convert values between Java types and FFM types. enum JavaConversionStep { // Pass through. - case pass + case placeholder + + // A fixed value + case constant(String) // 'value.$memorySegment()' - case swiftValueSelfSegment + indirect case swiftValueSelfSegment(JavaConversionStep) // call specified function using the placeholder as arguments. // If `withArena` is true, `arena$` argument is added. - case call(function: String, withArena: Bool) + indirect case call(JavaConversionStep, function: String, withArena: Bool) // Apply a method on the placeholder. // If `withArena` is true, `arena$` argument is added. - case method(methodName: String, arguments: [String] = [], withArena: Bool) + indirect case method(JavaConversionStep, methodName: String, arguments: [JavaConversionStep] = [], withArena: Bool) // Call 'new \(Type)(\(placeholder), swiftArena$)'. - case constructSwiftValue(JavaType) + indirect case constructSwiftValue(JavaConversionStep, JavaType) // Construct the type using the placeholder as arguments. - case construct(JavaType) + indirect case construct(JavaConversionStep, JavaType) // Casting the placeholder to the certain type. - case cast(JavaType) + indirect case cast(JavaConversionStep, JavaType) + + // Convert the results of the inner steps to a comma separated list. + indirect case commaSeparated([JavaConversionStep]) + + // Refer an exploded argument suffixed with `_\(name)`. + indirect case readOutParameter(JavaType, component: String) - var isPass: Bool { - return if case .pass = self { true } else { false } + var isPlaceholder: Bool { + return if case .placeholder = self { true } else { false } } } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index 036ee31e7..4a65f7cc6 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -138,7 +138,12 @@ extension FFMSwift2JavaGenerator { printImports(&printer) printModuleClass(&printer) { printer in - // TODO: print all "static" methods + + for decl in analysis.importedGlobalVariables { + self.log.trace("Print imported decl: \(decl)") + printFunctionDowncallMethods(&printer, decl) + } + for decl in analysis.importedGlobalFuncs { self.log.trace("Print imported decl: \(decl)") printFunctionDowncallMethods(&printer, decl) diff --git a/Sources/JExtractSwiftLib/FFM/ForeignValueLayouts.swift b/Sources/JExtractSwiftLib/FFM/ForeignValueLayouts.swift index 7cf1d4df0..3784a75a0 100644 --- a/Sources/JExtractSwiftLib/FFM/ForeignValueLayouts.swift +++ b/Sources/JExtractSwiftLib/FFM/ForeignValueLayouts.swift @@ -43,6 +43,7 @@ public struct ForeignValueLayout: CustomStringConvertible, Equatable { case .long: self = .SwiftInt64 case .float: self = .SwiftFloat case .double: self = .SwiftDouble + case .javaForeignMemorySegment: self = .SwiftPointer case .array, .class, .void: return nil } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index bab4c67f5..b15688dce 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -263,7 +263,12 @@ extension SwiftStandardLibraryTypeKind { case .double: .double case .void: .void case .string: .javaLangString - case .uint, .uint8, .uint32, .uint64, .unsafeRawPointer, .unsafeMutableRawPointer, .unsafePointer, .unsafeMutablePointer, .unsafeBufferPointer, .unsafeMutableBufferPointer: nil + case .uint, .uint8, .uint32, .uint64, + .unsafeRawPointer, .unsafeMutableRawPointer, + .unsafePointer, .unsafeMutablePointer, + .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, + .unsafeBufferPointer, .unsafeMutableBufferPointer: + nil } } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypeDecls.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypeDecls.swift index 51e1adcf7..5678b2bb6 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypeDecls.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypeDecls.swift @@ -30,6 +30,8 @@ enum SwiftStandardLibraryTypeKind: String, Hashable, CaseIterable { case double = "Double" case unsafeRawPointer = "UnsafeRawPointer" case unsafeMutableRawPointer = "UnsafeMutableRawPointer" + case unsafeRawBufferPointer = "UnsafeRawBufferPointer" + case unsafeMutableRawBufferPointer = "UnsafeMutableRawBufferPointer" case unsafePointer = "UnsafePointer" case unsafeMutablePointer = "UnsafeMutablePointer" case unsafeBufferPointer = "UnsafeBufferPointer" @@ -48,7 +50,8 @@ enum SwiftStandardLibraryTypeKind: String, Hashable, CaseIterable { switch self { case .bool, .double, .float, .int, .int8, .int16, .int32, .int64, .uint, .uint8, .uint16, .uint32, .uint64, .unsafeRawPointer, - .unsafeMutableRawPointer, .string, .void: + .unsafeMutableRawPointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, + .string, .void: false case .unsafePointer, .unsafeMutablePointer, .unsafeBufferPointer, @@ -56,6 +59,15 @@ enum SwiftStandardLibraryTypeKind: String, Hashable, CaseIterable { true } } + + var isPointer: Bool { + switch self { + case .unsafePointer, .unsafeMutablePointer, .unsafeRawPointer, .unsafeMutableRawPointer: + return true + default: + return false + } + } } /// Captures many types from the Swift standard library in their most basic diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index 353b9b318..c2ddd2a53 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -38,8 +38,7 @@ enum SwiftType: Equatable { asNominalType?.nominalTypeDecl } - /// Whether this is the "Void" type, which is actually an empty - /// tuple. + /// Whether this is the "Void" type, which is actually an empty tuple. var isVoid: Bool { switch self { case .tuple([]): @@ -51,6 +50,19 @@ enum SwiftType: Equatable { } } + /// Whether this is a pointer type. I.e 'Unsafe[Mutable][Raw]Pointer' + var isPointer: Bool { + switch self { + case .nominal(let nominal): + if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType { + return knownType.isPointer + } + default: + break + } + return false; + } + /// Reference type /// /// * Mutations don't require 'inout' convention. diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift index b7855e437..c707732e6 100644 --- a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift +++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift @@ -315,6 +315,24 @@ final class FunctionLoweringTests { ) } + @Test("Lowering UnsafeRawBufferPointer") + func lowerRawBufferPointer() throws { + try assertLoweredFunction( + """ + func swapRawBufferPointer(buffer: UnsafeRawBufferPointer) -> UnsafeMutableRawBufferPointer {} + """, + expectedCDecl: """ + @_cdecl("c_swapRawBufferPointer") + public func c_swapRawBufferPointer(_ buffer_pointer: UnsafeRawPointer?, _ buffer_count: Int, _ _result_pointer: UnsafeMutablePointer, _ _result_count: UnsafeMutablePointer) { + let _result = swapRawBufferPointer(buffer: UnsafeRawBufferPointer(start: buffer_pointer, count: buffer_count)) + _result_pointer.initialize(to: _result.baseAddress) + _result_count.initialize(to: _result.count) + } + """, + expectedCFunction: "void c_swapRawBufferPointer(const void *buffer_pointer, ptrdiff_t buffer_count, void **_result_pointer, ptrdiff_t *_result_count)" + ) + } + @Test("Lowering () -> Void type") func lowerSimpleClosureTypes() throws { try assertLoweredFunction(""" diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift index cd2bc1501..2405e0ac4 100644 --- a/Tests/JExtractSwiftTests/MethodImportTests.swift +++ b/Tests/JExtractSwiftTests/MethodImportTests.swift @@ -40,6 +40,8 @@ final class MethodImportTests { ) public func globalReturnClass() -> MySwiftClass + + public func swapRawBufferPointer(buffer: UnsafeRawBufferPointer) -> UnsafeMutableRawBufferPointer extension MySwiftClass { public func helloMemberInExtension() @@ -228,6 +230,53 @@ final class MethodImportTests { ) } + @Test("Import: func swapRawBufferPointer(buffer: _)") + func func_globalSwapRawBufferPointer() throws { + let st = Swift2JavaTranslator( + swiftModuleName: "__FakeModule" + ) + st.log.logLevel = .error + + try st.analyze(file: "Fake.swift", text: class_interfaceFile) + + let funcDecl = st.importedGlobalFuncs.first { + $0.name == "swapRawBufferPointer" + }! + + let generator = FFMSwift2JavaGenerator( + translator: st, + javaPackage: "com.example.swift", + swiftOutputDirectory: "/fake", + javaOutputDirectory: "/fake" + ) + + let output = CodePrinter.toString { printer in + generator.printJavaBindingWrapperMethod(&printer, funcDecl) + } + + assertOutput( + dump: true, + output, + expected: + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func swapRawBufferPointer(buffer: UnsafeRawBufferPointer) -> UnsafeMutableRawBufferPointer + * } + */ + public static java.lang.foreign.MemorySegment swapRawBufferPointer(java.lang.foreign.MemorySegment buffer) { + try(var arena$ = Arena.ofConfined()) { + MemorySegment _result_pointer = arena$.allocate(SwiftValueLayout.SWIFT_POINTER); + MemorySegment _result_count = arena$.allocate(SwiftValueLayout.SWIFT_INT64); + swiftjava___FakeModule_swapRawBufferPointer_buffer.call(buffer, buffer.byteSize(), _result_pointer, _result_count); + return _result_pointer.get(SwiftValueLayout.SWIFT_POINTER, 0).reinterpret(_result_count.get(SwiftValueLayout.SWIFT_INT64, 0)); + } + } + """ + ) + } + @Test func method_class_helloMemberFunction() throws { let st = Swift2JavaTranslator( From 5a209c93f036ceb2c2cdc9f5bb58c8053d9da309 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Mon, 23 Jun 2025 16:42:45 -0700 Subject: [PATCH 009/141] [JExtract] Bridge closures with UnsafeRawBufferPointer parameter First step to bridging closures with conversions. --- .../MySwiftLibrary/MySwiftLibrary.swift | 4 + .../com/example/swift/HelloJava2Swift.java | 3 + ...Swift2JavaGenerator+FunctionLowering.swift | 82 +++++++++++++--- .../JExtractSwiftLib/FFM/ConversionStep.swift | 53 +++++++++- ...t2JavaGenerator+JavaBindingsPrinting.swift | 50 +++++++--- ...MSwift2JavaGenerator+JavaTranslation.swift | 88 +++++++++++++---- .../FuncCallbackImportTests.swift | 97 +++++++++++++++++++ .../FunctionLoweringTests.swift | 18 ++++ 8 files changed, 351 insertions(+), 44 deletions(-) diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index 8b6066cf7..18b6546d7 100644 --- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -53,6 +53,10 @@ public func globalReceiveRawBuffer(buf: UnsafeRawBufferPointer) -> Int { public var globalBuffer: UnsafeRawBufferPointer = UnsafeRawBufferPointer(UnsafeMutableRawBufferPointer.allocate(byteCount: 124, alignment: 1)) +public func withBuffer(body: (UnsafeRawBufferPointer) -> Void) { + body(globalBuffer) +} + // ==== Internal helpers func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: String = #function) { diff --git a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java index f3073201f..c12d82dd2 100644 --- a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java +++ b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java @@ -47,6 +47,9 @@ static void examples() { SwiftKit.trace("getGlobalBuffer().byteSize()=" + MySwiftLibrary.getGlobalBuffer().byteSize()); + MySwiftLibrary.withBuffer((buf) -> { + SwiftKit.trace("withBuffer{$0.byteSize()}=" + buf.byteSize()); + }); // Example of using an arena; MyClass.deinit is run at end of scope try (var arena = SwiftArena.ofConfined()) { MySwiftClass obj = MySwiftClass.init(2222, 7777, arena); diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index 2c7520ddc..db764e2eb 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -332,14 +332,15 @@ struct CdeclLowering { var parameters: [SwiftParameter] = [] var parameterConversions: [ConversionStep] = [] - for parameter in fn.parameters { - if let _ = try? CType(cdeclType: parameter.type) { - parameters.append(SwiftParameter(convention: .byValue, type: parameter.type)) - parameterConversions.append(.placeholder) - } else { - // Non-trivial types are not yet supported. - throw LoweringError.unhandledType(.function(fn)) - } + for (i, parameter) in fn.parameters.enumerated() { + let parameterName = parameter.parameterName ?? "_\(i)" + let loweredParam = try lowerClosureParameter( + parameter.type, + convention: parameter.convention, + parameterName: parameterName + ) + parameters.append(contentsOf: loweredParam.cdeclParameters) + parameterConversions.append(loweredParam.conversion) } let resultType: SwiftType @@ -352,15 +353,74 @@ struct CdeclLowering { throw LoweringError.unhandledType(.function(fn)) } - // Ignore the conversions for now, since we don't support non-trivial types yet. - _ = (parameterConversions, resultConversion) + let isCompatibleWithC = parameterConversions.allSatisfy(\.isPlaceholder) && resultConversion.isPlaceholder return ( type: .function(SwiftFunctionType(convention: .c, parameters: parameters, resultType: resultType)), - conversion: .placeholder + conversion: isCompatibleWithC ? .placeholder : .closureLowering(parameters: parameterConversions, result: resultConversion) ) } + func lowerClosureParameter( + _ type: SwiftType, + convention: SwiftParameterConvention, + parameterName: String + ) throws -> LoweredParameter { + // If there is a 1:1 mapping between this Swift type and a C type, we just + // return it. + if let _ = try? CType(cdeclType: type) { + return LoweredParameter( + cdeclParameters: [ + SwiftParameter( + convention: .byValue, + parameterName: parameterName, + type: type + ), + ], + conversion: .placeholder + ) + } + + switch type { + case .nominal(let nominal): + if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType { + switch knownType { + case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: + // pointer buffers are lowered to (raw-pointer, count) pair. + let isMutable = knownType == .unsafeMutableRawBufferPointer + return LoweredParameter( + cdeclParameters: [ + SwiftParameter( + convention: .byValue, + parameterName: "\(parameterName)_pointer", + type: .optional(isMutable ? knownTypes.unsafeMutableRawPointer : knownTypes.unsafeRawPointer) + ), + SwiftParameter( + convention: .byValue, + parameterName: "\(parameterName)_count", + type: knownTypes.int + ), + ], + conversion: .tuplify([ + .member(.placeholder, member: "baseAddress"), + .member(.placeholder, member: "count") + ]) + ) + + default: + throw LoweringError.unhandledType(type) + } + } + + // Custom types are not supported yet. + throw LoweringError.unhandledType(type) + + case .function, .metatype, .optional, .tuple: + // TODO: Implement + throw LoweringError.unhandledType(type) + } + } + /// Lower a Swift result type to cdecl out parameters and return type. /// /// - Parameters: diff --git a/Sources/JExtractSwiftLib/FFM/ConversionStep.swift b/Sources/JExtractSwiftLib/FFM/ConversionStep.swift index 315acc600..c7fa53e39 100644 --- a/Sources/JExtractSwiftLib/FFM/ConversionStep.swift +++ b/Sources/JExtractSwiftLib/FFM/ConversionStep.swift @@ -58,6 +58,8 @@ enum ConversionStep: Equatable { /// Perform multiple conversions using the same input. case aggregate([ConversionStep], name: String?) + indirect case closureLowering(parameters: [ConversionStep], result: ConversionStep) + indirect case member(ConversionStep, member: String) /// Count the number of times that the placeholder occurs within this @@ -73,13 +75,20 @@ enum ConversionStep: Equatable { inner.placeholderCount case .initialize(_, arguments: let arguments): arguments.reduce(0) { $0 + $1.argument.placeholderCount } - case .placeholder, .tupleExplode: + case .placeholder, .tupleExplode, .closureLowering: 1 case .tuplify(let elements), .aggregate(let elements, _): elements.reduce(0) { $0 + $1.placeholderCount } } } + var isPlaceholder: Bool { + if case .placeholder = self { + return true + } + return false + } + /// Convert the conversion step into an expression with the given /// value as the placeholder value in the expression. func asExprSyntax(placeholder: String, bodyItems: inout [CodeBlockItemSyntax]) -> ExprSyntax? { @@ -165,6 +174,48 @@ enum ConversionStep: Equatable { } } return nil + + case .closureLowering(let parameterSteps, let resultStep): + var body: [CodeBlockItemSyntax] = [] + + // Lower parameters. + var params: [String] = [] + var args: [ExprSyntax] = [] + for (i, parameterStep) in parameterSteps.enumerated() { + let paramName = "_\(i)" + params.append(paramName) + if case .tuplify(let elemSteps) = parameterStep { + for elemStep in elemSteps { + if let elemExpr = elemStep.asExprSyntax(placeholder: paramName, bodyItems: &body) { + args.append(elemExpr) + } + } + } else if let paramExpr = parameterStep.asExprSyntax(placeholder: paramName, bodyItems: &body) { + args.append(paramExpr) + } + } + + // Call the lowered closure with lowered parameters. + let loweredResult = "\(placeholder)(\(args.map(\.description).joined(separator: ", ")))" + + // Raise the lowered result. + let result = resultStep.asExprSyntax(placeholder: loweredResult.description, bodyItems: &body) + body.append("return \(result)") + + // Construct the closure expression. + var closure = ExprSyntax( + """ + { (\(raw: params.joined(separator: ", "))) in + } + """ + ).cast(ClosureExprSyntax.self) + + closure.statements = CodeBlockItemListSyntax { + body.map { + $0.with(\.leadingTrivia, [.newlines(1), .spaces(4)]) + } + } + return ExprSyntax(closure) } } } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index ba88869d9..a6cc6b26d 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -251,7 +251,6 @@ extension FFMSwift2JavaGenerator { ) } else { // Otherwise, the lambda must be wrapped with the lowered function instance. - assertionFailure("should be unreachable at this point") let apiParams = functionType.parameters.flatMap { $0.javaParameters.map { param in "\(param.type) \(param.name)" } } @@ -262,13 +261,38 @@ extension FFMSwift2JavaGenerator { public interface \(functionType.name) { \(functionType.result.javaResultType) apply(\(apiParams.joined(separator: ", "))); } - private static MemorySegment $toUpcallStub(\(functionType.name) fi, Arena arena) { - return \(cdeclDescriptor).toUpcallStub(() -> { - fi() - }, arena); - } """ ) + + let cdeclParams = functionType.cdeclType.parameters.map( { "\($0.parameterName!)" }) + + printer.printBraceBlock( + """ + private static MemorySegment $toUpcallStub(\(functionType.name) fi, Arena arena) + """ + ) { printer in + printer.print( + """ + return \(cdeclDescriptor).toUpcallStub((\(cdeclParams.joined(separator: ", "))) -> { + """ + ) + printer.indent() + var convertedArgs: [String] = [] + for param in functionType.parameters { + let arg = param.conversion.render(&printer, param.javaParameters[0].name) + convertedArgs.append(arg) + } + + let call = "fi.apply(\(convertedArgs.joined(separator: ", ")))" + let result = functionType.result.conversion.render(&printer, call) + if functionType.result.javaResultType == .void { + printer.print("\(result);") + } else { + printer.print("return \(result);") + } + printer.outdent() + printer.print("}, arena);") + } } } @@ -419,7 +443,7 @@ extension JavaConversionStep { /// Whether the conversion uses SwiftArena. var requiresSwiftArena: Bool { switch self { - case .placeholder, .constant, .readOutParameter: + case .placeholder, .explodedName, .constant, .readMemorySegment: return false case .constructSwiftValue: return true @@ -436,9 +460,9 @@ extension JavaConversionStep { /// Whether the conversion uses temporary Arena. var requiresTemporaryArena: Bool { switch self { - case .placeholder, .constant: + case .placeholder, .explodedName, .constant: return false - case .readOutParameter: + case .readMemorySegment: return true case .cast(let inner, _), .construct(let inner, _), .constructSwiftValue(let inner, _), .swiftValueSelfSegment(let inner): return inner.requiresSwiftArena @@ -459,6 +483,9 @@ extension JavaConversionStep { case .placeholder: return placeholder + case .explodedName(let component): + return "\(placeholder)_\(component)" + case .swiftValueSelfSegment: return "\(placeholder).$memorySegment()" @@ -491,8 +518,9 @@ extension JavaConversionStep { case .constant(let value): return value - case .readOutParameter(let javaType, let name): - return "\(placeholder)_\(name).get(\(ForeignValueLayout(javaType: javaType)!), 0)" + case .readMemorySegment(let inner, let javaType): + let inner = inner.render(&printer, placeholder) + return "\(inner).get(\(ForeignValueLayout(javaType: javaType)!), 0)" } } } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 6f17158b6..af1ddd09f 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -113,6 +113,8 @@ struct TranslatedFunctionType { var name: String var parameters: [TranslatedParameter] var result: TranslatedResult + var swiftType: SwiftFunctionType + var cdeclType: SwiftFunctionType /// Whether or not this functional interface with C ABI compatible. var isCompatibleWithC: Bool { @@ -159,13 +161,19 @@ struct JavaTranslation { case .function, .initializer: decl.name } + // Signature. + let translatedSignature = try translate(loweredFunctionSignature: loweredSignature, methodName: javaName) + // Closures. var funcTypes: [TranslatedFunctionType] = [] for (idx, param) in decl.functionSignature.parameters.enumerated() { switch param.type { case .function(let funcTy): let paramName = param.parameterName ?? "_\(idx)" - let translatedClosure = try translateFunctionType(name: paramName, swiftType: funcTy) + guard case .function( let cdeclTy) = loweredSignature.parameters[idx].cdeclParameters[0].type else { + preconditionFailure("closure parameter wasn't lowered to a function type; \(funcTy)") + } + let translatedClosure = try translateFunctionType(name: paramName, swiftType: funcTy, cdeclType: cdeclTy) funcTypes.append(translatedClosure) case .tuple: // TODO: Implement @@ -175,9 +183,6 @@ struct JavaTranslation { } } - // Signature. - let translatedSignature = try translate(loweredFunctionSignature: loweredSignature, methodName: javaName) - return TranslatedFunctionDecl( name: javaName, functionTypes: funcTypes, @@ -189,23 +194,16 @@ struct JavaTranslation { /// Translate Swift closure type to Java functional interface. func translateFunctionType( name: String, - swiftType: SwiftFunctionType + swiftType: SwiftFunctionType, + cdeclType: SwiftFunctionType ) throws -> TranslatedFunctionType { var translatedParams: [TranslatedParameter] = [] for (i, param) in swiftType.parameters.enumerated() { let paramName = param.parameterName ?? "_\(i)" - if let cType = try? CType(cdeclType: param.type) { - let translatedParam = TranslatedParameter( - javaParameters: [ - JavaParameter(type: cType.javaType, name: paramName) - ], - conversion: .placeholder - ) - translatedParams.append(translatedParam) - continue - } - throw JavaTranslationError.unhandledType(.function(swiftType)) + translatedParams.append( + try translateClosureParameter(param.type, convention: param.convention, parameterName: paramName) + ) } guard let resultCType = try? CType(cdeclType: swiftType.resultType) else { @@ -221,10 +219,55 @@ struct JavaTranslation { return TranslatedFunctionType( name: name, parameters: translatedParams, - result: transltedResult + result: transltedResult, + swiftType: swiftType, + cdeclType: cdeclType ) } + func translateClosureParameter( + _ type: SwiftType, + convention: SwiftParameterConvention, + parameterName: String + ) throws -> TranslatedParameter { + if let cType = try? CType(cdeclType: type) { + return TranslatedParameter( + javaParameters: [ + JavaParameter(type: cType.javaType, name: parameterName) + ], + conversion: .placeholder + ) + } + + switch type { + case .nominal(let nominal): + if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType { + switch knownType { + case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: + return TranslatedParameter( + javaParameters: [ + JavaParameter(type: .javaForeignMemorySegment, name: parameterName) + ], + conversion: .method( + .explodedName(component: "pointer"), + methodName: "reinterpret", + arguments: [ + .explodedName(component: "count") + ], + withArena: false + ) + ) + default: + break + } + } + default: + break + } + throw JavaTranslationError.unhandledType(type) + } + + /// Translate a Swift API signature to the user-facing Java API signature. /// /// Note that the result signature is for the high-level Java API, not the @@ -424,10 +467,10 @@ struct JavaTranslation { JavaParameter(type: .long, name: "count"), ], conversion: .method( - .readOutParameter(.javaForeignMemorySegment, component: "pointer"), + .readMemorySegment(.explodedName(component: "pointer"), as: .javaForeignMemorySegment), methodName: "reinterpret", arguments: [ - .readOutParameter(.long, component: "count") + .readMemorySegment(.explodedName(component: "count"), as: .long), ], withArena: false ) @@ -483,9 +526,12 @@ struct JavaTranslation { /// Describes how to convert values between Java types and FFM types. enum JavaConversionStep { - // Pass through. + // The input case placeholder + // The input exploded into components. + case explodedName(component: String) + // A fixed value case constant(String) @@ -513,7 +559,7 @@ enum JavaConversionStep { indirect case commaSeparated([JavaConversionStep]) // Refer an exploded argument suffixed with `_\(name)`. - indirect case readOutParameter(JavaType, component: String) + indirect case readMemorySegment(JavaConversionStep, as: JavaType) var isPlaceholder: Bool { return if case .placeholder = self { true } else { false } diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index 457e3a7a0..c738914ac 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -31,6 +31,7 @@ final class FuncCallbackImportTests { public func callMe(callback: () -> Void) public func callMeMore(callback: (UnsafeRawPointer, Float) -> Int, fn: () -> ()) + public func withBuffer(body: (UnsafeRawBufferPointer) -> Int) """ @Test("Import: public func callMe(callback: () -> Void)") @@ -237,4 +238,100 @@ final class FuncCallbackImportTests { ) } + @Test("Import: public func withBuffer(body: (UnsafeRawBufferPointer) -> Int)") + func func_withBuffer_body() throws { + let st = Swift2JavaTranslator( + swiftModuleName: "__FakeModule" + ) + st.log.logLevel = .error + + try st.analyze(file: "Fake.swift", text: Self.class_interfaceFile) + + let funcDecl = st.importedGlobalFuncs.first { $0.name == "withBuffer" }! + + let generator = FFMSwift2JavaGenerator( + translator: st, + javaPackage: "com.example.swift", + swiftOutputDirectory: "/fake", + javaOutputDirectory: "/fake" + ) + + let output = CodePrinter.toString { printer in + generator.printFunctionDowncallMethods(&printer, funcDecl) + } + + assertOutput( + output, + expected: + """ + // ==== -------------------------------------------------- + // withBuffer + /** + * {@snippet lang=c : + * void swiftjava___FakeModule_withBuffer_body(ptrdiff_t (*body)(const void *, ptrdiff_t)) + * } + */ + private static class swiftjava___FakeModule_withBuffer_body { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* body: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + __FakeModule.findOrThrow("swiftjava___FakeModule_withBuffer_body"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static void call(java.lang.foreign.MemorySegment body) { + try { + if (SwiftKit.TRACE_DOWNCALLS) { + SwiftKit.traceDowncall(body); + } + HANDLE.invokeExact(body); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + /** + * {snippet lang=c : + * ptrdiff_t (*)(const void *, ptrdiff_t) + * } + */ + private static class $body { + @FunctionalInterface + public interface Function { + long apply(java.lang.foreign.MemorySegment _0, long _1); + } + private static final FunctionDescriptor DESC = FunctionDescriptor.of( + /* -> */SwiftValueLayout.SWIFT_INT, + /* _0: */SwiftValueLayout.SWIFT_POINTER, + /* _1: */SwiftValueLayout.SWIFT_INT + ); + private static final MethodHandle HANDLE = SwiftKit.upcallHandle(Function.class, "apply", DESC); + private static MemorySegment toUpcallStub(Function fi, Arena arena) { + return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); + } + } + } + public static class withBuffer { + @FunctionalInterface + public interface body { + long apply(java.lang.foreign.MemorySegment _0); + } + private static MemorySegment $toUpcallStub(body fi, Arena arena) { + return swiftjava___FakeModule_withBuffer_body.$body.toUpcallStub((_0_pointer, _0_count) -> { + return fi.apply(_0_pointer.reinterpret(_0_count)); + }, arena); + } + } + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func withBuffer(body: (UnsafeRawBufferPointer) -> Int) + * } + */ + public static void withBuffer(withBuffer.body body) { + try(var arena$ = Arena.ofConfined()) { + swiftjava___FakeModule_withBuffer_body.call(withBuffer.$toUpcallStub(body, arena$)); + } + } + """ + ) + } } diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift index c707732e6..9422bbdb0 100644 --- a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift +++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift @@ -333,6 +333,24 @@ final class FunctionLoweringTests { ) } + @Test("Lowering non-C-compatible closures") + func lowerComplexClosureParameter() throws { + try assertLoweredFunction( + """ + func withBuffer(body: (UnsafeRawBufferPointer) -> Int) {} + """, + expectedCDecl: """ + @_cdecl("c_withBuffer") + public func c_withBuffer(_ body: @convention(c) (UnsafeRawPointer?, Int) -> Int) { + withBuffer(body: { (_0) in + return body(_0.baseAddress, _0.count) + }) + } + """, + expectedCFunction: "void c_withBuffer(ptrdiff_t (*body)(const void *, ptrdiff_t))" + ) + } + @Test("Lowering () -> Void type") func lowerSimpleClosureTypes() throws { try assertLoweredFunction(""" From 23055cde668d0d64fb921addf506887e0db5c795 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Tue, 24 Jun 2025 17:07:07 +0900 Subject: [PATCH 010/141] Resolve & jextract subcommands and further dis-entangle commands (#276) --- .github/actions/prepare_env/action.yml | 6 +- .github/workflows/pull_request.yml | 26 +- .gitignore | 3 + .licenseignore | 3 +- .unacceptablelanguageignore | 13 +- ...d-logic.java-common-conventions.gradle.kts | 2 +- Package.swift | 51 +- .../JExtractSwiftCommandPlugin.swift | 159 --- .../JExtractSwiftCommandPlugin/_PluginsShared | 1 - .../JExtractSwiftPlugin.swift | 45 +- .../JavaCompilerPlugin.swift | 21 +- .../SwiftJavaPluginProtocol.swift | 4 +- Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift | 67 +- Samples/JavaDependencySampleApp/Package.swift | 6 +- .../JavaDependencySampleApp/ci-validate.sh | 8 + Samples/SwiftAndJavaJarSampleLib/LICENSE.txt | 202 +++ .../SwiftAndJavaJarSampleLib/Package.swift | 2 +- .../Sources/MySwiftLibrary/MySwiftClass.swift | 69 ++ .../MySwiftLibrary/MySwiftLibrary.swift | 55 +- Samples/SwiftAndJavaJarSampleLib/build.gradle | 72 +- .../SwiftAndJavaJarSampleLib/ci-validate.sh | 56 + Samples/SwiftKitSampleApp/Package.swift | 2 +- .../MySwiftLibrary/_RuntimeWorkarounds.swift | 22 - Samples/SwiftKitSampleApp/build.gradle | 70 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 26 +- .../FFM/FFMSwift2JavaGenerator.swift | 24 + .../JNI/JNISwift2JavaGenerator.swift | 2 +- Sources/JExtractSwiftLib/Swift2Java.swift | 7 +- .../Swift2JavaTranslator.swift | 11 +- .../Configuration.swift | 24 +- .../GenerationMode.swift | 2 +- .../GradleDependencyParsing.swift | 48 + .../JavaTranslator+Configuration.swift | 10 +- .../Commands/ConfigureCommand.swift | 40 +- .../Commands/JExtractCommand.swift | 98 ++ .../Commands/ResolveCommand.swift | 126 +- .../Commands/SwiftJava+JExtract.swift | 40 - ...teWrappers.swift => WrapJavaCommand.swift} | 105 +- Sources/SwiftJavaTool/CommonOptions.swift | 83 +- Sources/SwiftJavaTool/SwiftJava.swift | 271 +--- .../SwiftJavaBaseAsyncParsableCommand.swift | 59 +- Sources/_CShims/process_shims.c | 340 ------ Sources/_Subprocess/API.swift | 370 ++++++ Sources/_Subprocess/AsyncBufferSequence.swift | 99 ++ Sources/_Subprocess/Buffer.swift | 104 ++ Sources/_Subprocess/Configuration.swift | 851 +++++++++++++ Sources/_Subprocess/Error.swift | 141 +++ Sources/_Subprocess/Execution.swift | 192 +++ Sources/_Subprocess/IO/Input.swift | 315 +++++ Sources/_Subprocess/IO/Output.swift | 298 +++++ Sources/_Subprocess/LockedState.swift | 160 --- .../Platforms/Subprocess+Darwin.swift | 625 +++++----- .../Platforms/Subprocess+Linux.swift | 398 +++--- .../Platforms/Subprocess+Unix.swift | 522 ++++---- .../Platforms/Subprocess+Windows.swift | 1088 +++++++++-------- Sources/_Subprocess/Result.swift | 123 ++ Sources/_Subprocess/Subprocess+API.swift | 465 ------- .../Subprocess+AsyncDataSequence.swift | 86 -- .../Subprocess+Configuration.swift | 769 ------------ Sources/_Subprocess/Subprocess+IO.swift | 437 ------- Sources/_Subprocess/Subprocess+Teardown.swift | 125 -- Sources/_Subprocess/Subprocess.swift | 315 ----- .../Input+Foundation.swift | 179 +++ .../Output+Foundation.swift | 53 + .../Span+SubprocessFoundation.swift | 76 ++ Sources/_Subprocess/Teardown.swift | 217 ++++ Sources/_Subprocess/_nio_locks.swift | 526 -------- .../include/process_shims.h | 30 +- .../include/target_conditionals.h} | 10 +- Sources/_SubprocessCShims/process_shims.c | 682 +++++++++++ SwiftKit/build.gradle | 2 +- .../org/swift/swiftkit/SwiftValueLayout.java | 4 +- .../swiftkit/SwiftValueWitnessTable.java | 1 - .../swiftkit/SwiftRuntimeMetadataTest.java | 74 +- .../Asserts/LoweringAssertions.swift | 13 +- .../Asserts/TextAssertions.swift | 6 +- .../FuncCallbackImportTests.swift | 19 +- .../FunctionDescriptorImportTests.swift | 13 +- .../MethodImportTests.swift | 56 +- .../GradleDependencyParsingTests.swift | 45 + docker/Dockerfile | 10 +- docker/install_jdk.sh | 133 +- docker/install_untested_nightly_swift.sh | 43 - 83 files changed, 6462 insertions(+), 5494 deletions(-) delete mode 100644 Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift delete mode 120000 Plugins/JExtractSwiftCommandPlugin/_PluginsShared create mode 100644 Samples/SwiftAndJavaJarSampleLib/LICENSE.txt create mode 100644 Samples/SwiftAndJavaJarSampleLib/Sources/MySwiftLibrary/MySwiftClass.swift create mode 100755 Samples/SwiftAndJavaJarSampleLib/ci-validate.sh delete mode 100644 Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/_RuntimeWorkarounds.swift create mode 100644 Sources/JavaKitConfigurationShared/GradleDependencyParsing.swift create mode 100644 Sources/SwiftJavaTool/Commands/JExtractCommand.swift delete mode 100644 Sources/SwiftJavaTool/Commands/SwiftJava+JExtract.swift rename Sources/SwiftJavaTool/Commands/{SwiftJava+GenerateWrappers.swift => WrapJavaCommand.swift} (53%) delete mode 100644 Sources/_CShims/process_shims.c create mode 100644 Sources/_Subprocess/API.swift create mode 100644 Sources/_Subprocess/AsyncBufferSequence.swift create mode 100644 Sources/_Subprocess/Buffer.swift create mode 100644 Sources/_Subprocess/Configuration.swift create mode 100644 Sources/_Subprocess/Error.swift create mode 100644 Sources/_Subprocess/Execution.swift create mode 100644 Sources/_Subprocess/IO/Input.swift create mode 100644 Sources/_Subprocess/IO/Output.swift delete mode 100644 Sources/_Subprocess/LockedState.swift create mode 100644 Sources/_Subprocess/Result.swift delete mode 100644 Sources/_Subprocess/Subprocess+API.swift delete mode 100644 Sources/_Subprocess/Subprocess+AsyncDataSequence.swift delete mode 100644 Sources/_Subprocess/Subprocess+Configuration.swift delete mode 100644 Sources/_Subprocess/Subprocess+IO.swift delete mode 100644 Sources/_Subprocess/Subprocess+Teardown.swift delete mode 100644 Sources/_Subprocess/Subprocess.swift create mode 100644 Sources/_Subprocess/SubprocessFoundation/Input+Foundation.swift create mode 100644 Sources/_Subprocess/SubprocessFoundation/Output+Foundation.swift create mode 100644 Sources/_Subprocess/SubprocessFoundation/Span+SubprocessFoundation.swift create mode 100644 Sources/_Subprocess/Teardown.swift delete mode 100644 Sources/_Subprocess/_nio_locks.swift rename Sources/{_CShims => _SubprocessCShims}/include/process_shims.h (74%) rename Sources/{_CShims/include/_CShimsTargetConditionals.h => _SubprocessCShims/include/target_conditionals.h} (76%) create mode 100644 Sources/_SubprocessCShims/process_shims.c create mode 100644 Tests/JavaKitConfigurationSharedTests/GradleDependencyParsingTests.swift delete mode 100755 docker/install_untested_nightly_swift.sh diff --git a/.github/actions/prepare_env/action.yml b/.github/actions/prepare_env/action.yml index ebcc8aed2..dfd0a4353 100644 --- a/.github/actions/prepare_env/action.yml +++ b/.github/actions/prepare_env/action.yml @@ -7,13 +7,13 @@ runs: - name: Install System Dependencies run: apt-get -qq update && apt-get -qq install -y make curl wget libjemalloc2 libjemalloc-dev shell: bash - - name: Cache JDK + - name: Cache JDKs id: cache-jdk uses: actions/cache@v4 continue-on-error: true with: - path: /usr/lib/jvm/default-jdk/ - key: ${{ runner.os }}-jdk-${{ matrix.jdk_vendor }}-${{ hashFiles('/usr/lib/jvm/default-jdk/*') }} + path: /usr/lib/jvm/ + key: ${{ runner.os }}-jdk-${{ matrix.jdk_vendor }}-${{ hashFiles('/usr/lib/jvm/*') }} restore-keys: | ${{ runner.os }}-jdk- - name: Install JDK diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 8fa774bd0..39fa7ee6c 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -20,8 +20,7 @@ jobs: strategy: fail-fast: true matrix: - # swift_version: ['nightly-main'] - swift_version: ['6.0.2'] + swift_version: ['6.1.2'] os_version: ['jammy'] jdk_vendor: ['Corretto'] container: @@ -36,7 +35,7 @@ jobs: - name: Gradle :SwiftKit:build run: ./gradlew build -x test - name: Gradle :SwiftKit:check - run: ./gradlew :SwiftKit:check --info + run: ./gradlew :SwiftKit:check --debug - name: Gradle compile JMH benchmarks run: ./gradlew compileJmh --info @@ -46,8 +45,7 @@ jobs: strategy: fail-fast: false matrix: - # swift_version: ['nightly-main'] - swift_version: ['6.0.2'] + swift_version: ['6.1.2'] os_version: ['jammy'] jdk_vendor: ['Corretto'] container: @@ -70,8 +68,7 @@ jobs: strategy: fail-fast: false matrix: - # swift_version: ['nightly-main'] - swift_version: ['6.0.2'] + swift_version: ['6.1.2'] os_version: ['jammy'] jdk_vendor: ['Corretto'] container: @@ -90,8 +87,7 @@ jobs: strategy: fail-fast: false matrix: - # swift_version: ['nightly-main'] - swift_version: ['6.0.2'] + swift_version: ['6.1.2'] os_version: ['jammy'] jdk_vendor: ['Corretto'] container: @@ -110,8 +106,7 @@ jobs: strategy: fail-fast: false matrix: - # swift_version: ['nightly-main'] - swift_version: ['6.0.2'] + swift_version: ['6.1.2'] os_version: ['jammy'] jdk_vendor: ['Corretto'] container: @@ -130,8 +125,7 @@ jobs: strategy: fail-fast: false matrix: - # swift_version: ['nightly-main'] - swift_version: ['6.0.2'] + swift_version: ['6.1.2'] os_version: ['jammy'] jdk_vendor: ['Corretto'] container: @@ -150,8 +144,7 @@ jobs: strategy: fail-fast: false matrix: - # swift_version: ['nightly-main'] - swift_version: ['6.0.2'] + swift_version: ['6.1.2'] os_version: ['jammy'] jdk_vendor: ['Corretto'] container: @@ -170,8 +163,7 @@ jobs: strategy: fail-fast: false matrix: - # swift_version: ['nightly-main'] - swift_version: ['6.0.2'] + swift_version: ['6.1.2'] os_version: ['jammy'] jdk_vendor: ['Corretto'] container: diff --git a/.gitignore b/.gitignore index 6712182f3..f77390a73 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +.swift-version +.sdkmanrc + .DS_Store .build .idea diff --git a/.licenseignore b/.licenseignore index df3de377a..003cac25c 100644 --- a/.licenseignore +++ b/.licenseignore @@ -47,5 +47,6 @@ Plugins/**/_PluginsShared Plugins/**/0_PLEASE_SYMLINK* Plugins/PluginsShared/JavaKitConfigurationShared Samples/JavaDependencySampleApp/gradle -Sources/_Subprocess/_nio_locks.swift +Sources/_Subprocess/** +Sources/_SubprocessCShims/** Samples/gradle diff --git a/.unacceptablelanguageignore b/.unacceptablelanguageignore index 0310d1c36..92a1d726e 100644 --- a/.unacceptablelanguageignore +++ b/.unacceptablelanguageignore @@ -2,16 +2,5 @@ Sources/SwiftJavaBootstrapJavaTool/SwiftJavaBootstrapJavaTool.swift Sources/_Subprocess/Platforms/Subprocess+Darwin.swift Sources/_Subprocess/Platforms/Subprocess+Linux.swift Sources/_Subprocess/Platforms/Subprocess+Unix.swift -Sources/_Subprocess/Platforms/Subprocess+Unix.swift -Sources/_Subprocess/Platforms/Subprocess+Unix.swift -Sources/_Subprocess/Platforms/Subprocess+Unix.swift -Sources/_Subprocess/Platforms/Subprocess+Unix.swift -Sources/_Subprocess/Platforms/Subprocess+Unix.swift -Sources/_Subprocess/Subprocess+Teardown.swift -Sources/_Subprocess/Subprocess+Teardown.swift -Sources/_Subprocess/Subprocess+Teardown.swift -Sources/_Subprocess/Subprocess+Teardown.swift -Sources/_Subprocess/Subprocess+Teardown.swift -Sources/_Subprocess/Subprocess+Teardown.swift -Sources/_Subprocess/Subprocess+Teardown.swift +Sources/_Subprocess/Teardown.swift Sources/_Subprocess/Subprocess.swift \ No newline at end of file diff --git a/BuildLogic/src/main/kotlin/build-logic.java-common-conventions.gradle.kts b/BuildLogic/src/main/kotlin/build-logic.java-common-conventions.gradle.kts index ef88ce157..fb10bee0f 100644 --- a/BuildLogic/src/main/kotlin/build-logic.java-common-conventions.gradle.kts +++ b/BuildLogic/src/main/kotlin/build-logic.java-common-conventions.gradle.kts @@ -23,7 +23,7 @@ plugins { java { toolchain { - languageVersion = JavaLanguageVersion.of(22) + languageVersion = JavaLanguageVersion.of(24) } } diff --git a/Package.swift b/Package.swift index 55102874a..58ee1840b 100644 --- a/Package.swift +++ b/Package.swift @@ -11,6 +11,7 @@ import Foundation // Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home. func findJavaHome() -> String { if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] { + print("JAVA_HOME = \(home)") return home } @@ -85,7 +86,7 @@ let javaIncludePath = "\(javaHome)/include" let package = Package( name: "SwiftJava", platforms: [ - .macOS(.v10_15) + .macOS(.v15) ], products: [ // ==== JavaKit (i.e. calling Java directly Swift utilities) @@ -174,12 +175,6 @@ let package = Package( "JExtractSwiftPlugin" ] ), - .plugin( - name: "JExtractSwiftCommandPlugin", - targets: [ - "JExtractSwiftCommandPlugin" - ] - ), // ==== Examples @@ -195,6 +190,10 @@ let package = Package( .package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"), .package(url: "https://github.com/apple/swift-system", from: "1.4.0"), +// // FIXME: swift-subprocess stopped supporting 6.0 when it moved into a package; +// // we'll need to drop 6.0 as well, but currently blocked on doing so by swiftpm plugin pending design questions +// .package(url: "https://github.com/swiftlang/swift-subprocess.git", revision: "de15b67f7871c8a039ef7f4813eb39a8878f61a6"), + // Benchmarking .package(url: "https://github.com/ordo-one/package-benchmark", .upToNextMajor(from: "1.4.0")), ], @@ -363,7 +362,8 @@ let package = Package( "JavaTypes", "JavaKitShared", "JavaKitConfigurationShared", - "_Subprocess", // using process spawning + // .product(name: "Subprocess", package: "swift-subprocess") + "_Subprocess", ], swiftSettings: [ .swiftLanguageMode(.v5), @@ -379,6 +379,7 @@ let package = Package( .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), .product(name: "ArgumentParser", package: "swift-argument-parser"), + .product(name: "SystemPackage", package: "swift-system"), "JavaKit", "JavaKitJar", "JavaKitNetwork", @@ -387,11 +388,14 @@ let package = Package( "JavaKitShared", "JavaKitConfigurationShared", ], - swiftSettings: [ .swiftLanguageMode(.v5), .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]), .enableUpcomingFeature("BareSlashRegexLiterals"), + .define( + "SYSTEM_PACKAGE_DARWIN", + .when(platforms: [.macOS, .macCatalyst, .iOS, .watchOS, .tvOS, .visionOS])), + .define("SYSTEM_PACKAGE"), ] ), @@ -419,16 +423,6 @@ let package = Package( "SwiftJavaTool" ] ), - .plugin( - name: "JExtractSwiftCommandPlugin", - capability: .command( - intent: .custom(verb: "jextract", description: "Extract Java accessors from Swift module"), - permissions: [ - ]), - dependencies: [ - "SwiftJavaTool" - ] - ), .testTarget( name: "JavaKitTests", @@ -467,6 +461,15 @@ let package = Package( ] ), + .testTarget( + name: "JavaKitConfigurationSharedTests", + dependencies: ["JavaKitConfigurationShared"], + swiftSettings: [ + .swiftLanguageMode(.v5), + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) + ] + ), + .testTarget( name: "JExtractSwiftTests", dependencies: [ @@ -480,7 +483,7 @@ let package = Package( // Experimental Foundation Subprocess Copy .target( - name: "_CShims", + name: "_SubprocessCShims", swiftSettings: [ .swiftLanguageMode(.v5) ] @@ -488,11 +491,15 @@ let package = Package( .target( name: "_Subprocess", dependencies: [ - "_CShims", + "_SubprocessCShims", .product(name: "SystemPackage", package: "swift-system"), ], swiftSettings: [ - .swiftLanguageMode(.v5) + .swiftLanguageMode(.v5), + .define( + "SYSTEM_PACKAGE_DARWIN", + .when(platforms: [.macOS, .macCatalyst, .iOS, .watchOS, .tvOS, .visionOS])), + .define("SYSTEM_PACKAGE"), ] ), ] diff --git a/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift b/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift deleted file mode 100644 index f97feab68..000000000 --- a/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift +++ /dev/null @@ -1,159 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import Foundation -import PackagePlugin - -@main -final class JExtractSwiftCommandPlugin: SwiftJavaPluginProtocol, BuildToolPlugin, CommandPlugin { - - var pluginName: String = "swift-java-command" - var verbose: Bool = getEnvironmentBool("SWIFT_JAVA_VERBOSE") - - /// Build the target before attempting to extract from it. - /// This avoids trying to extract from broken sources. - /// - /// You may disable this if confident that input targets sources are correct and there's no need to kick off a pre-build for some reason. - var buildInputs: Bool = true - - /// Build the target once swift-java sources have been generated. - /// This helps verify that the generated output is correct, and won't miscompile on the next build. - var buildOutputs: Bool = true - - func createBuildCommands(context: PluginContext, target: any Target) async throws -> [Command] { - // FIXME: This is not a build plugin but SwiftPM forces us to impleme the protocol anyway? rdar://139556637 - return [] - } - - func performCommand(context: PluginContext, arguments: [String]) throws { - // Plugin can't have dependencies, so we have some naive argument parsing instead: - self.verbose = arguments.contains("-v") || arguments.contains("--verbose") - - for target in context.package.targets { - guard getSwiftJavaConfigPath(target: target) != nil else { - log("[swift-java-command] Skipping jextract step: Missing swift-java.config for target '\(target.name)'") - continue - } - - do { - let extraArguments = arguments.filter { arg in - arg != "-v" && arg != "--verbose" - } - print("[swift-java-command] Extracting Java wrappers from target: '\(target.name)'...") - try performCommand(context: context, target: target, extraArguments: extraArguments) - } catch { - print("[swift-java-command] error: Failed to extract from target '\(target.name)': \(error)") - } - - print("[swift-java-command] Done.") - } - print("[swift-java-command] Generating sources: " + "done".green + ".") - } - - func prepareJExtractArguments(context: PluginContext, target: Target) throws -> [String] { - guard let sourceModule = target.sourceModule else { return [] } - - // Note: Target doesn't have a directoryURL counterpart to directory, - // so we cannot eliminate this deprecation warning. - let sourceDir = target.directory.string - - let configuration = try readConfiguration(sourceDir: "\(sourceDir)") - - var arguments: [String] = [ - "--input-swift", sourceDir, - "--swift-module", sourceModule.name, - "--output-java", context.outputJavaDirectory.path(percentEncoded: false), - "--output-swift", context.outputSwiftDirectory.path(percentEncoded: false), - // TODO: "--build-cache-directory", ... - // Since plugins cannot depend on libraries we cannot detect what the output files will be, - // as it depends on the contents of the input files. Therefore we have to implement this as a prebuild plugin. - // We'll have to make up some caching inside the tool so we don't re-parse files which have not changed etc. - ] - // arguments.append(sourceDir) // TODO: we could do this shape maybe? to have the dirs last? - if let package = configuration?.javaPackage, !package.isEmpty { - ["--java-package", package] - } - - return arguments - } - - /// Perform the command on a specific target. - func performCommand(context: PluginContext, target: Target, extraArguments: [String]) throws { - guard let sourceModule = target.sourceModule else { return } - - if self.buildInputs { - // Make sure the target can builds properly - log("Pre-building target '\(target.name)' before extracting sources...") - let targetBuildResult = try self.packageManager.build(.target(target.name), parameters: .init()) - - guard targetBuildResult.succeeded else { - print("[swift-java-command] Build of '\(target.name)' failed: \(targetBuildResult.logText)") - return - } - } - - let arguments = try prepareJExtractArguments(context: context, target: target) - - try runExtract(context: context, target: target, arguments: arguments + extraArguments) - - if self.buildOutputs { - // Building the *products* since we need to build the dylib that contains our newly generated sources, - // so just building the target again would not be enough. We build all products which we affected using - // our source generation, which usually would be just a product dylib with our library. - // - // In practice, we'll always want to build after generating; either here, - // or via some other task before we run any Java code, calling into Swift. - log("Post-extract building products with target '\(target.name)'...") - for product in context.package.products where product.targets.contains(where: { $0.id == target.id }) { - log("Post-extract building product '\(product.name)'...") - let buildResult = try self.packageManager.build(.product(product.name), parameters: .init()) - - if buildResult.succeeded { - log("Post-extract build: " + "done".green + ".") - } else { - log("Post-extract build: " + "done".red + "!") - } - } - } - } - - func runExtract(context: PluginContext, target: Target, arguments: [String]) throws { - let process = Process() - process.executableURL = try context.tool(named: "SwiftJavaTool").url - process.arguments = arguments - - do { - log("Execute: \(process.executableURL!.absoluteURL.relativePath) \(arguments.joined(separator: " "))") - - try process.run() - process.waitUntilExit() - - assert(process.terminationStatus == 0, "Process failed with exit code: \(process.terminationStatus)") - } catch { - print("[swift-java-command] Failed to extract Java sources for target: '\(target.name); Error: \(error)") - } - } - -} - -// Mini coloring helper, since we cannot have dependencies we keep it minimal here -extension String { - var red: String { - "\u{001B}[0;31m" + "\(self)" + "\u{001B}[0;0m" - } - var green: String { - "\u{001B}[0;32m" + "\(self)" + "\u{001B}[0;0m" - } -} - diff --git a/Plugins/JExtractSwiftCommandPlugin/_PluginsShared b/Plugins/JExtractSwiftCommandPlugin/_PluginsShared deleted file mode 120000 index de623a5ee..000000000 --- a/Plugins/JExtractSwiftCommandPlugin/_PluginsShared +++ /dev/null @@ -1 +0,0 @@ -../PluginsShared \ No newline at end of file diff --git a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift index 4b52df26d..7bfb51cf0 100644 --- a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift +++ b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift @@ -40,6 +40,10 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { } let sourceDir = target.directory.string + + // The name of the configuration file JavaKit.config from the target for + // which we are generating Swift wrappers for Java classes. + let configFile = URL(filePath: sourceDir).appending(path: "swift-java.config") let configuration = try readConfiguration(sourceDir: "\(sourceDir)") guard let javaPackage = configuration?.javaPackage else { @@ -54,28 +58,55 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { let outputSwiftDirectory = context.outputSwiftDirectory var arguments: [String] = [ - "--input-swift", sourceDir, + /*subcommand=*/"jextract", "--swift-module", sourceModule.name, + "--input-swift", sourceDir, "--output-java", outputJavaDirectory.path(percentEncoded: false), "--output-swift", outputSwiftDirectory.path(percentEncoded: false), + // since SwiftPM requires all "expected" files do end up being written + // and we don't know which files will have actual thunks generated... we force jextract to write even empty files. + "--write-empty-files", // TODO: "--build-cache-directory", ... // Since plugins cannot depend on libraries we cannot detect what the output files will be, // as it depends on the contents of the input files. Therefore we have to implement this as a prebuild plugin. // We'll have to make up some caching inside the tool so we don't re-parse files which have not changed etc. ] - // arguments.append(sourceDir) if !javaPackage.isEmpty { - arguments.append(contentsOf: ["--java-package", javaPackage]) + arguments += ["--java-package", javaPackage] } + let swiftFiles = sourceModule.sourceFiles.map { $0.url }.filter { + $0.pathExtension == "swift" + } + + var outputSwiftFiles: [URL] = swiftFiles.compactMap { sourceFileURL in + guard sourceFileURL.isFileURL else { + return nil as URL? + } + + let sourceFilePath = sourceFileURL.path + guard sourceFilePath.starts(with: sourceDir) else { + fatalError("Could not get relative path for source file \(sourceFilePath)") + } + var outputURL = outputSwiftDirectory + .appending(path: String(sourceFilePath.dropFirst(sourceDir.count).dropLast(sourceFileURL.lastPathComponent.count + 1))) + + let inputFileName = sourceFileURL.deletingPathExtension().lastPathComponent + return outputURL.appending(path: "\(inputFileName)+SwiftJava.swift") + } + + // Append the "module file" that contains any thunks for global func definitions + outputSwiftFiles += [ + outputSwiftDirectory.appending(path: "\(sourceModule.name)Module+SwiftJava.swift") + ] + return [ - .prebuildCommand( + .buildCommand( displayName: "Generate Java wrappers for Swift types", executable: toolURL, arguments: arguments, - // inputFiles: [ configFile ] + swiftFiles, - // outputFiles: outputJavaFiles - outputFilesDirectory: outputSwiftDirectory + inputFiles: [ configFile ] + swiftFiles, + outputFiles: outputSwiftFiles ) ] } diff --git a/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift b/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift index b93fe403a..559551809 100644 --- a/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift +++ b/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift @@ -34,7 +34,7 @@ struct JavaCompilerBuildToolPlugin: BuildToolPlugin { // The name of the configuration file JavaKit.config from the target for // which we are generating Swift wrappers for Java classes. - let configFile = URL(filePath: sourceDir).appending(path: "Java2Swift.config") + let configFile = URL(filePath: sourceDir).appending(path: "swift-java.config") let config: Configuration? if let configData = try? Data(contentsOf: configFile) { @@ -45,18 +45,21 @@ struct JavaCompilerBuildToolPlugin: BuildToolPlugin { // The class files themselves will be generated into the build directory // for this target. - let classFiles = javaFiles.map { sourceFileURL in + let classFiles = javaFiles.compactMap { sourceFileURL in + guard sourceFileURL.isFileURL else { + return nil as URL? + } + let sourceFilePath = sourceFileURL.path guard sourceFilePath.starts(with: sourceDir) else { fatalError("Could not get relative path for source file \(sourceFilePath)") } - return URL( - filePath: context.pluginWorkDirectoryURL.path - ).appending(path: "Java") - .appending(path: String(sourceFilePath.dropFirst(sourceDir.count))) - .deletingPathExtension() - .appendingPathExtension("class") + return URL(filePath: context.pluginWorkDirectoryURL.path) + .appending(path: "Java") + .appending(path: String(sourceFilePath.dropFirst(sourceDir.count))) + .deletingPathExtension() + .appendingPathExtension("class") } let javaHome = URL(filePath: findJavaHome()) @@ -73,7 +76,7 @@ struct JavaCompilerBuildToolPlugin: BuildToolPlugin { "-parameters", // keep parameter names, which allows us to emit them in generated Swift decls ] + (config?.compilerVersionArgs ?? []), inputFiles: javaFiles, - outputFiles: classFiles + outputFiles: classFiles // FIXME: this is not quite enough, javac may generate more files for closures etc, which we don't know about unless we compile first ) ] } diff --git a/Plugins/PluginsShared/SwiftJavaPluginProtocol.swift b/Plugins/PluginsShared/SwiftJavaPluginProtocol.swift index 68f8964e5..57e38ef7a 100644 --- a/Plugins/PluginsShared/SwiftJavaPluginProtocol.swift +++ b/Plugins/PluginsShared/SwiftJavaPluginProtocol.swift @@ -21,8 +21,6 @@ protocol SwiftJavaPluginProtocol { extension SwiftJavaPluginProtocol { func log(_ message: @autoclosure () -> String, terminator: String = "\n") { -// if self.verbose { - print("[\(pluginName)] \(message())", terminator: terminator) -// } + print("[\(pluginName)] \(message())", terminator: terminator) } } diff --git a/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift index effdcc533..cbbbe4252 100644 --- a/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift +++ b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift @@ -44,7 +44,7 @@ struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { log("Config was: \(config)") var javaDependencies = config.dependencies ?? [] - /// Find the manifest files from other Java2Swift executions in any targets + /// Find the manifest files from other swift-java executions in any targets /// this target depends on. var dependentConfigFiles: [(String, URL)] = [] func searchForConfigFiles(in target: any Target) { @@ -88,26 +88,14 @@ struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { } var arguments: [String] = [] - arguments += argumentsModuleName(sourceModule: sourceModule) + arguments += argumentsSwiftModule(sourceModule: sourceModule) arguments += argumentsOutputDirectory(context: context) - - arguments += dependentConfigFiles.flatMap { moduleAndConfigFile in - let (moduleName, configFile) = moduleAndConfigFile - return [ - "--depends-on", - "\(moduleName)=\(configFile.path(percentEncoded: false))" - ] - } - arguments.append(configFile.path(percentEncoded: false)) + arguments += argumentsDependedOnConfigs(dependentConfigFiles) -// guard let classes = config.classes else { -// log("Config at \(configFile) did not have 'classes' configured, skipping java2swift step.") -// return [] -// } let classes = config.classes ?? [:] - print("Classes to wrap: \(classes.map(\.key))") + print("[swift-java-plugin] Classes to wrap (\(classes.count)): \(classes.map(\.key))") - /// Determine the set of Swift files that will be emitted by the Java2Swift tool. + /// Determine the set of Swift files that will be emitted by the swift-java tool. // TODO: this is not precise and won't work with more advanced Java files, e.g. lambdas etc. let outputDirectoryGenerated = self.outputDirectory(context: context, generated: true) let outputSwiftFiles = classes.map { (javaClassName, swiftName) in @@ -165,12 +153,9 @@ struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { .buildCommand( displayName: displayName, executable: executable, - arguments: [ - // FIXME: change to 'resolve' subcommand - "--fetch", configFile.path(percentEncoded: false), - "--swift-module", sourceModule.name, - "--output-directory", outputDirectory(context: context, generated: false).path(percentEncoded: false) - ], + arguments: ["resolve"] + + argumentsOutputDirectory(context: context, generated: false) + + argumentsSwiftModule(sourceModule: sourceModule), environment: [:], inputFiles: [configFile], outputFiles: fetchDependenciesOutputFiles @@ -181,19 +166,24 @@ struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { } if !outputSwiftFiles.isEmpty { + arguments += [ configFile.path(percentEncoded: false) ] + let displayName = "Wrapping \(classes.count) Java classes in Swift target '\(sourceModule.name)'" log("Prepared: \(displayName)") commands += [ .buildCommand( displayName: displayName, executable: executable, - arguments: arguments, + arguments: ["wrap-java"] + + arguments, inputFiles: compiledClassFiles + fetchDependenciesOutputFiles + [ configFile ], outputFiles: outputSwiftFiles ) ] - } else { - log("No Swift output files, skip wrapping") + } + + if commands.isEmpty { + log("No swift-java commands for module '\(sourceModule.name)'") } return commands @@ -201,19 +191,38 @@ struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { } extension SwiftJavaBuildToolPlugin { - func argumentsModuleName(sourceModule: Target) -> [String] { + func argumentsSwiftModule(sourceModule: Target) -> [String] { return [ "--swift-module", sourceModule.name ] } - + + // FIXME: remove this and the deprecated property inside SwiftJava, this is a workaround + // since we cannot have the same option in common options and in the top level + // command from which we get into sub commands. The top command will NOT have this option. + func argumentsSwiftModuleDeprecated(sourceModule: Target) -> [String] { + return [ + "--swift-module-deprecated", sourceModule.name + ] + } + func argumentsOutputDirectory(context: PluginContext, generated: Bool = true) -> [String] { return [ "--output-directory", outputDirectory(context: context, generated: generated).path(percentEncoded: false) ] } - + + func argumentsDependedOnConfigs(_ dependentConfigFiles: [(String, URL)]) -> [String] { + dependentConfigFiles.flatMap { moduleAndConfigFile in + let (moduleName, configFile) = moduleAndConfigFile + return [ + "--depends-on", + "\(moduleName)=\(configFile.path(percentEncoded: false))" + ] + } + } + func outputDirectory(context: PluginContext, generated: Bool = true) -> URL { let dir = context.pluginWorkDirectoryURL if generated { diff --git a/Samples/JavaDependencySampleApp/Package.swift b/Samples/JavaDependencySampleApp/Package.swift index b39e7b811..4fa331160 100644 --- a/Samples/JavaDependencySampleApp/Package.swift +++ b/Samples/JavaDependencySampleApp/Package.swift @@ -43,11 +43,7 @@ let javaIncludePath = "\(javaHome)/include" let package = Package( name: "JavaDependencySampleApp", platforms: [ - .macOS(.v13), - .iOS(.v13), - .tvOS(.v13), - .watchOS(.v6), - .macCatalyst(.v13), + .macOS(.v15), ], products: [ diff --git a/Samples/JavaDependencySampleApp/ci-validate.sh b/Samples/JavaDependencySampleApp/ci-validate.sh index 7012b8413..2758e6aaf 100755 --- a/Samples/JavaDependencySampleApp/ci-validate.sh +++ b/Samples/JavaDependencySampleApp/ci-validate.sh @@ -3,4 +3,12 @@ set -e set -x +# invoke resolve as part of a build run swift run --disable-sandbox + +# explicitly invoke resolve without explicit path or dependency +# the dependencies should be uses from the --swift-module +swift run swift-java resolve \ + Sources/JavaCommonsCSV/swift-java.config \ + --swift-module JavaCommonsCSV \ + --output-directory .build/plugins/outputs/javadependencysampleapp/JavaCommonsCSV/destination/SwiftJavaPlugin/ diff --git a/Samples/SwiftAndJavaJarSampleLib/LICENSE.txt b/Samples/SwiftAndJavaJarSampleLib/LICENSE.txt new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/Samples/SwiftAndJavaJarSampleLib/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Samples/SwiftAndJavaJarSampleLib/Package.swift b/Samples/SwiftAndJavaJarSampleLib/Package.swift index 5f1322398..c4c604eeb 100644 --- a/Samples/SwiftAndJavaJarSampleLib/Package.swift +++ b/Samples/SwiftAndJavaJarSampleLib/Package.swift @@ -43,7 +43,7 @@ let javaIncludePath = "\(javaHome)/include" let package = Package( name: "SwiftAndJavaJarSampleLib", platforms: [ - .macOS(.v10_15) + .macOS(.v15) ], products: [ .library( diff --git a/Samples/SwiftAndJavaJarSampleLib/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/SwiftAndJavaJarSampleLib/Sources/MySwiftLibrary/MySwiftClass.swift new file mode 100644 index 000000000..c842715cd --- /dev/null +++ b/Samples/SwiftAndJavaJarSampleLib/Sources/MySwiftLibrary/MySwiftClass.swift @@ -0,0 +1,69 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// This is a "plain Swift" file containing various types of declarations, +// that is exported to Java by using the `jextract-swift` tool. +// +// No annotations are necessary on the Swift side to perform the export. + +#if os(Linux) +import Glibc +#else +import Darwin.C +#endif + +public class MySwiftClass { + + public var len: Int + public var cap: Int + + public init(len: Int, cap: Int) { + self.len = len + self.cap = cap + + p("\(MySwiftClass.self).len = \(self.len)") + p("\(MySwiftClass.self).cap = \(self.cap)") + let addr = unsafeBitCast(self, to: UInt64.self) + p("initializer done, self = 0x\(String(addr, radix: 16, uppercase: true))") + } + + deinit { + let addr = unsafeBitCast(self, to: UInt64.self) + p("Deinit, self = 0x\(String(addr, radix: 16, uppercase: true))") + } + + public var counter: Int32 = 0 + + public func voidMethod() { + p("") + } + + public func takeIntMethod(i: Int) { + p("i:\(i)") + } + + public func echoIntMethod(i: Int) -> Int { + p("i:\(i)") + return i + } + + public func makeIntMethod() -> Int { + p("make int -> 12") + return 12 + } + + public func makeRandomIntMethod() -> Int { + return Int.random(in: 1..<256) + } +} \ No newline at end of file diff --git a/Samples/SwiftAndJavaJarSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftAndJavaJarSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift index 84e4618fd..e900fdd0f 100644 --- a/Samples/SwiftAndJavaJarSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftAndJavaJarSampleLib/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -39,63 +39,10 @@ public func globalCallMeRunnable(run: () -> ()) { run() } -public class MySwiftClass { - - public var len: Int - public var cap: Int - - public init(len: Int, cap: Int) { - self.len = len - self.cap = cap - - p("\(MySwiftClass.self).len = \(self.len)") - p("\(MySwiftClass.self).cap = \(self.cap)") - let addr = unsafeBitCast(self, to: UInt64.self) - p("initializer done, self = 0x\(String(addr, radix: 16, uppercase: true))") - } - - deinit { - let addr = unsafeBitCast(self, to: UInt64.self) - p("Deinit, self = 0x\(String(addr, radix: 16, uppercase: true))") - } - - public var counter: Int32 = 0 - - public func voidMethod() { - p("") - } - - public func takeIntMethod(i: Int) { - p("i:\(i)") - } - - public func echoIntMethod(i: Int) -> Int { - p("i:\(i)") - return i - } - - public func makeIntMethod() -> Int { - p("make int -> 12") - return 12 - } - - public func makeRandomIntMethod() -> Int { - return Int.random(in: 1..<256) - } -} - // ==== Internal helpers -private func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: String = #function) { +func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: String = #function) { print("[swift][\(file):\(line)](\(function)) \(msg)") fflush(stdout) } -#if os(Linux) -// FIXME: why do we need this workaround? -@_silgen_name("_objc_autoreleaseReturnValue") -public func _objc_autoreleaseReturnValue(a: Any) {} - -@_silgen_name("objc_autoreleaseReturnValue") -public func objc_autoreleaseReturnValue(a: Any) {} -#endif diff --git a/Samples/SwiftAndJavaJarSampleLib/build.gradle b/Samples/SwiftAndJavaJarSampleLib/build.gradle index 2125011db..cc4c79d40 100644 --- a/Samples/SwiftAndJavaJarSampleLib/build.gradle +++ b/Samples/SwiftAndJavaJarSampleLib/build.gradle @@ -38,7 +38,7 @@ repositories { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(22)) + languageVersion.set(JavaLanguageVersion.of(24)) } } @@ -49,38 +49,56 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter") } -// This is for development, when we edit the Swift swift-java project, the outputs of the generated sources may change. -// Thus, we also need to watch and re-build the top level project. -def compileSwiftJExtractPlugin = tasks.register("compileSwiftJExtractPlugin", Exec) { - description = "Rebuild the swift-java root project" +def swiftProductsWithJExtractPlugin() { + def stdout = new ByteArrayOutputStream() + def stderr = new ByteArrayOutputStream() - inputs.file(new File(rootDir, "Package.swift")) - inputs.dir(new File(rootDir, "Sources")) - outputs.dir(new File(rootDir, ".build")) + def result = exec { + commandLine 'swift', 'package', 'describe', '--type', 'json' + standardOutput = stdout + errorOutput = stderr + ignoreExitValue = true + } + + def jsonOutput = stdout.toString() + + if (result.exitValue == 0) { + def json = new JsonSlurper().parseText(jsonOutput) + def products = json.targets + .findAll { target -> + target.product_dependencies?.contains("JExtractSwiftPlugin") + } + .collectMany { target -> + target.product_memberships ?: [] + } + return products + } else { + logger.warn("Command failed: ${stderr.toString()}") + return [] + } +} - workingDir = rootDir +def swiftCheckValid = tasks.register("swift-check-valid", Exec) { commandLine "swift" - args("build", - "-c", swiftBuildConfiguration(), - "--product", "SwiftKitSwift", - "--product", "JExtractSwiftPlugin", - "--product", "JExtractSwiftCommandPlugin") + args("-version") } def jextract = tasks.register("jextract", Exec) { - description = "Builds swift sources, including swift-java source generation" - dependsOn compileSwiftJExtractPlugin + description = "Generate Java wrappers for swift target" + dependsOn swiftCheckValid // only because we depend on "live developing" the plugin while using this project to test it inputs.file(new File(rootDir, "Package.swift")) inputs.dir(new File(rootDir, "Sources")) + // If the package description changes, we should execute jextract again, maybe we added jextract to new targets inputs.file(new File(projectDir, "Package.swift")) - inputs.dir(new File(projectDir, "Sources")) - // TODO: we can use package describe --type json to figure out which targets depend on JExtractSwiftPlugin and will produce outputs - // Avoid adding this directory, but create the expected one specifically for all targets - // which WILL produce sources because they have the plugin + // monitor all targets/products which depend on the JExtract plugin + swiftProductsWithJExtractPlugin().each { + logger.info("[swift-java:jextract (Gradle)] Swift input target: ${it}") + inputs.dir(new File(layout.projectDirectory.asFile, "Sources/${it}".toString())) + } outputs.dir(layout.buildDirectory.dir("../.build/plugins/outputs/${layout.projectDirectory.asFile.getName().toLowerCase()}")) File baseSwiftPluginOutputsDir = layout.buildDirectory.dir("../.build/plugins/outputs/").get().asFile @@ -96,7 +114,16 @@ def jextract = tasks.register("jextract", Exec) { workingDir = layout.projectDirectory commandLine "swift" - args("package", "jextract", "-v", "--log-level", "info") // TODO: pass log level from Gradle build + args("build") // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build + // If we wanted to execute a specific subcommand, we can like this: + // args("run",/* + // "swift-java", "jextract", + // "--swift-module", "MySwiftLibrary", + // // java.package is obtained from the swift-java.config in the swift module + // "--output-java", "${layout.buildDirectory.dir(".build/plugins/outputs/${layout.projectDirectory.asFile.getName().toLowerCase()}/JExtractSwiftPlugin/src/generated/java").get()}", + // "--output-swift", "${layout.buildDirectory.dir(".build/plugins/outputs/${layout.projectDirectory.asFile.getName().toLowerCase()}/JExtractSwiftPlugin/Sources").get()}", + // "--log-level", (logging.level <= LogLevel.INFO ? "debug" : */"info") + // ) } @@ -126,7 +153,6 @@ tasks.named('test', Test) { // ==== Jar publishing List swiftProductDylibPaths() { - def process = ['swift', 'package', 'describe', '--type', 'json'].execute() process.waitFor() @@ -143,8 +169,6 @@ List swiftProductDylibPaths() { target.product_memberships }.flatten() - - def productDylibPaths = products.collect { logger.info("[swift-java] Include Swift product: '${it}' in product resource paths.") "${layout.projectDirectory}/.build/${swiftBuildConfiguration()}/lib${it}.dylib" diff --git a/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh b/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh new file mode 100755 index 000000000..1b4769e8d --- /dev/null +++ b/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh @@ -0,0 +1,56 @@ +#!/bin/sh + +set -e +set -x + +./gradlew jar + +SWIFT_VERSION="$(swift -version | awk '/Swift version/ { print $3 }')" + +# we make sure to build and run with JDK 24 because the runtime needs latest JDK, unlike Gradle which needed 21. +if [ "$(uname -s)" = 'Darwin' ] +then + export OS='osx' +elif [ "$(uname -s)" = 'Linux' ] +then + export OS='linux' + export PATH="${PATH}:/usr/lib/jvm/jdk-24/bin" # we need to make sure to use the latest JDK to actually compile/run the executable +fi + +# check if we can compile a plain Example file that uses the generated Java bindings that should be in the generated jar +# The classpath MUST end with a * if it contains jar files, and must not if it directly contains class files. +SWIFTKIT_CLASSPATH="$(pwd)/../../SwiftKit/build/libs/*" +MYLIB_CLASSPATH="$(pwd)/build/libs/*" +CLASSPATH="$(pwd)/:${SWIFTKIT_CLASSPATH}:${MYLIB_CLASSPATH}" +echo "CLASSPATH = ${CLASSPATH}" + +javac -cp "${CLASSPATH}" Example.java + +if [ "$(uname -s)" = 'Linux' ] +then + SWIFT_LIB_PATHS=/usr/lib/swift/linux + SWIFT_LIB_PATHS="${SWIFT_LIB_PATHS}:$(find . | grep libMySwiftLibrary.so$ | sort | head -n1 | xargs dirname)" + + # if we are on linux, find the Swiftly or System-wide installed libraries dir + SWIFT_CORE_LIB=$(find "$HOME"/.local -name "libswiftCore.so" 2>/dev/null | grep "$SWIFT_VERSION" | head -n1) + if [ -n "$SWIFT_CORE_LIB" ]; then + SWIFT_LIB_PATHS="${SWIFT_LIB_PATHS}:$(dirname "$SWIFT_CORE_LIB")" + else + # maybe there is one installed system-wide in /usr/lib? + SWIFT_CORE_LIB2=$(find /usr/lib -name "libswiftCore.so" 2>/dev/null | grep "$SWIFT_VERSION" | head -n1) + if [ -n "$SWIFT_CORE_LIB2" ]; then + SWIFT_LIB_PATHS="${SWIFT_LIB_PATHS}:$(dirname "$SWIFT_CORE_LIB2")" + fi + fi +elif [ "$(uname -s)" = 'Darwin' ] +then + SWIFT_LIB_PATHS=$(find "$(swiftly use --print-location)" | grep dylib$ | grep libswiftCore | grep macos | xargs dirname) + SWIFT_LIB_PATHS="${SWIFT_LIB_PATHS}:$(pwd)/$(find . | grep libMySwiftLibrary.dylib$ | sort | head -n1 | xargs dirname)" +fi +echo "SWIFT_LIB_PATHS = ${SWIFT_LIB_PATHS}" + +# Can we run the example? +java --enable-native-access=ALL-UNNAMED \ + -Djava.library.path="${SWIFT_LIB_PATHS}" \ + -cp "${CLASSPATH}" \ + Example diff --git a/Samples/SwiftKitSampleApp/Package.swift b/Samples/SwiftKitSampleApp/Package.swift index 65de7d3af..34d8fcd37 100644 --- a/Samples/SwiftKitSampleApp/Package.swift +++ b/Samples/SwiftKitSampleApp/Package.swift @@ -43,7 +43,7 @@ let javaIncludePath = "\(javaHome)/include" let package = Package( name: "SwiftKitSampleApp", platforms: [ - .macOS(.v10_15) + .macOS(.v15) ], products: [ .library( diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/_RuntimeWorkarounds.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/_RuntimeWorkarounds.swift deleted file mode 100644 index f3056973e..000000000 --- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/_RuntimeWorkarounds.swift +++ /dev/null @@ -1,22 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -#if os(Linux) -// FIXME: why do we need this workaround? -@_silgen_name("_objc_autoreleaseReturnValue") -public func _objc_autoreleaseReturnValue(a: Any) {} - -@_silgen_name("objc_autoreleaseReturnValue") -public func objc_autoreleaseReturnValue(a: Any) {} -#endif diff --git a/Samples/SwiftKitSampleApp/build.gradle b/Samples/SwiftKitSampleApp/build.gradle index 18a55f445..647eaba94 100644 --- a/Samples/SwiftKitSampleApp/build.gradle +++ b/Samples/SwiftKitSampleApp/build.gradle @@ -12,9 +12,11 @@ // //===----------------------------------------------------------------------===// +import groovy.json.JsonSlurper import org.swift.swiftkit.gradle.BuildUtils import java.nio.file.* +import kotlinx.serialization.json.* plugins { id("build-logic.java-application-conventions") @@ -30,42 +32,61 @@ repositories { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(22)) + languageVersion.set(JavaLanguageVersion.of(24)) } } +def swiftProductsWithJExtractPlugin() { + def stdout = new ByteArrayOutputStream() + def stderr = new ByteArrayOutputStream() -// This is for development, when we edit the Swift swift-java project, the outputs of the generated sources may change. -// Thus, we also need to watch and re-build the top level project. -def compileSwiftJExtractPlugin = tasks.register("compileSwiftJExtractPlugin", Exec) { - description = "Rebuild the swift-java root project" + def result = exec { + commandLine 'swift', 'package', 'describe', '--type', 'json' + standardOutput = stdout + errorOutput = stderr + ignoreExitValue = true + } + + def jsonOutput = stdout.toString() + + if (result.exitValue == 0) { + def json = new JsonSlurper().parseText(jsonOutput) + def products = json.targets + .findAll { target -> + target.product_dependencies?.contains("JExtractSwiftPlugin") + } + .collectMany { target -> + target.product_memberships ?: [] + } + return products + } else { + logger.warn("Command failed: ${stderr.toString()}") + return [] + } +} - inputs.file(new File(rootDir, "Package.swift")) - inputs.dir(new File(rootDir, "Sources")) - outputs.dir(new File(rootDir, ".build")) - workingDir = rootDir +def swiftCheckValid = tasks.register("swift-check-valid", Exec) { commandLine "swift" - args("build", - "--product", "SwiftKitSwift", - "--product", "JExtractSwiftPlugin", - "--product", "JExtractSwiftCommandPlugin") + args("-version") } def jextract = tasks.register("jextract", Exec) { - description = "Builds swift sources, including swift-java source generation" - dependsOn compileSwiftJExtractPlugin + description = "Generate Java wrappers for swift target" + dependsOn swiftCheckValid // only because we depend on "live developing" the plugin while using this project to test it inputs.file(new File(rootDir, "Package.swift")) inputs.dir(new File(rootDir, "Sources")) + // If the package description changes, we should execute jextract again, maybe we added jextract to new targets inputs.file(new File(projectDir, "Package.swift")) - inputs.dir(new File(projectDir, "Sources")) - // TODO: we can use package describe --type json to figure out which targets depend on JExtractSwiftPlugin and will produce outputs - // Avoid adding this directory, but create the expected one specifically for all targets - // which WILL produce sources because they have the plugin + // monitor all targets/products which depend on the JExtract plugin + swiftProductsWithJExtractPlugin().each { + logger.info("[swift-java:jextract (Gradle)] Swift input target: ${it}") + inputs.dir(new File(layout.projectDirectory.asFile, "Sources/${it}".toString())) + } outputs.dir(layout.buildDirectory.dir("../.build/plugins/outputs/${layout.projectDirectory.asFile.getName().toLowerCase()}")) File baseSwiftPluginOutputsDir = layout.buildDirectory.dir("../.build/plugins/outputs/").get().asFile @@ -81,7 +102,16 @@ def jextract = tasks.register("jextract", Exec) { workingDir = layout.projectDirectory commandLine "swift" - args("package", "jextract", "-v", "--log-level", "debug") // TODO: pass log level from Gradle build + args("build") // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build + // If we wanted to execute a specific subcommand, we can like this: + // args("run",/* + // "swift-java", "jextract", + // "--swift-module", "MySwiftLibrary", + // // java.package is obtained from the swift-java.config in the swift module + // "--output-java", "${layout.buildDirectory.dir(".build/plugins/outputs/${layout.projectDirectory.asFile.getName().toLowerCase()}/JExtractSwiftPlugin/src/generated/java").get()}", + // "--output-swift", "${layout.buildDirectory.dir(".build/plugins/outputs/${layout.projectDirectory.asFile.getName().toLowerCase()}/JExtractSwiftPlugin/Sources").get()}", + // "--log-level", (logging.level <= LogLevel.INFO ? "debug" : */"info") + // ) } // Add the java-swift generated Java sources diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift index 02d715a00..54ce0d72e 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -21,6 +21,19 @@ extension FFMSwift2JavaGenerator { try writeSwiftThunkSources(printer: &printer) } + package func writeSwiftExpectedEmptySources() throws { + for expectedFileName in self.expectedOutputSwiftFiles { + log.trace("Write empty file: \(expectedFileName) ...") + + var printer = CodePrinter() + printer.print("// Empty file generated on purpose") + try printer.writeContents( + outputDirectory: self.swiftOutputDirectory, + javaPackagePath: nil, + filename: expectedFileName) + } + } + package func writeSwiftThunkSources(printer: inout CodePrinter) throws { let moduleFilenameBase = "\(self.swiftModuleName)Module+SwiftJava" let moduleFilename = "\(moduleFilenameBase).swift" @@ -32,15 +45,16 @@ extension FFMSwift2JavaGenerator { if let outputFile = try printer.writeContents( outputDirectory: self.swiftOutputDirectory, javaPackagePath: nil, - filename: moduleFilename) - { - print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile)") + filename: moduleFilename) { + print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile))") + self.expectedOutputSwiftFiles.remove(moduleFilename) } } catch { log.warning("Failed to write to Swift thunks: \(moduleFilename)") } // === All types + // FIXME: write them all into the same file they were declared from +SwiftJava for (_, ty) in self.analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) { let fileNameBase = "\(ty.swiftNominal.qualifiedName)+SwiftJava" let filename = "\(fileNameBase).swift" @@ -52,9 +66,9 @@ extension FFMSwift2JavaGenerator { if let outputFile = try printer.writeContents( outputDirectory: self.swiftOutputDirectory, javaPackagePath: nil, - filename: filename) - { - print("[swift-java] Generated: \(fileNameBase.bold).swift (at \(outputFile)") + filename: filename) { + print("[swift-java] Generated: \(fileNameBase.bold).swift (at \(outputFile))") + self.expectedOutputSwiftFiles.remove(filename) } } catch { log.warning("Failed to write to Swift thunks: \(filename)") diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index 4a65f7cc6..09fcf858a 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -15,6 +15,7 @@ import JavaTypes import SwiftSyntax import SwiftSyntaxBuilder +import struct Foundation.URL package class FFMSwift2JavaGenerator: Swift2JavaGenerator { let log: Logger @@ -35,6 +36,9 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { /// Cached Java translation result. 'nil' indicates failed translation. var translatedDecls: [ImportedFunc: TranslatedFunctionDecl?] = [:] + /// Because we need to write empty files for SwiftPM, keep track which files we didn't write yet, + /// and write an empty file for those. + var expectedOutputSwiftFiles: Set package init( translator: Swift2JavaTranslator, @@ -50,6 +54,20 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { self.javaOutputDirectory = javaOutputDirectory self.symbolTable = translator.symbolTable self.swiftStdlibTypes = translator.swiftStdlibTypeDecls + + // If we are forced to write empty files, construct the expected outputs + if translator.config.writeEmptyFiles ?? false { + self.expectedOutputSwiftFiles = Set(translator.inputs.compactMap { (input) -> String? in + guard let filePathPart = input.filePath.split(separator: "/\(translator.swiftModuleName)/").last else { + return nil + } + + return String(filePathPart.replacing(".swift", with: "+SwiftJava.swift")) + }) + self.expectedOutputSwiftFiles.insert("\(translator.swiftModuleName)Module+SwiftJava.swift") + } else { + self.expectedOutputSwiftFiles = [] + } } func generate() throws { @@ -58,6 +76,12 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { try writeExportedJavaSources() print("[swift-java] Generated Java sources (package: '\(javaPackage)') in: \(javaOutputDirectory)/") + + let pendingFileCount = self.expectedOutputSwiftFiles.count + if pendingFileCount > 0 { + print("[swift-java] Write empty [\(pendingFileCount)] 'expected' files in: \(swiftOutputDirectory)/") + try writeSwiftExpectedEmptySources() + } } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index b15688dce..8ec4b4377 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -87,7 +87,7 @@ extension JNISwift2JavaGenerator { javaPackagePath: javaPackagePath, filename: moduleFilename ) { - print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile)") + print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile))") } } catch { logger.warning("Failed to write to Swift thunks: \(moduleFilename)") diff --git a/Sources/JExtractSwiftLib/Swift2Java.swift b/Sources/JExtractSwiftLib/Swift2Java.swift index 9b1b06bcd..dad252997 100644 --- a/Sources/JExtractSwiftLib/Swift2Java.swift +++ b/Sources/JExtractSwiftLib/Swift2Java.swift @@ -30,21 +30,18 @@ public struct SwiftToJava { fatalError("Missing '--swift-module' name.") } - let translator = Swift2JavaTranslator( - swiftModuleName: swiftModule - ) + let translator = Swift2JavaTranslator(config: config) translator.log.logLevel = config.logLevel ?? .info if config.javaPackage == nil || config.javaPackage!.isEmpty { translator.log.warning("Configured java package is '', consider specifying concrete package for generated sources.") } - print("===== CONFIG ==== \(config)") - guard let inputSwift = config.inputSwiftDirectory else { fatalError("Missing '--swift-input' directory!") } + translator.log.info("Input swift = \(inputSwift)") let inputPaths = inputSwift.split(separator: ",").map { URL(string: String($0))! } translator.log.info("Input paths = \(inputPaths)") diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index 857a053a6..07d8cafbc 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -16,6 +16,7 @@ import Foundation import JavaTypes import SwiftBasicFormat import SwiftParser +import JavaKitConfigurationShared import SwiftSyntax /// Takes swift interfaces and translates them into Java used to access those. @@ -24,6 +25,8 @@ public final class Swift2JavaTranslator { package var log = Logger(label: "translator", logLevel: .info) + let config: Configuration + // ==== Input struct Input { @@ -53,9 +56,13 @@ public final class Swift2JavaTranslator { } public init( - swiftModuleName: String + config: Configuration ) { - self.symbolTable = SwiftSymbolTable(parsedModuleName: swiftModuleName) + guard let swiftModule = config.swiftModule else { + fatalError("Missing 'swiftModule' name.") // FIXME: can we make it required in config? but we shared config for many cases + } + self.config = config + self.symbolTable = SwiftSymbolTable(parsedModuleName: swiftModule) // Create a mock of the Swift standard library. var parsedSwiftModule = SwiftParsedModuleSymbolTable(moduleName: "Swift") diff --git a/Sources/JavaKitConfigurationShared/Configuration.swift b/Sources/JavaKitConfigurationShared/Configuration.swift index 2298b9fa0..876b577be 100644 --- a/Sources/JavaKitConfigurationShared/Configuration.swift +++ b/Sources/JavaKitConfigurationShared/Configuration.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Copyright (c) 2024-2025 Apple Inc. and the Swift.org project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -38,19 +38,17 @@ public struct Configuration: Codable { public var outputJavaDirectory: String? - public var mode: GenerationMode? + public var mode: JExtractGenerationMode? + + public var writeEmptyFiles: Bool? // FIXME: default it to false, but that plays not nice with Codable // ==== java 2 swift --------------------------------------------------------- - /// The Java class path that should be passed along to the Java2Swift tool. + /// The Java class path that should be passed along to the swift-java tool. public var classpath: String? = nil public var classpathEntries: [String] { - guard let classpath else { - return [] - } - - return classpath.split(separator: ":").map(String.init) + return classpath?.split(separator: ":").map(String.init) ?? [] } /// The Java classes that should be translated to Swift. The keys are @@ -80,6 +78,12 @@ public struct JavaDependencyDescriptor: Hashable, Codable { public var artifactID: String public var version: String + public init(groupID: String, artifactID: String, version: String) { + self.groupID = groupID + self.artifactID = artifactID + self.version = version + } + public init(from decoder: any Decoder) throws { let container = try decoder.singleValueContainer() let string = try container.decode(String.self) @@ -154,7 +158,7 @@ public func findSwiftJavaClasspaths(in basePath: String = FileManager.default.cu let baseURL = URL(fileURLWithPath: basePath) var classpathEntries: [String] = [] - print("[debug][swift-java] Searching for *.swift-java.classpath files in: \(baseURL)") + print("[debug][swift-java] Searching for *.swift-java.classpath files in: \(baseURL.absoluteString)") guard let enumerator = fileManager.enumerator(at: baseURL, includingPropertiesForKeys: []) else { print("[warning][swift-java] Failed to get enumerator for \(baseURL)") return [] @@ -162,7 +166,7 @@ public func findSwiftJavaClasspaths(in basePath: String = FileManager.default.cu for case let fileURL as URL in enumerator { if fileURL.lastPathComponent.hasSuffix(".swift-java.classpath") { - print("[debug][swift-java] Constructing classpath with entries from: \(fileURL.relativePath)") + print("[debug][swift-java] Constructing classpath with entries from: \(fileURL.path)") if let contents = try? String(contentsOf: fileURL) { let entries = contents.split(separator: ":").map(String.init) for entry in entries { diff --git a/Sources/JavaKitConfigurationShared/GenerationMode.swift b/Sources/JavaKitConfigurationShared/GenerationMode.swift index ea30a4364..b4a964769 100644 --- a/Sources/JavaKitConfigurationShared/GenerationMode.swift +++ b/Sources/JavaKitConfigurationShared/GenerationMode.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -public enum GenerationMode: String, Codable { +public enum JExtractGenerationMode: String, Codable { /// Foreign Value and Memory API case ffm diff --git a/Sources/JavaKitConfigurationShared/GradleDependencyParsing.swift b/Sources/JavaKitConfigurationShared/GradleDependencyParsing.swift new file mode 100644 index 000000000..bd4aada34 --- /dev/null +++ b/Sources/JavaKitConfigurationShared/GradleDependencyParsing.swift @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Foundation + +// Regex is not sendable yet so we can't cache it in a let +fileprivate var GradleDependencyDescriptorRegex: Regex<(Substring, Substring, Substring, Substring)> { + try! Regex(#"^([^:]+):([^:]+):(\d[^:]+)$"#) // TODO: improve the regex to be more precise +} + +// note: can't use `package` access level since it would break in usage in plugins in `_PluginsShared`. +public func parseDependencyDescriptor(_ descriptor: String) -> JavaDependencyDescriptor? { + guard let match = try? GradleDependencyDescriptorRegex.firstMatch(in: descriptor) else { + return nil + } + + let groupID = String(match.1) + let artifactID = String(match.2) + let version = String(match.3) + + return JavaDependencyDescriptor(groupID: groupID, artifactID: artifactID, version: version) +} + +// note: can't use `package` access level since it would break in usage in plugins in `_PluginsShared`. +public func parseDependencyDescriptors(_ string: String) -> [JavaDependencyDescriptor] { + let descriptors = string.components(separatedBy: ",") + var parsedDependencies: [JavaDependencyDescriptor] = [] + parsedDependencies.reserveCapacity(descriptors.count) + + for descriptor in descriptors { + if let dependency = parseDependencyDescriptor(descriptor) { + parsedDependencies.append(dependency) + } + } + + return parsedDependencies +} \ No newline at end of file diff --git a/Sources/SwiftJavaLib/JavaTranslator+Configuration.swift b/Sources/SwiftJavaLib/JavaTranslator+Configuration.swift index e0f6d0cb4..0f764633d 100644 --- a/Sources/SwiftJavaLib/JavaTranslator+Configuration.swift +++ b/Sources/SwiftJavaLib/JavaTranslator+Configuration.swift @@ -16,11 +16,11 @@ import Foundation import JavaKitConfigurationShared extension JavaTranslator { - /// Read a configuration file from the given URL. - package static func readConfiguration(from url: URL) throws -> Configuration { - let contents = try Data(contentsOf: url) - return try JSONDecoder().decode(Configuration.self, from: contents) - } +// /// Read a configuration file from the given URL. +// package static func readConfiguration(from url: URL) throws -> Configuration { +// let contents = try Data(contentsOf: url) +// return try JSONDecoder().decode(Configuration.self, from: contents) +// } /// Load the configuration file with the given name to populate the known set of /// translated Java classes. diff --git a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift index 8574888d4..2355ea81b 100644 --- a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift +++ b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift @@ -66,42 +66,11 @@ extension SwiftJava { extension SwiftJava.ConfigureCommand { mutating func runSwiftJavaCommand(config: inout Configuration) async throws { - // Form a class path from all of our input sources: - // * Command-line option --classpath - let classpathOptionEntries: [String] = self.commonJVMOptions.classpath.flatMap { $0.split(separator: ":").map(String.init) } - let classpathFromEnv = ProcessInfo.processInfo.environment["CLASSPATH"]?.split(separator: ":").map(String.init) ?? [] - let classpathFromConfig: [String] = config.classpath?.split(separator: ":").map(String.init) ?? [] - print("[debug][swift-java] Base classpath from config: \(classpathFromConfig)") - - var classpathEntries: [String] = classpathFromConfig - - let swiftJavaCachedModuleClasspath = findSwiftJavaClasspaths(in: - // self.effectiveCacheDirectory ?? - FileManager.default.currentDirectoryPath) - print("[debug][swift-java] Classpath from *.swift-java.classpath files: \(swiftJavaCachedModuleClasspath)") - classpathEntries += swiftJavaCachedModuleClasspath - - if !classpathOptionEntries.isEmpty { - print("[debug][swift-java] Classpath from options: \(classpathOptionEntries)") - classpathEntries += classpathOptionEntries - } else { - // * Base classpath from CLASSPATH env variable - print("[debug][swift-java] Classpath from environment: \(classpathFromEnv)") - classpathEntries += classpathFromEnv - } - - let extraClasspath = input ?? "" // FIXME: just use the -cp as usual - let extraClasspathEntries = extraClasspath.split(separator: ":").map(String.init) - print("[debug][swift-java] Extra classpath: \(extraClasspathEntries)") - classpathEntries += extraClasspathEntries + let classpathEntries = self.configureCommandJVMClasspath( + searchDirs: [self.effectiveSwiftModuleURL], config: config) - // Bring up the Java VM when necessary - - if logLevel >= .debug { - let classpathString = classpathEntries.joined(separator: ":") - print("[debug][swift-java] Initialize JVM with classpath: \(classpathString)") - } - let jvm = try JavaVirtualMachine.shared(classpath: classpathEntries) + let jvm = + try self.makeJVM(classpathEntries: classpathEntries) try emitConfiguration(classpath: self.commonJVMOptions.classpath, environment: jvm.environment()) } @@ -178,6 +147,7 @@ extension SwiftJava.ConfigureCommand { // Write the file. try writeContents( contents, + outputDirectory: self.actualOutputDirectory, to: "swift-java.config", description: "swift-java configuration file" ) diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift new file mode 100644 index 000000000..c7fe51a82 --- /dev/null +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -0,0 +1,98 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Foundation +import ArgumentParser +import SwiftJavaLib +import JavaKit +import JavaKitJar +import SwiftJavaLib +import JExtractSwiftLib +import JavaKitConfigurationShared + +/// Extract Java bindings from Swift sources or interface files. +/// +/// Example usage: +/// ``` +/// > swift-java jextract +// --input-swift Sources/SwiftyBusiness \ +/// --output-swift .build/.../outputs/SwiftyBusiness \ +/// --output-Java .build/.../outputs/Java +/// ``` +extension SwiftJava { + + struct JExtractCommand: SwiftJavaBaseAsyncParsableCommand, HasCommonOptions { + static let configuration = CommandConfiguration( + commandName: "jextract", // TODO: wrap-swift? + abstract: "Wrap Swift functions and types with Java bindings, making them available to be called from Java") + + @OptionGroup var commonOptions: SwiftJava.CommonOptions + + @Option(help: "The mode of generation to use for the output files. Used with jextract mode.") + var mode: JExtractGenerationMode = .ffm + + @Option(help: "The name of the Swift module into which the resulting Swift types will be generated.") + var swiftModule: String + + var effectiveSwiftModule: String { + swiftModule + } + + @Option(help: "The Java package the generated Java code should be emitted into.") + var javaPackage: String? = nil + + @Option(help: "The directory where generated Swift files should be written. Generally used with jextract mode.") + var outputSwift: String + + @Option(help: "The directory where generated Java files should be written. Generally used with jextract mode.") + var outputJava: String + + @Flag(help: "Some build systems require an output to be present when it was 'expected', even if empty. This is used by the JExtractSwiftPlugin build plugin, but otherwise should not be necessary.") + var writeEmptyFiles: Bool = false + } +} + +extension SwiftJava.JExtractCommand { + func runSwiftJavaCommand(config: inout Configuration) async throws { + if let javaPackage { + config.javaPackage = javaPackage + } + config.swiftModule = self.effectiveSwiftModule + config.outputJavaDirectory = outputJava + config.outputSwiftDirectory = outputSwift + config.writeEmptyFiles = writeEmptyFiles + + if let inputSwift = commonOptions.inputSwift { + config.inputSwiftDirectory = inputSwift + } else if let swiftModule = config.swiftModule { + // This is a "good guess" technically a target can be somewhere else, but then you can use --input-swift + config.inputSwiftDirectory = "\(FileManager.default.currentDirectoryPath)/Sources/\(swiftModule)" + } + + print("[debug][swift-java] Running 'swift-java jextract' in mode: " + "\(self.mode)".bold) + + try jextractSwift(config: config) + } +} + +extension SwiftJava.JExtractCommand { + func jextractSwift( + config: Configuration + ) throws { + try SwiftToJava(config: config).run() + } + +} + +extension JExtractGenerationMode: ExpressibleByArgument {} diff --git a/Sources/SwiftJavaTool/Commands/ResolveCommand.swift b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift index eb96e4904..4f022d717 100644 --- a/Sources/SwiftJavaTool/Commands/ResolveCommand.swift +++ b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift @@ -22,14 +22,22 @@ import SwiftJavaLib import JavaKitConfigurationShared import JavaKitShared import _Subprocess +#if canImport(System) +import System +#else +@preconcurrency import SystemPackage +#endif + +typealias Configuration = JavaKitConfigurationShared.Configuration extension SwiftJava { - struct ResolveCommand: SwiftJavaBaseAsyncParsableCommand { + struct ResolveCommand: SwiftJavaBaseAsyncParsableCommand, HasCommonOptions, HasCommonJVMOptions { static let configuration = CommandConfiguration( commandName: "resolve", abstract: "Resolve dependencies and write the resulting swift-java.classpath file") @OptionGroup var commonOptions: SwiftJava.CommonOptions + @OptionGroup var commonJVMOptions: SwiftJava.CommonJVMOptions @Option(help: "The name of the Swift module into which the resulting Swift types will be generated.") var swiftModule: String @@ -38,31 +46,58 @@ extension SwiftJava { swiftModule } + @Argument( + help: """ + Additional configuration paths (swift-java.config) files, with defined 'dependencies', \ + or dependency descriptors formatted as 'groupID:artifactID:version' separated by ','. \ + May be empty, in which case the target Swift module's configuration's 'dependencies' will be used. + """ + ) + var input: String? } } extension SwiftJava.ResolveCommand { + var SwiftJavaClasspathPrefix: String { "SWIFT_JAVA_CLASSPATH:" } + var printRuntimeClasspathTaskName: String { "printRuntimeClasspath" } + mutating func runSwiftJavaCommand(config: inout Configuration) async throws { - fatalError("NOT IMPLEMENTED: resolve") - } -} + var dependenciesToResolve: [JavaDependencyDescriptor] = [] + if let input, let inputDependencies = parseDependencyDescriptor(input) { + dependenciesToResolve.append(inputDependencies) + } + if let dependencies = config.dependencies { + dependenciesToResolve += dependencies + } + if dependenciesToResolve.isEmpty { + print("[warn][swift-java] Attempted to 'resolve' dependencies but no dependencies specified in swift-java.config or command input!") + return + } + let dependenciesClasspath = + try await resolveDependencies(swiftModule: swiftModule, dependencies: dependenciesToResolve) -extension SwiftJava { - var SwiftJavaClasspathPrefix: String { "SWIFT_JAVA_CLASSPATH:" } + // FIXME: disentangle the output directory from SwiftJava and then make it a required option in this Command + guard let outputDirectory = self.commonOptions.outputDirectory else { + fatalError("error: Must specify --output-directory in 'resolve' mode! This option will become explicitly required") + } - var printRuntimeClasspathTaskName: String { "printRuntimeClasspath" } + try writeSwiftJavaClasspathFile( + swiftModule: swiftModule, + outputDirectory: outputDirectory, + resolvedClasspath: dependenciesClasspath) + } - func fetchDependencies(swiftModule: String, - dependencies: [JavaDependencyDescriptor]) async throws -> ResolvedDependencyClasspath { + func resolveDependencies( + swiftModule: String, dependencies: [JavaDependencyDescriptor] + ) async throws -> ResolvedDependencyClasspath { let deps = dependencies.map { $0.descriptionGradleStyle } print("[debug][swift-java] Resolve and fetch dependencies for: \(deps)") let dependenciesClasspath = await resolveDependencies(dependencies: dependencies) let classpathEntries = dependenciesClasspath.split(separator: ":") - print("[info][swift-java] Resolved classpath for \(deps.count) dependencies of '\(swiftModule)', classpath entries: \(classpathEntries.count), ", terminator: "") print("done.".green) @@ -88,37 +123,39 @@ extension SwiftJava { try! printGradleProject(directory: resolverDir, dependencies: dependencies) - let process = try! await Subprocess.run( - .at(.init(resolverDir.appendingPathComponent("gradlew").path)), - arguments: [ - "--no-daemon", - "--rerun-tasks", - "\(printRuntimeClasspathTaskName)", - ], - workingDirectory: .init(platformString: resolverDir.path) - ) - - let outString = String( - data: process.standardOutput, - encoding: .utf8 - ) - let errString = String( - data: process.standardError, - encoding: .utf8 - ) + if #available(macOS 15, *) { + let process = try! await _Subprocess.run( + .path(FilePath(resolverDir.appendingPathComponent("gradlew").path)), + arguments: [ + "--no-daemon", + "--rerun-tasks", + "\(printRuntimeClasspathTaskName)", + ], + workingDirectory: Optional(FilePath(resolverDir.path)), + // TODO: we could move to stream processing the outputs + output: .string(limit: Int.max, encoding: UTF8.self), // Don't limit output, we know it will be reasonable size + error: .string(limit: Int.max, encoding: UTF8.self) // Don't limit output, we know it will be reasonable size + ) + + let outString = process.standardOutput ?? "" + let errString = process.standardError ?? "" + + let classpathOutput: String + if let found = outString.split(separator: "\n").first(where: { $0.hasPrefix(self.SwiftJavaClasspathPrefix) }) { + classpathOutput = String(found) + } else if let found = errString.split(separator: "\n").first(where: { $0.hasPrefix(self.SwiftJavaClasspathPrefix) }) { + classpathOutput = String(found) + } else { + let suggestDisablingSandbox = "It may be that the Sandbox has prevented dependency fetching, please re-run with '--disable-sandbox'." + fatalError("Gradle output had no SWIFT_JAVA_CLASSPATH! \(suggestDisablingSandbox). \n" + + "Output was:<<<\(outString ?? "")>>>; Err was:<<<\(errString ?? "")>>>") + } - let classpathOutput: String - if let found = outString?.split(separator: "\n").first(where: { $0.hasPrefix(self.SwiftJavaClasspathPrefix) }) { - classpathOutput = String(found) - } else if let found = errString?.split(separator: "\n").first(where: { $0.hasPrefix(self.SwiftJavaClasspathPrefix) }) { - classpathOutput = String(found) + return String(classpathOutput.dropFirst(SwiftJavaClasspathPrefix.count)) } else { - let suggestDisablingSandbox = "It may be that the Sandbox has prevented dependency fetching, please re-run with '--disable-sandbox'." - fatalError("Gradle output had no SWIFT_JAVA_CLASSPATH! \(suggestDisablingSandbox). \n" + - "Output was:<<<\(outString ?? "")>>>; Err was:<<<\(errString ?? "")>>>") + // Subprocess is unavailable + fatalError("Subprocess is unavailable yet required to execute `gradlew` subprocess. Please update to macOS 15+") } - - return String(classpathOutput.dropFirst(SwiftJavaClasspathPrefix.count)) } func printGradleProject(directory: URL, dependencies: [JavaDependencyDescriptor]) throws { @@ -153,9 +190,9 @@ extension SwiftJava { try settingsGradleText.write(to: settingsGradle, atomically: true, encoding: .utf8) } - mutating func writeFetchedDependenciesClasspath( + mutating func writeSwiftJavaClasspathFile( swiftModule: String, - cacheDir: String, + outputDirectory: String, resolvedClasspath: ResolvedDependencyClasspath) throws { // Convert the artifact name to a module name // e.g. reactive-streams -> ReactiveStreams @@ -163,13 +200,14 @@ extension SwiftJava { // The file contents are just plain let contents = resolvedClasspath.classpath - print("[debug][swift-java] Resolved dependency: \(commonJVMOptions.classpath)") + let filename = "\(swiftModule).swift-java.classpath" + print("[debug][swift-java] Write resolved dependencies to: \(outputDirectory)/\(filename)") // Write the file try writeContents( contents, - outputDirectoryOverride: URL(fileURLWithPath: cacheDir), - to: "\(swiftModule).swift-java.classpath", + outputDirectory: URL(fileURLWithPath: outputDirectory), + to: filename, description: "swift-java.classpath file for module \(swiftModule)" ) } @@ -184,8 +222,6 @@ extension SwiftJava { var searchDir = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) while searchDir.pathComponents.count > 1 { - print("[COPY] Search dir: \(searchDir)") - let gradlewFile = searchDir.appendingPathComponent("gradlew") let gradlewExists = FileManager.default.fileExists(atPath: gradlewFile.path) guard gradlewExists else { diff --git a/Sources/SwiftJavaTool/Commands/SwiftJava+JExtract.swift b/Sources/SwiftJavaTool/Commands/SwiftJava+JExtract.swift deleted file mode 100644 index 79c96d3ed..000000000 --- a/Sources/SwiftJavaTool/Commands/SwiftJava+JExtract.swift +++ /dev/null @@ -1,40 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import Foundation -import ArgumentParser -import SwiftJavaLib -import JavaKit -import JavaKitJar -import SwiftJavaLib -import JExtractSwiftLib -import JavaKitConfigurationShared - -/// Extract Java bindings from Swift sources or interface files. -/// -/// Example usage: -/// ``` -/// > swift-java --input-swift Sources/SwiftyBusiness \ -/// --output-swift .build/.../outputs/SwiftyBusiness \ -/// --output-Java .build/.../outputs/Java -/// ``` -extension SwiftJava { - - mutating func jextractSwift( - config: Configuration - ) throws { - try SwiftToJava(config: config).run() - } - -} diff --git a/Sources/SwiftJavaTool/Commands/SwiftJava+GenerateWrappers.swift b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift similarity index 53% rename from Sources/SwiftJavaTool/Commands/SwiftJava+GenerateWrappers.swift rename to Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift index 676b278d9..349144abf 100644 --- a/Sources/SwiftJavaTool/Commands/SwiftJava+GenerateWrappers.swift +++ b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Copyright (c) 2024-2025 Apple Inc. and the Swift.org project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -21,9 +21,109 @@ import SwiftJavaLib import JavaKitConfigurationShared extension SwiftJava { + + struct WrapJavaCommand: SwiftJavaBaseAsyncParsableCommand, HasCommonOptions, HasCommonJVMOptions { + static let configuration = CommandConfiguration( + commandName: "wrap-java", + abstract: "Wrap Java classes with corresponding Swift bindings.") + + @OptionGroup var commonOptions: SwiftJava.CommonOptions + @OptionGroup var commonJVMOptions: SwiftJava.CommonJVMOptions + + @Option(help: "The name of the Swift module into which the resulting Swift types will be generated.") + var swiftModule: String + + var effectiveSwiftModule: String { + swiftModule + } + + @Option( + help: """ + A swift-java configuration file for a given Swift module name on which this module depends, + e.g., JavaKitJar=Sources/JavaKitJar/Java2Swift.config. There should be one of these options + for each Swift module that this module depends on (transitively) that contains wrapped Java sources. + """ + ) + var dependsOn: [String] = [] + + @Option(help: "The names of Java classes whose declared native methods will be implemented in Swift.") + var swiftNativeImplementation: [String] = [] + + @Option(help: "Cache directory for intermediate results and other outputs between runs") + var cacheDirectory: String? + + @Argument(help: "Path to .jar file whose Java classes should be wrapped using Swift bindings") + var input: String + } +} + +extension SwiftJava.WrapJavaCommand { + + mutating func runSwiftJavaCommand(config: inout Configuration) async throws { + // Get base classpath configuration for this target and configuration + var classpathSearchDirs = [self.effectiveSwiftModuleURL] + if let cacheDir = self.cacheDirectory { + print("[trace][swift-java] Cache directory: \(cacheDir)") + classpathSearchDirs += [URL(fileURLWithPath: cacheDir)] + } else { + print("[trace][swift-java] Cache directory: none") + } + print("[trace][swift-java] INPUT: \(input)") + + var classpathEntries = self.configureCommandJVMClasspath( + searchDirs: classpathSearchDirs, config: config) + + // Load all of the dependent configurations and associate them with Swift modules. + let dependentConfigs = try self.loadDependentConfigs() + print("[debug][swift-java] Dependent configs: \(dependentConfigs.count)") + + // Include classpath entries which libs we depend on require... + for (fromModule, config) in dependentConfigs { + print("[trace][swift-java] Add dependent config (\(fromModule)) classpath elements: \(config.classpathEntries.count)") + // TODO: may need to resolve the dependent configs rather than just get their configs + // TODO: We should cache the resolved classpaths as well so we don't do it many times + for entry in config.classpathEntries { + print("[trace][swift-java] Add dependent config (\(fromModule)) classpath element: \(entry)") + classpathEntries.append(entry) + } + } + + let jvm = try self.makeJVM(classpathEntries: classpathEntries) + + try self.generateWrappers( + config: config, + // classpathEntries: classpathEntries, + dependentConfigs: dependentConfigs, + environment: jvm.environment() + ) + } +} + +extension SwiftJava.WrapJavaCommand { + + /// Load all dependent configs configured with `--depends-on` and return a list of + /// `(SwiftModuleName, Configuration)` tuples. + func loadDependentConfigs() throws -> [(String, Configuration)] { + try dependsOn.map { dependentConfig in + guard let equalLoc = dependentConfig.firstIndex(of: "=") else { + throw JavaToSwiftError.badConfigOption(dependentConfig) + } + + let afterEqual = dependentConfig.index(after: equalLoc) + let swiftModuleName = String(dependentConfig[.. [String] { + // Form a class path from all of our input sources: + // * Command-line option --classpath + let classpathOptionEntries: [String] = self.classpathEntries + let classpathFromEnv = ProcessInfo.processInfo.environment["CLASSPATH"]?.split(separator: ":").map(String.init) ?? [] + print("[debug][swift-java] Base classpath from CLASSPATH environment: \(classpathFromEnv)") + let classpathFromConfig: [String] = config.classpath?.split(separator: ":").map(String.init) ?? [] + print("[debug][swift-java] Base classpath from config: \(classpathFromConfig)") + + var classpathEntries: [String] = classpathFromConfig + + for searchDir in searchDirs { + let classPathFilesSearchDirectory = searchDir.path + print("[debug][swift-java] Search *.swift-java.classpath in: \(classPathFilesSearchDirectory)") + let foundSwiftJavaClasspath = findSwiftJavaClasspaths(in: classPathFilesSearchDirectory) + + print("[debug][swift-java] Classpath from *.swift-java.classpath files: \(foundSwiftJavaClasspath)") + classpathEntries += foundSwiftJavaClasspath + } + + if !classpathOptionEntries.isEmpty { + print("[debug][swift-java] Classpath from options: \(classpathOptionEntries)") + classpathEntries += classpathOptionEntries + } else { + // * Base classpath from CLASSPATH env variable + print("[debug][swift-java] Classpath from environment: \(classpathFromEnv)") + classpathEntries += classpathFromEnv + } + + let extraClasspath = self.commonJVMOptions.classpath + let extraClasspathEntries = extraClasspath.split(separator: ":").map(String.init) + print("[debug][swift-java] Extra classpath: \(extraClasspathEntries)") + classpathEntries += extraClasspathEntries + + // Bring up the Java VM when necessary + + // if logLevel >= .debug { + let classpathString = classpathEntries.joined(separator: ":") + print("[debug][swift-java] Initialize JVM with classpath: \(classpathString)") + // } + + return classpathEntries + } + + func makeJVM(classpathEntries: [String]) throws -> JavaVirtualMachine { + try JavaVirtualMachine.shared(classpath: classpathEntries) + } } \ No newline at end of file diff --git a/Sources/SwiftJavaTool/SwiftJava.swift b/Sources/SwiftJavaTool/SwiftJava.swift index 0e6e64ae6..07eacce42 100644 --- a/Sources/SwiftJavaTool/SwiftJava.swift +++ b/Sources/SwiftJavaTool/SwiftJava.swift @@ -27,7 +27,7 @@ import JavaKitShared /// Command-line utility to drive the export of Java classes into Swift types. @main -struct SwiftJava: SwiftJavaBaseAsyncParsableCommand { // FIXME: this is just a normal async command, no parsing happening here +struct SwiftJava: AsyncParsableCommand { static var _commandName: String { "swift-java" } static let configuration = CommandConfiguration( @@ -35,257 +35,33 @@ struct SwiftJava: SwiftJavaBaseAsyncParsableCommand { // FIXME: this is just a n subcommands: [ ConfigureCommand.self, ResolveCommand.self, + WrapJavaCommand.self, + JExtractCommand.self ]) - @Option(help: "The name of the Swift module into which the resulting Swift types will be generated.") - var swiftModule: String? - - var effectiveSwiftModule: String { - swiftModule ?? "UnknownSwiftModule" - } - - @Option( - help: - "A Java2Swift configuration file for a given Swift module name on which this module depends, e.g., JavaKitJar=Sources/JavaKitJar/Java2Swift.config. There should be one of these options for each Swift module that this module depends on (transitively) that contains wrapped Java sources." - ) - var dependsOn: [String] = [] - - @Flag(help: "Fetch dependencies from given target (containing swift-java configuration) or dependency string") - var fetch: Bool = false - - @Option( - help: "The names of Java classes whose declared native methods will be implemented in Swift." - ) - var swiftNativeImplementation: [String] = [] - - @Option(help: "The directory where generated Swift files should be written. Generally used with jextract mode.") - var outputSwift: String? = nil - - @Option(help: "The directory where generated Java files should be written. Generally used with jextract mode.") - var outputJava: String? = nil - - @Option(help: "The Java package the generated Java code should be emitted into.") - var javaPackage: String? = nil - - @Option(help: "The mode of generation to use for the output files. Used with jextract mode.") - var mode: GenerationMode = .ffm - -// // TODO: clarify this vs outputSwift (history: outputSwift is jextract, and this was java2swift) -// @Option(name: .shortAndLong, help: "The directory in which to output the generated Swift files or the SwiftJava configuration file.") -// var outputDirectory: String? = nil - - @Option(name: .shortAndLong, help: "Directory where to write cached values (e.g. swift-java.classpath files)") - var cacheDirectory: String? = nil - - @OptionGroup var commonOptions: SwiftJava.CommonOptions - @OptionGroup var commonJVMOptions: SwiftJava.CommonJVMOptions - - var effectiveCacheDirectory: String? { - if let cacheDirectory { - return cacheDirectory - } else if let outputDirectory = commonOptions.outputDirectory { - return outputDirectory - } else { - return nil + public static func main() async { + do { + var command = try parseAsRoot(nil) + if var asyncCommand = command as? AsyncParsableCommand { + try await asyncCommand.run() + } else { + try command.run() + } + } catch { + print("Invocation: \(CommandLine.arguments.joined(separator: " "))") + exit(withError: error) } } - @Argument( - help: "The input file, which is either a Java2Swift configuration file or (if '-jar' was specified) a Jar file." - ) - var input: String? // FIXME: top level command cannot have input argument like this - - // FIXME: this is subcommands - /// Describes what kind of generation action is being performed by swift-java. - enum ToolMode { - // /// Generate a configuration file given a Jar file. - // case configuration(extraClasspath: String) // FIXME: this is more like "extract" configuration from classpath - - /// Generate Swift wrappers for Java classes based on the given - /// configuration. - case classWrappers - - /// Fetch dependencies for a module - case fetchDependencies - - /// Extract Java bindings from provided Swift sources. - case jextract // TODO: carry jextract specific config here? - } - - mutating func runSwiftJavaCommand(config: inout Configuration) async throws { - guard CommandLine.arguments.count > 2 else { + mutating func run() async throws { + guard CommandLine.arguments.count >= 2 else { // there's no "default" command, print USAGE when no arguments/parameters are passed. print("error: Must specify mode subcommand (e.g. configure, resolve, jextract, ...).\n\n\(Self.helpMessage())") return } - if let javaPackage { - config.javaPackage = javaPackage - } - - // Determine the mode in which we'll execute. - let toolMode: ToolMode - // TODO: some options are exclusive to each other so we should detect that - if let inputSwift = commonOptions.inputSwift { - guard let inputSwift = commonOptions.inputSwift else { - print("[swift-java] --input-swift enabled 'jextract' mode, however no --output-swift directory was provided!\n\(Self.helpMessage())") - return - } - guard let outputSwift else { - print("[swift-java] --output-swift enabled 'jextract' mode, however no --output-swift directory was provided!\n\(Self.helpMessage())") - return - } - guard let outputJava else { - print("[swift-java] --output-java enabled 'jextract' mode, however no --output-java directory was provided!\n\(Self.helpMessage())") - return - } - config.swiftModule = self.swiftModule ?? "UnknownModule" - config.inputSwiftDirectory = inputSwift - config.outputSwiftDirectory = outputSwift - config.outputJavaDirectory = outputJava - - toolMode = .jextract -// } else if jar { -// guard let input else { -// fatalError("Mode -jar requires path\n\(Self.helpMessage())") -// } -// toolMode = .configuration(extraClasspath: input) - } else if fetch { - guard let input else { - fatalError("Mode 'fetch' requires path\n\(Self.helpMessage())") - } - config = try JavaTranslator.readConfiguration(from: URL(fileURLWithPath: input)) - guard let dependencies = config.dependencies else { - print("[swift-java] Running in 'fetch dependencies' mode but dependencies list was empty!") - print("[swift-java] Nothing to do: done.") - return - } - toolMode = .fetchDependencies - } else { - guard let input else { - fatalError("Mode -jar requires path\n\(Self.helpMessage())") - } - config = try JavaTranslator.readConfiguration(from: URL(fileURLWithPath: input)) - toolMode = .classWrappers - } - - print("[debug][swift-java] Running swift-java in mode: " + "\(toolMode.prettyName)".bold) - - let swiftModule: String = - self.swiftModule ?? - self.effectiveSwiftModule.split(separator: "/").dropLast().last.map(String.init) ?? "__UnknownModule" - - // Load all of the dependent configurations and associate them with Swift - // modules. - let dependentConfigs = try dependsOn.map { dependentConfig in - guard let equalLoc = dependentConfig.firstIndex(of: "=") else { - throw JavaToSwiftError.badConfigOption(dependentConfig) - } - - let afterEqual = dependentConfig.index(after: equalLoc) - let swiftModuleName = String(dependentConfig[.. (javaClassName: String, swiftName: String) { @@ -324,14 +100,3 @@ extension JavaToSwiftError: CustomStringConvertible { } } -extension SwiftJava.ToolMode { - var prettyName: String { - switch self { - case .fetchDependencies: "Fetch dependencies" - case .classWrappers: "Wrap Java classes" - case .jextract: "JExtract Swift for Java" - } - } -} - -extension GenerationMode: ExpressibleByArgument {} diff --git a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift index afe4f80c2..20b177af6 100644 --- a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift +++ b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift @@ -28,6 +28,7 @@ import JavaKitShared protocol SwiftJavaBaseAsyncParsableCommand: AsyncParsableCommand { var logLevel: Logger.Level { get set } + /// Must be implemented with an `@OptionGroup` in Command implementations var commonOptions: SwiftJava.CommonOptions { get set } var effectiveSwiftModule: String { get } @@ -36,10 +37,16 @@ protocol SwiftJavaBaseAsyncParsableCommand: AsyncParsableCommand { } +extension SwiftJavaBaseAsyncParsableCommand { + var outputDirectory: String? { + self.commonOptions.outputDirectory + } +} + extension SwiftJavaBaseAsyncParsableCommand { public mutating func run() async { - print("[info][swift-java] Run: \(CommandLine.arguments.joined(separator: " "))") - print("[info][swift-java] Current work directory: \(URL(fileURLWithPath: "."))") + print("[info][swift-java] Run \(Self.self): \(CommandLine.arguments.joined(separator: " "))") + print("[info][swift-java] Current work directory: \(URL(fileURLWithPath: ".").path)") do { var config = try readInitialConfiguration(command: self) @@ -59,20 +66,10 @@ extension SwiftJavaBaseAsyncParsableCommand { extension SwiftJavaBaseAsyncParsableCommand { mutating func writeContents( _ contents: String, - to filename: String, description: String) throws { - try writeContents( - contents, - outputDirectoryOverride: self.actualOutputDirectory, - to: filename, - description: description) - } - - mutating func writeContents( - _ contents: String, - outputDirectoryOverride: Foundation.URL?, + outputDirectory: Foundation.URL?, to filename: String, description: String) throws { - guard let outputDir = (outputDirectoryOverride ?? actualOutputDirectory) else { + guard let outputDir = outputDirectory else { print("// \(filename) - \(description)") print(contents) return @@ -90,7 +87,7 @@ extension SwiftJavaBaseAsyncParsableCommand { // Write the file: let file = outputDir.appendingPathComponent(filename) - print("[debug][swift-java] Writing \(description) to '\(file.path)'... ", terminator: "") + print("[trace][swift-java] Writing \(description) to '\(file.path)'... ", terminator: "") try contents.write(to: file, atomically: true, encoding: .utf8) print("done.".green) } @@ -106,22 +103,22 @@ extension SwiftJavaBaseAsyncParsableCommand { self.commonOptions.logLevel = newValue } } + + var effectiveSwiftModuleURL: Foundation.URL { + let fm = FileManager.default + return URL(fileURLWithPath: fm.currentDirectoryPath + "/Sources/\(self.effectiveSwiftModule)") + } } extension SwiftJavaBaseAsyncParsableCommand { var moduleBaseDir: Foundation.URL? { -// if let outputDirectory = commonOptions.outputDirectory { -// if outputDirectory == "-" { -// return nil -// } -// + if let outputDirectory = commonOptions.outputDirectory { + if outputDirectory == "-" { + return nil + } // print("[debug][swift-java] Module base directory based on outputDirectory!") // return URL(fileURLWithPath: outputDirectory) -// } - -// guard let swiftModule else { -// return nil -// } + } // Put the result into Sources/\(swiftModule). let baseDir = URL(fileURLWithPath: ".") @@ -137,7 +134,7 @@ extension SwiftJavaBaseAsyncParsableCommand { /// /// Returns `nil` only when we should emit the files to standard output. var actualOutputDirectory: Foundation.URL? { - if let outputDirectory = commonOptions.outputDirectory { + if let outputDirectory = self.commonOptions.outputDirectory { if outputDirectory == "-" { return nil } @@ -152,15 +149,7 @@ extension SwiftJavaBaseAsyncParsableCommand { // For generated Swift sources, put them into a "generated" subdirectory. // The configuration file goes at the top level. - let outputDir: Foundation.URL - // if jar { - // precondition(self.input != nil, "-jar mode requires path to jar to be specified as input path") - outputDir = baseDir - // } else { - // outputDir = baseDir - // .appendingPathComponent("generated", isDirectory: true) - // } - + let outputDir: Foundation.URL = baseDir return outputDir } diff --git a/Sources/_CShims/process_shims.c b/Sources/_CShims/process_shims.c deleted file mode 100644 index fe96c675c..000000000 --- a/Sources/_CShims/process_shims.c +++ /dev/null @@ -1,340 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -#include "include/_CShimsTargetConditionals.h" -#include "include/process_shims.h" - -#if !TARGET_OS_WINDOWS -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -int _was_process_exited(int status) { - return WIFEXITED(status); -} - -int _get_exit_code(int status) { - return WEXITSTATUS(status); -} - -int _was_process_signaled(int status) { - return WIFSIGNALED(status); -} - -int _get_signal_code(int status) { - return WTERMSIG(status); -} - -int _was_process_suspended(int status) { - return WIFSTOPPED(status); -} - -#if TARGET_OS_LINUX -#include - -int _shims_snprintf( - char * _Nonnull str, - int len, - const char * _Nonnull format, - char * _Nonnull str1, - char * _Nonnull str2 -) { - return snprintf(str, len, format, str1, str2); -} -#endif - -// MARK: - Darwin (posix_spawn) -#if TARGET_OS_MAC - -int _subprocess_spawn( - pid_t * _Nonnull pid, - const char * _Nonnull exec_path, - const posix_spawn_file_actions_t _Nullable * _Nonnull file_actions, - const posix_spawnattr_t _Nullable * _Nonnull spawn_attrs, - char * _Nullable const args[_Nonnull], - char * _Nullable const env[_Nullable], - uid_t * _Nullable uid, - gid_t * _Nullable gid, - int number_of_sgroups, const gid_t * _Nullable sgroups, - int create_session -) { - int require_pre_fork = uid != NULL || - gid != NULL || - number_of_sgroups > 0 || - create_session > 0; - - if (require_pre_fork != 0) { -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated" - pid_t childPid = vfork(); -#pragma GCC diagnostic pop - if (childPid != 0) { - *pid = childPid; - return childPid < 0 ? errno : 0; - } - - if (number_of_sgroups > 0 && sgroups != NULL) { - if (setgroups(number_of_sgroups, sgroups) != 0) { - return errno; - } - } - - if (uid != NULL) { - if (setuid(*uid) != 0) { - return errno; - } - } - - if (gid != NULL) { - if (setgid(*gid) != 0) { - return errno; - } - } - - if (create_session != 0) { - (void)setsid(); - } - } - - // Set POSIX_SPAWN_SETEXEC if we already forked - if (require_pre_fork) { - short flags = 0; - int rc = posix_spawnattr_getflags(spawn_attrs, &flags); - if (rc != 0) { - return rc; - } - - rc = posix_spawnattr_setflags( - (posix_spawnattr_t *)spawn_attrs, flags | POSIX_SPAWN_SETEXEC - ); - if (rc != 0) { - return rc; - } - } - - // Spawn - return posix_spawn(pid, exec_path, file_actions, spawn_attrs, args, env); -} - -#endif // TARGET_OS_MAC - -// MARK: - Linux (fork/exec + posix_spawn fallback) - -#if _POSIX_SPAWN -static int _subprocess_posix_spawn_fallback( - pid_t * _Nonnull pid, - const char * _Nonnull exec_path, - const char * _Nullable working_directory, - const int file_descriptors[_Nonnull], - char * _Nullable const args[_Nonnull], - char * _Nullable const env[_Nullable], - gid_t * _Nullable process_group_id -) { - // Setup stdin, stdout, and stderr - posix_spawn_file_actions_t file_actions; - - int rc = posix_spawn_file_actions_init(&file_actions); - if (rc != 0) { return rc; } - if (file_descriptors[0] >= 0) { - rc = posix_spawn_file_actions_adddup2( - &file_actions, file_descriptors[0], STDIN_FILENO - ); - if (rc != 0) { return rc; } - } - if (file_descriptors[2] >= 0) { - rc = posix_spawn_file_actions_adddup2( - &file_actions, file_descriptors[2], STDOUT_FILENO - ); - if (rc != 0) { return rc; } - } - if (file_descriptors[4] >= 0) { - rc = posix_spawn_file_actions_adddup2( - &file_actions, file_descriptors[4], STDERR_FILENO - ); - if (rc != 0) { return rc; } - } - - // Close parent side - if (file_descriptors[1] >= 0) { - rc = posix_spawn_file_actions_addclose(&file_actions, file_descriptors[1]); - if (rc != 0) { return rc; } - } - if (file_descriptors[3] >= 0) { - rc = posix_spawn_file_actions_addclose(&file_actions, file_descriptors[3]); - if (rc != 0) { return rc; } - } - if (file_descriptors[5] >= 0) { - rc = posix_spawn_file_actions_addclose(&file_actions, file_descriptors[5]); - if (rc != 0) { return rc; } - } - - // Setup spawnattr - posix_spawnattr_t spawn_attr; - rc = posix_spawnattr_init(&spawn_attr); - if (rc != 0) { return rc; } - // Masks - sigset_t no_signals; - sigset_t all_signals; - sigemptyset(&no_signals); - sigfillset(&all_signals); - rc = posix_spawnattr_setsigmask(&spawn_attr, &no_signals); - if (rc != 0) { return rc; } - rc = posix_spawnattr_setsigdefault(&spawn_attr, &all_signals); - if (rc != 0) { return rc; } - // Flags - short flags = POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF; - if (process_group_id != NULL) { - flags |= POSIX_SPAWN_SETPGROUP; - rc = posix_spawnattr_setpgroup(&spawn_attr, *process_group_id); - if (rc != 0) { return rc; } - } - rc = posix_spawnattr_setflags(&spawn_attr, flags); - - // Spawn! - rc = posix_spawn( - pid, exec_path, - &file_actions, &spawn_attr, - args, env - ); - posix_spawn_file_actions_destroy(&file_actions); - posix_spawnattr_destroy(&spawn_attr); - return rc; -} -#endif // _POSIX_SPAWN - -int _subprocess_fork_exec( - pid_t * _Nonnull pid, - const char * _Nonnull exec_path, - const char * _Nullable working_directory, - const int file_descriptors[_Nonnull], - char * _Nullable const args[_Nonnull], - char * _Nullable const env[_Nullable], - uid_t * _Nullable uid, - gid_t * _Nullable gid, - gid_t * _Nullable process_group_id, - int number_of_sgroups, const gid_t * _Nullable sgroups, - int create_session, - void (* _Nullable configurator)(void) -) { - int require_pre_fork = working_directory != NULL || - uid != NULL || - gid != NULL || - process_group_id != NULL || - (number_of_sgroups > 0 && sgroups != NULL) || - create_session || - configurator != NULL; - -#if _POSIX_SPAWN - // If posix_spawn is available on this platform and - // we do not require prefork, use posix_spawn if possible. - // - // (Glibc's posix_spawn does not support - // `POSIX_SPAWN_SETEXEC` therefore we have to keep - // using fork/exec if `require_pre_fork` is true. - if (require_pre_fork == 0) { - return _subprocess_posix_spawn_fallback( - pid, exec_path, - working_directory, - file_descriptors, - args, env, - process_group_id - ); - } -#endif - - pid_t child_pid = fork(); - if (child_pid != 0) { - *pid = child_pid; - return child_pid < 0 ? errno : 0; - } - - if (working_directory != NULL) { - if (chdir(working_directory) != 0) { - return errno; - } - } - - - if (uid != NULL) { - if (setuid(*uid) != 0) { - return errno; - } - } - - if (gid != NULL) { - if (setgid(*gid) != 0) { - return errno; - } - } - - if (number_of_sgroups > 0 && sgroups != NULL) { - if (setgroups(number_of_sgroups, sgroups) != 0) { - return errno; - } - } - - if (create_session != 0) { - (void)setsid(); - } - - if (process_group_id != NULL) { - (void)setpgid(0, *process_group_id); - } - - // Bind stdin, stdout, and stderr - int rc = 0; - if (file_descriptors[0] >= 0) { - rc = dup2(file_descriptors[0], STDIN_FILENO); - if (rc < 0) { return errno; } - } - if (file_descriptors[2] >= 0) { - rc = dup2(file_descriptors[2], STDOUT_FILENO); - if (rc < 0) { return errno; } - } - if (file_descriptors[4] >= 0) { - rc = dup2(file_descriptors[4], STDERR_FILENO); - if (rc < 0) { return errno; } - } - // Close parent side - if (file_descriptors[1] >= 0) { - rc = close(file_descriptors[1]); - } - if (file_descriptors[3] >= 0) { - rc = close(file_descriptors[3]); - } - if (file_descriptors[4] >= 0) { - rc = close(file_descriptors[5]); - } - if (rc != 0) { - return errno; - } - // Run custom configuratior - if (configurator != NULL) { - configurator(); - } - // Finally, exec - execve(exec_path, args, env); - // If we got here, something went wrong - return errno; -} - -#endif // !TARGET_OS_WINDOWS - diff --git a/Sources/_Subprocess/API.swift b/Sources/_Subprocess/API.swift new file mode 100644 index 000000000..9673d3e11 --- /dev/null +++ b/Sources/_Subprocess/API.swift @@ -0,0 +1,370 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#if canImport(System) +import System +#else +@preconcurrency import SystemPackage +#endif + +// MARK: - Collected Result + +/// Run a executable with given parameters asynchrously and returns +/// a `CollectedResult` containing the output of the child process. +/// - Parameters: +/// - executable: The executable to run. +/// - arguments: The arguments to pass to the executable. +/// - environment: The environment in which to run the executable. +/// - workingDirectory: The working directory in which to run the executable. +/// - platformOptions: The platform specific options to use +/// when running the executable. +/// - input: The input to send to the executable. +/// - output: The method to use for redirecting the standard output. +/// - error: The method to use for redirecting the standard error. +/// - Returns a CollectedResult containing the result of the run. +@available(macOS 15.0, *) // FIXME: manually added availability +public func run< + Input: InputProtocol, + Output: OutputProtocol, + Error: OutputProtocol +>( + _ executable: Executable, + arguments: Arguments = [], + environment: Environment = .inherit, + workingDirectory: FilePath? = nil, + platformOptions: PlatformOptions = PlatformOptions(), + input: Input = .none, + output: Output = .string, + error: Error = .discarded +) async throws -> CollectedResult { + let configuration = Configuration( + executable: executable, + arguments: arguments, + environment: environment, + workingDirectory: workingDirectory, + platformOptions: platformOptions + ) + return try await run( + configuration, + input: input, + output: output, + error: error + ) +} + +// MARK: - Custom Execution Body + +/// Run a executable with given parameters and a custom closure +/// to manage the running subprocess' lifetime and its IOs. +/// - Parameters: +/// - executable: The executable to run. +/// - arguments: The arguments to pass to the executable. +/// - environment: The environment in which to run the executable. +/// - workingDirectory: The working directory in which to run the executable. +/// - platformOptions: The platform specific options to use +/// when running the executable. +/// - input: The input to send to the executable. +/// - output: How to manage the executable standard ouput. +/// - error: How to manager executable standard error. +/// - isolation: the isolation context to run the body closure. +/// - body: The custom execution body to manually control the running process +/// - Returns a ExecutableResult type containing the return value +/// of the closure. +@available(macOS 15.0, *) // FIXME: manually added availability +public func run( + _ executable: Executable, + arguments: Arguments = [], + environment: Environment = .inherit, + workingDirectory: FilePath? = nil, + platformOptions: PlatformOptions = PlatformOptions(), + input: Input = .none, + output: Output, + error: Error, + isolation: isolated (any Actor)? = #isolation, + body: ((Execution) async throws -> Result) +) async throws -> ExecutionResult where Output.OutputType == Void, Error.OutputType == Void { + return try await Configuration( + executable: executable, + arguments: arguments, + environment: environment, + workingDirectory: workingDirectory, + platformOptions: platformOptions + ) + .run(input: input, output: output, error: error, body) +} + +/// Run a executable with given parameters and a custom closure +/// to manage the running subprocess' lifetime and write to its +/// standard input via `StandardInputWriter` +/// - Parameters: +/// - executable: The executable to run. +/// - arguments: The arguments to pass to the executable. +/// - environment: The environment in which to run the executable. +/// - workingDirectory: The working directory in which to run the executable. +/// - platformOptions: The platform specific options to use +/// when running the executable. +/// - output:How to handle executable's standard output +/// - error: How to handle executable's standard error +/// - isolation: the isolation context to run the body closure. +/// - body: The custom execution body to manually control the running process +/// - Returns a ExecutableResult type containing the return value +/// of the closure. +@available(macOS 15.0, *) // FIXME: manually added availability +public func run( + _ executable: Executable, + arguments: Arguments = [], + environment: Environment = .inherit, + workingDirectory: FilePath? = nil, + platformOptions: PlatformOptions = PlatformOptions(), + output: Output, + error: Error, + isolation: isolated (any Actor)? = #isolation, + body: ((Execution, StandardInputWriter) async throws -> Result) +) async throws -> ExecutionResult where Output.OutputType == Void, Error.OutputType == Void { + return try await Configuration( + executable: executable, + arguments: arguments, + environment: environment, + workingDirectory: workingDirectory, + platformOptions: platformOptions + ) + .run(output: output, error: error, body) +} + +// MARK: - Configuration Based + +/// Run a `Configuration` asynchrously and returns +/// a `CollectedResult` containing the output of the child process. +/// - Parameters: +/// - configuration: The `Subprocess` configuration to run. +/// - input: The input to send to the executable. +/// - output: The method to use for redirecting the standard output. +/// - error: The method to use for redirecting the standard error. +/// - Returns a CollectedResult containing the result of the run. +@available(macOS 15.0, *) // FIXME: manually added availability +public func run< + Input: InputProtocol, + Output: OutputProtocol, + Error: OutputProtocol +>( + _ configuration: Configuration, + input: Input = .none, + output: Output = .string, + error: Error = .discarded +) async throws -> CollectedResult { + let result = try await configuration.run( + input: input, + output: output, + error: error + ) { execution in + let ( + standardOutput, + standardError + ) = try await execution.captureIOs() + return ( + processIdentifier: execution.processIdentifier, + standardOutput: standardOutput, + standardError: standardError + ) + } + return CollectedResult( + processIdentifier: result.value.processIdentifier, + terminationStatus: result.terminationStatus, + standardOutput: result.value.standardOutput, + standardError: result.value.standardError + ) +} + +/// Run a executable with given parameters specified by a `Configuration` +/// - Parameters: +/// - configuration: The `Subprocess` configuration to run. +/// - output: The method to use for redirecting the standard output. +/// - error: The method to use for redirecting the standard error. +/// - isolation: the isolation context to run the body closure. +/// - body: The custom configuration body to manually control +/// the running process and write to its standard input. +/// - Returns a ExecutableResult type containing the return value +/// of the closure. +@available(macOS 15.0, *) // FIXME: manually added availability +public func run( + _ configuration: Configuration, + output: Output, + error: Error, + isolation: isolated (any Actor)? = #isolation, + body: ((Execution, StandardInputWriter) async throws -> Result) +) async throws -> ExecutionResult where Output.OutputType == Void, Error.OutputType == Void { + return try await configuration.run(output: output, error: error, body) +} + +// MARK: - Detached + +/// Run a executable with given parameters and return its process +/// identifier immediately without monitoring the state of the +/// subprocess nor waiting until it exits. +/// +/// This method is useful for launching subprocesses that outlive their +/// parents (for example, daemons and trampolines). +/// +/// - Parameters: +/// - executable: The executable to run. +/// - arguments: The arguments to pass to the executable. +/// - environment: The environment to use for the process. +/// - workingDirectory: The working directory for the process. +/// - platformOptions: The platform specific options to use for the process. +/// - input: A file descriptor to bind to the subprocess' standard input. +/// - output: A file descriptor to bind to the subprocess' standard output. +/// - error: A file descriptor to bind to the subprocess' standard error. +/// - Returns: the process identifier for the subprocess. +@available(macOS 15.0, *) // FIXME: manually added availability +public func runDetached( + _ executable: Executable, + arguments: Arguments = [], + environment: Environment = .inherit, + workingDirectory: FilePath? = nil, + platformOptions: PlatformOptions = PlatformOptions(), + input: FileDescriptor? = nil, + output: FileDescriptor? = nil, + error: FileDescriptor? = nil +) throws -> ProcessIdentifier { + let config: Configuration = Configuration( + executable: executable, + arguments: arguments, + environment: environment, + workingDirectory: workingDirectory, + platformOptions: platformOptions + ) + return try runDetached(config, input: input, output: output, error: error) +} + +/// Run a executable with given configuration and return its process +/// identifier immediately without monitoring the state of the +/// subprocess nor waiting until it exits. +/// +/// This method is useful for launching subprocesses that outlive their +/// parents (for example, daemons and trampolines). +/// +/// - Parameters: +/// - configuration: The `Subprocess` configuration to run. +/// - input: A file descriptor to bind to the subprocess' standard input. +/// - output: A file descriptor to bind to the subprocess' standard output. +/// - error: A file descriptor to bind to the subprocess' standard error. +/// - Returns: the process identifier for the subprocess. +@available(macOS 15.0, *) // FIXME: manually added availability +public func runDetached( + _ configuration: Configuration, + input: FileDescriptor? = nil, + output: FileDescriptor? = nil, + error: FileDescriptor? = nil +) throws -> ProcessIdentifier { + switch (input, output, error) { + case (.none, .none, .none): + let processOutput = DiscardedOutput() + let processError = DiscardedOutput() + return try configuration.spawn( + withInput: NoInput().createPipe(), + output: processOutput, + outputPipe: try processOutput.createPipe(), + error: processError, + errorPipe: try processError.createPipe() + ).processIdentifier + case (.none, .none, .some(let errorFd)): + let processOutput = DiscardedOutput() + let processError = FileDescriptorOutput(fileDescriptor: errorFd, closeAfterSpawningProcess: false) + return try configuration.spawn( + withInput: NoInput().createPipe(), + output: processOutput, + outputPipe: try processOutput.createPipe(), + error: processError, + errorPipe: try processError.createPipe() + ).processIdentifier + case (.none, .some(let outputFd), .none): + let processOutput = FileDescriptorOutput(fileDescriptor: outputFd, closeAfterSpawningProcess: false) + let processError = DiscardedOutput() + return try configuration.spawn( + withInput: NoInput().createPipe(), + output: processOutput, + outputPipe: try processOutput.createPipe(), + error: processError, + errorPipe: try processError.createPipe() + ).processIdentifier + case (.none, .some(let outputFd), .some(let errorFd)): + let processOutput = FileDescriptorOutput( + fileDescriptor: outputFd, + closeAfterSpawningProcess: false + ) + let processError = FileDescriptorOutput( + fileDescriptor: errorFd, + closeAfterSpawningProcess: false + ) + return try configuration.spawn( + withInput: NoInput().createPipe(), + output: processOutput, + outputPipe: try processOutput.createPipe(), + error: processError, + errorPipe: try processError.createPipe() + ).processIdentifier + case (.some(let inputFd), .none, .none): + let processOutput = DiscardedOutput() + let processError = DiscardedOutput() + return try configuration.spawn( + withInput: FileDescriptorInput( + fileDescriptor: inputFd, + closeAfterSpawningProcess: false + ).createPipe(), + output: processOutput, + outputPipe: try processOutput.createPipe(), + error: processError, + errorPipe: try processError.createPipe() + ).processIdentifier + case (.some(let inputFd), .none, .some(let errorFd)): + let processOutput = DiscardedOutput() + let processError = FileDescriptorOutput( + fileDescriptor: errorFd, + closeAfterSpawningProcess: false + ) + return try configuration.spawn( + withInput: FileDescriptorInput(fileDescriptor: inputFd, closeAfterSpawningProcess: false).createPipe(), + output: processOutput, + outputPipe: try processOutput.createPipe(), + error: processError, + errorPipe: try processError.createPipe() + ).processIdentifier + case (.some(let inputFd), .some(let outputFd), .none): + let processOutput = FileDescriptorOutput( + fileDescriptor: outputFd, + closeAfterSpawningProcess: false + ) + let processError = DiscardedOutput() + return try configuration.spawn( + withInput: FileDescriptorInput(fileDescriptor: inputFd, closeAfterSpawningProcess: false).createPipe(), + output: processOutput, + outputPipe: try processOutput.createPipe(), + error: processError, + errorPipe: try processError.createPipe() + ).processIdentifier + case (.some(let inputFd), .some(let outputFd), .some(let errorFd)): + let processOutput = FileDescriptorOutput( + fileDescriptor: outputFd, + closeAfterSpawningProcess: false + ) + let processError = FileDescriptorOutput( + fileDescriptor: errorFd, + closeAfterSpawningProcess: false + ) + return try configuration.spawn( + withInput: FileDescriptorInput(fileDescriptor: inputFd, closeAfterSpawningProcess: false).createPipe(), + output: processOutput, + outputPipe: try processOutput.createPipe(), + error: processError, + errorPipe: try processError.createPipe() + ).processIdentifier + } +} diff --git a/Sources/_Subprocess/AsyncBufferSequence.swift b/Sources/_Subprocess/AsyncBufferSequence.swift new file mode 100644 index 000000000..4316f7ebb --- /dev/null +++ b/Sources/_Subprocess/AsyncBufferSequence.swift @@ -0,0 +1,99 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#if canImport(System) +import System +#else +@preconcurrency import SystemPackage +#endif + +internal struct AsyncBufferSequence: AsyncSequence, Sendable { + internal typealias Failure = any Swift.Error + + internal typealias Element = SequenceOutput.Buffer + + @_nonSendable + internal struct Iterator: AsyncIteratorProtocol { + internal typealias Element = SequenceOutput.Buffer + + private let fileDescriptor: TrackedFileDescriptor + private var buffer: [UInt8] + private var currentPosition: Int + private var finished: Bool + + internal init(fileDescriptor: TrackedFileDescriptor) { + self.fileDescriptor = fileDescriptor + self.buffer = [] + self.currentPosition = 0 + self.finished = false + } + + internal mutating func next() async throws -> SequenceOutput.Buffer? { + let data = try await self.fileDescriptor.wrapped.readChunk( + upToLength: readBufferSize + ) + if data == nil { + // We finished reading. Close the file descriptor now + try self.fileDescriptor.safelyClose() + return nil + } + return data + } + } + + private let fileDescriptor: TrackedFileDescriptor + + init(fileDescriptor: TrackedFileDescriptor) { + self.fileDescriptor = fileDescriptor + } + + internal func makeAsyncIterator() -> Iterator { + return Iterator(fileDescriptor: self.fileDescriptor) + } +} + +// MARK: - Page Size +import _SubprocessCShims + +#if canImport(Darwin) +import Darwin +internal import MachO.dyld + +private let _pageSize: Int = { + Int(_subprocess_vm_size()) +}() +#elseif canImport(WinSDK) +import WinSDK +private let _pageSize: Int = { + var sysInfo: SYSTEM_INFO = SYSTEM_INFO() + GetSystemInfo(&sysInfo) + return Int(sysInfo.dwPageSize) +}() +#elseif os(WASI) +// WebAssembly defines a fixed page size +private let _pageSize: Int = 65_536 +#elseif canImport(Android) +@preconcurrency import Android +private let _pageSize: Int = Int(getpagesize()) +#elseif canImport(Glibc) +@preconcurrency import Glibc +private let _pageSize: Int = Int(getpagesize()) +#elseif canImport(Musl) +@preconcurrency import Musl +private let _pageSize: Int = Int(getpagesize()) +#elseif canImport(C) +private let _pageSize: Int = Int(getpagesize()) +#endif // canImport(Darwin) + +@inline(__always) +internal var readBufferSize: Int { + return _pageSize +} diff --git a/Sources/_Subprocess/Buffer.swift b/Sources/_Subprocess/Buffer.swift new file mode 100644 index 000000000..3ce73d7ea --- /dev/null +++ b/Sources/_Subprocess/Buffer.swift @@ -0,0 +1,104 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +@preconcurrency internal import Dispatch + +extension SequenceOutput { + /// A immutable collection of bytes + public struct Buffer: Sendable { + #if os(Windows) + private var data: [UInt8] + + internal init(data: [UInt8]) { + self.data = data + } + #else + private var data: DispatchData + + internal init(data: DispatchData) { + self.data = data + } + #endif + } +} + +// MARK: - Properties +extension SequenceOutput.Buffer { + /// Number of bytes stored in the buffer + public var count: Int { + return self.data.count + } + + /// A Boolean value indicating whether the collection is empty. + public var isEmpty: Bool { + return self.data.isEmpty + } +} + +// MARK: - Accessors +extension SequenceOutput.Buffer { + #if !SubprocessSpan + /// Access the raw bytes stored in this buffer + /// - Parameter body: A closure with an `UnsafeRawBufferPointer` parameter that + /// points to the contiguous storage for the type. If no such storage exists, + /// the method creates it. If body has a return value, this method also returns + /// that value. The argument is valid only for the duration of the + /// closure’s SequenceOutput. + /// - Returns: The return value, if any, of the body closure parameter. + public func withUnsafeBytes( + _ body: (UnsafeRawBufferPointer) throws -> ResultType + ) rethrows -> ResultType { + return try self._withUnsafeBytes(body) + } + #endif // !SubprocessSpan + + internal func _withUnsafeBytes( + _ body: (UnsafeRawBufferPointer) throws -> ResultType + ) rethrows -> ResultType { + #if os(Windows) + return try self.data.withUnsafeBytes(body) + #else + // Although DispatchData was designed to be uncontiguous, in practice + // we found that almost all DispatchData are contiguous. Therefore + // we can access this body in O(1) most of the time. + return try self.data.withUnsafeBytes { ptr in + let bytes = UnsafeRawBufferPointer(start: ptr, count: self.data.count) + return try body(bytes) + } + #endif + } + + private enum SpanBacking { + case pointer(UnsafeBufferPointer) + case array([UInt8]) + } +} + +// MARK: - Hashable, Equatable +extension SequenceOutput.Buffer: Equatable, Hashable { + #if os(Windows) + // Compiler generated conformances + #else + public static func == (lhs: SequenceOutput.Buffer, rhs: SequenceOutput.Buffer) -> Bool { + return lhs.data.elementsEqual(rhs.data) + } + + public func hash(into hasher: inout Hasher) { + self.data.withUnsafeBytes { ptr in + let bytes = UnsafeRawBufferPointer( + start: ptr, + count: self.data.count + ) + hasher.combine(bytes: bytes) + } + } + #endif +} diff --git a/Sources/_Subprocess/Configuration.swift b/Sources/_Subprocess/Configuration.swift new file mode 100644 index 000000000..4dad1a479 --- /dev/null +++ b/Sources/_Subprocess/Configuration.swift @@ -0,0 +1,851 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#if canImport(System) +import System +#else +@preconcurrency import SystemPackage +#endif + +#if canImport(Darwin) +import Darwin +#elseif canImport(Bionic) +import Bionic +#elseif canImport(Glibc) +import Glibc +#elseif canImport(Musl) +import Musl +#elseif canImport(WinSDK) +import WinSDK +#endif + +internal import Dispatch + +/// A collection of configurations parameters to use when +/// spawning a subprocess. +public struct Configuration: Sendable { + /// The executable to run. + public var executable: Executable + /// The arguments to pass to the executable. + public var arguments: Arguments + /// The environment to use when running the executable. + public var environment: Environment + /// The working directory to use when running the executable. + public var workingDirectory: FilePath + /// The platform specifc options to use when + /// running the subprocess. + public var platformOptions: PlatformOptions + + public init( + executable: Executable, + arguments: Arguments = [], + environment: Environment = .inherit, + workingDirectory: FilePath? = nil, + platformOptions: PlatformOptions = PlatformOptions() + ) { + self.executable = executable + self.arguments = arguments + self.environment = environment + self.workingDirectory = workingDirectory ?? .currentWorkingDirectory + self.platformOptions = platformOptions + } + + @available(macOS 15.0, *) // FIXME: manually added availability + internal func run< + Result, + Output: OutputProtocol, + Error: OutputProtocol + >( + output: Output, + error: Error, + isolation: isolated (any Actor)? = #isolation, + _ body: ( + Execution, + StandardInputWriter + ) async throws -> Result + ) async throws -> ExecutionResult { + let input = CustomWriteInput() + + let inputPipe = try input.createPipe() + let outputPipe = try output.createPipe() + let errorPipe = try error.createPipe() + + let execution = try self.spawn( + withInput: inputPipe, + output: output, + outputPipe: outputPipe, + error: error, + errorPipe: errorPipe + ) + // After spawn, cleanup child side fds + try await self.cleanup( + execution: execution, + inputPipe: inputPipe, + outputPipe: outputPipe, + errorPipe: errorPipe, + childSide: true, + parentSide: false, + attemptToTerminateSubProcess: false + ) + return try await withAsyncTaskCleanupHandler { + async let waitingStatus = try await monitorProcessTermination( + forProcessWithIdentifier: execution.processIdentifier + ) + // Body runs in the same isolation + let result = try await body( + execution, + .init(fileDescriptor: inputPipe.writeFileDescriptor!) + ) + return ExecutionResult( + terminationStatus: try await waitingStatus, + value: result + ) + } onCleanup: { + // Attempt to terminate the child process + // Since the task has already been cancelled, + // this is the best we can do + try? await self.cleanup( + execution: execution, + inputPipe: inputPipe, + outputPipe: outputPipe, + errorPipe: errorPipe, + childSide: false, + parentSide: true, + attemptToTerminateSubProcess: true + ) + } + } + + @available(macOS 15.0, *) // FIXME: manually added availability + internal func run< + Result, + Input: InputProtocol, + Output: OutputProtocol, + Error: OutputProtocol + >( + input: Input, + output: Output, + error: Error, + isolation: isolated (any Actor)? = #isolation, + _ body: ((Execution) async throws -> Result) + ) async throws -> ExecutionResult { + + let inputPipe = try input.createPipe() + let outputPipe = try output.createPipe() + let errorPipe = try error.createPipe() + + let execution = try self.spawn( + withInput: inputPipe, + output: output, + outputPipe: outputPipe, + error: error, + errorPipe: errorPipe + ) + // After spawn, clean up child side + try await self.cleanup( + execution: execution, + inputPipe: inputPipe, + outputPipe: outputPipe, + errorPipe: errorPipe, + childSide: true, + parentSide: false, + attemptToTerminateSubProcess: false + ) + + return try await withAsyncTaskCleanupHandler { + return try await withThrowingTaskGroup( + of: TerminationStatus?.self, + returning: ExecutionResult.self + ) { group in + group.addTask { + if let writeFd = inputPipe.writeFileDescriptor { + let writer = StandardInputWriter(fileDescriptor: writeFd) + try await input.write(with: writer) + try await writer.finish() + } + return nil + } + group.addTask { + return try await monitorProcessTermination( + forProcessWithIdentifier: execution.processIdentifier + ) + } + + // Body runs in the same isolation + let result = try await body(execution) + var status: TerminationStatus? = nil + while let monitorResult = try await group.next() { + if let monitorResult = monitorResult { + status = monitorResult + } + } + return ExecutionResult(terminationStatus: status!, value: result) + } + } onCleanup: { + // Attempt to terminate the child process + // Since the task has already been cancelled, + // this is the best we can do + try? await self.cleanup( + execution: execution, + inputPipe: inputPipe, + outputPipe: outputPipe, + errorPipe: errorPipe, + childSide: false, + parentSide: true, + attemptToTerminateSubProcess: true + ) + } + } +} + +extension Configuration: CustomStringConvertible, CustomDebugStringConvertible { + public var description: String { + return """ + Configuration( + executable: \(self.executable.description), + arguments: \(self.arguments.description), + environment: \(self.environment.description), + workingDirectory: \(self.workingDirectory), + platformOptions: \(self.platformOptions.description(withIndent: 1)) + ) + """ + } + + public var debugDescription: String { + return """ + Configuration( + executable: \(self.executable.debugDescription), + arguments: \(self.arguments.debugDescription), + environment: \(self.environment.debugDescription), + workingDirectory: \(self.workingDirectory), + platformOptions: \(self.platformOptions.description(withIndent: 1)) + ) + """ + } +} + +// MARK: - Cleanup +extension Configuration { + /// Close each input individually, and throw the first error if there's multiple errors thrown + @Sendable + @available(macOS 15.0, *) // FIXME: manually added availability + private func cleanup< + Output: OutputProtocol, + Error: OutputProtocol + >( + execution: Execution, + inputPipe: CreatedPipe, + outputPipe: CreatedPipe, + errorPipe: CreatedPipe, + childSide: Bool, + parentSide: Bool, + attemptToTerminateSubProcess: Bool + ) async throws { + func captureError(_ work: () throws -> Void) -> Swift.Error? { + do { + try work() + return nil + } catch { + // Ignore badFileDescriptor for double close + return error + } + } + + guard childSide || parentSide || attemptToTerminateSubProcess else { + return + } + + // Attempt to teardown the subprocess + if attemptToTerminateSubProcess { + await execution.teardown( + using: self.platformOptions.teardownSequence + ) + } + + var inputError: Swift.Error? + var outputError: Swift.Error? + var errorError: Swift.Error? // lol + + if childSide { + inputError = captureError { + try inputPipe.readFileDescriptor?.safelyClose() + } + outputError = captureError { + try outputPipe.writeFileDescriptor?.safelyClose() + } + errorError = captureError { + try errorPipe.writeFileDescriptor?.safelyClose() + } + } + + if parentSide { + inputError = captureError { + try inputPipe.writeFileDescriptor?.safelyClose() + } + outputError = captureError { + try outputPipe.readFileDescriptor?.safelyClose() + } + errorError = captureError { + try errorPipe.readFileDescriptor?.safelyClose() + } + } + + if let inputError = inputError { + throw inputError + } + + if let outputError = outputError { + throw outputError + } + + if let errorError = errorError { + throw errorError + } + } + + /// Close each input individually, and throw the first error if there's multiple errors thrown + @Sendable + internal func cleanupPreSpawn( + input: CreatedPipe, + output: CreatedPipe, + error: CreatedPipe + ) throws { + var inputError: Swift.Error? + var outputError: Swift.Error? + var errorError: Swift.Error? + + do { + try input.readFileDescriptor?.safelyClose() + try input.writeFileDescriptor?.safelyClose() + } catch { + inputError = error + } + + do { + try output.readFileDescriptor?.safelyClose() + try output.writeFileDescriptor?.safelyClose() + } catch { + outputError = error + } + + do { + try error.readFileDescriptor?.safelyClose() + try error.writeFileDescriptor?.safelyClose() + } catch { + errorError = error + } + + if let inputError = inputError { + throw inputError + } + if let outputError = outputError { + throw outputError + } + if let errorError = errorError { + throw errorError + } + } +} + +// MARK: - Executable + +/// `Executable` defines how the executable should +/// be looked up for execution. +public struct Executable: Sendable, Hashable { + internal enum Storage: Sendable, Hashable { + case executable(String) + case path(FilePath) + } + + internal let storage: Storage + + private init(_config: Storage) { + self.storage = _config + } + + /// Locate the executable by its name. + /// `Subprocess` will use `PATH` value to + /// determine the full path to the executable. + public static func name(_ executableName: String) -> Self { + return .init(_config: .executable(executableName)) + } + /// Locate the executable by its full path. + /// `Subprocess` will use this path directly. + public static func path(_ filePath: FilePath) -> Self { + return .init(_config: .path(filePath)) + } + /// Returns the full executable path given the environment value. + public func resolveExecutablePath(in environment: Environment) throws -> FilePath { + let path = try self.resolveExecutablePath(withPathValue: environment.pathValue()) + return FilePath(path) + } +} + +extension Executable: CustomStringConvertible, CustomDebugStringConvertible { + public var description: String { + switch storage { + case .executable(let executableName): + return executableName + case .path(let filePath): + return filePath.string + } + } + + public var debugDescription: String { + switch storage { + case .executable(let string): + return "executable(\(string))" + case .path(let filePath): + return "path(\(filePath.string))" + } + } +} + +extension Executable { + internal func possibleExecutablePaths( + withPathValue pathValue: String? + ) -> Set { + switch self.storage { + case .executable(let executableName): + #if os(Windows) + // Windows CreateProcessW accepts executable name directly + return Set([executableName]) + #else + var results: Set = [] + // executableName could be a full path + results.insert(executableName) + // Get $PATH from environment + let searchPaths: Set + if let pathValue = pathValue { + let localSearchPaths = pathValue.split(separator: ":").map { String($0) } + searchPaths = Set(localSearchPaths).union(Self.defaultSearchPaths) + } else { + searchPaths = Self.defaultSearchPaths + } + for path in searchPaths { + results.insert( + FilePath(path).appending(executableName).string + ) + } + return results + #endif + case .path(let executablePath): + return Set([executablePath.string]) + } + } +} + +// MARK: - Arguments + +/// A collection of arguments to pass to the subprocess. +public struct Arguments: Sendable, ExpressibleByArrayLiteral, Hashable { + public typealias ArrayLiteralElement = String + + internal let storage: [StringOrRawBytes] + internal let executablePathOverride: StringOrRawBytes? + + /// Create an Arguments object using the given literal values + public init(arrayLiteral elements: String...) { + self.storage = elements.map { .string($0) } + self.executablePathOverride = nil + } + /// Create an Arguments object using the given array + public init(_ array: [String]) { + self.storage = array.map { .string($0) } + self.executablePathOverride = nil + } + + #if !os(Windows) // Windows does NOT support arg0 override + /// Create an `Argument` object using the given values, but + /// override the first Argument value to `executablePathOverride`. + /// If `executablePathOverride` is nil, + /// `Arguments` will automatically use the executable path + /// as the first argument. + /// - Parameters: + /// - executablePathOverride: the value to override the first argument. + /// - remainingValues: the rest of the argument value + public init(executablePathOverride: String?, remainingValues: [String]) { + self.storage = remainingValues.map { .string($0) } + if let executablePathOverride = executablePathOverride { + self.executablePathOverride = .string(executablePathOverride) + } else { + self.executablePathOverride = nil + } + } + + /// Create an `Argument` object using the given values, but + /// override the first Argument value to `executablePathOverride`. + /// If `executablePathOverride` is nil, + /// `Arguments` will automatically use the executable path + /// as the first argument. + /// - Parameters: + /// - executablePathOverride: the value to override the first argument. + /// - remainingValues: the rest of the argument value + public init(executablePathOverride: [UInt8]?, remainingValues: [[UInt8]]) { + self.storage = remainingValues.map { .rawBytes($0) } + if let override = executablePathOverride { + self.executablePathOverride = .rawBytes(override) + } else { + self.executablePathOverride = nil + } + } + + public init(_ array: [[UInt8]]) { + self.storage = array.map { .rawBytes($0) } + self.executablePathOverride = nil + } + #endif +} + +extension Arguments: CustomStringConvertible, CustomDebugStringConvertible { + public var description: String { + var result: [String] = self.storage.map(\.description) + + if let override = self.executablePathOverride { + result.insert("override\(override.description)", at: 0) + } + return result.description + } + + public var debugDescription: String { return self.description } +} + +// MARK: - Environment + +/// A set of environment variables to use when executing the subprocess. +public struct Environment: Sendable, Hashable { + internal enum Configuration: Sendable, Hashable { + case inherit([String: String]) + case custom([String: String]) + #if !os(Windows) + case rawBytes([[UInt8]]) + #endif + } + + internal let config: Configuration + + init(config: Configuration) { + self.config = config + } + /// Child process should inherit the same environment + /// values from its parent process. + public static var inherit: Self { + return .init(config: .inherit([:])) + } + /// Override the provided `newValue` in the existing `Environment` + public func updating(_ newValue: [String: String]) -> Self { + return .init(config: .inherit(newValue)) + } + /// Use custom environment variables + public static func custom(_ newValue: [String: String]) -> Self { + return .init(config: .custom(newValue)) + } + + #if !os(Windows) + /// Use custom environment variables of raw bytes + public static func custom(_ newValue: [[UInt8]]) -> Self { + return .init(config: .rawBytes(newValue)) + } + #endif +} + +extension Environment: CustomStringConvertible, CustomDebugStringConvertible { + public var description: String { + switch self.config { + case .custom(let customDictionary): + return """ + Custom environment: + \(customDictionary) + """ + case .inherit(let updateValue): + return """ + Inherting current environment with updates: + \(updateValue) + """ + #if !os(Windows) + case .rawBytes(let rawBytes): + return """ + Raw bytes: + \(rawBytes) + """ + #endif + } + } + + public var debugDescription: String { + return self.description + } + + internal static func currentEnvironmentValues() -> [String: String] { + return self.withCopiedEnv { environments in + var results: [String: String] = [:] + for env in environments { + let environmentString = String(cString: env) + + #if os(Windows) + // Windows GetEnvironmentStringsW API can return + // magic environment variables set by the cmd shell + // that starts with `=` + // We should exclude these values + if environmentString.utf8.first == Character("=").utf8.first { + continue + } + #endif // os(Windows) + + guard let delimiter = environmentString.firstIndex(of: "=") else { + continue + } + + let key = String(environmentString[environmentString.startIndex.. UnsafeMutablePointer { + switch self { + case .string(let string): + return strdup(string) + case .rawBytes(let rawBytes): + return strdup(rawBytes) + } + } + + var stringValue: String? { + switch self { + case .string(let string): + return string + case .rawBytes(let rawBytes): + return String(decoding: rawBytes, as: UTF8.self) + } + } + + var description: String { + switch self { + case .string(let string): + return string + case .rawBytes(let bytes): + return bytes.description + } + } + + var count: Int { + switch self { + case .string(let string): + return string.count + case .rawBytes(let rawBytes): + return strnlen(rawBytes, Int.max) + } + } + + func hash(into hasher: inout Hasher) { + // If Raw bytes is valid UTF8, hash it as so + switch self { + case .string(let string): + hasher.combine(string) + case .rawBytes(let bytes): + if let stringValue = self.stringValue { + hasher.combine(stringValue) + } else { + hasher.combine(bytes) + } + } + } +} + +/// A simple wrapper on `FileDescriptor` plus a flag indicating +/// whether it should be closed automactially when done. +internal struct TrackedFileDescriptor: Hashable { + internal let closeWhenDone: Bool + internal let wrapped: FileDescriptor + + internal init( + _ wrapped: FileDescriptor, + closeWhenDone: Bool + ) { + self.wrapped = wrapped + self.closeWhenDone = closeWhenDone + } + + internal func safelyClose() throws { + guard self.closeWhenDone else { + return + } + + do { + try self.wrapped.close() + } catch { + guard let errno: Errno = error as? Errno else { + throw error + } + if errno != .badFileDescriptor { + throw errno + } + } + } + + internal var platformDescriptor: PlatformFileDescriptor { + return self.wrapped.platformDescriptor + } +} + +internal struct CreatedPipe { + internal let readFileDescriptor: TrackedFileDescriptor? + internal let writeFileDescriptor: TrackedFileDescriptor? + + internal init( + readFileDescriptor: TrackedFileDescriptor?, + writeFileDescriptor: TrackedFileDescriptor? + ) { + self.readFileDescriptor = readFileDescriptor + self.writeFileDescriptor = writeFileDescriptor + } + + internal init(closeWhenDone: Bool) throws { + let pipe = try FileDescriptor.pipe() + + self.readFileDescriptor = .init( + pipe.readEnd, + closeWhenDone: closeWhenDone + ) + self.writeFileDescriptor = .init( + pipe.writeEnd, + closeWhenDone: closeWhenDone + ) + } +} + +extension FilePath { + static var currentWorkingDirectory: Self { + let path = getcwd(nil, 0)! + defer { free(path) } + return .init(String(cString: path)) + } +} + +extension Optional where Wrapped: Collection { + func withOptionalUnsafeBufferPointer( + _ body: ((UnsafeBufferPointer)?) throws -> Result + ) rethrows -> Result { + switch self { + case .some(let wrapped): + guard let array: [Wrapped.Element] = wrapped as? Array else { + return try body(nil) + } + return try array.withUnsafeBufferPointer { ptr in + return try body(ptr) + } + case .none: + return try body(nil) + } + } +} + +extension Optional where Wrapped == String { + func withOptionalCString( + _ body: ((UnsafePointer)?) throws -> Result + ) rethrows -> Result { + switch self { + case .none: + return try body(nil) + case .some(let wrapped): + return try wrapped.withCString { + return try body($0) + } + } + } + + var stringValue: String { + return self ?? "nil" + } +} + +internal func withAsyncTaskCleanupHandler( + _ body: () async throws -> Result, + onCleanup handler: @Sendable @escaping () async -> Void, + isolation: isolated (any Actor)? = #isolation +) async rethrows -> Result { + return try await withThrowingTaskGroup( + of: Void.self, + returning: Result.self + ) { group in + group.addTask { + // Keep this task sleep indefinitely until the parent task is cancelled. + // `Task.sleep` throws `CancellationError` when the task is canceled + // before the time ends. We then run the cancel handler. + do { while true { try await Task.sleep(nanoseconds: 1_000_000_000) } } catch {} + // Run task cancel handler + await handler() + } + + do { + let result = try await body() + group.cancelAll() + return result + } catch { + await handler() + throw error + } + } +} diff --git a/Sources/_Subprocess/Error.swift b/Sources/_Subprocess/Error.swift new file mode 100644 index 000000000..bf1c91147 --- /dev/null +++ b/Sources/_Subprocess/Error.swift @@ -0,0 +1,141 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#if canImport(Darwin) +import Darwin +#elseif canImport(Bionic) +import Bionic +#elseif canImport(Glibc) +import Glibc +#elseif canImport(Musl) +import Musl +#elseif canImport(WinSDK) +import WinSDK +#endif + +/// Error thrown from Subprocess +public struct SubprocessError: Swift.Error, Hashable, Sendable { + /// The error code of this error + public let code: SubprocessError.Code + /// The underlying error that caused this error, if any + public let underlyingError: UnderlyingError? +} + +// MARK: - Error Codes +extension SubprocessError { + /// A SubprocessError Code + public struct Code: Hashable, Sendable { + internal enum Storage: Hashable, Sendable { + case spawnFailed + case executableNotFound(String) + case failedToChangeWorkingDirectory(String) + case failedToReadFromSubprocess + case failedToWriteToSubprocess + case failedToMonitorProcess + // Signal + case failedToSendSignal(Int32) + // Windows Only + case failedToTerminate + case failedToSuspend + case failedToResume + case failedToCreatePipe + case invalidWindowsPath(String) + } + + public var value: Int { + switch self.storage { + case .spawnFailed: + return 0 + case .executableNotFound(_): + return 1 + case .failedToChangeWorkingDirectory(_): + return 2 + case .failedToReadFromSubprocess: + return 3 + case .failedToWriteToSubprocess: + return 4 + case .failedToMonitorProcess: + return 5 + case .failedToSendSignal(_): + return 6 + case .failedToTerminate: + return 7 + case .failedToSuspend: + return 8 + case .failedToResume: + return 9 + case .failedToCreatePipe: + return 10 + case .invalidWindowsPath(_): + return 11 + } + } + + internal let storage: Storage + + internal init(_ storage: Storage) { + self.storage = storage + } + } +} + +// MARK: - Description +extension SubprocessError: CustomStringConvertible, CustomDebugStringConvertible { + public var description: String { + switch self.code.storage { + case .spawnFailed: + return "Failed to spawn the new process." + case .executableNotFound(let executableName): + return "Executable \"\(executableName)\" is not found or cannot be executed." + case .failedToChangeWorkingDirectory(let workingDirectory): + return "Failed to set working directory to \"\(workingDirectory)\"." + case .failedToReadFromSubprocess: + return "Failed to read bytes from the child process with underlying error: \(self.underlyingError!)" + case .failedToWriteToSubprocess: + return "Failed to write bytes to the child process." + case .failedToMonitorProcess: + return "Failed to monitor the state of child process with underlying error: \(self.underlyingError!)" + case .failedToSendSignal(let signal): + return "Failed to send signal \(signal) to the child process." + case .failedToTerminate: + return "Failed to terminate the child process." + case .failedToSuspend: + return "Failed to suspend the child process." + case .failedToResume: + return "Failed to resume the child process." + case .failedToCreatePipe: + return "Failed to create a pipe to communicate to child process." + case .invalidWindowsPath(let badPath): + return "\"\(badPath)\" is not a valid Windows path." + } + } + + public var debugDescription: String { self.description } +} + +extension SubprocessError { + /// The underlying error that caused this SubprocessError. + /// - On Unix-like systems, `UnderlyingError` wraps `errno` from libc; + /// - On Windows, `UnderlyingError` wraps Windows Error code + public struct UnderlyingError: Swift.Error, RawRepresentable, Hashable, Sendable { + #if os(Windows) + public typealias RawValue = DWORD + #else + public typealias RawValue = Int32 + #endif + + public let rawValue: RawValue + + public init(rawValue: RawValue) { + self.rawValue = rawValue + } + } +} diff --git a/Sources/_Subprocess/Execution.swift b/Sources/_Subprocess/Execution.swift new file mode 100644 index 000000000..8da9b4924 --- /dev/null +++ b/Sources/_Subprocess/Execution.swift @@ -0,0 +1,192 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#if canImport(System) +import System +#else +@preconcurrency import SystemPackage +#endif + +#if canImport(Darwin) +import Darwin +#elseif canImport(Bionic) +import Bionic +#elseif canImport(Glibc) +import Glibc +#elseif canImport(Musl) +import Musl +#elseif canImport(WinSDK) +import WinSDK +#endif + +import Synchronization + +/// An object that repersents a subprocess that has been +/// executed. You can use this object to send signals to the +/// child process as well as stream its output and error. +@available(macOS 15.0, *) // FIXME: manually added availability +public final class Execution< + Output: OutputProtocol, + Error: OutputProtocol +>: Sendable { + /// The process identifier of the current execution + public let processIdentifier: ProcessIdentifier + + internal let output: Output + internal let error: Error + internal let outputPipe: CreatedPipe + internal let errorPipe: CreatedPipe + internal let outputConsumptionState: Atomic + #if os(Windows) + internal let consoleBehavior: PlatformOptions.ConsoleBehavior + + init( + processIdentifier: ProcessIdentifier, + output: Output, + error: Error, + outputPipe: CreatedPipe, + errorPipe: CreatedPipe, + consoleBehavior: PlatformOptions.ConsoleBehavior + ) { + self.processIdentifier = processIdentifier + self.output = output + self.error = error + self.outputPipe = outputPipe + self.errorPipe = errorPipe + self.outputConsumptionState = Atomic(0) + self.consoleBehavior = consoleBehavior + } + #else + init( + processIdentifier: ProcessIdentifier, + output: Output, + error: Error, + outputPipe: CreatedPipe, + errorPipe: CreatedPipe + ) { + self.processIdentifier = processIdentifier + self.output = output + self.error = error + self.outputPipe = outputPipe + self.errorPipe = errorPipe + self.outputConsumptionState = Atomic(0) + } + #endif // os(Windows) +} + +@available(macOS 15.0, *) // FIXME: manually added availability +extension Execution where Output == SequenceOutput { + /// The standard output of the subprocess. + /// + /// Accessing this property will **fatalError** if this property was + /// accessed multiple times. Subprocess communicates with parent process + /// via pipe under the hood and each pipe can only be consumed once. + @available(macOS 15.0, *) // FIXME: manually added availability + public var standardOutput: some AsyncSequence { + let consumptionState = self.outputConsumptionState.bitwiseXor( + OutputConsumptionState.standardOutputConsumed.rawValue, + ordering: .relaxed + ).newValue + + guard OutputConsumptionState(rawValue: consumptionState).contains(.standardOutputConsumed), + let fd = self.outputPipe.readFileDescriptor + else { + fatalError("The standard output has already been consumed") + } + return AsyncBufferSequence(fileDescriptor: fd) + } +} + +@available(macOS 15.0, *) // FIXME: manually added availability +extension Execution where Error == SequenceOutput { + /// The standard error of the subprocess. + /// + /// Accessing this property will **fatalError** if this property was + /// accessed multiple times. Subprocess communicates with parent process + /// via pipe under the hood and each pipe can only be consumed once. + @available(macOS 15.0, *) // FIXME: manually added availability + public var standardError: some AsyncSequence { + let consumptionState = self.outputConsumptionState.bitwiseXor( + OutputConsumptionState.standardErrorConsumed.rawValue, + ordering: .relaxed + ).newValue + + guard OutputConsumptionState(rawValue: consumptionState).contains(.standardErrorConsumed), + let fd = self.errorPipe.readFileDescriptor + else { + fatalError("The standard output has already been consumed") + } + return AsyncBufferSequence(fileDescriptor: fd) + } +} + +// MARK: - Output Capture +internal enum OutputCapturingState: Sendable { + case standardOutputCaptured(Output) + case standardErrorCaptured(Error) +} + +internal struct OutputConsumptionState: OptionSet { + typealias RawValue = UInt8 + + internal let rawValue: UInt8 + + internal init(rawValue: UInt8) { + self.rawValue = rawValue + } + + static let standardOutputConsumed: Self = .init(rawValue: 0b0001) + static let standardErrorConsumed: Self = .init(rawValue: 0b0010) +} + +internal typealias CapturedIOs< + Output: Sendable, + Error: Sendable +> = (standardOutput: Output, standardError: Error) + +@available(macOS 15.0, *) // FIXME: manually added availability +extension Execution { + internal func captureIOs() async throws -> CapturedIOs< + Output.OutputType, Error.OutputType + > { + return try await withThrowingTaskGroup( + of: OutputCapturingState.self + ) { group in + group.addTask { + let stdout = try await self.output.captureOutput( + from: self.outputPipe.readFileDescriptor + ) + return .standardOutputCaptured(stdout) + } + group.addTask { + let stderr = try await self.error.captureOutput( + from: self.errorPipe.readFileDescriptor + ) + return .standardErrorCaptured(stderr) + } + + var stdout: Output.OutputType! + var stderror: Error.OutputType! + while let state = try await group.next() { + switch state { + case .standardOutputCaptured(let output): + stdout = output + case .standardErrorCaptured(let error): + stderror = error + } + } + return ( + standardOutput: stdout, + standardError: stderror + ) + } + } +} diff --git a/Sources/_Subprocess/IO/Input.swift b/Sources/_Subprocess/IO/Input.swift new file mode 100644 index 000000000..5aad5d94e --- /dev/null +++ b/Sources/_Subprocess/IO/Input.swift @@ -0,0 +1,315 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#if canImport(System) +import System +#else +@preconcurrency import SystemPackage +#endif + +#if SubprocessFoundation + +#if canImport(Darwin) +// On Darwin always prefer system Foundation +import Foundation +#else +// On other platforms prefer FoundationEssentials +import FoundationEssentials +#endif + +#endif // SubprocessFoundation + +// MARK: - Input + +/// `InputProtocol` defines the `write(with:)` method that a type +/// must implement to serve as the input source for a subprocess. +public protocol InputProtocol: Sendable, ~Copyable { + /// Asynchronously write the input to the subprocess using the + /// write file descriptor + func write(with writer: StandardInputWriter) async throws +} + +/// A concrete `Input` type for subprocesses that indicates +/// the absence of input to the subprocess. On Unix-like systems, +/// `NoInput` redirects the standard input of the subprocess +/// to `/dev/null`, while on Windows, it does not bind any +/// file handle to the subprocess standard input handle. +public struct NoInput: InputProtocol { + internal func createPipe() throws -> CreatedPipe { + #if os(Windows) + // On Windows, instead of binding to dev null, + // we don't set the input handle in the `STARTUPINFOW` + // to signal no input + return CreatedPipe( + readFileDescriptor: nil, + writeFileDescriptor: nil + ) + #else + let devnull: FileDescriptor = try .openDevNull(withAcessMode: .readOnly) + return CreatedPipe( + readFileDescriptor: .init(devnull, closeWhenDone: true), + writeFileDescriptor: nil + ) + #endif + } + + public func write(with writer: StandardInputWriter) async throws { + // noop + } + + internal init() {} +} + +/// A concrete `Input` type for subprocesses that +/// reads input from a specified `FileDescriptor`. +/// Developers have the option to instruct the `Subprocess` to +/// automatically close the provided `FileDescriptor` +/// after the subprocess is spawned. +public struct FileDescriptorInput: InputProtocol { + private let fileDescriptor: FileDescriptor + private let closeAfterSpawningProcess: Bool + + internal func createPipe() throws -> CreatedPipe { + return CreatedPipe( + readFileDescriptor: .init( + self.fileDescriptor, + closeWhenDone: self.closeAfterSpawningProcess + ), + writeFileDescriptor: nil + ) + } + + public func write(with writer: StandardInputWriter) async throws { + // noop + } + + internal init( + fileDescriptor: FileDescriptor, + closeAfterSpawningProcess: Bool + ) { + self.fileDescriptor = fileDescriptor + self.closeAfterSpawningProcess = closeAfterSpawningProcess + } +} + +/// A concrete `Input` type for subprocesses that reads input +/// from a given type conforming to `StringProtocol`. +/// Developers can specify the string encoding to use when +/// encoding the string to data, which defaults to UTF-8. +public struct StringInput< + InputString: StringProtocol & Sendable, + Encoding: Unicode.Encoding +>: InputProtocol { + private let string: InputString + + public func write(with writer: StandardInputWriter) async throws { + guard let array = self.string.byteArray(using: Encoding.self) else { + return + } + _ = try await writer.write(array) + } + + internal init(string: InputString, encoding: Encoding.Type) { + self.string = string + } +} + +/// A concrete `Input` type for subprocesses that reads input +/// from a given `UInt8` Array. +public struct ArrayInput: InputProtocol { + private let array: [UInt8] + + public func write(with writer: StandardInputWriter) async throws { + _ = try await writer.write(self.array) + } + + internal init(array: [UInt8]) { + self.array = array + } +} + +/// A concrete `Input` type for subprocess that indicates that +/// the Subprocess should read its input from `StandardInputWriter`. +public struct CustomWriteInput: InputProtocol { + public func write(with writer: StandardInputWriter) async throws { + // noop + } + + internal init() {} +} + +extension InputProtocol where Self == NoInput { + /// Create a Subprocess input that specfies there is no input + public static var none: Self { .init() } +} + +extension InputProtocol where Self == FileDescriptorInput { + /// Create a Subprocess input from a `FileDescriptor` and + /// specify whether the `FileDescriptor` should be closed + /// after the process is spawned. + public static func fileDescriptor( + _ fd: FileDescriptor, + closeAfterSpawningProcess: Bool + ) -> Self { + return .init( + fileDescriptor: fd, + closeAfterSpawningProcess: closeAfterSpawningProcess + ) + } +} + +extension InputProtocol { + /// Create a Subprocess input from a `Array` of `UInt8`. + public static func array( + _ array: [UInt8] + ) -> Self where Self == ArrayInput { + return ArrayInput(array: array) + } + + /// Create a Subprocess input from a type that conforms to `StringProtocol` + public static func string< + InputString: StringProtocol & Sendable + >( + _ string: InputString + ) -> Self where Self == StringInput { + return .init(string: string, encoding: UTF8.self) + } + + /// Create a Subprocess input from a type that conforms to `StringProtocol` + public static func string< + InputString: StringProtocol & Sendable, + Encoding: Unicode.Encoding + >( + _ string: InputString, + using encoding: Encoding.Type + ) -> Self where Self == StringInput { + return .init(string: string, encoding: encoding) + } +} + +extension InputProtocol { + internal func createPipe() throws -> CreatedPipe { + if let noInput = self as? NoInput { + return try noInput.createPipe() + } else if let fdInput = self as? FileDescriptorInput { + return try fdInput.createPipe() + } + // Base implementation + return try CreatedPipe(closeWhenDone: true) + } +} + +// MARK: - StandardInputWriter + +/// A writer that writes to the standard input of the subprocess. +public final actor StandardInputWriter: Sendable { + + internal let fileDescriptor: TrackedFileDescriptor + + init(fileDescriptor: TrackedFileDescriptor) { + self.fileDescriptor = fileDescriptor + } + + /// Write an array of UInt8 to the standard input of the subprocess. + /// - Parameter array: The sequence of bytes to write. + /// - Returns number of bytes written. + public func write( + _ array: [UInt8] + ) async throws -> Int { + return try await self.fileDescriptor.wrapped.write(array) + } + + /// Write a StringProtocol to the standard input of the subprocess. + /// - Parameters: + /// - string: The string to write. + /// - encoding: The encoding to use when converting string to bytes + /// - Returns number of bytes written. + public func write( + _ string: some StringProtocol, + using encoding: Encoding.Type = UTF8.self + ) async throws -> Int { + if let array = string.byteArray(using: encoding) { + return try await self.write(array) + } + return 0 + } + + /// Signal all writes are finished + public func finish() async throws { + try self.fileDescriptor.safelyClose() + } +} + +extension StringProtocol { + #if SubprocessFoundation + private func convertEncoding( + _ encoding: Encoding.Type + ) -> String.Encoding? { + switch encoding { + case is UTF8.Type: + return .utf8 + case is UTF16.Type: + return .utf16 + case is UTF32.Type: + return .utf32 + default: + return nil + } + } + #endif + package func byteArray(using encoding: Encoding.Type) -> [UInt8]? { + if Encoding.self == Unicode.ASCII.self { + let isASCII = self.utf8.allSatisfy { + return Character(Unicode.Scalar($0)).isASCII + } + + guard isASCII else { + return nil + } + return Array(self.utf8) + } + if Encoding.self == UTF8.self { + return Array(self.utf8) + } + if Encoding.self == UTF16.self { + return Array(self.utf16).flatMap { input in + var uint16: UInt16 = input + return withUnsafeBytes(of: &uint16) { ptr in + Array(ptr) + } + } + } + #if SubprocessFoundation + if let stringEncoding = self.convertEncoding(encoding), + let encoded = self.data(using: stringEncoding) + { + return Array(encoded) + } + return nil + #else + return nil + #endif + } +} + +extension String { + package init( + decodingBytes bytes: [T], + as encoding: Encoding.Type + ) { + self = bytes.withUnsafeBytes { raw in + String( + decoding: raw.bindMemory(to: Encoding.CodeUnit.self).lazy.map { $0 }, + as: encoding + ) + } + } +} diff --git a/Sources/_Subprocess/IO/Output.swift b/Sources/_Subprocess/IO/Output.swift new file mode 100644 index 000000000..be186dd6b --- /dev/null +++ b/Sources/_Subprocess/IO/Output.swift @@ -0,0 +1,298 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#if canImport(System) +import System +#else +@preconcurrency import SystemPackage +#endif +internal import Dispatch + +// MARK: - Output + +/// `OutputProtocol` specifies the set of methods that a type +/// must implement to serve as the output target for a subprocess. +/// Instead of developing custom implementations of `OutputProtocol`, +/// it is recommended to utilize the default implementations provided +/// by the `Subprocess` library to specify the output handling requirements. +public protocol OutputProtocol: Sendable, ~Copyable { + associatedtype OutputType: Sendable + + /// Convert the output from buffer to expected output type + func output(from buffer: some Sequence) throws -> OutputType + + /// The max amount of data to collect for this output. + var maxSize: Int { get } +} + +extension OutputProtocol { + /// The max amount of data to collect for this output. + public var maxSize: Int { 128 * 1024 } +} + +/// A concrete `Output` type for subprocesses that indicates that +/// the `Subprocess` should not collect or redirect output +/// from the child process. On Unix-like systems, `DiscardedOutput` +/// redirects the standard output of the subprocess to `/dev/null`, +/// while on Windows, it does not bind any file handle to the +/// subprocess standard output handle. +public struct DiscardedOutput: OutputProtocol { + public typealias OutputType = Void + + internal func createPipe() throws -> CreatedPipe { + #if os(Windows) + // On Windows, instead of binding to dev null, + // we don't set the input handle in the `STARTUPINFOW` + // to signal no output + return CreatedPipe( + readFileDescriptor: nil, + writeFileDescriptor: nil + ) + #else + let devnull: FileDescriptor = try .openDevNull(withAcessMode: .readOnly) + return CreatedPipe( + readFileDescriptor: .init(devnull, closeWhenDone: true), + writeFileDescriptor: nil + ) + #endif + } + + internal init() {} +} + +/// A concrete `Output` type for subprocesses that +/// writes output to a specified `FileDescriptor`. +/// Developers have the option to instruct the `Subprocess` to +/// automatically close the provided `FileDescriptor` +/// after the subprocess is spawned. +public struct FileDescriptorOutput: OutputProtocol { + public typealias OutputType = Void + + private let closeAfterSpawningProcess: Bool + private let fileDescriptor: FileDescriptor + + internal func createPipe() throws -> CreatedPipe { + return CreatedPipe( + readFileDescriptor: nil, + writeFileDescriptor: .init( + self.fileDescriptor, + closeWhenDone: self.closeAfterSpawningProcess + ) + ) + } + + internal init( + fileDescriptor: FileDescriptor, + closeAfterSpawningProcess: Bool + ) { + self.fileDescriptor = fileDescriptor + self.closeAfterSpawningProcess = closeAfterSpawningProcess + } +} + +/// A concrete `Output` type for subprocesses that collects output +/// from the subprocess as `String` with the given encoding. +/// This option must be used with he `run()` method that +/// returns a `CollectedResult`. +public struct StringOutput: OutputProtocol { + public typealias OutputType = String? + public let maxSize: Int + private let encoding: Encoding.Type + + public func output(from buffer: some Sequence) throws -> String? { + // FIXME: Span to String + let array = Array(buffer) + return String(decodingBytes: array, as: Encoding.self) + } + + internal init(limit: Int, encoding: Encoding.Type) { + self.maxSize = limit + self.encoding = encoding + } +} + +/// A concrete `Output` type for subprocesses that collects output +/// from the subprocess as `[UInt8]`. This option must be used with +/// the `run()` method that returns a `CollectedResult` +public struct BytesOutput: OutputProtocol { + public typealias OutputType = [UInt8] + public let maxSize: Int + + internal func captureOutput(from fileDescriptor: TrackedFileDescriptor?) async throws -> [UInt8] { + return try await withCheckedThrowingContinuation { continuation in + guard let fileDescriptor = fileDescriptor else { + // Show not happen due to type system constraints + fatalError("Trying to capture output without file descriptor") + } + fileDescriptor.wrapped.readUntilEOF(upToLength: self.maxSize) { result in + switch result { + case .success(let data): + // FIXME: remove workaround for + // rdar://143992296 + // https://github.com/swiftlang/swift-subprocess/issues/3 + #if os(Windows) + continuation.resume(returning: data) + #else + continuation.resume(returning: data.array()) + #endif + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + public func output(from buffer: some Sequence) throws -> [UInt8] { + fatalError("Not implemented") + } + + internal init(limit: Int) { + self.maxSize = limit + } +} + +/// A concrete `Output` type for subprocesses that redirects +/// the child output to the `.standardOutput` (a sequence) or `.standardError` +/// property of `Execution`. This output type is +/// only applicable to the `run()` family that takes a custom closure. +public struct SequenceOutput: OutputProtocol { + public typealias OutputType = Void + + internal init() {} +} + +extension OutputProtocol where Self == DiscardedOutput { + /// Create a Subprocess output that discards the output + public static var discarded: Self { .init() } +} + +extension OutputProtocol where Self == FileDescriptorOutput { + /// Create a Subprocess output that writes output to a `FileDescriptor` + /// and optionally close the `FileDescriptor` once process spawned. + public static func fileDescriptor( + _ fd: FileDescriptor, + closeAfterSpawningProcess: Bool + ) -> Self { + return .init(fileDescriptor: fd, closeAfterSpawningProcess: closeAfterSpawningProcess) + } +} + +extension OutputProtocol where Self == StringOutput { + /// Create a `Subprocess` output that collects output as + /// UTF8 String with 128kb limit. + public static var string: Self { + .init(limit: 128 * 1024, encoding: UTF8.self) + } +} + +extension OutputProtocol { + /// Create a `Subprocess` output that collects output as + /// `String` using the given encoding up to limit it bytes. + public static func string( + limit: Int, + encoding: Encoding.Type + ) -> Self where Self == StringOutput { + return .init(limit: limit, encoding: encoding) + } +} + +extension OutputProtocol where Self == BytesOutput { + /// Create a `Subprocess` output that collects output as + /// `Buffer` with 128kb limit. + public static var bytes: Self { .init(limit: 128 * 1024) } + + /// Create a `Subprocess` output that collects output as + /// `Buffer` up to limit it bytes. + public static func bytes(limit: Int) -> Self { + return .init(limit: limit) + } +} + +extension OutputProtocol where Self == SequenceOutput { + /// Create a `Subprocess` output that redirects the output + /// to the `.standardOutput` (or `.standardError`) property + /// of `Execution` as `AsyncSequence`. + public static var sequence: Self { .init() } +} + +// MARK: - Default Implementations +extension OutputProtocol { + @_disfavoredOverload + internal func createPipe() throws -> CreatedPipe { + if let discard = self as? DiscardedOutput { + return try discard.createPipe() + } else if let fdOutput = self as? FileDescriptorOutput { + return try fdOutput.createPipe() + } + // Base pipe based implementation for everything else + return try CreatedPipe(closeWhenDone: true) + } + + /// Capture the output from the subprocess up to maxSize + @_disfavoredOverload + internal func captureOutput( + from fileDescriptor: TrackedFileDescriptor? + ) async throws -> OutputType { + if let bytesOutput = self as? BytesOutput { + return try await bytesOutput.captureOutput(from: fileDescriptor) as! Self.OutputType + } + return try await withCheckedThrowingContinuation { continuation in + if OutputType.self == Void.self { + continuation.resume(returning: () as! OutputType) + return + } + guard let fileDescriptor = fileDescriptor else { + // Show not happen due to type system constraints + fatalError("Trying to capture output without file descriptor") + } + + fileDescriptor.wrapped.readUntilEOF(upToLength: self.maxSize) { result in + do { + switch result { + case .success(let data): + // FIXME: remove workaround for + // rdar://143992296 + // https://github.com/swiftlang/swift-subprocess/issues/3 + let output = try self.output(from: data) + continuation.resume(returning: output) + case .failure(let error): + continuation.resume(throwing: error) + } + } catch { + continuation.resume(throwing: error) + } + } + } + } +} + +extension OutputProtocol where OutputType == Void { + internal func captureOutput(from fileDescriptor: TrackedFileDescriptor?) async throws {} + + public func output(from buffer: some Sequence) throws { + // noop + } +} + +extension DispatchData { + internal func array() -> [UInt8] { + var result: [UInt8]? + self.enumerateBytes { buffer, byteIndex, stop in + let currentChunk = Array(UnsafeRawBufferPointer(buffer)) + if result == nil { + result = currentChunk + } else { + result?.append(contentsOf: currentChunk) + } + } + return result ?? [] + } +} diff --git a/Sources/_Subprocess/LockedState.swift b/Sources/_Subprocess/LockedState.swift deleted file mode 100644 index e095668ca..000000000 --- a/Sources/_Subprocess/LockedState.swift +++ /dev/null @@ -1,160 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -#if canImport(os) -internal import os -#if FOUNDATION_FRAMEWORK && canImport(C.os.lock) -internal import C.os.lock -#endif -#elseif canImport(Bionic) -import Bionic -#elseif canImport(Glibc) -import Glibc -#elseif canImport(Musl) -import Musl -#elseif canImport(WinSDK) -import WinSDK -#endif - -package struct LockedState { - - // Internal implementation for a cheap lock to aid sharing code across platforms - private struct _Lock { -#if canImport(os) - typealias Primitive = os_unfair_lock -#elseif canImport(Bionic) || canImport(Glibc) || canImport(Musl) - typealias Primitive = pthread_mutex_t -#elseif canImport(WinSDK) - typealias Primitive = SRWLOCK -#elseif os(WASI) - // WASI is single-threaded, so we don't need a lock. - typealias Primitive = Void -#endif - - typealias PlatformLock = UnsafeMutablePointer - var _platformLock: PlatformLock - - fileprivate static func initialize(_ platformLock: PlatformLock) { -#if canImport(os) - platformLock.initialize(to: os_unfair_lock()) -#elseif canImport(Bionic) || canImport(Glibc) - pthread_mutex_init(platformLock, nil) -#elseif canImport(WinSDK) - InitializeSRWLock(platformLock) -#elseif os(WASI) - // no-op -#endif - } - - fileprivate static func deinitialize(_ platformLock: PlatformLock) { -#if canImport(Bionic) || canImport(Glibc) - pthread_mutex_destroy(platformLock) -#endif - platformLock.deinitialize(count: 1) - } - - static fileprivate func lock(_ platformLock: PlatformLock) { -#if canImport(os) - os_unfair_lock_lock(platformLock) -#elseif canImport(Bionic) || canImport(Glibc) - pthread_mutex_lock(platformLock) -#elseif canImport(WinSDK) - AcquireSRWLockExclusive(platformLock) -#elseif os(WASI) - // no-op -#endif - } - - static fileprivate func unlock(_ platformLock: PlatformLock) { -#if canImport(os) - os_unfair_lock_unlock(platformLock) -#elseif canImport(Bionic) || canImport(Glibc) - pthread_mutex_unlock(platformLock) -#elseif canImport(WinSDK) - ReleaseSRWLockExclusive(platformLock) -#elseif os(WASI) - // no-op -#endif - } - } - - private class _Buffer: ManagedBuffer { - deinit { - withUnsafeMutablePointerToElements { - _Lock.deinitialize($0) - } - } - } - - private let _buffer: ManagedBuffer - - package init(initialState: State) { - _buffer = _Buffer.create(minimumCapacity: 1, makingHeaderWith: { buf in - buf.withUnsafeMutablePointerToElements { - _Lock.initialize($0) - } - return initialState - }) - } - - package func withLock(_ body: @Sendable (inout State) throws -> T) rethrows -> T { - try withLockUnchecked(body) - } - - package func withLockUnchecked(_ body: (inout State) throws -> T) rethrows -> T { - try _buffer.withUnsafeMutablePointers { state, lock in - _Lock.lock(lock) - defer { _Lock.unlock(lock) } - return try body(&state.pointee) - } - } - - // Ensures the managed state outlives the locked scope. - package func withLockExtendingLifetimeOfState(_ body: @Sendable (inout State) throws -> T) rethrows -> T { - try _buffer.withUnsafeMutablePointers { state, lock in - _Lock.lock(lock) - return try withExtendedLifetime(state.pointee) { - defer { _Lock.unlock(lock) } - return try body(&state.pointee) - } - } - } -} - -extension LockedState where State == Void { - package init() { - self.init(initialState: ()) - } - - package func withLock(_ body: @Sendable () throws -> R) rethrows -> R { - return try withLock { _ in - try body() - } - } - - package func lock() { - _buffer.withUnsafeMutablePointerToElements { lock in - _Lock.lock(lock) - } - } - - package func unlock() { - _buffer.withUnsafeMutablePointerToElements { lock in - _Lock.unlock(lock) - } - } -} - -extension LockedState: @unchecked Sendable where State: Sendable {} - diff --git a/Sources/_Subprocess/Platforms/Subprocess+Darwin.swift b/Sources/_Subprocess/Platforms/Subprocess+Darwin.swift index 4ac2276e2..cd5c310aa 100644 --- a/Sources/_Subprocess/Platforms/Subprocess+Darwin.swift +++ b/Sources/_Subprocess/Platforms/Subprocess+Darwin.swift @@ -2,306 +2,144 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception // -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 +// See https://swift.org/LICENSE.txt for license information // //===----------------------------------------------------------------------===// #if canImport(Darwin) -#if canImport(FoundationEssentials) -import FoundationEssentials -#elseif canImport(Foundation) -import Foundation -#endif - import Darwin -import Dispatch -import SystemPackage - -#if FOUNDATION_FRAMEWORK -@_implementationOnly import _FoundationCShims +internal import Dispatch +#if canImport(System) +import System #else -import _CShims +@preconcurrency import SystemPackage #endif -// Darwin specific implementation -extension Subprocess.Configuration { - internal typealias StringOrRawBytes = Subprocess.StringOrRawBytes +import _SubprocessCShims - internal func spawn( - withInput input: Subprocess.ExecutionInput, - output: Subprocess.ExecutionOutput, - error: Subprocess.ExecutionOutput - ) throws -> Subprocess { - let (executablePath, - env, argv, - intendedWorkingDir, - uidPtr, gidPtr, supplementaryGroups - ) = try self.preSpawn() - defer { - for ptr in env { ptr?.deallocate() } - for ptr in argv { ptr?.deallocate() } - uidPtr?.deallocate() - gidPtr?.deallocate() - } +#if SubprocessFoundation - // Setup file actions and spawn attributes - var fileActions: posix_spawn_file_actions_t? = nil - var spawnAttributes: posix_spawnattr_t? = nil - // Setup stdin, stdout, and stderr - posix_spawn_file_actions_init(&fileActions) - defer { - posix_spawn_file_actions_destroy(&fileActions) - } - // Input - var result: Int32 = -1 - if let inputRead = input.getReadFileDescriptor() { - result = posix_spawn_file_actions_adddup2(&fileActions, inputRead.rawValue, 0) - guard result == 0 else { - try self.cleanupAll(input: input, output: output, error: error) - throw POSIXError(.init(rawValue: result) ?? .ENODEV) - } - } - if let inputWrite = input.getWriteFileDescriptor() { - // Close parent side - result = posix_spawn_file_actions_addclose(&fileActions, inputWrite.rawValue) - guard result == 0 else { - try self.cleanupAll(input: input, output: output, error: error) - throw POSIXError(.init(rawValue: result) ?? .ENODEV) - } - } - // Output - if let outputWrite = output.getWriteFileDescriptor() { - result = posix_spawn_file_actions_adddup2(&fileActions, outputWrite.rawValue, 1) - guard result == 0 else { - try self.cleanupAll(input: input, output: output, error: error) - throw POSIXError(.init(rawValue: result) ?? .ENODEV) - } - } - if let outputRead = output.getReadFileDescriptor() { - // Close parent side - result = posix_spawn_file_actions_addclose(&fileActions, outputRead.rawValue) - guard result == 0 else { - try self.cleanupAll(input: input, output: output, error: error) - throw POSIXError(.init(rawValue: result) ?? .ENODEV) - } - } - // Error - if let errorWrite = error.getWriteFileDescriptor() { - result = posix_spawn_file_actions_adddup2(&fileActions, errorWrite.rawValue, 2) - guard result == 0 else { - try self.cleanupAll(input: input, output: output, error: error) - throw POSIXError(.init(rawValue: result) ?? .ENODEV) - } - } - if let errorRead = error.getReadFileDescriptor() { - // Close parent side - result = posix_spawn_file_actions_addclose(&fileActions, errorRead.rawValue) - guard result == 0 else { - try self.cleanupAll(input: input, output: output, error: error) - throw POSIXError(.init(rawValue: result) ?? .ENODEV) - } - } - // Setup spawnAttributes - posix_spawnattr_init(&spawnAttributes) - defer { - posix_spawnattr_destroy(&spawnAttributes) - } - var noSignals = sigset_t() - var allSignals = sigset_t() - sigemptyset(&noSignals) - sigfillset(&allSignals) - posix_spawnattr_setsigmask(&spawnAttributes, &noSignals) - posix_spawnattr_setsigdefault(&spawnAttributes, &allSignals) - // Configure spawnattr - var spawnAttributeError: Int32 = 0 - var flags: Int32 = POSIX_SPAWN_CLOEXEC_DEFAULT | - POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF - if let pgid = self.platformOptions.processGroupID { - flags |= POSIX_SPAWN_SETPGROUP - spawnAttributeError = posix_spawnattr_setpgroup(&spawnAttributes, pid_t(pgid)) - } - spawnAttributeError = posix_spawnattr_setflags(&spawnAttributes, Int16(flags)) - // Set QualityOfService - // spanattr_qos seems to only accept `QOS_CLASS_UTILITY` or `QOS_CLASS_BACKGROUND` - // and returns an error of `EINVAL` if anything else is provided - if spawnAttributeError == 0 && self.platformOptions.qualityOfService == .utility{ - spawnAttributeError = posix_spawnattr_set_qos_class_np(&spawnAttributes, QOS_CLASS_UTILITY) - } else if spawnAttributeError == 0 && self.platformOptions.qualityOfService == .background { - spawnAttributeError = posix_spawnattr_set_qos_class_np(&spawnAttributes, QOS_CLASS_BACKGROUND) - } - - // Setup cwd - var chdirError: Int32 = 0 - if intendedWorkingDir != .currentWorkingDirectory { - chdirError = intendedWorkingDir.withPlatformString { workDir in - return posix_spawn_file_actions_addchdir_np(&fileActions, workDir) - } - } - - // Error handling - if chdirError != 0 || spawnAttributeError != 0 { - try self.cleanupAll(input: input, output: output, error: error) - if spawnAttributeError != 0 { - throw POSIXError(.init(rawValue: result) ?? .ENODEV) - } +#if canImport(Darwin) +// On Darwin always prefer system Foundation +import Foundation +#else +// On other platforms prefer FoundationEssentials +import FoundationEssentials +#endif - if chdirError != 0 { - throw CocoaError(.fileNoSuchFile, userInfo: [ - .debugDescriptionErrorKey: "Cannot failed to change the working directory to \(intendedWorkingDir) with errno \(chdirError)" - ]) - } - } - // Run additional config - if let spawnConfig = self.platformOptions.preSpawnProcessConfigurator { - try spawnConfig(&spawnAttributes, &fileActions) - } - // Spawn - var pid: pid_t = 0 - let spawnError: CInt = executablePath.withCString { exePath in - return supplementaryGroups.withOptionalUnsafeBufferPointer { sgroups in - return _subprocess_spawn( - &pid, exePath, - &fileActions, &spawnAttributes, - argv, env, - uidPtr, gidPtr, - Int32(supplementaryGroups?.count ?? 0), sgroups?.baseAddress, - self.platformOptions.createSession ? 1 : 0 - ) - } - } - // Spawn error - if spawnError != 0 { - try self.cleanupAll(input: input, output: output, error: error) - throw POSIXError(.init(rawValue: spawnError) ?? .ENODEV) - } - return Subprocess( - processIdentifier: .init(value: pid), - executionInput: input, - executionOutput: output, - executionError: error - ) - } -} +#endif // SubprocessFoundation -// Special keys used in Error's user dictionary -extension String { - static let debugDescriptionErrorKey = "NSDebugDescription" -} +// MARK: - PlatformOptions -// MARK: - Platform Specific Options -extension Subprocess { - /// The collection of platform-specific settings - /// to configure the subprocess when running - public struct PlatformOptions: Sendable { - public var qualityOfService: QualityOfService = .default - /// Set user ID for the subprocess - public var userID: uid_t? = nil - /// Set the real and effective group ID and the saved - /// set-group-ID of the subprocess, equivalent to calling - /// `setgid()` on the child process. - /// Group ID is used to control permissions, particularly - /// for file access. - public var groupID: gid_t? = nil - /// Set list of supplementary group IDs for the subprocess - public var supplementaryGroups: [gid_t]? = nil - /// Set the process group for the subprocess, equivalent to - /// calling `setpgid()` on the child process. - /// Process group ID is used to group related processes for - /// controlling signals. - public var processGroupID: pid_t? = nil - /// Creates a session and sets the process group ID - /// i.e. Detach from the terminal. - public var createSession: Bool = false - /// A lightweight code requirement that you use to - /// evaluate the executable for a launching process. - public var launchRequirementData: Data? = nil - /// An ordered list of steps in order to tear down the child - /// process in case the parent task is cancelled before - /// the child proces terminates. - /// Always ends in sending a `.kill` signal at the end. - public var teardownSequence: [TeardownStep] = [] - /// A closure to configure platform-specific - /// spawning constructs. This closure enables direct - /// configuration or override of underlying platform-specific - /// spawn settings that `Subprocess` utilizes internally, - /// in cases where Subprocess does not provide higher-level - /// APIs for such modifications. - /// - /// On Darwin, Subprocess uses `posix_spawn()` as the - /// underlying spawning mechanism. This closure allows - /// modification of the `posix_spawnattr_t` spawn attribute - /// and file actions `posix_spawn_file_actions_t` before - /// they are sent to `posix_spawn()`. - public var preSpawnProcessConfigurator: ( +/// The collection of platform-specific settings +/// to configure the subprocess when running +public struct PlatformOptions: Sendable { + public var qualityOfService: QualityOfService = .default + /// Set user ID for the subprocess + public var userID: uid_t? = nil + /// Set the real and effective group ID and the saved + /// set-group-ID of the subprocess, equivalent to calling + /// `setgid()` on the child process. + /// Group ID is used to control permissions, particularly + /// for file access. + public var groupID: gid_t? = nil + /// Set list of supplementary group IDs for the subprocess + public var supplementaryGroups: [gid_t]? = nil + /// Set the process group for the subprocess, equivalent to + /// calling `setpgid()` on the child process. + /// Process group ID is used to group related processes for + /// controlling signals. + public var processGroupID: pid_t? = nil + /// Creates a session and sets the process group ID + /// i.e. Detach from the terminal. + public var createSession: Bool = false + /// An ordered list of steps in order to tear down the child + /// process in case the parent task is cancelled before + /// the child proces terminates. + /// Always ends in sending a `.kill` signal at the end. + public var teardownSequence: [TeardownStep] = [] + /// A closure to configure platform-specific + /// spawning constructs. This closure enables direct + /// configuration or override of underlying platform-specific + /// spawn settings that `Subprocess` utilizes internally, + /// in cases where Subprocess does not provide higher-level + /// APIs for such modifications. + /// + /// On Darwin, Subprocess uses `posix_spawn()` as the + /// underlying spawning mechanism. This closure allows + /// modification of the `posix_spawnattr_t` spawn attribute + /// and file actions `posix_spawn_file_actions_t` before + /// they are sent to `posix_spawn()`. + public var preSpawnProcessConfigurator: + ( @Sendable ( inout posix_spawnattr_t?, inout posix_spawn_file_actions_t? ) throws -> Void )? = nil - public init() {} - } + public init() {} } -extension Subprocess.PlatformOptions: Hashable { - public static func == (lhs: Subprocess.PlatformOptions, rhs: Subprocess.PlatformOptions) -> Bool { - // Since we can't compare closure equality, - // as long as preSpawnProcessConfigurator is set - // always returns false so that `PlatformOptions` - // with it set will never equal to each other - if lhs.preSpawnProcessConfigurator != nil || - rhs.preSpawnProcessConfigurator != nil { - return false - } - return lhs.qualityOfService == rhs.qualityOfService && - lhs.userID == rhs.userID && - lhs.groupID == rhs.groupID && - lhs.supplementaryGroups == rhs.supplementaryGroups && - lhs.processGroupID == rhs.processGroupID && - lhs.createSession == rhs.createSession && - lhs.launchRequirementData == rhs.launchRequirementData - } - - public func hash(into hasher: inout Hasher) { - hasher.combine(self.qualityOfService) - hasher.combine(self.userID) - hasher.combine(self.groupID) - hasher.combine(self.supplementaryGroups) - hasher.combine(self.processGroupID) - hasher.combine(self.createSession) - hasher.combine(self.launchRequirementData) - // Since we can't really hash closures, - // use an UUID such that as long as - // `preSpawnProcessConfigurator` is set, it will - // never equal to other PlatformOptions - if self.preSpawnProcessConfigurator != nil { - hasher.combine(UUID()) - } +extension PlatformOptions { + #if SubprocessFoundation + public typealias QualityOfService = Foundation.QualityOfService + #else + /// Constants that indicate the nature and importance of work to the system. + /// + /// Work with higher quality of service classes receive more resources + /// than work with lower quality of service classes whenever + /// there’s resource contention. + public enum QualityOfService: Int, Sendable { + /// Used for work directly involved in providing an + /// interactive UI. For example, processing control + /// events or drawing to the screen. + case userInteractive = 0x21 + /// Used for performing work that has been explicitly requested + /// by the user, and for which results must be immediately + /// presented in order to allow for further user interaction. + /// For example, loading an email after a user has selected + /// it in a message list. + case userInitiated = 0x19 + /// Used for performing work which the user is unlikely to be + /// immediately waiting for the results. This work may have been + /// requested by the user or initiated automatically, and often + /// operates at user-visible timescales using a non-modal + /// progress indicator. For example, periodic content updates + /// or bulk file operations, such as media import. + case utility = 0x11 + /// Used for work that is not user initiated or visible. + /// In general, a user is unaware that this work is even happening. + /// For example, pre-fetching content, search indexing, backups, + /// or syncing of data with external systems. + case background = 0x09 + /// Indicates no explicit quality of service information. + /// Whenever possible, an appropriate quality of service is determined + /// from available sources. Otherwise, some quality of service level + /// between `.userInteractive` and `.utility` is used. + case `default` = -1 } + #endif } -extension Subprocess.PlatformOptions : CustomStringConvertible, CustomDebugStringConvertible { +extension PlatformOptions: CustomStringConvertible, CustomDebugStringConvertible { internal func description(withIndent indent: Int) -> String { let indent = String(repeating: " ", count: indent * 4) return """ -PlatformOptions( -\(indent) qualityOfService: \(self.qualityOfService), -\(indent) userID: \(String(describing: userID)), -\(indent) groupID: \(String(describing: groupID)), -\(indent) supplementaryGroups: \(String(describing: supplementaryGroups)), -\(indent) processGroupID: \(String(describing: processGroupID)), -\(indent) createSession: \(createSession), -\(indent) launchRequirementData: \(String(describing: launchRequirementData)), -\(indent) preSpawnProcessConfigurator: \(self.preSpawnProcessConfigurator == nil ? "not set" : "set") -\(indent)) -""" + PlatformOptions( + \(indent) qualityOfService: \(self.qualityOfService), + \(indent) userID: \(String(describing: userID)), + \(indent) groupID: \(String(describing: groupID)), + \(indent) supplementaryGroups: \(String(describing: supplementaryGroups)), + \(indent) processGroupID: \(String(describing: processGroupID)), + \(indent) createSession: \(createSession), + \(indent) preSpawnProcessConfigurator: \(self.preSpawnProcessConfigurator == nil ? "not set" : "set") + \(indent)) + """ } public var description: String { @@ -313,11 +151,243 @@ PlatformOptions( } } +// MARK: - Spawn +extension Configuration { + @available(macOS 15.0, *) // FIXME: manually added availability + internal func spawn< + Output: OutputProtocol, + Error: OutputProtocol + >( + withInput inputPipe: CreatedPipe, + output: Output, + outputPipe: CreatedPipe, + error: Error, + errorPipe: CreatedPipe + ) throws -> Execution { + // Instead of checking if every possible executable path + // is valid, spawn each directly and catch ENOENT + let possiblePaths = self.executable.possibleExecutablePaths( + withPathValue: self.environment.pathValue() + ) + return try self.preSpawn { args throws -> Execution in + let (env, uidPtr, gidPtr, supplementaryGroups) = args + for possibleExecutablePath in possiblePaths { + var pid: pid_t = 0 + + // Setup Arguments + let argv: [UnsafeMutablePointer?] = self.arguments.createArgs( + withExecutablePath: possibleExecutablePath + ) + defer { + for ptr in argv { ptr?.deallocate() } + } + + // Setup file actions and spawn attributes + var fileActions: posix_spawn_file_actions_t? = nil + var spawnAttributes: posix_spawnattr_t? = nil + // Setup stdin, stdout, and stderr + posix_spawn_file_actions_init(&fileActions) + defer { + posix_spawn_file_actions_destroy(&fileActions) + } + // Input + var result: Int32 = -1 + if let inputRead = inputPipe.readFileDescriptor { + result = posix_spawn_file_actions_adddup2(&fileActions, inputRead.wrapped.rawValue, 0) + guard result == 0 else { + try self.cleanupPreSpawn(input: inputPipe, output: outputPipe, error: errorPipe) + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: result) + ) + } + } + if let inputWrite = inputPipe.writeFileDescriptor { + // Close parent side + result = posix_spawn_file_actions_addclose(&fileActions, inputWrite.wrapped.rawValue) + guard result == 0 else { + try self.cleanupPreSpawn(input: inputPipe, output: outputPipe, error: errorPipe) + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: result) + ) + } + } + // Output + if let outputWrite = outputPipe.writeFileDescriptor { + result = posix_spawn_file_actions_adddup2(&fileActions, outputWrite.wrapped.rawValue, 1) + guard result == 0 else { + try self.cleanupPreSpawn(input: inputPipe, output: outputPipe, error: errorPipe) + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: result) + ) + } + } + if let outputRead = outputPipe.readFileDescriptor { + // Close parent side + result = posix_spawn_file_actions_addclose(&fileActions, outputRead.wrapped.rawValue) + guard result == 0 else { + try self.cleanupPreSpawn(input: inputPipe, output: outputPipe, error: errorPipe) + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: result) + ) + } + } + // Error + if let errorWrite = errorPipe.writeFileDescriptor { + result = posix_spawn_file_actions_adddup2(&fileActions, errorWrite.wrapped.rawValue, 2) + guard result == 0 else { + try self.cleanupPreSpawn(input: inputPipe, output: outputPipe, error: errorPipe) + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: result) + ) + } + } + if let errorRead = errorPipe.readFileDescriptor { + // Close parent side + result = posix_spawn_file_actions_addclose(&fileActions, errorRead.wrapped.rawValue) + guard result == 0 else { + try self.cleanupPreSpawn(input: inputPipe, output: outputPipe, error: errorPipe) + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: result) + ) + } + } + // Setup spawnAttributes + posix_spawnattr_init(&spawnAttributes) + defer { + posix_spawnattr_destroy(&spawnAttributes) + } + var noSignals = sigset_t() + var allSignals = sigset_t() + sigemptyset(&noSignals) + sigfillset(&allSignals) + posix_spawnattr_setsigmask(&spawnAttributes, &noSignals) + posix_spawnattr_setsigdefault(&spawnAttributes, &allSignals) + // Configure spawnattr + var spawnAttributeError: Int32 = 0 + var flags: Int32 = POSIX_SPAWN_CLOEXEC_DEFAULT | POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF + if let pgid = self.platformOptions.processGroupID { + flags |= POSIX_SPAWN_SETPGROUP + spawnAttributeError = posix_spawnattr_setpgroup(&spawnAttributes, pid_t(pgid)) + } + spawnAttributeError = posix_spawnattr_setflags(&spawnAttributes, Int16(flags)) + // Set QualityOfService + // spanattr_qos seems to only accept `QOS_CLASS_UTILITY` or `QOS_CLASS_BACKGROUND` + // and returns an error of `EINVAL` if anything else is provided + if spawnAttributeError == 0 && self.platformOptions.qualityOfService == .utility { + spawnAttributeError = posix_spawnattr_set_qos_class_np(&spawnAttributes, QOS_CLASS_UTILITY) + } else if spawnAttributeError == 0 && self.platformOptions.qualityOfService == .background { + spawnAttributeError = posix_spawnattr_set_qos_class_np(&spawnAttributes, QOS_CLASS_BACKGROUND) + } + + // Setup cwd + let intendedWorkingDir = self.workingDirectory.string + let chdirError: Int32 = intendedWorkingDir.withPlatformString { workDir in + return posix_spawn_file_actions_addchdir_np(&fileActions, workDir) + } + + // Error handling + if chdirError != 0 || spawnAttributeError != 0 { + try self.cleanupPreSpawn(input: inputPipe, output: outputPipe, error: errorPipe) + if spawnAttributeError != 0 { + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: spawnAttributeError) + ) + } + + if chdirError != 0 { + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: spawnAttributeError) + ) + } + } + // Run additional config + if let spawnConfig = self.platformOptions.preSpawnProcessConfigurator { + try spawnConfig(&spawnAttributes, &fileActions) + } + + // Spawn + let spawnError: CInt = possibleExecutablePath.withCString { exePath in + return supplementaryGroups.withOptionalUnsafeBufferPointer { sgroups in + return _subprocess_spawn( + &pid, + exePath, + &fileActions, + &spawnAttributes, + argv, + env, + uidPtr, + gidPtr, + Int32(supplementaryGroups?.count ?? 0), + sgroups?.baseAddress, + self.platformOptions.createSession ? 1 : 0 + ) + } + } + // Spawn error + if spawnError != 0 { + if spawnError == ENOENT { + // Move on to another possible path + continue + } + // Throw all other errors + try self.cleanupPreSpawn( + input: inputPipe, + output: outputPipe, + error: errorPipe + ) + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: spawnError) + ) + } + return Execution( + processIdentifier: .init(value: pid), + output: output, + error: error, + outputPipe: outputPipe, + errorPipe: errorPipe + ) + } + + // If we reach this point, it means either the executable path + // or working directory is not valid. Since posix_spawn does not + // provide which one is not valid, here we make a best effort guess + // by checking whether the working directory is valid. This technically + // still causes TOUTOC issue, but it's the best we can do for error recovery. + try self.cleanupPreSpawn(input: inputPipe, output: outputPipe, error: errorPipe) + let workingDirectory = self.workingDirectory.string + guard Configuration.pathAccessible(workingDirectory, mode: F_OK) else { + throw SubprocessError( + code: .init(.failedToChangeWorkingDirectory(workingDirectory)), + underlyingError: .init(rawValue: ENOENT) + ) + } + throw SubprocessError( + code: .init(.executableNotFound(self.executable.description)), + underlyingError: .init(rawValue: ENOENT) + ) + } + } +} + +// Special keys used in Error's user dictionary +extension String { + static let debugDescriptionErrorKey = "NSDebugDescription" +} + // MARK: - Process Monitoring @Sendable internal func monitorProcessTermination( - forProcessWithIdentifier pid: Subprocess.ProcessIdentifier -) async throws -> Subprocess.TerminationStatus { + forProcessWithIdentifier pid: ProcessIdentifier +) async throws -> TerminationStatus { return try await withCheckedThrowingContinuation { continuation in let source = DispatchSource.makeProcessSource( identifier: pid.value, @@ -329,7 +399,12 @@ internal func monitorProcessTermination( var siginfo = siginfo_t() let rc = waitid(P_PID, id_t(pid.value), &siginfo, WEXITED) guard rc == 0 else { - continuation.resume(throwing: POSIXError(.init(rawValue: errno) ?? .ENODEV)) + continuation.resume( + throwing: SubprocessError( + code: .init(.failedToMonitorProcess), + underlyingError: .init(rawValue: errno) + ) + ) return } switch siginfo.si_code { @@ -350,4 +425,4 @@ internal func monitorProcessTermination( } } -#endif // canImport(Darwin) +#endif // canImport(Darwin) diff --git a/Sources/_Subprocess/Platforms/Subprocess+Linux.swift b/Sources/_Subprocess/Platforms/Subprocess+Linux.swift index 9debf2e39..23c9b36e5 100644 --- a/Sources/_Subprocess/Platforms/Subprocess+Linux.swift +++ b/Sources/_Subprocess/Platforms/Subprocess+Linux.swift @@ -2,196 +2,210 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception // -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 +// See https://swift.org/LICENSE.txt for license information // //===----------------------------------------------------------------------===// -#if canImport(Glibc) +#if canImport(Glibc) || canImport(Bionic) || canImport(Musl) + +#if canImport(System) +import System +#else +@preconcurrency import SystemPackage +#endif +#if canImport(Glibc) import Glibc -import Dispatch -import SystemPackage -import FoundationEssentials -import _CShims +#elseif canImport(Bionic) +import Bionic +#elseif canImport(Musl) +import Musl +#endif -// Linux specific implementations -extension Subprocess.Configuration { - internal typealias StringOrRawBytes = Subprocess.StringOrRawBytes +internal import Dispatch - internal func spawn( - withInput input: Subprocess.ExecutionInput, - output: Subprocess.ExecutionOutput, - error: Subprocess.ExecutionOutput - ) throws -> Subprocess { +import Synchronization +import _SubprocessCShims + +// Linux specific implementations +extension Configuration { + internal func spawn< + Output: OutputProtocol, + Error: OutputProtocol + >( + withInput inputPipe: CreatedPipe, + output: Output, + outputPipe: CreatedPipe, + error: Error, + errorPipe: CreatedPipe + ) throws -> Execution { _setupMonitorSignalHandler() - let (executablePath, - env, argv, - intendedWorkingDir, - uidPtr, gidPtr, - supplementaryGroups - ) = try self.preSpawn() - var processGroupIDPtr: UnsafeMutablePointer? = nil - if let processGroupID = self.platformOptions.processGroupID { - processGroupIDPtr = .allocate(capacity: 1) - processGroupIDPtr?.pointee = gid_t(processGroupID) - } - defer { - for ptr in env { ptr?.deallocate() } - for ptr in argv { ptr?.deallocate() } - uidPtr?.deallocate() - gidPtr?.deallocate() - processGroupIDPtr?.deallocate() - } + // Instead of checking if every possible executable path + // is valid, spawn each directly and catch ENOENT + let possiblePaths = self.executable.possibleExecutablePaths( + withPathValue: self.environment.pathValue() + ) - let fileDescriptors: [CInt] = [ - input.getReadFileDescriptor()?.rawValue ?? -1, - input.getWriteFileDescriptor()?.rawValue ?? -1, - output.getWriteFileDescriptor()?.rawValue ?? -1, - output.getReadFileDescriptor()?.rawValue ?? -1, - error.getWriteFileDescriptor()?.rawValue ?? -1, - error.getReadFileDescriptor()?.rawValue ?? -1 - ] + return try self.preSpawn { args throws -> Execution in + let (env, uidPtr, gidPtr, supplementaryGroups) = args - var workingDirectory: String? - if intendedWorkingDir != FilePath.currentWorkingDirectory { - // Only pass in working directory if it's different - workingDirectory = intendedWorkingDir.string - } - // Spawn - var pid: pid_t = 0 - let spawnError: CInt = executablePath.withCString { exePath in - return workingDirectory.withOptionalCString { workingDir in - return supplementaryGroups.withOptionalUnsafeBufferPointer { sgroups in - return fileDescriptors.withUnsafeBufferPointer { fds in - return _subprocess_fork_exec( - &pid, exePath, workingDir, - fds.baseAddress!, - argv, env, - uidPtr, gidPtr, - processGroupIDPtr, - CInt(supplementaryGroups?.count ?? 0), sgroups?.baseAddress, - self.platformOptions.createSession ? 1 : 0, - self.platformOptions.preSpawnProcessConfigurator - ) + for possibleExecutablePath in possiblePaths { + var processGroupIDPtr: UnsafeMutablePointer? = nil + if let processGroupID = self.platformOptions.processGroupID { + processGroupIDPtr = .allocate(capacity: 1) + processGroupIDPtr?.pointee = gid_t(processGroupID) + } + // Setup Arguments + let argv: [UnsafeMutablePointer?] = self.arguments.createArgs( + withExecutablePath: possibleExecutablePath + ) + defer { + for ptr in argv { ptr?.deallocate() } + } + // Setup input + let fileDescriptors: [CInt] = [ + inputPipe.readFileDescriptor?.wrapped.rawValue ?? -1, + inputPipe.writeFileDescriptor?.wrapped.rawValue ?? -1, + outputPipe.writeFileDescriptor?.wrapped.rawValue ?? -1, + outputPipe.readFileDescriptor?.wrapped.rawValue ?? -1, + errorPipe.writeFileDescriptor?.wrapped.rawValue ?? -1, + errorPipe.readFileDescriptor?.wrapped.rawValue ?? -1, + ] + + let workingDirectory: String = self.workingDirectory.string + // Spawn + var pid: pid_t = 0 + let spawnError: CInt = possibleExecutablePath.withCString { exePath in + return workingDirectory.withCString { workingDir in + return supplementaryGroups.withOptionalUnsafeBufferPointer { sgroups in + return fileDescriptors.withUnsafeBufferPointer { fds in + return _subprocess_fork_exec( + &pid, + exePath, + workingDir, + fds.baseAddress!, + argv, + env, + uidPtr, + gidPtr, + processGroupIDPtr, + CInt(supplementaryGroups?.count ?? 0), + sgroups?.baseAddress, + self.platformOptions.createSession ? 1 : 0, + self.platformOptions.preSpawnProcessConfigurator + ) + } + } } } + // Spawn error + if spawnError != 0 { + if spawnError == ENOENT { + // Move on to another possible path + continue + } + // Throw all other errors + try self.cleanupPreSpawn( + input: inputPipe, + output: outputPipe, + error: errorPipe + ) + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: spawnError) + ) + } + return Execution( + processIdentifier: .init(value: pid), + output: output, + error: error, + outputPipe: outputPipe, + errorPipe: errorPipe + ) } + + // If we reach this point, it means either the executable path + // or working directory is not valid. Since posix_spawn does not + // provide which one is not valid, here we make a best effort guess + // by checking whether the working directory is valid. This technically + // still causes TOUTOC issue, but it's the best we can do for error recovery. + try self.cleanupPreSpawn(input: inputPipe, output: outputPipe, error: errorPipe) + let workingDirectory = self.workingDirectory.string + guard Configuration.pathAccessible(workingDirectory, mode: F_OK) else { + throw SubprocessError( + code: .init(.failedToChangeWorkingDirectory(workingDirectory)), + underlyingError: .init(rawValue: ENOENT) + ) + } + throw SubprocessError( + code: .init(.executableNotFound(self.executable.description)), + underlyingError: .init(rawValue: ENOENT) + ) } - // Spawn error - if spawnError != 0 { - try self.cleanupAll(input: input, output: output, error: error) - throw POSIXError(.init(rawValue: spawnError) ?? .ENODEV) - } - return Subprocess( - processIdentifier: .init(value: pid), - executionInput: input, - executionOutput: output, - executionError: error - ) } } // MARK: - Platform Specific Options -extension Subprocess { - /// The collection of platform-specific settings - /// to configure the subprocess when running - public struct PlatformOptions: Sendable { - // Set user ID for the subprocess - public var userID: uid_t? = nil - /// Set the real and effective group ID and the saved - /// set-group-ID of the subprocess, equivalent to calling - /// `setgid()` on the child process. - /// Group ID is used to control permissions, particularly - /// for file access. - public var groupID: gid_t? = nil - // Set list of supplementary group IDs for the subprocess - public var supplementaryGroups: [gid_t]? = nil - /// Set the process group for the subprocess, equivalent to - /// calling `setpgid()` on the child process. - /// Process group ID is used to group related processes for - /// controlling signals. - public var processGroupID: pid_t? = nil - // Creates a session and sets the process group ID - // i.e. Detach from the terminal. - public var createSession: Bool = false - /// An ordered list of steps in order to tear down the child - /// process in case the parent task is cancelled before - /// the child proces terminates. - /// Always ends in sending a `.kill` signal at the end. - public var teardownSequence: [TeardownStep] = [] - /// A closure to configure platform-specific - /// spawning constructs. This closure enables direct - /// configuration or override of underlying platform-specific - /// spawn settings that `Subprocess` utilizes internally, - /// in cases where Subprocess does not provide higher-level - /// APIs for such modifications. - /// - /// On Linux, Subprocess uses `fork/exec` as the - /// underlying spawning mechanism. This closure is called - /// after `fork()` but before `exec()`. You may use it to - /// call any necessary process setup functions. - public var preSpawnProcessConfigurator: (@convention(c) @Sendable () -> Void)? = nil - public init() {} - } -} - -extension Subprocess.PlatformOptions: Hashable { - public static func ==( - lhs: Subprocess.PlatformOptions, - rhs: Subprocess.PlatformOptions - ) -> Bool { - // Since we can't compare closure equality, - // as long as preSpawnProcessConfigurator is set - // always returns false so that `PlatformOptions` - // with it set will never equal to each other - if lhs.preSpawnProcessConfigurator != nil || - rhs.preSpawnProcessConfigurator != nil { - return false - } - return lhs.userID == rhs.userID && - lhs.groupID == rhs.groupID && - lhs.supplementaryGroups == rhs.supplementaryGroups && - lhs.processGroupID == rhs.processGroupID && - lhs.createSession == rhs.createSession - } +/// The collection of platform-specific settings +/// to configure the subprocess when running +public struct PlatformOptions: Sendable { + // Set user ID for the subprocess + public var userID: uid_t? = nil + /// Set the real and effective group ID and the saved + /// set-group-ID of the subprocess, equivalent to calling + /// `setgid()` on the child process. + /// Group ID is used to control permissions, particularly + /// for file access. + public var groupID: gid_t? = nil + // Set list of supplementary group IDs for the subprocess + public var supplementaryGroups: [gid_t]? = nil + /// Set the process group for the subprocess, equivalent to + /// calling `setpgid()` on the child process. + /// Process group ID is used to group related processes for + /// controlling signals. + public var processGroupID: pid_t? = nil + // Creates a session and sets the process group ID + // i.e. Detach from the terminal. + public var createSession: Bool = false + /// An ordered list of steps in order to tear down the child + /// process in case the parent task is cancelled before + /// the child proces terminates. + /// Always ends in sending a `.kill` signal at the end. + public var teardownSequence: [TeardownStep] = [] + /// A closure to configure platform-specific + /// spawning constructs. This closure enables direct + /// configuration or override of underlying platform-specific + /// spawn settings that `Subprocess` utilizes internally, + /// in cases where Subprocess does not provide higher-level + /// APIs for such modifications. + /// + /// On Linux, Subprocess uses `fork/exec` as the + /// underlying spawning mechanism. This closure is called + /// after `fork()` but before `exec()`. You may use it to + /// call any necessary process setup functions. + public var preSpawnProcessConfigurator: (@convention(c) @Sendable () -> Void)? = nil - public func hash(into hasher: inout Hasher) { - hasher.combine(userID) - hasher.combine(groupID) - hasher.combine(supplementaryGroups) - hasher.combine(processGroupID) - hasher.combine(createSession) - // Since we can't really hash closures, - // use an UUID such that as long as - // `preSpawnProcessConfigurator` is set, it will - // never equal to other PlatformOptions - if self.preSpawnProcessConfigurator != nil { - hasher.combine(UUID()) - } - } + public init() {} } -extension Subprocess.PlatformOptions : CustomStringConvertible, CustomDebugStringConvertible { +extension PlatformOptions: CustomStringConvertible, CustomDebugStringConvertible { internal func description(withIndent indent: Int) -> String { let indent = String(repeating: " ", count: indent * 4) return """ -PlatformOptions( -\(indent) userID: \(String(describing: userID)), -\(indent) groupID: \(String(describing: groupID)), -\(indent) supplementaryGroups: \(String(describing: supplementaryGroups)), -\(indent) processGroupID: \(String(describing: processGroupID)), -\(indent) createSession: \(createSession), -\(indent) preSpawnProcessConfigurator: \(self.preSpawnProcessConfigurator == nil ? "not set" : "set") -\(indent)) -""" + PlatformOptions( + \(indent) userID: \(String(describing: userID)), + \(indent) groupID: \(String(describing: groupID)), + \(indent) supplementaryGroups: \(String(describing: supplementaryGroups)), + \(indent) processGroupID: \(String(describing: processGroupID)), + \(indent) createSession: \(createSession), + \(indent) preSpawnProcessConfigurator: \(self.preSpawnProcessConfigurator == nil ? "not set" : "set") + \(indent)) + """ } public var description: String { @@ -211,12 +225,13 @@ extension String { // MARK: - Process Monitoring @Sendable internal func monitorProcessTermination( - forProcessWithIdentifier pid: Subprocess.ProcessIdentifier -) async throws -> Subprocess.TerminationStatus { + forProcessWithIdentifier pid: ProcessIdentifier +) async throws -> TerminationStatus { return try await withCheckedThrowingContinuation { continuation in _childProcessContinuations.withLock { continuations in if let existing = continuations.removeValue(forKey: pid.value), - case .status(let existingStatus) = existing { + case .status(let existingStatus) = existing + { // We already have existing status to report continuation.resume(returning: existingStatus) } else { @@ -228,28 +243,26 @@ internal func monitorProcessTermination( } private enum ContinuationOrStatus { - case continuation(CheckedContinuation) - case status(Subprocess.TerminationStatus) + case continuation(CheckedContinuation) + case status(TerminationStatus) } -private let _childProcessContinuations: LockedState< - [pid_t: ContinuationOrStatus] -> = LockedState(initialState: [:]) +private let _childProcessContinuations: + Mutex< + [pid_t: ContinuationOrStatus] + > = Mutex([:]) + +private let signalSource: SendableSourceSignal = SendableSourceSignal() -private var signalSource: (any DispatchSourceSignal)? = nil private let setup: () = { - signalSource = DispatchSource.makeSignalSource( - signal: SIGCHLD, - queue: .global() - ) - signalSource?.setEventHandler { + signalSource.setEventHandler { _childProcessContinuations.withLock { continuations in while true { var siginfo = siginfo_t() guard waitid(P_ALL, id_t(0), &siginfo, WEXITED) == 0 else { return } - var status: Subprocess.TerminationStatus? = nil + var status: TerminationStatus? = nil switch siginfo.si_code { case .init(CLD_EXITED): status = .exited(siginfo._sifields._sigchld.si_status) @@ -265,7 +278,8 @@ private let setup: () = { if let status = status { let pid = siginfo._sifields._sigchld.si_pid if let existing = continuations.removeValue(forKey: pid), - case .continuation(let c) = existing { + case .continuation(let c) = existing + { c.resume(returning: status) } else { // We don't have continuation yet, just state status @@ -275,13 +289,33 @@ private let setup: () = { } } } - signalSource?.resume() + signalSource.resume() }() +/// Unchecked Sendable here since this class is only explicitly +/// initialzied once during the lifetime of the process +final class SendableSourceSignal: @unchecked Sendable { + private let signalSource: DispatchSourceSignal + + func setEventHandler(handler: @escaping DispatchSourceHandler) { + self.signalSource.setEventHandler(handler: handler) + } + + func resume() { + self.signalSource.resume() + } + + init() { + self.signalSource = DispatchSource.makeSignalSource( + signal: SIGCHLD, + queue: .global() + ) + } +} + private func _setupMonitorSignalHandler() { // Only executed once setup } -#endif // canImport(Glibc) - +#endif // canImport(Glibc) || canImport(Bionic) || canImport(Musl) diff --git a/Sources/_Subprocess/Platforms/Subprocess+Unix.swift b/Sources/_Subprocess/Platforms/Subprocess+Unix.swift index bd1103010..ae8fd639b 100644 --- a/Sources/_Subprocess/Platforms/Subprocess+Unix.swift +++ b/Sources/_Subprocess/Platforms/Subprocess+Unix.swift @@ -2,119 +2,143 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception // -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 +// See https://swift.org/LICENSE.txt for license information // //===----------------------------------------------------------------------===// -#if canImport(Darwin) || canImport(Glibc) +#if canImport(Darwin) || canImport(Glibc) || canImport(Bionic) || canImport(Musl) -#if canImport(FoundationEssentials) -import FoundationEssentials -#elseif canImport(Foundation) -import Foundation +#if canImport(System) +import System +#else +@preconcurrency import SystemPackage #endif +import _SubprocessCShims + #if canImport(Darwin) import Darwin +#elseif canImport(Bionic) +import Bionic #elseif canImport(Glibc) import Glibc +#elseif canImport(Musl) +import Musl #endif -#if FOUNDATION_FRAMEWORK -@_implementationOnly import _FoundationCShims -#else -import _CShims -#endif - -import Dispatch -import SystemPackage +package import Dispatch // MARK: - Signals -extension Subprocess { - /// Signals are standardized messages sent to a running program - /// to trigger specific behavior, such as quitting or error handling. - public struct Signal : Hashable, Sendable { - /// The underlying platform specific value for the signal - public let rawValue: Int32 - - private init(rawValue: Int32) { - self.rawValue = rawValue - } - /// The `.interrupt` signal is sent to a process by its - /// controlling terminal when a user wishes to interrupt - /// the process. - public static var interrupt: Self { .init(rawValue: SIGINT) } - /// The `.terminate` signal is sent to a process to request its - /// termination. Unlike the `.kill` signal, it can be caught - /// and interpreted or ignored by the process. This allows - /// the process to perform nice termination releasing resources - /// and saving state if appropriate. `.interrupt` is nearly - /// identical to `.terminate`. - public static var terminate: Self { .init(rawValue: SIGTERM) } - /// The `.suspend` signal instructs the operating system - /// to stop a process for later resumption. - public static var suspend: Self { .init(rawValue: SIGSTOP) } - /// The `resume` signal instructs the operating system to - /// continue (restart) a process previously paused by the - /// `.suspend` signal. - public static var resume: Self { .init(rawValue: SIGCONT) } - /// The `.kill` signal is sent to a process to cause it to - /// terminate immediately (kill). In contrast to `.terminate` - /// and `.interrupt`, this signal cannot be caught or ignored, - /// and the receiving process cannot perform any - /// clean-up upon receiving this signal. - public static var kill: Self { .init(rawValue: SIGKILL) } - /// The `.terminalClosed` signal is sent to a process when - /// its controlling terminal is closed. In modern systems, - /// this signal usually means that the controlling pseudo - /// or virtual terminal has been closed. - public static var terminalClosed: Self { .init(rawValue: SIGHUP) } - /// The `.quit` signal is sent to a process by its controlling - /// terminal when the user requests that the process quit - /// and perform a core dump. - public static var quit: Self { .init(rawValue: SIGQUIT) } - /// The `.userDefinedOne` signal is sent to a process to indicate - /// user-defined conditions. - public static var userDefinedOne: Self { .init(rawValue: SIGUSR1) } - /// The `.userDefinedTwo` signal is sent to a process to indicate - /// user-defined conditions. - public static var userDefinedTwo: Self { .init(rawValue: SIGUSR2) } - /// The `.alarm` signal is sent to a process when the corresponding - /// time limit is reached. - public static var alarm: Self { .init(rawValue: SIGALRM) } - /// The `.windowSizeChange` signal is sent to a process when - /// its controlling terminal changes its size (a window change). - public static var windowSizeChange: Self { .init(rawValue: SIGWINCH) } +/// Signals are standardized messages sent to a running program +/// to trigger specific behavior, such as quitting or error handling. +public struct Signal: Hashable, Sendable { + /// The underlying platform specific value for the signal + public let rawValue: Int32 + + private init(rawValue: Int32) { + self.rawValue = rawValue + } + + /// The `.interrupt` signal is sent to a process by its + /// controlling terminal when a user wishes to interrupt + /// the process. + public static var interrupt: Self { .init(rawValue: SIGINT) } + /// The `.terminate` signal is sent to a process to request its + /// termination. Unlike the `.kill` signal, it can be caught + /// and interpreted or ignored by the process. This allows + /// the process to perform nice termination releasing resources + /// and saving state if appropriate. `.interrupt` is nearly + /// identical to `.terminate`. + public static var terminate: Self { .init(rawValue: SIGTERM) } + /// The `.suspend` signal instructs the operating system + /// to stop a process for later resumption. + public static var suspend: Self { .init(rawValue: SIGSTOP) } + /// The `resume` signal instructs the operating system to + /// continue (restart) a process previously paused by the + /// `.suspend` signal. + public static var resume: Self { .init(rawValue: SIGCONT) } + /// The `.kill` signal is sent to a process to cause it to + /// terminate immediately (kill). In contrast to `.terminate` + /// and `.interrupt`, this signal cannot be caught or ignored, + /// and the receiving process cannot perform any + /// clean-up upon receiving this signal. + public static var kill: Self { .init(rawValue: SIGKILL) } + /// The `.terminalClosed` signal is sent to a process when + /// its controlling terminal is closed. In modern systems, + /// this signal usually means that the controlling pseudo + /// or virtual terminal has been closed. + public static var terminalClosed: Self { .init(rawValue: SIGHUP) } + /// The `.quit` signal is sent to a process by its controlling + /// terminal when the user requests that the process quit + /// and perform a core dump. + public static var quit: Self { .init(rawValue: SIGQUIT) } + /// The `.userDefinedOne` signal is sent to a process to indicate + /// user-defined conditions. + public static var userDefinedOne: Self { .init(rawValue: SIGUSR1) } + /// The `.userDefinedTwo` signal is sent to a process to indicate + /// user-defined conditions. + public static var userDefinedTwo: Self { .init(rawValue: SIGUSR2) } + /// The `.alarm` signal is sent to a process when the corresponding + /// time limit is reached. + public static var alarm: Self { .init(rawValue: SIGALRM) } + /// The `.windowSizeChange` signal is sent to a process when + /// its controlling terminal changes its size (a window change). + public static var windowSizeChange: Self { .init(rawValue: SIGWINCH) } +} + +// MARK: - ProcessIdentifier + +/// A platform independent identifier for a Subprocess. +public struct ProcessIdentifier: Sendable, Hashable, Codable { + /// The platform specific process identifier value + public let value: pid_t + + public init(value: pid_t) { + self.value = value } +} +extension ProcessIdentifier: CustomStringConvertible, CustomDebugStringConvertible { + public var description: String { "\(self.value)" } + + public var debugDescription: String { "\(self.value)" } +} + +@available(macOS 15.0, *) // FIXME: manually added availability +extension Execution { /// Send the given signal to the child process. /// - Parameters: /// - signal: The signal to send. /// - shouldSendToProcessGroup: Whether this signal should be sent to /// the entire process group. - public func send(_ signal: Signal, toProcessGroup shouldSendToProcessGroup: Bool) throws { + public func send( + signal: Signal, + toProcessGroup shouldSendToProcessGroup: Bool = false + ) throws { let pid = shouldSendToProcessGroup ? -(self.processIdentifier.value) : self.processIdentifier.value guard kill(pid, signal.rawValue) == 0 else { - throw POSIXError(.init(rawValue: errno)!) + throw SubprocessError( + code: .init(.failedToSendSignal(signal.rawValue)), + underlyingError: .init(rawValue: errno) + ) } } - internal func tryTerminate() -> Error? { + internal func tryTerminate() -> Swift.Error? { do { - try self.send(.kill, toProcessGroup: true) + try self.send(signal: .kill) } catch { - guard let posixError: POSIXError = error as? POSIXError else { + guard let posixError: SubprocessError = error as? SubprocessError else { return error } // Ignore ESRCH (no such process) - if posixError.code != .ESRCH { + if let underlyingError = posixError.underlyingError, + underlyingError.rawValue != ESRCH + { return error } } @@ -123,21 +147,32 @@ extension Subprocess { } // MARK: - Environment Resolution -extension Subprocess.Environment { - internal static let pathEnvironmentVariableName = "PATH" +extension Environment { + internal static let pathVariableName = "PATH" internal func pathValue() -> String? { switch self.config { case .inherit(let overrides): // If PATH value exists in overrides, use it - if let value = overrides[.string(Self.pathEnvironmentVariableName)] { - return value.stringValue + if let value = overrides[Self.pathVariableName] { + return value } // Fall back to current process - return ProcessInfo.processInfo.environment[Self.pathEnvironmentVariableName] + return Self.currentEnvironmentValues()[Self.pathVariableName] case .custom(let fullEnvironment): - if let value = fullEnvironment[.string(Self.pathEnvironmentVariableName)] { - return value.stringValue + if let value = fullEnvironment[Self.pathVariableName] { + return value + } + return nil + case .rawBytes(let rawBytesArray): + let needle: [UInt8] = Array("\(Self.pathVariableName)=".utf8) + for row in rawBytesArray { + guard row.starts(with: needle) else { + continue + } + // Attempt to + let pathValue = row.dropFirst(needle.count) + return String(decoding: pathValue, as: UTF8.self) } return nil } @@ -147,8 +182,8 @@ extension Subprocess.Environment { // manually deallocated internal func createEnv() -> [UnsafeMutablePointer?] { func createFullCString( - fromKey keyContainer: Subprocess.StringOrRawBytes, - value valueContainer: Subprocess.StringOrRawBytes + fromKey keyContainer: StringOrRawBytes, + value valueContainer: StringOrRawBytes ) -> UnsafeMutablePointer { let rawByteKey: UnsafeMutablePointer = keyContainer.createRawBytes() let rawByteValue: UnsafeMutablePointer = valueContainer.createRawBytes() @@ -170,21 +205,12 @@ extension Subprocess.Environment { var env: [UnsafeMutablePointer?] = [] switch self.config { case .inherit(let updates): - var current = ProcessInfo.processInfo.environment - for (keyContainer, valueContainer) in updates { - if let stringKey = keyContainer.stringValue { - // Remove the value from current to override it - current.removeValue(forKey: stringKey) - } - // Fast path - if case .string(let stringKey) = keyContainer, - case .string(let stringValue) = valueContainer { - let fullString = "\(stringKey)=\(stringValue)" - env.append(strdup(fullString)) - continue - } - - env.append(createFullCString(fromKey: keyContainer, value: valueContainer)) + var current = Self.currentEnvironmentValues() + for (key, value) in updates { + // Remove the value from current to override it + current.removeValue(forKey: key) + let fullString = "\(key)=\(value)" + env.append(strdup(fullString)) } // Add the rest of `current` to env for (key, value) in current { @@ -192,24 +218,43 @@ extension Subprocess.Environment { env.append(strdup(fullString)) } case .custom(let customValues): - for (keyContainer, valueContainer) in customValues { - // Fast path - if case .string(let stringKey) = keyContainer, - case .string(let stringValue) = valueContainer { - let fullString = "\(stringKey)=\(stringValue)" - env.append(strdup(fullString)) - continue - } - env.append(createFullCString(fromKey: keyContainer, value: valueContainer)) + for (key, value) in customValues { + let fullString = "\(key)=\(value)" + env.append(strdup(fullString)) + } + case .rawBytes(let rawBytesArray): + for rawBytes in rawBytesArray { + env.append(strdup(rawBytes)) } } env.append(nil) return env } + + internal static func withCopiedEnv(_ body: ([UnsafeMutablePointer]) -> R) -> R { + var values: [UnsafeMutablePointer] = [] + // This lock is taken by calls to getenv, so we want as few callouts to other code as possible here. + _subprocess_lock_environ() + guard + let environments: UnsafeMutablePointer?> = + _subprocess_get_environ() + else { + _subprocess_unlock_environ() + return body([]) + } + var curr = environments + while let value = curr.pointee { + values.append(strdup(value)) + curr = curr.advanced(by: 1) + } + _subprocess_unlock_environ() + defer { values.forEach { free($0) } } + return body(values) + } } // MARK: Args Creation -extension Subprocess.Arguments { +extension Arguments { // This method follows the standard "create" rule: `args` needs to be // manually deallocated internal func createArgs(withExecutablePath executablePath: String) -> [UnsafeMutablePointer?] { @@ -225,42 +270,23 @@ extension Subprocess.Arguments { } } -// MARK: - ProcessIdentifier -extension Subprocess { - /// A platform independent identifier for a subprocess. - public struct ProcessIdentifier: Sendable, Hashable, Codable { - /// The platform specific process identifier value - public let value: pid_t - - public init(value: pid_t) { - self.value = value - } - } -} - -extension Subprocess.ProcessIdentifier : CustomStringConvertible, CustomDebugStringConvertible { - public var description: String { "\(self.value)" } - - public var debugDescription: String { "\(self.value)" } -} - // MARK: - Executable Searching -extension Subprocess.Executable { +extension Executable { internal static var defaultSearchPaths: Set { return Set([ "/usr/bin", "/bin", "/usr/sbin", "/sbin", - "/usr/local/bin" + "/usr/local/bin", ]) } - internal func resolveExecutablePath(withPathValue pathValue: String?) -> String? { + internal func resolveExecutablePath(withPathValue pathValue: String?) throws -> String { switch self.storage { case .executable(let executableName): // If the executableName in is already a full path, return it directly - if Subprocess.Configuration.pathAccessible(executableName, mode: X_OK) { + if Configuration.pathAccessible(executableName, mode: X_OK) { return executableName } // Get $PATH from environment @@ -274,50 +300,38 @@ extension Subprocess.Executable { for path in searchPaths { let fullPath = "\(path)/\(executableName)" - let fileExists = Subprocess.Configuration.pathAccessible(fullPath, mode: X_OK) + let fileExists = Configuration.pathAccessible(fullPath, mode: X_OK) if fileExists { return fullPath } } + throw SubprocessError( + code: .init(.executableNotFound(executableName)), + underlyingError: nil + ) case .path(let executablePath): // Use path directly return executablePath.string } - return nil } } -// MARK: - Configuration -extension Subprocess.Configuration { - internal func preSpawn() throws -> ( - executablePath: String, +// MARK: - PreSpawn +extension Configuration { + internal typealias PreSpawnArgs = ( env: [UnsafeMutablePointer?], - argv: [UnsafeMutablePointer?], - intendedWorkingDir: FilePath, uidPtr: UnsafeMutablePointer?, gidPtr: UnsafeMutablePointer?, supplementaryGroups: [gid_t]? - ) { + ) + + internal func preSpawn( + _ work: (PreSpawnArgs) throws -> Result + ) throws -> Result { // Prepare environment let env = self.environment.createEnv() - // Prepare executable path - guard let executablePath = self.executable.resolveExecutablePath( - withPathValue: self.environment.pathValue()) else { - for ptr in env { ptr?.deallocate() } - throw CocoaError(.executableNotLoadable, userInfo: [ - .debugDescriptionErrorKey : "\(self.executable.description) is not an executable" - ]) - } - // Prepare arguments - let argv: [UnsafeMutablePointer?] = self.arguments.createArgs(withExecutablePath: executablePath) - // Prepare workingDir - let intendedWorkingDir = self.workingDirectory - guard Self.pathAccessible(intendedWorkingDir.string, mode: F_OK) else { + defer { for ptr in env { ptr?.deallocate() } - for ptr in argv { ptr?.deallocate() } - throw CocoaError(.fileNoSuchFile, userInfo: [ - .debugDescriptionErrorKey : "Failed to set working directory to \(intendedWorkingDir)" - ]) } var uidPtr: UnsafeMutablePointer? = nil @@ -325,21 +339,28 @@ extension Subprocess.Configuration { uidPtr = .allocate(capacity: 1) uidPtr?.pointee = userID } + defer { + uidPtr?.deallocate() + } var gidPtr: UnsafeMutablePointer? = nil if let groupID = self.platformOptions.groupID { gidPtr = .allocate(capacity: 1) gidPtr?.pointee = groupID } + defer { + gidPtr?.deallocate() + } var supplementaryGroups: [gid_t]? if let groupsValue = self.platformOptions.supplementaryGroups { supplementaryGroups = groupsValue } - return ( - executablePath: executablePath, - env: env, argv: argv, - intendedWorkingDir: intendedWorkingDir, - uidPtr: uidPtr, gidPtr: gidPtr, - supplementaryGroups: supplementaryGroups + return try work( + ( + env: env, + uidPtr: uidPtr, + gidPtr: gidPtr, + supplementaryGroups: supplementaryGroups + ) ) } @@ -359,11 +380,11 @@ extension FileDescriptor { return devnull } - internal var platformDescriptor: Subprocess.PlatformFileDescriptor { + internal var platformDescriptor: PlatformFileDescriptor { return self } - internal func readChunk(upToLength maxLength: Int) async throws -> Data? { + package func readChunk(upToLength maxLength: Int) async throws -> SequenceOutput.Buffer? { return try await withCheckedThrowingContinuation { continuation in DispatchIO.read( fromFileDescriptor: self.rawValue, @@ -371,88 +392,125 @@ extension FileDescriptor { runningHandlerOn: .global() ) { data, error in if error != 0 { - continuation.resume(throwing: POSIXError(.init(rawValue: error) ?? .ENODEV)) + continuation.resume( + throwing: SubprocessError( + code: .init(.failedToReadFromSubprocess), + underlyingError: .init(rawValue: error) + ) + ) return } if data.isEmpty { continuation.resume(returning: nil) } else { - continuation.resume(returning: Data(data)) + continuation.resume(returning: SequenceOutput.Buffer(data: data)) } } } } - internal func readUntilEOF(upToLength maxLength: Int) async throws -> Data { - return try await withCheckedThrowingContinuation { continuation in - let dispatchIO = DispatchIO( - type: .stream, - fileDescriptor: self.rawValue, - queue: .global() - ) { error in - if error != 0 { - continuation.resume(throwing: POSIXError(.init(rawValue: error) ?? .ENODEV)) - } + internal func readUntilEOF( + upToLength maxLength: Int, + resultHandler: sending @escaping (Swift.Result) -> Void + ) { + let dispatchIO = DispatchIO( + type: .stream, + fileDescriptor: self.rawValue, + queue: .global() + ) { error in } + var buffer: DispatchData? + dispatchIO.read( + offset: 0, + length: maxLength, + queue: .global() + ) { done, data, error in + guard error == 0, let chunkData = data else { + dispatchIO.close() + resultHandler( + .failure( + SubprocessError( + code: .init(.failedToReadFromSubprocess), + underlyingError: .init(rawValue: error) + ) + ) + ) + return } - var buffer: Data = Data() - dispatchIO.read( - offset: 0, - length: maxLength, - queue: .global() - ) { done, data, error in - guard error == 0 else { - continuation.resume(throwing: POSIXError(.init(rawValue: error) ?? .ENODEV)) - return - } - if let data = data { - buffer += Data(data) - } - if done { - dispatchIO.close() - continuation.resume(returning: buffer) - } + // Easy case: if we are done and buffer is nil, this means + // there is only one chunk of data + if done && buffer == nil { + dispatchIO.close() + buffer = chunkData + resultHandler(.success(chunkData)) + return + } + + if buffer == nil { + buffer = chunkData + } else { + buffer?.append(chunkData) + } + + if done { + dispatchIO.close() + resultHandler(.success(buffer!)) + return } } } - internal func write(_ data: S) async throws where S.Element == UInt8 { - try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) -> Void in - let dispatchData: DispatchData = Array(data).withUnsafeBytes { - return DispatchData(bytes: $0) - } - DispatchIO.write( - toFileDescriptor: self.rawValue, - data: dispatchData, - runningHandlerOn: .global() - ) { _, error in - guard error == 0 else { - continuation.resume( - throwing: POSIXError( - .init(rawValue: error) ?? .ENODEV) + package func write( + _ array: [UInt8] + ) async throws -> Int { + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + let dispatchData = array.withUnsafeBytes { + return DispatchData( + bytesNoCopy: $0, + deallocator: .custom( + nil, + { + // noop + } ) - return + ) + } + self.write(dispatchData) { writtenLength, error in + if let error = error { + continuation.resume(throwing: error) + } else { + continuation.resume(returning: writtenLength) } - continuation.resume() } } } -} -extension Subprocess { - internal typealias PlatformFileDescriptor = FileDescriptor -} - -// MARK: - Read Buffer Size -extension Subprocess { - @inline(__always) - internal static var readBufferSize: Int { -#if canImport(Darwin) - return 16384 -#else - // FIXME: Use Platform.pageSize here - return 4096 -#endif // canImport(Darwin) + package func write( + _ dispatchData: DispatchData, + queue: DispatchQueue = .global(), + completion: @escaping (Int, Error?) -> Void + ) { + DispatchIO.write( + toFileDescriptor: self.rawValue, + data: dispatchData, + runningHandlerOn: queue + ) { unwritten, error in + let unwrittenLength = unwritten?.count ?? 0 + let writtenLength = dispatchData.count - unwrittenLength + guard error != 0 else { + completion(writtenLength, nil) + return + } + completion( + writtenLength, + SubprocessError( + code: .init(.failedToWriteToSubprocess), + underlyingError: .init(rawValue: error) + ) + ) + } } } -#endif // canImport(Darwin) || canImport(Glibc) +internal typealias PlatformFileDescriptor = FileDescriptor + +#endif // canImport(Darwin) || canImport(Glibc) || canImport(Bionic) || canImport(Musl) diff --git a/Sources/_Subprocess/Platforms/Subprocess+Windows.swift b/Sources/_Subprocess/Platforms/Subprocess+Windows.swift index 978cb139e..aaf53ea01 100644 --- a/Sources/_Subprocess/Platforms/Subprocess+Windows.swift +++ b/Sources/_Subprocess/Platforms/Subprocess+Windows.swift @@ -2,53 +2,66 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception // -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 +// See https://swift.org/LICENSE.txt for license information // //===----------------------------------------------------------------------===// #if canImport(WinSDK) import WinSDK -import Dispatch -import SystemPackage -import FoundationEssentials +internal import Dispatch +#if canImport(System) +import System +#else +@preconcurrency import SystemPackage +#endif // Windows specific implementation -extension Subprocess.Configuration { - internal func spawn( - withInput input: Subprocess.ExecutionInput, - output: Subprocess.ExecutionOutput, - error: Subprocess.ExecutionOutput - ) throws -> Subprocess { +extension Configuration { + internal func spawn< + Output: OutputProtocol, + Error: OutputProtocol + >( + withInput inputPipe: CreatedPipe, + output: Output, + outputPipe: CreatedPipe, + error: Error, + errorPipe: CreatedPipe + ) throws -> Execution { // Spawn differently depending on whether // we need to spawn as a user - if let userCredentials = self.platformOptions.userCredentials { - return try self.spawnAsUser( - withInput: input, - output: output, - error: error, - userCredentials: userCredentials - ) - } else { + guard let userCredentials = self.platformOptions.userCredentials else { return try self.spawnDirect( - withInput: input, + withInput: inputPipe, output: output, - error: error + outputPipe: outputPipe, + error: error, + errorPipe: errorPipe ) } + return try self.spawnAsUser( + withInput: inputPipe, + output: output, + outputPipe: outputPipe, + error: error, + errorPipe: errorPipe, + userCredentials: userCredentials + ) } - internal func spawnDirect( - withInput input: Subprocess.ExecutionInput, - output: Subprocess.ExecutionOutput, - error: Subprocess.ExecutionOutput - ) throws -> Subprocess { + internal func spawnDirect< + Output: OutputProtocol, + Error: OutputProtocol + >( + withInput inputPipe: CreatedPipe, + output: Output, + outputPipe: CreatedPipe, + error: Error, + errorPipe: CreatedPipe + ) throws -> Execution { let ( applicationName, commandAndArgs, @@ -56,9 +69,9 @@ extension Subprocess.Configuration { intendedWorkingDir ) = try self.preSpawn() var startupInfo = try self.generateStartupInfo( - withInput: input, - output: output, - error: error + withInput: inputPipe, + output: outputPipe, + error: errorPipe ) var processInfo: PROCESS_INFORMATION = PROCESS_INFORMATION() var createProcessFlags = self.generateCreateProcessFlag() @@ -78,8 +91,8 @@ extension Subprocess.Configuration { let created = CreateProcessW( applicationNameW, UnsafeMutablePointer(mutating: commandAndArgsW), - nil, // lpProcessAttributes - nil, // lpThreadAttributes + nil, // lpProcessAttributes + nil, // lpThreadAttributes true, // bInheritHandles createProcessFlags, UnsafeMutableRawPointer(mutating: environmentW), @@ -89,14 +102,14 @@ extension Subprocess.Configuration { ) guard created else { let windowsError = GetLastError() - try self.cleanupAll( - input: input, - output: output, - error: error + try self.cleanupPreSpawn( + input: inputPipe, + output: outputPipe, + error: errorPipe ) - throw CocoaError.windowsError( - underlying: windowsError, - errorCode: .fileWriteUnknown + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: windowsError) ) } } @@ -106,47 +119,52 @@ extension Subprocess.Configuration { // We don't need the handle objects, so close it right away guard CloseHandle(processInfo.hThread) else { let windowsError = GetLastError() - try self.cleanupAll( - input: input, - output: output, - error: error + try self.cleanupPreSpawn( + input: inputPipe, + output: outputPipe, + error: errorPipe ) - throw CocoaError.windowsError( - underlying: windowsError, - errorCode: .fileReadUnknown + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: windowsError) ) } guard CloseHandle(processInfo.hProcess) else { let windowsError = GetLastError() - try self.cleanupAll( - input: input, - output: output, - error: error + try self.cleanupPreSpawn( + input: inputPipe, + output: outputPipe, + error: errorPipe ) - throw CocoaError.windowsError( - underlying: windowsError, - errorCode: .fileReadUnknown + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: windowsError) ) } - let pid = Subprocess.ProcessIdentifier( - processID: processInfo.dwProcessId, - threadID: processInfo.dwThreadId + let pid = ProcessIdentifier( + value: processInfo.dwProcessId ) - return Subprocess( + return Execution( processIdentifier: pid, - executionInput: input, - executionOutput: output, - executionError: error, + output: output, + error: error, + outputPipe: outputPipe, + errorPipe: errorPipe, consoleBehavior: self.platformOptions.consoleBehavior ) } - internal func spawnAsUser( - withInput input: Subprocess.ExecutionInput, - output: Subprocess.ExecutionOutput, - error: Subprocess.ExecutionOutput, - userCredentials: Subprocess.PlatformOptions.UserCredentials - ) throws -> Subprocess { + internal func spawnAsUser< + Output: OutputProtocol, + Error: OutputProtocol + >( + withInput inputPipe: CreatedPipe, + output: Output, + outputPipe: CreatedPipe, + error: Error, + errorPipe: CreatedPipe, + userCredentials: PlatformOptions.UserCredentials + ) throws -> Execution { let ( applicationName, commandAndArgs, @@ -154,9 +172,9 @@ extension Subprocess.Configuration { intendedWorkingDir ) = try self.preSpawn() var startupInfo = try self.generateStartupInfo( - withInput: input, - output: output, - error: error + withInput: inputPipe, + output: outputPipe, + error: errorPipe ) var processInfo: PROCESS_INFORMATION = PROCESS_INFORMATION() var createProcessFlags = self.generateCreateProcessFlag() @@ -197,14 +215,14 @@ extension Subprocess.Configuration { ) guard created else { let windowsError = GetLastError() - try self.cleanupAll( - input: input, - output: output, - error: error + try self.cleanupPreSpawn( + input: inputPipe, + output: outputPipe, + error: errorPipe ) - throw CocoaError.windowsError( - underlying: windowsError, - errorCode: .fileWriteUnknown + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: windowsError) ) } } @@ -217,201 +235,175 @@ extension Subprocess.Configuration { // We don't need the handle objects, so close it right away guard CloseHandle(processInfo.hThread) else { let windowsError = GetLastError() - try self.cleanupAll( - input: input, - output: output, - error: error + try self.cleanupPreSpawn( + input: inputPipe, + output: outputPipe, + error: errorPipe ) - throw CocoaError.windowsError( - underlying: windowsError, - errorCode: .fileReadUnknown + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: windowsError) ) } guard CloseHandle(processInfo.hProcess) else { let windowsError = GetLastError() - try self.cleanupAll( - input: input, - output: output, - error: error + try self.cleanupPreSpawn( + input: inputPipe, + output: outputPipe, + error: errorPipe ) - throw CocoaError.windowsError( - underlying: windowsError, - errorCode: .fileReadUnknown + throw SubprocessError( + code: .init(.spawnFailed), + underlyingError: .init(rawValue: windowsError) ) } - let pid = Subprocess.ProcessIdentifier( - processID: processInfo.dwProcessId, - threadID: processInfo.dwThreadId + let pid = ProcessIdentifier( + value: processInfo.dwProcessId ) - return Subprocess( + return Execution( processIdentifier: pid, - executionInput: input, - executionOutput: output, - executionError: error, + output: output, + error: error, + outputPipe: outputPipe, + errorPipe: errorPipe, consoleBehavior: self.platformOptions.consoleBehavior ) } } // MARK: - Platform Specific Options -extension Subprocess { - /// The collection of platform-specific settings - /// to configure the subprocess when running - public struct PlatformOptions: Sendable { - /// A `UserCredentials` to use spawning the subprocess - /// as a different user - public struct UserCredentials: Sendable, Hashable { - // The name of the user. This is the name - // of the user account to run as. - public var username: String - // The clear-text password for the account. - public var password: String - // The name of the domain or server whose account database - // contains the account. - public var domain: String? - } - /// `ConsoleBehavior` defines how should the console appear - /// when spawning a new process - public struct ConsoleBehavior: Sendable, Hashable { - internal enum Storage: Sendable, Hashable { - case createNew - case detatch - case inherit - } +/// The collection of platform-specific settings +/// to configure the subprocess when running +public struct PlatformOptions: Sendable { + /// A `UserCredentials` to use spawning the subprocess + /// as a different user + public struct UserCredentials: Sendable, Hashable { + // The name of the user. This is the name + // of the user account to run as. + public var username: String + // The clear-text password for the account. + public var password: String + // The name of the domain or server whose account database + // contains the account. + public var domain: String? + } - internal let storage: Storage + /// `ConsoleBehavior` defines how should the console appear + /// when spawning a new process + public struct ConsoleBehavior: Sendable, Hashable { + internal enum Storage: Sendable, Hashable { + case createNew + case detatch + case inherit + } - private init(_ storage: Storage) { - self.storage = storage - } + internal let storage: Storage - /// The subprocess has a new console, instead of - /// inheriting its parent's console (the default). - public static let createNew: Self = .init(.createNew) - /// For console processes, the new process does not - /// inherit its parent's console (the default). - /// The new process can call the `AllocConsole` - /// function at a later time to create a console. - public static let detatch: Self = .init(.detatch) - /// The subprocess inherits its parent's console. - public static let inherit: Self = .init(.inherit) + private init(_ storage: Storage) { + self.storage = storage } - /// `ConsoleBehavior` defines how should the window appear - /// when spawning a new process - public struct WindowStyle: Sendable, Hashable { - internal enum Storage: Sendable, Hashable { - case normal - case hidden - case maximized - case minimized - } + /// The subprocess has a new console, instead of + /// inheriting its parent's console (the default). + public static let createNew: Self = .init(.createNew) + /// For console processes, the new process does not + /// inherit its parent's console (the default). + /// The new process can call the `AllocConsole` + /// function at a later time to create a console. + public static let detatch: Self = .init(.detatch) + /// The subprocess inherits its parent's console. + public static let inherit: Self = .init(.inherit) + } - internal let storage: Storage + /// `ConsoleBehavior` defines how should the window appear + /// when spawning a new process + public struct WindowStyle: Sendable, Hashable { + internal enum Storage: Sendable, Hashable { + case normal + case hidden + case maximized + case minimized + } - internal var platformStyle: WORD { - switch self.storage { - case .hidden: return WORD(SW_HIDE) - case .maximized: return WORD(SW_SHOWMAXIMIZED) - case .minimized: return WORD(SW_SHOWMINIMIZED) - default: return WORD(SW_SHOWNORMAL) - } - } + internal let storage: Storage - private init(_ storage: Storage) { - self.storage = storage + internal var platformStyle: WORD { + switch self.storage { + case .hidden: return WORD(SW_HIDE) + case .maximized: return WORD(SW_SHOWMAXIMIZED) + case .minimized: return WORD(SW_SHOWMINIMIZED) + default: return WORD(SW_SHOWNORMAL) } + } - /// Activates and displays a window of normal size - public static let normal: Self = .init(.normal) - /// Does not activate a new window - public static let hidden: Self = .init(.hidden) - /// Activates the window and displays it as a maximized window. - public static let maximized: Self = .init(.maximized) - /// Activates the window and displays it as a minimized window. - public static let minimized: Self = .init(.minimized) + private init(_ storage: Storage) { + self.storage = storage } - /// Sets user credentials when starting the process as another user - public var userCredentials: UserCredentials? = nil - /// The console behavior of the new process, - /// default to inheriting the console from parent process - public var consoleBehavior: ConsoleBehavior = .inherit - /// Window style to use when the process is started - public var windowStyle: WindowStyle = .normal - /// Whether to create a new process group for the new - /// process. The process group includes all processes - /// that are descendants of this root process. - /// The process identifier of the new process group - /// is the same as the process identifier. - public var createProcessGroup: Bool = false - /// A closure to configure platform-specific - /// spawning constructs. This closure enables direct - /// configuration or override of underlying platform-specific - /// spawn settings that `Subprocess` utilizes internally, - /// in cases where Subprocess does not provide higher-level - /// APIs for such modifications. - /// - /// On Windows, Subprocess uses `CreateProcessW()` as the - /// underlying spawning mechanism. This closure allows - /// modification of the `dwCreationFlags` creation flag - /// and startup info `STARTUPINFOW` before - /// they are sent to `CreateProcessW()`. - public var preSpawnProcessConfigurator: ( + /// Activates and displays a window of normal size + public static let normal: Self = .init(.normal) + /// Does not activate a new window + public static let hidden: Self = .init(.hidden) + /// Activates the window and displays it as a maximized window. + public static let maximized: Self = .init(.maximized) + /// Activates the window and displays it as a minimized window. + public static let minimized: Self = .init(.minimized) + } + + /// Sets user credentials when starting the process as another user + public var userCredentials: UserCredentials? = nil + /// The console behavior of the new process, + /// default to inheriting the console from parent process + public var consoleBehavior: ConsoleBehavior = .inherit + /// Window style to use when the process is started + public var windowStyle: WindowStyle = .normal + /// Whether to create a new process group for the new + /// process. The process group includes all processes + /// that are descendants of this root process. + /// The process identifier of the new process group + /// is the same as the process identifier. + public var createProcessGroup: Bool = false + /// An ordered list of steps in order to tear down the child + /// process in case the parent task is cancelled before + /// the child proces terminates. + /// Always ends in forcefully terminate at the end. + public var teardownSequence: [TeardownStep] = [] + /// A closure to configure platform-specific + /// spawning constructs. This closure enables direct + /// configuration or override of underlying platform-specific + /// spawn settings that `Subprocess` utilizes internally, + /// in cases where Subprocess does not provide higher-level + /// APIs for such modifications. + /// + /// On Windows, Subprocess uses `CreateProcessW()` as the + /// underlying spawning mechanism. This closure allows + /// modification of the `dwCreationFlags` creation flag + /// and startup info `STARTUPINFOW` before + /// they are sent to `CreateProcessW()`. + public var preSpawnProcessConfigurator: + ( @Sendable ( inout DWORD, inout STARTUPINFOW ) throws -> Void )? = nil - public init() {} - } -} - -extension Subprocess.PlatformOptions: Hashable { - public static func == ( - lhs: Subprocess.PlatformOptions, - rhs: Subprocess.PlatformOptions - ) -> Bool { - // Since we can't compare closure equality, - // as long as preSpawnProcessConfigurator is set - // always returns false so that `PlatformOptions` - // with it set will never equal to each other - if lhs.preSpawnProcessConfigurator != nil || rhs.preSpawnProcessConfigurator != nil { - return false - } - return lhs.userCredentials == rhs.userCredentials && lhs.consoleBehavior == rhs.consoleBehavior && lhs.windowStyle == rhs.windowStyle && - lhs.createProcessGroup == rhs.createProcessGroup - } - - public func hash(into hasher: inout Hasher) { - hasher.combine(userCredentials) - hasher.combine(consoleBehavior) - hasher.combine(windowStyle) - hasher.combine(createProcessGroup) - // Since we can't really hash closures, - // use an UUID such that as long as - // `preSpawnProcessConfigurator` is set, it will - // never equal to other PlatformOptions - if self.preSpawnProcessConfigurator != nil { - hasher.combine(UUID()) - } - } + public init() {} } -extension Subprocess.PlatformOptions : CustomStringConvertible, CustomDebugStringConvertible { +extension PlatformOptions: CustomStringConvertible, CustomDebugStringConvertible { internal func description(withIndent indent: Int) -> String { let indent = String(repeating: " ", count: indent * 4) return """ -PlatformOptions( -\(indent) userCredentials: \(String(describing: self.userCredentials)), -\(indent) consoleBehavior: \(String(describing: self.consoleBehavior)), -\(indent) windowStyle: \(String(describing: self.windowStyle)), -\(indent) createProcessGroup: \(self.createProcessGroup), -\(indent) preSpawnProcessConfigurator: \(self.preSpawnProcessConfigurator == nil ? "not set" : "set") -\(indent)) -""" + PlatformOptions( + \(indent) userCredentials: \(String(describing: self.userCredentials)), + \(indent) consoleBehavior: \(String(describing: self.consoleBehavior)), + \(indent) windowStyle: \(String(describing: self.windowStyle)), + \(indent) createProcessGroup: \(self.createProcessGroup), + \(indent) preSpawnProcessConfigurator: \(self.preSpawnProcessConfigurator == nil ? "not set" : "set") + \(indent)) + """ } public var description: String { @@ -426,8 +418,8 @@ PlatformOptions( // MARK: - Process Monitoring @Sendable internal func monitorProcessTermination( - forProcessWithIdentifier pid: Subprocess.ProcessIdentifier -) async throws -> Subprocess.TerminationStatus { + forProcessWithIdentifier pid: ProcessIdentifier +) async throws -> TerminationStatus { // Once the continuation resumes, it will need to unregister the wait, so // yield the wait handle back to the calling scope. var waitHandle: HANDLE? @@ -436,11 +428,13 @@ internal func monitorProcessTermination( _ = UnregisterWait(waitHandle) } } - guard let processHandle = OpenProcess( - DWORD(PROCESS_QUERY_INFORMATION | SYNCHRONIZE), - false, - pid.processID - ) else { + guard + let processHandle = OpenProcess( + DWORD(PROCESS_QUERY_INFORMATION | SYNCHRONIZE), + false, + pid.value + ) + else { return .exited(1) } @@ -449,19 +443,29 @@ internal func monitorProcessTermination( // other work. let context = Unmanaged.passRetained(continuation as AnyObject).toOpaque() let callback: WAITORTIMERCALLBACK = { context, _ in - let continuation = Unmanaged.fromOpaque(context!).takeRetainedValue() as! CheckedContinuation + let continuation = + Unmanaged.fromOpaque(context!).takeRetainedValue() as! CheckedContinuation continuation.resume() } // We only want the callback to fire once (and not be rescheduled.) Waiting // may take an arbitrarily long time, so let the thread pool know that too. let flags = ULONG(WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION) - guard RegisterWaitForSingleObject( - &waitHandle, processHandle, callback, context, INFINITE, flags - ) else { - continuation.resume(throwing: CocoaError.windowsError( - underlying: GetLastError(), - errorCode: .fileWriteUnknown) + guard + RegisterWaitForSingleObject( + &waitHandle, + processHandle, + callback, + context, + INFINITE, + flags + ) + else { + continuation.resume( + throwing: SubprocessError( + code: .init(.failedToMonitorProcess), + underlyingError: .init(rawValue: GetLastError()) + ) ) return } @@ -474,58 +478,62 @@ internal func monitorProcessTermination( return .exited(1) } let exitCodeValue = CInt(bitPattern: .init(status)) - if exitCodeValue >= 0 { - return .exited(status) - } else { + guard exitCodeValue >= 0 else { return .unhandledException(status) } + return .exited(status) } // MARK: - Subprocess Control -extension Subprocess { +@available(macOS 15.0, *) // FIXME: manually added availability +extension Execution { /// Terminate the current subprocess with the given exit code /// - Parameter exitCode: The exit code to use for the subprocess. public func terminate(withExitCode exitCode: DWORD) throws { - guard let processHandle = OpenProcess( - // PROCESS_ALL_ACCESS - DWORD(STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFFF), - false, - self.processIdentifier.processID - ) else { - throw CocoaError.windowsError( - underlying: GetLastError(), - errorCode: .fileWriteUnknown + guard + let processHandle = OpenProcess( + // PROCESS_ALL_ACCESS + DWORD(STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFFF), + false, + self.processIdentifier.value + ) + else { + throw SubprocessError( + code: .init(.failedToTerminate), + underlyingError: .init(rawValue: GetLastError()) ) } defer { CloseHandle(processHandle) } guard TerminateProcess(processHandle, exitCode) else { - throw CocoaError.windowsError( - underlying: GetLastError(), - errorCode: .fileWriteUnknown + throw SubprocessError( + code: .init(.failedToTerminate), + underlyingError: .init(rawValue: GetLastError()) ) } } /// Suspend the current subprocess public func suspend() throws { - guard let processHandle = OpenProcess( - // PROCESS_ALL_ACCESS - DWORD(STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFFF), - false, - self.processIdentifier.processID - ) else { - throw CocoaError.windowsError( - underlying: GetLastError(), - errorCode: .fileWriteUnknown + guard + let processHandle = OpenProcess( + // PROCESS_ALL_ACCESS + DWORD(STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFFF), + false, + self.processIdentifier.value + ) + else { + throw SubprocessError( + code: .init(.failedToSuspend), + underlyingError: .init(rawValue: GetLastError()) ) } defer { CloseHandle(processHandle) } - let NTSuspendProcess: Optional<(@convention(c) (HANDLE) -> LONG)> = + let NTSuspendProcess: (@convention(c) (HANDLE) -> LONG)? = unsafeBitCast( GetProcAddress( GetModuleHandleA("ntdll.dll"), @@ -534,53 +542,61 @@ extension Subprocess { to: Optional<(@convention(c) (HANDLE) -> LONG)>.self ) guard let NTSuspendProcess = NTSuspendProcess else { - throw CocoaError(.executableNotLoadable) + throw SubprocessError( + code: .init(.failedToSuspend), + underlyingError: .init(rawValue: GetLastError()) + ) } guard NTSuspendProcess(processHandle) >= 0 else { - throw CocoaError.windowsError( - underlying: GetLastError(), - errorCode: .fileWriteUnknown + throw SubprocessError( + code: .init(.failedToSuspend), + underlyingError: .init(rawValue: GetLastError()) ) } } /// Resume the current subprocess after suspension public func resume() throws { - guard let processHandle = OpenProcess( - // PROCESS_ALL_ACCESS - DWORD(STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFFF), - false, - self.processIdentifier.processID - ) else { - throw CocoaError.windowsError( - underlying: GetLastError(), - errorCode: .fileWriteUnknown + guard + let processHandle = OpenProcess( + // PROCESS_ALL_ACCESS + DWORD(STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFFF), + false, + self.processIdentifier.value + ) + else { + throw SubprocessError( + code: .init(.failedToResume), + underlyingError: .init(rawValue: GetLastError()) ) } defer { CloseHandle(processHandle) } - let NTResumeProcess: Optional<(@convention(c) (HANDLE) -> LONG)> = - unsafeBitCast( - GetProcAddress( - GetModuleHandleA("ntdll.dll"), - "NtResumeProcess" - ), - to: Optional<(@convention(c) (HANDLE) -> LONG)>.self - ) + let NTResumeProcess: (@convention(c) (HANDLE) -> LONG)? = + unsafeBitCast( + GetProcAddress( + GetModuleHandleA("ntdll.dll"), + "NtResumeProcess" + ), + to: Optional<(@convention(c) (HANDLE) -> LONG)>.self + ) guard let NTResumeProcess = NTResumeProcess else { - throw CocoaError(.executableNotLoadable) + throw SubprocessError( + code: .init(.failedToResume), + underlyingError: .init(rawValue: GetLastError()) + ) } guard NTResumeProcess(processHandle) >= 0 else { - throw CocoaError.windowsError( - underlying: GetLastError(), - errorCode: .fileWriteUnknown + throw SubprocessError( + code: .init(.failedToResume), + underlyingError: .init(rawValue: GetLastError()) ) } } - internal func tryTerminate() -> Error? { + internal func tryTerminate() -> Swift.Error? { do { try self.terminate(withExitCode: 0) } catch { @@ -591,35 +607,44 @@ extension Subprocess { } // MARK: - Executable Searching -extension Subprocess.Executable { +extension Executable { // Technically not needed for CreateProcess since // it takes process name. It's here to support // Executable.resolveExecutablePath - internal func resolveExecutablePath(withPathValue pathValue: String?) -> String? { + internal func resolveExecutablePath(withPathValue pathValue: String?) throws -> String { switch self.storage { case .executable(let executableName): - return executableName.withCString( + return try executableName.withCString( encodedAs: UTF16.self - ) { exeName -> String? in - return pathValue.withOptionalCString( + ) { exeName -> String in + return try pathValue.withOptionalCString( encodedAs: UTF16.self - ) { path -> String? in + ) { path -> String in let pathLenth = SearchPathW( path, exeName, - nil, 0, nil, nil + nil, + 0, + nil, + nil ) guard pathLenth > 0 else { - return nil + throw SubprocessError( + code: .init(.executableNotFound(executableName)), + underlyingError: .init(rawValue: GetLastError()) + ) } return withUnsafeTemporaryAllocation( - of: WCHAR.self, capacity: Int(pathLenth) + 1 + of: WCHAR.self, + capacity: Int(pathLenth) + 1 ) { _ = SearchPathW( path, - exeName, nil, + exeName, + nil, pathLenth + 1, - $0.baseAddress, nil + $0.baseAddress, + nil ) return String(decodingCString: $0.baseAddress!, as: UTF16.self) } @@ -633,49 +658,60 @@ extension Subprocess.Executable { } // MARK: - Environment Resolution -extension Subprocess.Environment { - internal static let pathEnvironmentVariableName = "Path" +extension Environment { + internal static let pathVariableName = "Path" internal func pathValue() -> String? { switch self.config { case .inherit(let overrides): // If PATH value exists in overrides, use it - if let value = overrides[.string(Self.pathEnvironmentVariableName)] { - return value.stringValue + if let value = overrides[Self.pathVariableName] { + return value } // Fall back to current process - return ProcessInfo.processInfo.environment[Self.pathEnvironmentVariableName] + return Self.currentEnvironmentValues()[Self.pathVariableName] case .custom(let fullEnvironment): - if let value = fullEnvironment[.string(Self.pathEnvironmentVariableName)] { - return value.stringValue + if let value = fullEnvironment[Self.pathVariableName] { + return value } return nil } } + + internal static func withCopiedEnv(_ body: ([UnsafeMutablePointer]) -> R) -> R { + var values: [UnsafeMutablePointer] = [] + guard let pwszEnvironmentBlock = GetEnvironmentStringsW() else { + return body([]) + } + defer { FreeEnvironmentStringsW(pwszEnvironmentBlock) } + + var pwszEnvironmentEntry: LPWCH? = pwszEnvironmentBlock + while let value = pwszEnvironmentEntry { + let entry = String(decodingCString: value, as: UTF16.self) + if entry.isEmpty { break } + values.append(entry.withCString { _strdup($0)! }) + pwszEnvironmentEntry = pwszEnvironmentEntry?.advanced(by: wcslen(value) + 1) + } + defer { values.forEach { free($0) } } + return body(values) + } } // MARK: - ProcessIdentifier -extension Subprocess { - /// A platform independent identifier for a subprocess. - public struct ProcessIdentifier: Sendable, Hashable, Codable { - /// Windows specifc process identifier value - public let processID: DWORD - /// Windows specific thread identifier associated with process - public let threadID: DWORD - - internal init( - processID: DWORD, - threadID: DWORD - ) { - self.processID = processID - self.threadID = threadID - } + +/// A platform independent identifier for a subprocess. +public struct ProcessIdentifier: Sendable, Hashable, Codable { + /// Windows specifc process identifier value + public let value: DWORD + + internal init(value: DWORD) { + self.value = value } } -extension Subprocess.ProcessIdentifier: CustomStringConvertible, CustomDebugStringConvertible { +extension ProcessIdentifier: CustomStringConvertible, CustomDebugStringConvertible { public var description: String { - return "(processID: \(self.processID), threadID: \(self.threadID))" + return "(processID: \(self.value))" } public var debugDescription: String { @@ -684,7 +720,7 @@ extension Subprocess.ProcessIdentifier: CustomStringConvertible, CustomDebugStri } // MARK: - Private Utils -extension Subprocess.Configuration { +extension Configuration { private func preSpawn() throws -> ( applicationName: String?, commandAndArgs: String, @@ -692,44 +728,33 @@ extension Subprocess.Configuration { intendedWorkingDir: String ) { // Prepare environment - var env: [String : String] = [:] + var env: [String: String] = [:] switch self.environment.config { case .custom(let customValues): // Use the custom values directly - for customKey in customValues.keys { - guard case .string(let stringKey) = customKey, - let valueContainer = customValues[customKey], - case .string(let stringValue) = valueContainer else { - fatalError("Windows does not support non unicode String as environments") - } - env.updateValue(stringValue, forKey: stringKey) - } + env = customValues case .inherit(let updateValues): // Combine current environment - env = ProcessInfo.processInfo.environment - for updatingKey in updateValues.keys { - // Override the current environment values - guard case .string(let stringKey) = updatingKey, - let valueContainer = updateValues[updatingKey], - case .string(let stringValue) = valueContainer else { - fatalError("Windows does not support non unicode String as environments") - } - env.updateValue(stringValue, forKey: stringKey) + env = Environment.currentEnvironmentValues() + for (key, value) in updateValues { + env.updateValue(value, forKey: key) } } // On Windows, the PATH is required in order to locate dlls needed by // the process so we should also pass that to the child - let pathVariableName = Subprocess.Environment.pathEnvironmentVariableName + let pathVariableName = Environment.pathVariableName if env[pathVariableName] == nil, - let parentPath = ProcessInfo.processInfo.environment[pathVariableName] { + let parentPath = Environment.currentEnvironmentValues()[pathVariableName] + { env[pathVariableName] = parentPath } // The environment string must be terminated by a double // null-terminator. Otherwise, CreateProcess will fail with // INVALID_PARMETER. - let environmentString = env.map { - $0.key + "=" + $0.value - }.joined(separator: "\0") + "\0\0" + let environmentString = + env.map { + $0.key + "=" + $0.value + }.joined(separator: "\0") + "\0\0" // Prepare arguments let ( @@ -738,9 +763,12 @@ extension Subprocess.Configuration { ) = try self.generateWindowsCommandAndAgruments() // Validate workingDir guard Self.pathAccessible(self.workingDirectory.string) else { - throw CocoaError(.fileNoSuchFile, userInfo: [ - .debugDescriptionErrorKey : "Failed to set working directory to \(self.workingDirectory)" - ]) + throw SubprocessError( + code: .init( + .failedToChangeWorkingDirectory(self.workingDirectory.string) + ), + underlyingError: nil + ) } return ( applicationName: applicationName, @@ -767,9 +795,9 @@ extension Subprocess.Configuration { } private func generateStartupInfo( - withInput input: Subprocess.ExecutionInput, - output: Subprocess.ExecutionOutput, - error: Subprocess.ExecutionOutput + withInput input: CreatedPipe, + output: CreatedPipe, + error: CreatedPipe ) throws -> STARTUPINFOW { var info: STARTUPINFOW = STARTUPINFOW() info.cb = DWORD(MemoryLayout.size) @@ -781,10 +809,10 @@ extension Subprocess.Configuration { } // Bind IOs // Input - if let inputRead = input.getReadFileDescriptor() { + if let inputRead = input.readFileDescriptor { info.hStdInput = inputRead.platformDescriptor } - if let inputWrite = input.getWriteFileDescriptor() { + if let inputWrite = input.writeFileDescriptor { // Set parent side to be uninhertable SetHandleInformation( inputWrite.platformDescriptor, @@ -793,10 +821,10 @@ extension Subprocess.Configuration { ) } // Output - if let outputWrite = output.getWriteFileDescriptor() { + if let outputWrite = output.writeFileDescriptor { info.hStdOutput = outputWrite.platformDescriptor } - if let outputRead = output.getReadFileDescriptor() { + if let outputRead = output.readFileDescriptor { // Set parent side to be uninhertable SetHandleInformation( outputRead.platformDescriptor, @@ -805,10 +833,10 @@ extension Subprocess.Configuration { ) } // Error - if let errorWrite = error.getWriteFileDescriptor() { + if let errorWrite = error.writeFileDescriptor { info.hStdError = errorWrite.platformDescriptor } - if let errorRead = error.getReadFileDescriptor() { + if let errorRead = error.readFileDescriptor { // Set parent side to be uninhertable SetHandleInformation( errorRead.platformDescriptor, @@ -834,12 +862,12 @@ extension Subprocess.Configuration { // actually resolve the path. However, to maintain // the same behavior as other platforms, still check // here to make sure the executable actually exists - guard self.executable.resolveExecutablePath( - withPathValue: self.environment.pathValue() - ) != nil else { - throw CocoaError(.executableNotLoadable, userInfo: [ - .debugDescriptionErrorKey : "\(self.executable.description) is not an executable" - ]) + do { + _ = try self.executable.resolveExecutablePath( + withPathValue: self.environment.pathValue() + ) + } catch { + throw error } executableNameOrPath = name } @@ -876,7 +904,7 @@ extension Subprocess.Configuration { func quoteWindowsCommandArg(arg: String) -> String { // Windows escaping, adapted from Daniel Colascione's "Everyone quotes // command line arguments the wrong way" - Microsoft Developer Blog - if !arg.contains(where: {" \t\n\"".contains($0)}) { + if !arg.contains(where: { " \t\n\"".contains($0) }) { return arg } @@ -911,7 +939,7 @@ extension Subprocess.Configuration { break } let backslashCount = unquoted.distance(from: unquoted.startIndex, to: firstNonBackslash) - if (unquoted[firstNonBackslash] == "\"") { + if unquoted[firstNonBackslash] == "\"" { // This is a string of \ followed by a " e.g. foo\"bar. Escape the // backslashes and the quote quoted.append(String(repeating: "\\", count: backslashCount * 2 + 1)) @@ -939,20 +967,7 @@ extension Subprocess.Configuration { } // MARK: - PlatformFileDescriptor Type -extension Subprocess { - internal typealias PlatformFileDescriptor = HANDLE -} - -// MARK: - Read Buffer Size -extension Subprocess { - @inline(__always) - internal static var readBufferSize: Int { - // FIXME: Use Platform.pageSize here - var sysInfo: SYSTEM_INFO = SYSTEM_INFO() - GetSystemInfo(&sysInfo) - return Int(sysInfo.dwPageSize) - } -} +internal typealias PlatformFileDescriptor = HANDLE // MARK: - Pipe Support extension FileDescriptor { @@ -968,13 +983,14 @@ extension FileDescriptor { var readHandle: HANDLE? = nil var writeHandle: HANDLE? = nil guard CreatePipe(&readHandle, &writeHandle, &saAttributes, 0), - readHandle != INVALID_HANDLE_VALUE, - writeHandle != INVALID_HANDLE_VALUE, - let readHandle: HANDLE = readHandle, - let writeHandle: HANDLE = writeHandle else { - throw CocoaError.windowsError( - underlying: GetLastError(), - errorCode: .fileReadUnknown + readHandle != INVALID_HANDLE_VALUE, + writeHandle != INVALID_HANDLE_VALUE, + let readHandle: HANDLE = readHandle, + let writeHandle: HANDLE = writeHandle + else { + throw SubprocessError( + code: .init(.failedToCreatePipe), + underlyingError: .init(rawValue: GetLastError()) ) } let readFd = _open_osfhandle( @@ -992,147 +1008,138 @@ extension FileDescriptor { ) } - internal static func openDevNull( - withAcessMode mode: FileDescriptor.AccessMode - ) throws -> FileDescriptor { - return try "NUL".withPlatformString { - let handle = CreateFileW( - $0, - DWORD(GENERIC_WRITE), - DWORD(FILE_SHARE_WRITE), - nil, - DWORD(OPEN_EXISTING), - DWORD(FILE_ATTRIBUTE_NORMAL), - nil - ) - guard let handle = handle, - handle != INVALID_HANDLE_VALUE else { - throw CocoaError.windowsError( - underlying: GetLastError(), - errorCode: .fileReadUnknown - ) - } - let devnull = _open_osfhandle( - intptr_t(bitPattern: handle), - mode.rawValue - ) - return FileDescriptor(rawValue: devnull) - } - } - - var platformDescriptor: Subprocess.PlatformFileDescriptor { + var platformDescriptor: PlatformFileDescriptor { return HANDLE(bitPattern: _get_osfhandle(self.rawValue))! } - internal func read(upToLength maxLength: Int) async throws -> Data { - // TODO: Figure out a better way to asynchornously read + internal func readChunk(upToLength maxLength: Int) async throws -> SequenceOutput.Buffer? { return try await withCheckedThrowingContinuation { continuation in - DispatchQueue.global(qos: .userInitiated).async { - var totalBytesRead: Int = 0 - var lastError: DWORD? = nil - let values = Array( - unsafeUninitializedCapacity: maxLength - ) { buffer, initializedCount in - while true { - guard let baseAddress = buffer.baseAddress else { - initializedCount = 0 - break - } - let bufferPtr = baseAddress.advanced(by: totalBytesRead) - var bytesRead: DWORD = 0 - let readSucceed = ReadFile( - self.platformDescriptor, - UnsafeMutableRawPointer(mutating: bufferPtr), - DWORD(maxLength - totalBytesRead), - &bytesRead, - nil - ) - if !readSucceed { - // Windows throws ERROR_BROKEN_PIPE when the pipe is closed - let error = GetLastError() - if error == ERROR_BROKEN_PIPE { - // We are done reading - initializedCount = totalBytesRead - } else { - // We got some error - lastError = error - initializedCount = 0 - } - break - } else { - // We succesfully read the current round - totalBytesRead += Int(bytesRead) - } - - if totalBytesRead >= maxLength { - initializedCount = min(maxLength, totalBytesRead) - break - } - } - } - if let lastError = lastError { - continuation.resume(throwing: CocoaError.windowsError( - underlying: lastError, - errorCode: .fileReadUnknown) - ) - } else { - continuation.resume(returning: Data(values)) + self.readUntilEOF( + upToLength: maxLength + ) { result in + switch result { + case .failure(let error): + continuation.resume(throwing: error) + case .success(let bytes): + continuation.resume(returning: SequenceOutput.Buffer(data: bytes)) } } } } - internal func write(_ data: S) async throws where S.Element == UInt8 { - // TODO: Figure out a better way to asynchornously write - try await withCheckedThrowingContinuation { ( - continuation: CheckedContinuation - ) -> Void in - DispatchQueue.global(qos: .userInitiated).async { - let buffer = Array(data) - buffer.withUnsafeBytes { ptr in - var writtenBytes: DWORD = 0 - let writeSucceed = WriteFile( + internal func readUntilEOF( + upToLength maxLength: Int, + resultHandler: @Sendable @escaping (Swift.Result<[UInt8], any (Error & Sendable)>) -> Void + ) { + DispatchQueue.global(qos: .userInitiated).async { + var totalBytesRead: Int = 0 + var lastError: DWORD? = nil + let values = [UInt8]( + unsafeUninitializedCapacity: maxLength + ) { buffer, initializedCount in + while true { + guard let baseAddress = buffer.baseAddress else { + initializedCount = 0 + break + } + let bufferPtr = baseAddress.advanced(by: totalBytesRead) + var bytesRead: DWORD = 0 + let readSucceed = ReadFile( self.platformDescriptor, - ptr.baseAddress, - DWORD(buffer.count), - &writtenBytes, + UnsafeMutableRawPointer(mutating: bufferPtr), + DWORD(maxLength - totalBytesRead), + &bytesRead, nil ) - if !writeSucceed { - continuation.resume(throwing: CocoaError.windowsError( - underlying: GetLastError(), - errorCode: .fileWriteUnknown) - ) + if !readSucceed { + // Windows throws ERROR_BROKEN_PIPE when the pipe is closed + let error = GetLastError() + if error == ERROR_BROKEN_PIPE { + // We are done reading + initializedCount = totalBytesRead + } else { + // We got some error + lastError = error + initializedCount = 0 + } + break } else { - continuation.resume() + // We succesfully read the current round + totalBytesRead += Int(bytesRead) + } + + if totalBytesRead >= maxLength { + initializedCount = min(maxLength, totalBytesRead) + break } } } + if let lastError = lastError { + let windowsError = SubprocessError( + code: .init(.failedToReadFromSubprocess), + underlyingError: .init(rawValue: lastError) + ) + resultHandler(.failure(windowsError)) + } else { + resultHandler(.success(values)) + } } } -} - -extension String { - static let debugDescriptionErrorKey = "DebugDescription" -} -// MARK: - CocoaError + Win32 -internal let NSUnderlyingErrorKey = "NSUnderlyingError" + internal func write( + _ array: [UInt8] + ) async throws -> Int { + try await withCheckedThrowingContinuation { continuation in + // TODO: Figure out a better way to asynchornously write + DispatchQueue.global(qos: .userInitiated).async { + array.withUnsafeBytes { + self.write($0) { writtenLength, error in + if let error = error { + continuation.resume(throwing: error) + } else { + continuation.resume(returning: writtenLength) + } + } + } + } + } + } -extension CocoaError { - static func windowsError(underlying: DWORD, errorCode: Code) -> CocoaError { - let userInfo = [ - NSUnderlyingErrorKey : Win32Error(underlying) - ] - return CocoaError(errorCode, userInfo: userInfo) + package func write( + _ ptr: UnsafeRawBufferPointer, + completion: @escaping (Int, Swift.Error?) -> Void + ) { + func _write( + _ ptr: UnsafeRawBufferPointer, + count: Int, + completion: @escaping (Int, Swift.Error?) -> Void + ) { + var writtenBytes: DWORD = 0 + let writeSucceed = WriteFile( + self.platformDescriptor, + ptr.baseAddress, + DWORD(count), + &writtenBytes, + nil + ) + if !writeSucceed { + let error = SubprocessError( + code: .init(.failedToWriteToSubprocess), + underlyingError: .init(rawValue: GetLastError()) + ) + completion(Int(writtenBytes), error) + } else { + completion(Int(writtenBytes), nil) + } + } } } -private extension Optional where Wrapped == String { - func withOptionalCString( +extension Optional where Wrapped == String { + fileprivate func withOptionalCString( encodedAs targetEncoding: Encoding.Type, _ body: (UnsafePointer?) throws -> Result - ) rethrows -> Result where Encoding : _UnicodeEncoding { + ) rethrows -> Result where Encoding: _UnicodeEncoding { switch self { case .none: return try body(nil) @@ -1141,7 +1148,7 @@ private extension Optional where Wrapped == String { } } - func withOptionalNTPathRepresentation( + fileprivate func withOptionalNTPathRepresentation( _ body: (UnsafePointer?) throws -> Result ) throws -> Result { switch self { @@ -1159,23 +1166,30 @@ extension String { _ body: (UnsafePointer) throws -> Result ) throws -> Result { guard !isEmpty else { - throw CocoaError(.fileReadInvalidFileName) + throw SubprocessError( + code: .init(.invalidWindowsPath(self)), + underlyingError: nil + ) } var iter = self.utf8.makeIterator() - let bLeadingSlash = if [._slash, ._backslash].contains(iter.next()), iter.next()?.isLetter ?? false, iter.next() == ._colon { true } else { false } + let bLeadingSlash = + if [._slash, ._backslash].contains(iter.next()), iter.next()?.isLetter ?? false, iter.next() == ._colon { + true + } else { false } // Strip the leading `/` on a RFC8089 path (`/[drive-letter]:/...` ). A // leading slash indicates a rooted path on the drive for the current // working directory. - return try Substring(self.utf8.dropFirst(bLeadingSlash ? 1 : 0)).withCString(encodedAs: UTF16.self) { pwszPath in + return try Substring(self.utf8.dropFirst(bLeadingSlash ? 1 : 0)).withCString(encodedAs: UTF16.self) { + pwszPath in // 1. Normalize the path first. let dwLength: DWORD = GetFullPathNameW(pwszPath, 0, nil, nil) return try withUnsafeTemporaryAllocation(of: WCHAR.self, capacity: Int(dwLength)) { guard GetFullPathNameW(pwszPath, DWORD($0.count), $0.baseAddress, nil) > 0 else { - throw CocoaError.windowsError( - underlying: GetLastError(), - errorCode: .fileReadUnknown + throw SubprocessError( + code: .init(.invalidWindowsPath(self)), + underlyingError: .init(rawValue: GetLastError()) ) } @@ -1186,27 +1200,23 @@ extension String { } } -struct Win32Error: Error { - public typealias Code = DWORD - public let code: Code - - public static var errorDomain: String { - return "NSWin32ErrorDomain" - } - - public init(_ code: Code) { - self.code = code - } -} - -internal extension UInt8 { +extension UInt8 { static var _slash: UInt8 { UInt8(ascii: "/") } static var _backslash: UInt8 { UInt8(ascii: "\\") } static var _colon: UInt8 { UInt8(ascii: ":") } var isLetter: Bool? { - return (0x41 ... 0x5a) ~= self || (0x61 ... 0x7a) ~= self + return (0x41...0x5a) ~= self || (0x61...0x7a) ~= self + } +} + +extension OutputProtocol { + internal func output(from data: [UInt8]) throws -> OutputType { + return try data.withUnsafeBytes { + let span = RawSpan(_unsafeBytes: $0) + return try self.output(from: span) + } } } -#endif // canImport(WinSDK) +#endif // canImport(WinSDK) diff --git a/Sources/_Subprocess/Result.swift b/Sources/_Subprocess/Result.swift new file mode 100644 index 000000000..e1f798940 --- /dev/null +++ b/Sources/_Subprocess/Result.swift @@ -0,0 +1,123 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#if canImport(System) +import System +#else +@preconcurrency import SystemPackage +#endif + +// MARK: - Result + +/// A simple wrapper around the generic result returned by the +/// `run` closures with the corresponding `TerminationStatus` +/// of the child process. +public struct ExecutionResult { + /// The termination status of the child process + public let terminationStatus: TerminationStatus + /// The result returned by the closure passed to `.run` methods + public let value: Result + + internal init(terminationStatus: TerminationStatus, value: Result) { + self.terminationStatus = terminationStatus + self.value = value + } +} + +/// The result of a subprocess execution with its collected +/// standard output and standard error. +public struct CollectedResult< + Output: OutputProtocol, + Error: OutputProtocol +>: Sendable { + /// The process identifier for the executed subprocess + public let processIdentifier: ProcessIdentifier + /// The termination status of the executed subprocess + public let terminationStatus: TerminationStatus + public let standardOutput: Output.OutputType + public let standardError: Error.OutputType + + internal init( + processIdentifier: ProcessIdentifier, + terminationStatus: TerminationStatus, + standardOutput: Output.OutputType, + standardError: Error.OutputType + ) { + self.processIdentifier = processIdentifier + self.terminationStatus = terminationStatus + self.standardOutput = standardOutput + self.standardError = standardError + } +} + +// MARK: - CollectedResult Conformances +extension CollectedResult: Equatable where Output.OutputType: Equatable, Error.OutputType: Equatable {} + +extension CollectedResult: Hashable where Output.OutputType: Hashable, Error.OutputType: Hashable {} + +extension CollectedResult: Codable where Output.OutputType: Codable, Error.OutputType: Codable {} + +extension CollectedResult: CustomStringConvertible +where Output.OutputType: CustomStringConvertible, Error.OutputType: CustomStringConvertible { + public var description: String { + return """ + CollectedResult( + processIdentifier: \(self.processIdentifier), + terminationStatus: \(self.terminationStatus.description), + standardOutput: \(self.standardOutput.description) + standardError: \(self.standardError.description) + ) + """ + } +} + +extension CollectedResult: CustomDebugStringConvertible +where Output.OutputType: CustomDebugStringConvertible, Error.OutputType: CustomDebugStringConvertible { + public var debugDescription: String { + return """ + CollectedResult( + processIdentifier: \(self.processIdentifier), + terminationStatus: \(self.terminationStatus.description), + standardOutput: \(self.standardOutput.debugDescription) + standardError: \(self.standardError.debugDescription) + ) + """ + } +} + +// MARK: - ExecutionResult Conformances +extension ExecutionResult: Equatable where Result: Equatable {} + +extension ExecutionResult: Hashable where Result: Hashable {} + +extension ExecutionResult: Codable where Result: Codable {} + +extension ExecutionResult: CustomStringConvertible where Result: CustomStringConvertible { + public var description: String { + return """ + ExecutionResult( + terminationStatus: \(self.terminationStatus.description), + value: \(self.value.description) + ) + """ + } +} + +extension ExecutionResult: CustomDebugStringConvertible where Result: CustomDebugStringConvertible { + public var debugDescription: String { + return """ + ExecutionResult( + terminationStatus: \(self.terminationStatus.debugDescription), + value: \(self.value.debugDescription) + ) + """ + } +} diff --git a/Sources/_Subprocess/Subprocess+API.swift b/Sources/_Subprocess/Subprocess+API.swift deleted file mode 100644 index f9c8b1ecb..000000000 --- a/Sources/_Subprocess/Subprocess+API.swift +++ /dev/null @@ -1,465 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import SystemPackage -#if canImport(FoundationEssentials) -import FoundationEssentials -#elseif canImport(Foundation) -import Foundation -#endif - -extension Subprocess { - /// Run a executable with given parameters and capture its - /// standard output and standard error. - /// - Parameters: - /// - executable: The executable to run. - /// - arguments: The arguments to pass to the executable. - /// - environment: The environment to use for the process. - /// - workingDirectory: The working directory to use for the subprocess. - /// - platformOptions: The platform specific options to use - /// when running the executable. - /// - input: The input to send to the executable. - /// - output: The method to use for collecting the standard output. - /// - error: The method to use for collecting the standard error. - /// - Returns: `CollectedResult` which contains process identifier, - /// termination status, captured standard output and standard error. - public static func run( - _ executable: Executable, - arguments: Arguments = [], - environment: Environment = .inherit, - workingDirectory: FilePath? = nil, - platformOptions: PlatformOptions = PlatformOptions(), - input: InputMethod = .noInput, - output: CollectedOutputMethod = .collect(), - error: CollectedOutputMethod = .collect() - ) async throws -> CollectedResult { - let result = try await self.run( - executable, - arguments: arguments, - environment: environment, - workingDirectory: workingDirectory, - platformOptions: platformOptions, - input: input, - output: .init(method: output.method), - error: .init(method: error.method) - ) { subprocess in - let (standardOutput, standardError) = try await subprocess.captureIOs() - return ( - processIdentifier: subprocess.processIdentifier, - standardOutput: standardOutput, - standardError: standardError - ) - } - return CollectedResult( - processIdentifier: result.value.processIdentifier, - terminationStatus: result.terminationStatus, - standardOutput: result.value.standardOutput, - standardError: result.value.standardError - ) - } - - /// Run a executable with given parameters and capture its - /// standard output and standard error. - /// - Parameters: - /// - executable: The executable to run. - /// - arguments: The arguments to pass to the executable. - /// - environment: The environment to use for the process. - /// - workingDirectory: The working directory to use for the subprocess. - /// - platformOptions: The platform specific options to use - /// when running the executable. - /// - input: The input to send to the executable. - /// - output: The method to use for collecting the standard output. - /// - error: The method to use for collecting the standard error. - /// - Returns: `CollectedResult` which contains process identifier, - /// termination status, captured standard output and standard error. - public static func run( - _ executable: Executable, - arguments: Arguments = [], - environment: Environment = .inherit, - workingDirectory: FilePath? = nil, - platformOptions: PlatformOptions = PlatformOptions(), - input: some Sequence, - output: CollectedOutputMethod = .collect(), - error: CollectedOutputMethod = .collect() - ) async throws -> CollectedResult { - let result = try await self.run( - executable, - arguments: arguments, - environment: environment, - workingDirectory: workingDirectory, - platformOptions: platformOptions, - output: .init(method: output.method), - error: .init(method: output.method) - ) { subprocess, writer in - return try await withThrowingTaskGroup(of: CapturedIOs?.self) { group in - group.addTask { - try await writer.write(input) - try await writer.finish() - return nil - } - group.addTask { - return try await subprocess.captureIOs() - } - var capturedIOs: CapturedIOs! - while let result = try await group.next() { - if result != nil { - capturedIOs = result - } - } - return ( - processIdentifier: subprocess.processIdentifier, - standardOutput: capturedIOs.standardOutput, - standardError: capturedIOs.standardError - ) - } - } - return CollectedResult( - processIdentifier: result.value.processIdentifier, - terminationStatus: result.terminationStatus, - standardOutput: result.value.standardOutput, - standardError: result.value.standardError - ) - } - - /// Run a executable with given parameters and capture its - /// standard output and standard error. - /// - Parameters: - /// - executable: The executable to run. - /// - arguments: The arguments to pass to the executable. - /// - environment: The environment to use for the process. - /// - workingDirectory: The working directory to use for the subprocess. - /// - platformOptions: The platform specific options to use - /// when running the executable. - /// - input: The input to send to the executable. - /// - output: The method to use for collecting the standard output. - /// - error: The method to use for collecting the standard error. - /// - Returns: `CollectedResult` which contains process identifier, - /// termination status, captured standard output and standard error. - public static func run( - _ executable: Executable, - arguments: Arguments = [], - environment: Environment = .inherit, - workingDirectory: FilePath? = nil, - platformOptions: PlatformOptions = PlatformOptions(), - input: S, - output: CollectedOutputMethod = .collect(), - error: CollectedOutputMethod = .collect() - ) async throws -> CollectedResult where S.Element == UInt8 { - let result = try await self.run( - executable, - arguments: arguments, - environment: environment, - workingDirectory: workingDirectory, - platformOptions: platformOptions, - output: .init(method: output.method), - error: .init(method: output.method) - ) { subprocess, writer in - return try await withThrowingTaskGroup(of: CapturedIOs?.self) { group in - group.addTask { - try await writer.write(input) - try await writer.finish() - return nil - } - group.addTask { - return try await subprocess.captureIOs() - } - var capturedIOs: CapturedIOs! - while let result = try await group.next() { - capturedIOs = result - } - return ( - processIdentifier: subprocess.processIdentifier, - standardOutput: capturedIOs.standardOutput, - standardError: capturedIOs.standardError - ) - } - } - return CollectedResult( - processIdentifier: result.value.processIdentifier, - terminationStatus: result.terminationStatus, - standardOutput: result.value.standardOutput, - standardError: result.value.standardError - ) - } -} - -// MARK: Custom Execution Body -extension Subprocess { - /// Run a executable with given parameters and a custom closure - /// to manage the running subprocess' lifetime and its IOs. - /// - Parameters: - /// - executable: The executable to run. - /// - arguments: The arguments to pass to the executable. - /// - environment: The environment in which to run the executable. - /// - workingDirectory: The working directory in which to run the executable. - /// - platformOptions: The platform specific options to use - /// when running the executable. - /// - input: The input to send to the executable. - /// - output: The method to use for redirecting the standard output. - /// - error: The method to use for redirecting the standard error. - /// - body: The custom execution body to manually control the running process - /// - Returns a ExecutableResult type containing the return value - /// of the closure. - public static func run( - _ executable: Executable, - arguments: Arguments = [], - environment: Environment = .inherit, - workingDirectory: FilePath? = nil, - platformOptions: PlatformOptions = PlatformOptions(), - input: InputMethod = .noInput, - output: RedirectedOutputMethod = .redirectToSequence, - error: RedirectedOutputMethod = .redirectToSequence, - _ body: (sending @escaping (Subprocess) async throws -> R) - ) async throws -> ExecutionResult { - return try await Configuration( - executable: executable, - arguments: arguments, - environment: environment, - workingDirectory: workingDirectory, - platformOptions: platformOptions - ) - .run(input: input, output: output, error: error, body) - } - - /// Run a executable with given parameters and a custom closure - /// to manage the running subprocess' lifetime and its IOs. - /// - Parameters: - /// - executable: The executable to run. - /// - arguments: The arguments to pass to the executable. - /// - environment: The environment in which to run the executable. - /// - workingDirectory: The working directory in which to run the executable. - /// - platformOptions: The platform specific options to use - /// when running the executable. - /// - input: The input to send to the executable. - /// - output: The method to use for redirecting the standard output. - /// - error: The method to use for redirecting the standard error. - /// - body: The custom execution body to manually control the running process - /// - Returns a `ExecutableResult` type containing the return value - /// of the closure. - public static func run( - _ executable: Executable, - arguments: Arguments = [], - environment: Environment = .inherit, - workingDirectory: FilePath? = nil, - platformOptions: PlatformOptions = PlatformOptions(), - input: some Sequence, - output: RedirectedOutputMethod = .redirectToSequence, - error: RedirectedOutputMethod = .redirectToSequence, - _ body: (sending @escaping (Subprocess) async throws -> R) - ) async throws -> ExecutionResult { - return try await Configuration( - executable: executable, - arguments: arguments, - environment: environment, - workingDirectory: workingDirectory, - platformOptions: platformOptions - ) - .run(output: output, error: error) { execution, writer in - return try await withThrowingTaskGroup(of: R?.self) { group in - group.addTask { - try await writer.write(input) - try await writer.finish() - return nil - } - group.addTask { - return try await body(execution) - } - var result: R! - while let next = try await group.next() { - result = next - } - return result - } - } - } - - /// Run a executable with given parameters and a custom closure - /// to manage the running subprocess' lifetime and its IOs. - /// - Parameters: - /// - executable: The executable to run. - /// - arguments: The arguments to pass to the executable. - /// - environment: The environment in which to run the executable. - /// - workingDirectory: The working directory in which to run the executable. - /// - platformOptions: The platform specific options to use - /// when running the executable. - /// - input: The input to send to the executable. - /// - output: The method to use for redirecting the standard output. - /// - error: The method to use for redirecting the standard error. - /// - body: The custom execution body to manually control the running process - /// - Returns a `ExecutableResult` type containing the return value - /// of the closure. - public static func run( - _ executable: Executable, - arguments: Arguments = [], - environment: Environment = .inherit, - workingDirectory: FilePath? = nil, - platformOptions: PlatformOptions = PlatformOptions(), - input: S, - output: RedirectedOutputMethod = .redirectToSequence, - error: RedirectedOutputMethod = .redirectToSequence, - _ body: (sending @escaping (Subprocess) async throws -> R) - ) async throws -> ExecutionResult where S.Element == UInt8 { - return try await Configuration( - executable: executable, - arguments: arguments, - environment: environment, - workingDirectory: workingDirectory, - platformOptions: platformOptions - ) - .run(output: output, error: error) { execution, writer in - return try await withThrowingTaskGroup(of: R?.self) { group in - group.addTask { - try await writer.write(input) - try await writer.finish() - return nil - } - group.addTask { - return try await body(execution) - } - var result: R! - while let next = try await group.next() { - result = next - } - return result - } - } - } - - /// Run a executable with given parameters and a custom closure - /// to manage the running subprocess' lifetime and write to its - /// standard input via `StandardInputWriter` - /// - Parameters: - /// - executable: The executable to run. - /// - arguments: The arguments to pass to the executable. - /// - environment: The environment in which to run the executable. - /// - workingDirectory: The working directory in which to run the executable. - /// - platformOptions: The platform specific options to use - /// when running the executable. - /// - output: The method to use for redirecting the standard output. - /// - error: The method to use for redirecting the standard error. - /// - body: The custom execution body to manually control the running process - /// - Returns a ExecutableResult type containing the return value - /// of the closure. - public static func run( - _ executable: Executable, - arguments: Arguments = [], - environment: Environment = .inherit, - workingDirectory: FilePath? = nil, - platformOptions: PlatformOptions = PlatformOptions(), - output: RedirectedOutputMethod = .redirectToSequence, - error: RedirectedOutputMethod = .redirectToSequence, - _ body: (sending @escaping (Subprocess, StandardInputWriter) async throws -> R) - ) async throws -> ExecutionResult { - return try await Configuration( - executable: executable, - arguments: arguments, - environment: environment, - workingDirectory: workingDirectory, - platformOptions: platformOptions - ) - .run(output: output, error: error, body) - } -} - -// MARK: - Configuration Based -extension Subprocess { - /// Run a executable with given parameters specified by a - /// `Subprocess.Configuration` - /// - Parameters: - /// - configuration: The `Subprocess` configuration to run. - /// - output: The method to use for redirecting the standard output. - /// - error: The method to use for redirecting the standard error. - /// - body: The custom configuration body to manually control - /// the running process and write to its standard input. - /// - Returns a ExecutableResult type containing the return value - /// of the closure. - public static func run( - _ configuration: Configuration, - output: RedirectedOutputMethod = .redirectToSequence, - error: RedirectedOutputMethod = .redirectToSequence, - _ body: (sending @escaping (Subprocess, StandardInputWriter) async throws -> R) - ) async throws -> ExecutionResult { - return try await configuration.run(output: output, error: error, body) - } -} - -// MARK: - Detached -extension Subprocess { - /// Run a executable with given parameters and return its process - /// identifier immediately without monitoring the state of the - /// subprocess nor waiting until it exits. - /// - /// This method is useful for launching subprocesses that outlive their - /// parents (for example, daemons and trampolines). - /// - /// - Parameters: - /// - executable: The executable to run. - /// - arguments: The arguments to pass to the executable. - /// - environment: The environment to use for the process. - /// - workingDirectory: The working directory for the process. - /// - platformOptions: The platform specific options to use for the process. - /// - input: A file descriptor to bind to the subprocess' standard input. - /// - output: A file descriptor to bind to the subprocess' standard output. - /// - error: A file descriptor to bind to the subprocess' standard error. - /// - Returns: the process identifier for the subprocess. - public static func runDetached( - _ executable: Executable, - arguments: Arguments = [], - environment: Environment = .inherit, - workingDirectory: FilePath? = nil, - platformOptions: PlatformOptions = PlatformOptions(), - input: FileDescriptor? = nil, - output: FileDescriptor? = nil, - error: FileDescriptor? = nil - ) throws -> ProcessIdentifier { - // Create input - let executionInput: ExecutionInput - let executionOutput: ExecutionOutput - let executionError: ExecutionOutput - if let inputFd = input { - executionInput = .init(storage: .fileDescriptor(inputFd, false)) - } else { - let devnull: FileDescriptor = try .openDevNull(withAcessMode: .readOnly) - executionInput = .init(storage: .noInput(devnull)) - } - if let outputFd = output { - executionOutput = .init(storage: .fileDescriptor(outputFd, false)) - } else { - let devnull: FileDescriptor = try .openDevNull(withAcessMode: .writeOnly) - executionOutput = .init(storage: .discarded(devnull)) - } - if let errorFd = error { - executionError = .init( - storage: .fileDescriptor(errorFd, false) - ) - } else { - let devnull: FileDescriptor = try .openDevNull(withAcessMode: .writeOnly) - executionError = .init(storage: .discarded(devnull)) - } - // Spawn! - let config: Configuration = Configuration( - executable: executable, - arguments: arguments, - environment: environment, - workingDirectory: workingDirectory, - platformOptions: platformOptions - ) - return try config.spawn( - withInput: executionInput, - output: executionOutput, - error: executionError - ).processIdentifier - } -} - diff --git a/Sources/_Subprocess/Subprocess+AsyncDataSequence.swift b/Sources/_Subprocess/Subprocess+AsyncDataSequence.swift deleted file mode 100644 index a3a3e3936..000000000 --- a/Sources/_Subprocess/Subprocess+AsyncDataSequence.swift +++ /dev/null @@ -1,86 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import SystemPackage -import Dispatch - -#if canImport(FoundationEssentials) -import FoundationEssentials -#elseif canImport(Foundation) -import Foundation -#endif - -extension Subprocess { - public struct AsyncDataSequence: AsyncSequence, Sendable, _AsyncSequence { - public typealias Error = any Swift.Error - - public typealias Element = Data - - @_nonSendable - public struct Iterator: AsyncIteratorProtocol { - public typealias Element = Data - - private let fileDescriptor: FileDescriptor - private var buffer: [UInt8] - private var currentPosition: Int - private var finished: Bool - - internal init(fileDescriptor: FileDescriptor) { - self.fileDescriptor = fileDescriptor - self.buffer = [] - self.currentPosition = 0 - self.finished = false - } - - public mutating func next() async throws -> Data? { - let data = try await self.fileDescriptor.readChunk( - upToLength: Subprocess.readBufferSize - ) - if data == nil { - // We finished reading. Close the file descriptor now - try self.fileDescriptor.close() - return nil - } - return data - } - } - - private let fileDescriptor: FileDescriptor - - init(fileDescriptor: FileDescriptor) { - self.fileDescriptor = fileDescriptor - } - - public func makeAsyncIterator() -> Iterator { - return Iterator(fileDescriptor: self.fileDescriptor) - } - } -} - -extension RangeReplaceableCollection { - /// Creates a new instance of a collection containing the elements of an asynchronous sequence. - /// - /// - Parameter source: The asynchronous sequence of elements for the new collection. - @inlinable - public init(_ source: Source) async rethrows where Source.Element == Element { - self.init() - for try await item in source { - append(item) - } - } -} - -public protocol _AsyncSequence: AsyncSequence { - associatedtype Error -} diff --git a/Sources/_Subprocess/Subprocess+Configuration.swift b/Sources/_Subprocess/Subprocess+Configuration.swift deleted file mode 100644 index 0b7857903..000000000 --- a/Sources/_Subprocess/Subprocess+Configuration.swift +++ /dev/null @@ -1,769 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -@preconcurrency import SystemPackage - -#if canImport(Darwin) -import Darwin -#elseif canImport(Glibc) -import Glibc -#elseif canImport(WinSDK) -import WinSDK -#endif - -#if canImport(FoundationEssentials) -import FoundationEssentials -#elseif canImport(Foundation) -import Foundation -#endif - -extension Subprocess { - /// A collection of configurations parameters to use when - /// spawning a subprocess. - public struct Configuration: Sendable, Hashable { - - internal enum RunState: Sendable { - case workBody(Result) - case monitorChildProcess(TerminationStatus) - } - - /// The executable to run. - public var executable: Executable - /// The arguments to pass to the executable. - public var arguments: Arguments - /// The environment to use when running the executable. - public var environment: Environment - /// The working directory to use when running the executable. - public var workingDirectory: FilePath - /// The platform specifc options to use when - /// running the subprocess. - public var platformOptions: PlatformOptions - - public init( - executable: Executable, - arguments: Arguments = [], - environment: Environment = .inherit, - workingDirectory: FilePath? = nil, - platformOptions: PlatformOptions = PlatformOptions() - ) { - self.executable = executable - self.arguments = arguments - self.environment = environment - self.workingDirectory = workingDirectory ?? .currentWorkingDirectory - self.platformOptions = platformOptions - } - - /// Close each input individually, and throw the first error if there's multiple errors thrown - @Sendable - private func cleanup( - process: Subprocess, - childSide: Bool, parentSide: Bool, - attemptToTerminateSubProcess: Bool - ) async throws { - guard childSide || parentSide || attemptToTerminateSubProcess else { - return - } - - // Attempt to teardown the subprocess - if attemptToTerminateSubProcess { - await process.teardown( - using: self.platformOptions.teardownSequence - ) - } - - let inputCloseFunc: () throws -> Void - let outputCloseFunc: () throws -> Void - let errorCloseFunc: () throws -> Void - if childSide && parentSide { - // Close all - inputCloseFunc = process.executionInput.closeAll - outputCloseFunc = process.executionOutput.closeAll - errorCloseFunc = process.executionError.closeAll - } else if childSide { - // Close child only - inputCloseFunc = process.executionInput.closeChildSide - outputCloseFunc = process.executionOutput.closeChildSide - errorCloseFunc = process.executionError.closeChildSide - } else { - // Close parent only - inputCloseFunc = process.executionInput.closeParentSide - outputCloseFunc = process.executionOutput.closeParentSide - errorCloseFunc = process.executionError.closeParentSide - } - - var inputError: Error? - var outputError: Error? - var errorError: Error? // lol - do { - try inputCloseFunc() - } catch { - inputError = error - } - - do { - try outputCloseFunc() - } catch { - outputError = error - } - - do { - try errorCloseFunc() - } catch { - errorError = error // lolol - } - - if let inputError = inputError { - throw inputError - } - - if let outputError = outputError { - throw outputError - } - - if let errorError = errorError { - throw errorError - } - } - - /// Close each input individually, and throw the first error if there's multiple errors thrown - @Sendable - internal func cleanupAll( - input: ExecutionInput, - output: ExecutionOutput, - error: ExecutionOutput - ) throws { - var inputError: Error? - var outputError: Error? - var errorError: Error? - - do { - try input.closeAll() - } catch { - inputError = error - } - - do { - try output.closeAll() - } catch { - outputError = error - } - - do { - try error.closeAll() - } catch { - errorError = error - } - - if let inputError = inputError { - throw inputError - } - if let outputError = outputError { - throw outputError - } - if let errorError = errorError { - throw errorError - } - } - - internal func run( - output: RedirectedOutputMethod, - error: RedirectedOutputMethod, - _ body: sending @escaping (Subprocess, StandardInputWriter) async throws -> R - ) async throws -> ExecutionResult { - let (readFd, writeFd) = try FileDescriptor.pipe() - let executionInput: ExecutionInput = .init(storage: .customWrite(readFd, writeFd)) - let executionOutput: ExecutionOutput = try output.createExecutionOutput() - let executionError: ExecutionOutput = try error.createExecutionOutput() - let process: Subprocess = try self.spawn( - withInput: executionInput, - output: executionOutput, - error: executionError) - // After spawn, cleanup child side fds - try await self.cleanup( - process: process, - childSide: true, - parentSide: false, - attemptToTerminateSubProcess: false - ) - return try await withAsyncTaskCancellationHandler { - return try await withThrowingTaskGroup(of: RunState.self) { group in - group.addTask { - let status = try await monitorProcessTermination( - forProcessWithIdentifier: process.processIdentifier) - return .monitorChildProcess(status) - } - group.addTask { - do { - let result = try await body(process, .init(input: executionInput)) - try await self.cleanup( - process: process, - childSide: false, - parentSide: true, - attemptToTerminateSubProcess: false - ) - return .workBody(result) - } catch { - // Cleanup everything - try await self.cleanup( - process: process, - childSide: false, - parentSide: true, - attemptToTerminateSubProcess: false - ) - throw error - } - } - - var result: R! - var terminationStatus: TerminationStatus! - while let state = try await group.next() { - switch state { - case .monitorChildProcess(let status): - // We don't really care about termination status here - terminationStatus = status - case .workBody(let workResult): - result = workResult - } - } - return ExecutionResult(terminationStatus: terminationStatus, value: result) - } - } onCancel: { - // Attempt to terminate the child process - // Since the task has already been cancelled, - // this is the best we can do - try? await self.cleanup( - process: process, - childSide: true, - parentSide: true, - attemptToTerminateSubProcess: true - ) - } - } - - internal func run( - input: InputMethod, - output: RedirectedOutputMethod, - error: RedirectedOutputMethod, - _ body: (sending @escaping (Subprocess) async throws -> R) - ) async throws -> ExecutionResult { - let executionInput = try input.createExecutionInput() - let executionOutput = try output.createExecutionOutput() - let executionError = try error.createExecutionOutput() - let process = try self.spawn( - withInput: executionInput, - output: executionOutput, - error: executionError) - // After spawn, clean up child side - try await self.cleanup( - process: process, - childSide: true, - parentSide: false, - attemptToTerminateSubProcess: false - ) - return try await withAsyncTaskCancellationHandler { - return try await withThrowingTaskGroup(of: RunState.self) { group in - group.addTask { - let status = try await monitorProcessTermination( - forProcessWithIdentifier: process.processIdentifier) - return .monitorChildProcess(status) - } - group.addTask { - do { - let result = try await body(process) - try await self.cleanup( - process: process, - childSide: false, - parentSide: true, - attemptToTerminateSubProcess: false - ) - return .workBody(result) - } catch { - try await self.cleanup( - process: process, - childSide: false, - parentSide: true, - attemptToTerminateSubProcess: false - ) - throw error - } - } - - var result: R! - var terminationStatus: TerminationStatus! - while let state = try await group.next() { - switch state { - case .monitorChildProcess(let status): - terminationStatus = status - case .workBody(let workResult): - result = workResult - } - } - return ExecutionResult(terminationStatus: terminationStatus, value: result) - } - } onCancel: { - // Attempt to terminate the child process - // Since the task has already been cancelled, - // this is the best we can do - try? await self.cleanup( - process: process, - childSide: true, - parentSide: true, - attemptToTerminateSubProcess: true - ) - } - } - } -} - -extension Subprocess.Configuration : CustomStringConvertible, CustomDebugStringConvertible { - public var description: String { - return """ -Subprocess.Configuration( - executable: \(self.executable.description), - arguments: \(self.arguments.description), - environment: \(self.environment.description), - workingDirectory: \(self.workingDirectory), - platformOptions: \(self.platformOptions.description(withIndent: 1)) -) -""" - } - - public var debugDescription: String { - return """ -Subprocess.Configuration( - executable: \(self.executable.debugDescription), - arguments: \(self.arguments.debugDescription), - environment: \(self.environment.debugDescription), - workingDirectory: \(self.workingDirectory), - platformOptions: \(self.platformOptions.description(withIndent: 1)) -) -""" - } -} - -// MARK: - Executable -extension Subprocess { - /// `Subprocess.Executable` defines how should the executable - /// be looked up for execution. - public struct Executable: Sendable, Hashable { - internal enum Configuration: Sendable, Hashable { - case executable(String) - case path(FilePath) - } - - internal let storage: Configuration - - private init(_config: Configuration) { - self.storage = _config - } - - /// Locate the executable by its name. - /// `Subprocess` will use `PATH` value to - /// determine the full path to the executable. - public static func named(_ executableName: String) -> Self { - return .init(_config: .executable(executableName)) - } - /// Locate the executable by its full path. - /// `Subprocess` will use this path directly. - public static func at(_ filePath: FilePath) -> Self { - return .init(_config: .path(filePath)) - } - /// Returns the full executable path given the environment value. - public func resolveExecutablePath(in environment: Environment) -> FilePath? { - if let path = self.resolveExecutablePath(withPathValue: environment.pathValue()) { - return FilePath(path) - } - return nil - } - } -} - -extension Subprocess.Executable : CustomStringConvertible, CustomDebugStringConvertible { - public var description: String { - switch storage { - case .executable(let executableName): - return executableName - case .path(let filePath): - return filePath.string - } - } - - public var debugDescription: String { - switch storage { - case .executable(let string): - return "executable(\(string))" - case .path(let filePath): - return "path(\(filePath.string))" - } - } -} - -// MARK: - Arguments -extension Subprocess { - /// A collection of arguments to pass to the subprocess. - public struct Arguments: Sendable, ExpressibleByArrayLiteral, Hashable { - public typealias ArrayLiteralElement = String - - internal let storage: [StringOrRawBytes] - internal let executablePathOverride: StringOrRawBytes? - - /// Create an Arguments object using the given literal values - public init(arrayLiteral elements: String...) { - self.storage = elements.map { .string($0) } - self.executablePathOverride = nil - } - /// Create an Arguments object using the given array - public init(_ array: [String]) { - self.storage = array.map { .string($0) } - self.executablePathOverride = nil - } - -#if !os(Windows) // Windows does NOT support arg0 override - /// Create an `Argument` object using the given values, but - /// override the first Argument value to `executablePathOverride`. - /// If `executablePathOverride` is nil, - /// `Arguments` will automatically use the executable path - /// as the first argument. - /// - Parameters: - /// - executablePathOverride: the value to override the first argument. - /// - remainingValues: the rest of the argument value - public init(executablePathOverride: String?, remainingValues: [String]) { - self.storage = remainingValues.map { .string($0) } - if let executablePathOverride = executablePathOverride { - self.executablePathOverride = .string(executablePathOverride) - } else { - self.executablePathOverride = nil - } - } - - /// Create an `Argument` object using the given values, but - /// override the first Argument value to `executablePathOverride`. - /// If `executablePathOverride` is nil, - /// `Arguments` will automatically use the executable path - /// as the first argument. - /// - Parameters: - /// - executablePathOverride: the value to override the first argument. - /// - remainingValues: the rest of the argument value - public init(executablePathOverride: Data?, remainingValues: [Data]) { - self.storage = remainingValues.map { .rawBytes($0.toArray()) } - if let override = executablePathOverride { - self.executablePathOverride = .rawBytes(override.toArray()) - } else { - self.executablePathOverride = nil - } - } - - public init(_ array: [Data]) { - self.storage = array.map { .rawBytes($0.toArray()) } - self.executablePathOverride = nil - } -#endif - } -} - -extension Subprocess.Arguments : CustomStringConvertible, CustomDebugStringConvertible { - public var description: String { - var result: [String] = self.storage.map(\.description) - - if let override = self.executablePathOverride { - result.insert("override\(override.description)", at: 0) - } - return result.description - } - - public var debugDescription: String { return self.description } -} - -// MARK: - Environment -extension Subprocess { - /// A set of environment variables to use when executing the subprocess. - public struct Environment: Sendable, Hashable { - internal enum Configuration: Sendable, Hashable { - case inherit([StringOrRawBytes : StringOrRawBytes]) - case custom([StringOrRawBytes : StringOrRawBytes]) - } - - internal let config: Configuration - - init(config: Configuration) { - self.config = config - } - /// Child process should inherit the same environment - /// values from its parent process. - public static var inherit: Self { - return .init(config: .inherit([:])) - } - /// Override the provided `newValue` in the existing `Environment` - public func updating(_ newValue: [String : String]) -> Self { - return .init(config: .inherit(newValue.wrapToStringOrRawBytes())) - } - /// Use custom environment variables - public static func custom(_ newValue: [String : String]) -> Self { - return .init(config: .custom(newValue.wrapToStringOrRawBytes())) - } - -#if !os(Windows) - /// Override the provided `newValue` in the existing `Environment` - public func updating(_ newValue: [Data : Data]) -> Self { - return .init(config: .inherit(newValue.wrapToStringOrRawBytes())) - } - /// Use custom environment variables - public static func custom(_ newValue: [Data : Data]) -> Self { - return .init(config: .custom(newValue.wrapToStringOrRawBytes())) - } -#endif - } -} - -extension Subprocess.Environment : CustomStringConvertible, CustomDebugStringConvertible { - public var description: String { - switch self.config { - case .custom(let customDictionary): - return customDictionary.dictionaryDescription - case .inherit(let updateValue): - return "Inherting current environment with updates: \(updateValue.dictionaryDescription)" - } - } - - public var debugDescription: String { - return self.description - } -} - -fileprivate extension Dictionary where Key == String, Value == String { - func wrapToStringOrRawBytes() -> [Subprocess.StringOrRawBytes : Subprocess.StringOrRawBytes] { - var result = Dictionary< - Subprocess.StringOrRawBytes, - Subprocess.StringOrRawBytes - >(minimumCapacity: self.count) - for (key, value) in self { - result[.string(key)] = .string(value) - } - return result - } -} - -fileprivate extension Dictionary where Key == Data, Value == Data { - func wrapToStringOrRawBytes() -> [Subprocess.StringOrRawBytes : Subprocess.StringOrRawBytes] { - var result = Dictionary< - Subprocess.StringOrRawBytes, - Subprocess.StringOrRawBytes - >(minimumCapacity: self.count) - for (key, value) in self { - result[.rawBytes(key.toArray())] = .rawBytes(value.toArray()) - } - return result - } -} - -fileprivate extension Dictionary where Key == Subprocess.StringOrRawBytes, Value == Subprocess.StringOrRawBytes { - var dictionaryDescription: String { - var result = "[\n" - for (key, value) in self { - result += "\t\(key.description) : \(value.description),\n" - } - result += "]" - return result - } -} - -fileprivate extension Data { - func toArray() -> [T] { - return self.withUnsafeBytes { ptr in - return Array(ptr.bindMemory(to: T.self)) - } - } -} - -// MARK: - TerminationStatus -extension Subprocess { - /// An exit status of a subprocess. - @frozen - public enum TerminationStatus: Sendable, Hashable, Codable { - #if canImport(WinSDK) - public typealias Code = DWORD - #else - public typealias Code = CInt - #endif - - /// The subprocess was existed with the given code - case exited(Code) - /// The subprocess was signalled with given exception value - case unhandledException(Code) - /// Whether the current TerminationStatus is successful. - public var isSuccess: Bool { - switch self { - case .exited(let exitCode): - return exitCode == 0 - case .unhandledException(_): - return false - } - } - } -} - -extension Subprocess.TerminationStatus : CustomStringConvertible, CustomDebugStringConvertible { - public var description: String { - switch self { - case .exited(let code): - return "exited(\(code))" - case .unhandledException(let code): - return "unhandledException(\(code))" - } - } - - public var debugDescription: String { - return self.description - } -} - -// MARK: - Internal -extension Subprocess { - internal enum StringOrRawBytes: Sendable, Hashable { - case string(String) - case rawBytes([CChar]) - - // Return value needs to be deallocated manually by callee - func createRawBytes() -> UnsafeMutablePointer { - switch self { - case .string(let string): - return strdup(string) - case .rawBytes(let rawBytes): - return strdup(rawBytes) - } - } - - var stringValue: String? { - switch self { - case .string(let string): - return string - case .rawBytes(let rawBytes): - return String(validatingUTF8: rawBytes) - } - } - - var description: String { - switch self { - case .string(let string): - return string - case .rawBytes(let bytes): - return bytes.description - } - } - - var count: Int { - switch self { - case .string(let string): - return string.count - case .rawBytes(let rawBytes): - return strnlen(rawBytes, Int.max) - } - } - - func hash(into hasher: inout Hasher) { - // If Raw bytes is valid UTF8, hash it as so - switch self { - case .string(let string): - hasher.combine(string) - case .rawBytes(let bytes): - if let stringValue = self.stringValue { - hasher.combine(stringValue) - } else { - hasher.combine(bytes) - } - } - } - } -} - -extension FilePath { - static var currentWorkingDirectory: Self { - let path = getcwd(nil, 0)! - defer { free(path) } - return .init(String(cString: path)) - } -} - -extension Optional where Wrapped : Collection { - func withOptionalUnsafeBufferPointer(_ body: ((UnsafeBufferPointer)?) throws -> R) rethrows -> R { - switch self { - case .some(let wrapped): - guard let array: Array = wrapped as? Array else { - return try body(nil) - } - return try array.withUnsafeBufferPointer { ptr in - return try body(ptr) - } - case .none: - return try body(nil) - } - } -} - -extension Optional where Wrapped == String { - func withOptionalCString(_ body: ((UnsafePointer)?) throws -> R) rethrows -> R { - switch self { - case .none: - return try body(nil) - case .some(let wrapped): - return try wrapped.withCString { - return try body($0) - } - } - } - - var stringValue: String { - return self ?? "nil" - } -} - -// MARK: - Stubs for the one from Foundation -public enum QualityOfService: Int, Sendable { - case userInteractive = 0x21 - case userInitiated = 0x19 - case utility = 0x11 - case background = 0x09 - case `default` = -1 -} - -internal func withAsyncTaskCancellationHandler( - _ body: sending @escaping () async throws -> R, - onCancel handler: sending @escaping () async -> Void -) async rethrows -> R { - return try await withThrowingTaskGroup( - of: R?.self, - returning: R.self - ) { group in - group.addTask { - return try await body() - } - group.addTask { - // wait until cancelled - do { while true { try await Task.sleep(nanoseconds: 1_000_000_000) } } catch {} - // Run task cancel handler - await handler() - return nil - } - - while let result = try await group.next() { - if let result = result { - // As soon as the body finishes, cancel the group - group.cancelAll() - return result - } - } - fatalError("Unreachable") - } -} - diff --git a/Sources/_Subprocess/Subprocess+IO.swift b/Sources/_Subprocess/Subprocess+IO.swift deleted file mode 100644 index 1b43e4210..000000000 --- a/Sources/_Subprocess/Subprocess+IO.swift +++ /dev/null @@ -1,437 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -#if canImport(FoundationEssentials) -import FoundationEssentials -#elseif canImport(Foundation) -import Foundation -#endif - -import Dispatch -import SystemPackage - -// Naive Mutex so we don't have to update the macOS dependency -final class _Mutex: Sendable { - let lock = Lock() - - var value: Value - - init(_ value: Value) { - self.value = value - } - - func withLock(_ body: (inout Value) throws -> R) rethrows -> R { - self.lock.lock() - defer { self.lock.unlock() } - return try body(&self.value) - } -} - -// MARK: - Input -extension Subprocess { - /// `InputMethod` defines how should the standard input - /// of the subprocess receive inputs. - public struct InputMethod: Sendable, Hashable { - internal enum Storage: Sendable, Hashable { - case noInput - case fileDescriptor(FileDescriptor, Bool) - } - - internal let method: Storage - - internal init(method: Storage) { - self.method = method - } - - internal func createExecutionInput() throws -> ExecutionInput { - switch self.method { - case .noInput: - let devnull: FileDescriptor = try .openDevNull(withAcessMode: .readOnly) - return .init(storage: .noInput(devnull)) - case .fileDescriptor(let fileDescriptor, let closeWhenDone): - return .init(storage: .fileDescriptor(fileDescriptor, closeWhenDone)) - } - } - - /// Subprocess should read no input. This option is equivalent - /// to bind the stanard input to `/dev/null`. - public static var noInput: Self { - return .init(method: .noInput) - } - - /// Subprocess should read input from a given file descriptor. - /// - Parameters: - /// - fd: the file descriptor to read from - /// - closeAfterSpawningProcess: whether the file descriptor - /// should be automatically closed after subprocess is spawned. - public static func readFrom( - _ fd: FileDescriptor, - closeAfterSpawningProcess: Bool - ) -> Self { - return .init(method: .fileDescriptor(fd, closeAfterSpawningProcess)) - } - } -} - -extension Subprocess { - /// `CollectedOutputMethod` defines how should Subprocess collect - /// output from child process' standard output and standard error - public struct CollectedOutputMethod: Sendable, Hashable { - internal enum Storage: Sendable, Hashable { - case discarded - case fileDescriptor(FileDescriptor, Bool) - case collected(Int) - } - - internal let method: Storage - - internal init(method: Storage) { - self.method = method - } - - /// Subprocess shold dicard the child process output. - /// This option is equivalent to binding the child process - /// output to `/dev/null`. - public static var discard: Self { - return .init(method: .discarded) - } - /// Subprocess should write the child process output - /// to the file descriptor specified. - /// - Parameters: - /// - fd: the file descriptor to write to - /// - closeAfterSpawningProcess: whether to close the - /// file descriptor once the process is spawned. - public static func writeTo(_ fd: FileDescriptor, closeAfterSpawningProcess: Bool) -> Self { - return .init(method: .fileDescriptor(fd, closeAfterSpawningProcess)) - } - /// Subprocess should collect the child process output - /// as `Data` with the given limit in bytes. The default - /// limit is 128kb. - public static func collect(upTo limit: Int = 128 * 1024) -> Self { - return .init(method: .collected(limit)) - } - - internal func createExecutionOutput() throws -> ExecutionOutput { - switch self.method { - case .discarded: - // Bind to /dev/null - let devnull: FileDescriptor = try .openDevNull(withAcessMode: .writeOnly) - return .init(storage: .discarded(devnull)) - case .fileDescriptor(let fileDescriptor, let closeWhenDone): - return .init(storage: .fileDescriptor(fileDescriptor, closeWhenDone)) - case .collected(let limit): - let (readFd, writeFd) = try FileDescriptor.pipe() - return .init(storage: .collected(limit, readFd, writeFd)) - } - } - } - - /// `CollectedOutputMethod` defines how should Subprocess redirect - /// output from child process' standard output and standard error. - public struct RedirectedOutputMethod: Sendable, Hashable { - typealias Storage = CollectedOutputMethod.Storage - - internal let method: Storage - - internal init(method: Storage) { - self.method = method - } - - /// Subprocess shold dicard the child process output. - /// This option is equivalent to binding the child process - /// output to `/dev/null`. - public static var discard: Self { - return .init(method: .discarded) - } - /// Subprocess should redirect the child process output - /// to `Subprocess.standardOutput` or `Subprocess.standardError` - /// so they can be consumed as an AsyncSequence - public static var redirectToSequence: Self { - return .init(method: .collected(128 * 1024)) - } - /// Subprocess shold write the child process output - /// to the file descriptor specified. - /// - Parameters: - /// - fd: the file descriptor to write to - /// - closeAfterSpawningProcess: whether to close the - /// file descriptor once the process is spawned. - public static func writeTo( - _ fd: FileDescriptor, - closeAfterSpawningProcess: Bool - ) -> Self { - return .init(method: .fileDescriptor(fd, closeAfterSpawningProcess)) - } - - internal func createExecutionOutput() throws -> ExecutionOutput { - switch self.method { - case .discarded: - // Bind to /dev/null - let devnull: FileDescriptor = try .openDevNull(withAcessMode: .writeOnly) - return .init(storage: .discarded(devnull)) - case .fileDescriptor(let fileDescriptor, let closeWhenDone): - return .init(storage: .fileDescriptor(fileDescriptor, closeWhenDone)) - case .collected(let limit): - let (readFd, writeFd) = try FileDescriptor.pipe() - return .init(storage: .collected(limit, readFd, writeFd)) - } - } - } -} - -// MARK: - Execution IO -extension Subprocess { - internal final class ExecutionInput: Sendable, Hashable { - - - internal enum Storage: Sendable, Hashable { - case noInput(FileDescriptor?) - case customWrite(FileDescriptor?, FileDescriptor?) - case fileDescriptor(FileDescriptor?, Bool) - } - - let storage: _Mutex - - internal init(storage: Storage) { - self.storage = .init(storage) - } - - internal func getReadFileDescriptor() -> FileDescriptor? { - return self.storage.withLock { - switch $0 { - case .noInput(let readFd): - return readFd - case .customWrite(let readFd, _): - return readFd - case .fileDescriptor(let readFd, _): - return readFd - } - } - } - - internal func getWriteFileDescriptor() -> FileDescriptor? { - return self.storage.withLock { - switch $0 { - case .noInput(_), .fileDescriptor(_, _): - return nil - case .customWrite(_, let writeFd): - return writeFd - } - } - } - - internal func closeChildSide() throws { - try self.storage.withLock { - switch $0 { - case .noInput(let devnull): - try devnull?.close() - $0 = .noInput(nil) - case .customWrite(let readFd, let writeFd): - try readFd?.close() - $0 = .customWrite(nil, writeFd) - case .fileDescriptor(let fd, let closeWhenDone): - // User passed in fd - if closeWhenDone { - try fd?.close() - $0 = .fileDescriptor(nil, closeWhenDone) - } - } - } - } - - internal func closeParentSide() throws { - try self.storage.withLock { - switch $0 { - case .noInput(_), .fileDescriptor(_, _): - break - case .customWrite(let readFd, let writeFd): - // The parent fd should have been closed - // in the `body` when writer.finish() is called - // But in case it isn't call it agian - try writeFd?.close() - $0 = .customWrite(readFd, nil) - } - } - } - - internal func closeAll() throws { - try self.storage.withLock { - switch $0 { - case .noInput(let readFd): - try readFd?.close() - $0 = .noInput(nil) - case .customWrite(let readFd, let writeFd): - var readFdCloseError: Error? - var writeFdCloseError: Error? - do { - try readFd?.close() - } catch { - readFdCloseError = error - } - do { - try writeFd?.close() - } catch { - writeFdCloseError = error - } - $0 = .customWrite(nil, nil) - if let readFdCloseError { - throw readFdCloseError - } - if let writeFdCloseError { - throw writeFdCloseError - } - case .fileDescriptor(let fd, let closeWhenDone): - if closeWhenDone { - try fd?.close() - $0 = .fileDescriptor(nil, closeWhenDone) - } - } - } - } - - public func hash(into hasher: inout Hasher) { - self.storage.withLock { - hasher.combine($0) - } - } - - public static func == ( - lhs: Subprocess.ExecutionInput, - rhs: Subprocess.ExecutionInput - ) -> Bool { - return lhs.storage.withLock { lhsStorage in - rhs.storage.withLock { rhsStorage in - return lhsStorage == rhsStorage - } - } - } - } - - internal final class ExecutionOutput: Sendable { - internal enum Storage: Sendable { - case discarded(FileDescriptor?) - case fileDescriptor(FileDescriptor?, Bool) - case collected(Int, FileDescriptor?, FileDescriptor?) - } - - private let storage: _Mutex - - internal init(storage: Storage) { - self.storage = .init(storage) - } - - internal func getWriteFileDescriptor() -> FileDescriptor? { - return self.storage.withLock { - switch $0 { - case .discarded(let writeFd): - return writeFd - case .fileDescriptor(let writeFd, _): - return writeFd - case .collected(_, _, let writeFd): - return writeFd - } - } - } - - internal func getReadFileDescriptor() -> FileDescriptor? { - return self.storage.withLock { - switch $0 { - case .discarded(_), .fileDescriptor(_, _): - return nil - case .collected(_, let readFd, _): - return readFd - } - } - } - - internal func consumeCollectedFileDescriptor() -> (limit: Int, fd: FileDescriptor?)? { - return self.storage.withLock { - switch $0 { - case .discarded(_), .fileDescriptor(_, _): - // The output has been written somewhere else - return nil - case .collected(let limit, let readFd, let writeFd): - $0 = .collected(limit, nil, writeFd) - return (limit, readFd) - } - } - } - - internal func closeChildSide() throws { - try self.storage.withLock { - switch $0 { - case .discarded(let writeFd): - try writeFd?.close() - $0 = .discarded(nil) - case .fileDescriptor(let fd, let closeWhenDone): - // User passed fd - if closeWhenDone { - try fd?.close() - $0 = .fileDescriptor(nil, closeWhenDone) - } - case .collected(let limit, let readFd, let writeFd): - try writeFd?.close() - $0 = .collected(limit, readFd, nil) - } - } - } - - internal func closeParentSide() throws { - try self.storage.withLock { - switch $0 { - case .discarded(_), .fileDescriptor(_, _): - break - case .collected(let limit, let readFd, let writeFd): - try readFd?.close() - $0 = .collected(limit, nil, writeFd) - } - } - } - - internal func closeAll() throws { - try self.storage.withLock { - switch $0 { - case .discarded(let writeFd): - try writeFd?.close() - $0 = .discarded(nil) - case .fileDescriptor(let fd, let closeWhenDone): - if closeWhenDone { - try fd?.close() - $0 = .fileDescriptor(nil, closeWhenDone) - } - case .collected(let limit, let readFd, let writeFd): - var readFdCloseError: Error? - var writeFdCloseError: Error? - do { - try readFd?.close() - } catch { - readFdCloseError = error - } - do { - try writeFd?.close() - } catch { - writeFdCloseError = error - } - $0 = .collected(limit, nil, nil) - if let readFdCloseError { - throw readFdCloseError - } - if let writeFdCloseError { - throw writeFdCloseError - } - } - } - } - } -} - diff --git a/Sources/_Subprocess/Subprocess+Teardown.swift b/Sources/_Subprocess/Subprocess+Teardown.swift deleted file mode 100644 index 8b9af32dd..000000000 --- a/Sources/_Subprocess/Subprocess+Teardown.swift +++ /dev/null @@ -1,125 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -#if canImport(Darwin) || canImport(Glibc) - -#if canImport(Darwin) -import Darwin -#elseif canImport(Glibc) -import Glibc -#endif - -#if canImport(FoundationEssentials) -import FoundationEssentials -#elseif canImport(Foundation) -import Foundation -#endif - -extension Subprocess { - /// A step in the graceful shutdown teardown sequence. - /// It consists of a signal to send to the child process and the - /// number of nanoseconds allowed for the child process to exit - /// before proceeding to the next step. - public struct TeardownStep: Sendable, Hashable { - internal enum Storage: Sendable, Hashable { - case sendSignal(Signal, allowedNanoseconds: UInt64) - case kill - } - var storage: Storage - - /// Sends `signal` to the process and provides `allowedNanoSecondsToExit` - /// nanoseconds for the process to exit before proceeding to the next step. - /// The final step in the sequence will always send a `.kill` signal. - public static func sendSignal( - _ signal: Signal, - allowedNanoSecondsToExit: UInt64 - ) -> Self { - return Self( - storage: .sendSignal( - signal, - allowedNanoseconds: allowedNanoSecondsToExit - ) - ) - } - } -} - -extension Subprocess { - internal func runTeardownSequence(_ sequence: [TeardownStep]) async { - // First insert the `.kill` step - let finalSequence = sequence + [TeardownStep(storage: .kill)] - for step in finalSequence { - enum TeardownStepCompletion { - case processHasExited - case processStillAlive - case killedTheProcess - } - let stepCompletion: TeardownStepCompletion - - guard self.isAlive() else { - return - } - - switch step.storage { - case .sendSignal(let signal, let allowedNanoseconds): - stepCompletion = await withTaskGroup(of: TeardownStepCompletion.self) { group in - group.addTask { - do { - try await Task.sleep(nanoseconds: allowedNanoseconds) - return .processStillAlive - } catch { - // teardown(using:) cancells this task - // when process has exited - return .processHasExited - } - } - try? self.send(signal, toProcessGroup: false) - return await group.next()! - } - case .kill: - try? self.send(.kill, toProcessGroup: false) - stepCompletion = .killedTheProcess - } - - switch stepCompletion { - case .killedTheProcess, .processHasExited: - return - case .processStillAlive: - // Continue to next step - break - } - } - } -} - -extension Subprocess { - private func isAlive() -> Bool { - return kill(self.processIdentifier.value, 0) == 0 - } -} - -func withUncancelledTask( - returning: R.Type = R.self, - _ body: @Sendable @escaping () async -> R -) async -> R { - // This looks unstructured but it isn't, please note that we `await` `.value` of this task. - // The reason we need this separate `Task` is that in general, we cannot assume that code performs to our - // expectations if the task we run it on is already cancelled. However, in some cases we need the code to - // run regardless -- even if our task is already cancelled. Therefore, we create a new, uncancelled task here. - await Task { - await body() - }.value -} - -#endif // canImport(Darwin) || canImport(Glibc) diff --git a/Sources/_Subprocess/Subprocess.swift b/Sources/_Subprocess/Subprocess.swift deleted file mode 100644 index 35f4a2c21..000000000 --- a/Sources/_Subprocess/Subprocess.swift +++ /dev/null @@ -1,315 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import SystemPackage - -#if canImport(FoundationEssentials) -import FoundationEssentials -#elseif canImport(Foundation) -import Foundation -#endif - -/// An object that represents a subprocess of the current process. -/// -/// Using `Subprocess`, your program can run another program as a subprocess -/// and can monitor that program’s execution. A `Subprocess` object creates a -/// **separate executable** entity; it’s different from `Thread` because it doesn’t -/// share memory space with the process that creates it. -public struct Subprocess: Sendable { - /// The process identifier of the current subprocess - public let processIdentifier: ProcessIdentifier - - internal let executionInput: ExecutionInput - internal let executionOutput: ExecutionOutput - internal let executionError: ExecutionOutput -#if os(Windows) - internal let consoleBehavior: PlatformOptions.ConsoleBehavior -#endif - - /// The standard output of the subprocess. - /// Accessing this property will **fatalError** if - /// - `.output` wasn't set to `.redirectToSequence` when the subprocess was spawned; - /// - This property was accessed multiple times. Subprocess communicates with - /// parent process via pipe under the hood and each pipe can only be consumed ones. - public var standardOutput: some _AsyncSequence { - guard let (_, fd) = self.executionOutput - .consumeCollectedFileDescriptor() else { - fatalError("The standard output was not redirected") - } - guard let fd = fd else { - fatalError("The standard output has already been closed") - } - return AsyncDataSequence(fileDescriptor: fd) - } - - /// The standard error of the subprocess. - /// Accessing this property will **fatalError** if - /// - `.error` wasn't set to `.redirectToSequence` when the subprocess was spawned; - /// - This property was accessed multiple times. Subprocess communicates with - /// parent process via pipe under the hood and each pipe can only be consumed ones. - public var standardError: some _AsyncSequence { - guard let (_, fd) = self.executionError - .consumeCollectedFileDescriptor() else { - fatalError("The standard error was not redirected") - } - guard let fd = fd else { - fatalError("The standard error has already been closed") - } - return AsyncDataSequence(fileDescriptor: fd) - } -} - -// MARK: - Teardown -#if canImport(Darwin) || canImport(Glibc) -extension Subprocess { - /// Performs a sequence of teardown steps on the Subprocess. - /// Teardown sequence always ends with a `.kill` signal - /// - Parameter sequence: The steps to perform. - public func teardown(using sequence: [TeardownStep]) async { - await withUncancelledTask { - await self.runTeardownSequence(sequence) - } - } -} -#endif - -// MARK: - StandardInputWriter -extension Subprocess { - /// A writer that writes to the standard input of the subprocess. - public struct StandardInputWriter { - - private let input: ExecutionInput - - init(input: ExecutionInput) { - self.input = input - } - - /// Write a sequence of UInt8 to the standard input of the subprocess. - /// - Parameter sequence: The sequence of bytes to write. - public func write(_ sequence: S) async throws where S : Sequence, S.Element == UInt8 { - guard let fd: FileDescriptor = self.input.getWriteFileDescriptor() else { - fatalError("Attempting to write to a file descriptor that's already closed") - } - try await fd.write(sequence) - } - - /// Write a sequence of CChar to the standard input of the subprocess. - /// - Parameter sequence: The sequence of bytes to write. - public func write(_ sequence: S) async throws where S : Sequence, S.Element == CChar { - try await self.write(sequence.map { UInt8($0) }) - } - - /// Write a AsyncSequence of CChar to the standard input of the subprocess. - /// - Parameter sequence: The sequence of bytes to write. - public func write(_ asyncSequence: S) async throws where S.Element == CChar { - let sequence = try await Array(asyncSequence).map { UInt8($0) } - try await self.write(sequence) - } - - /// Write a AsyncSequence of UInt8 to the standard input of the subprocess. - /// - Parameter sequence: The sequence of bytes to write. - public func write(_ asyncSequence: S) async throws where S.Element == UInt8 { - let sequence = try await Array(asyncSequence) - try await self.write(sequence) - } - - /// Signal all writes are finished - public func finish() async throws { - try self.input.closeParentSide() - } - } -} - -@available(macOS, unavailable) -@available(iOS, unavailable) -@available(tvOS, unavailable) -@available(watchOS, unavailable) -@available(*, unavailable) -extension Subprocess.StandardInputWriter : Sendable {} - -// MARK: - Result -extension Subprocess { - /// A simple wrapper around the generic result returned by the - /// `run` closures with the corresponding `TerminationStatus` - /// of the child process. - public struct ExecutionResult: Sendable { - /// The termination status of the child process - public let terminationStatus: TerminationStatus - /// The result returned by the closure passed to `.run` methods - public let value: T - - internal init(terminationStatus: TerminationStatus, value: T) { - self.terminationStatus = terminationStatus - self.value = value - } - } - - /// The result of a subprocess execution with its collected - /// standard output and standard error. - public struct CollectedResult: Sendable, Hashable, Codable { - /// The process identifier for the executed subprocess - public let processIdentifier: ProcessIdentifier - /// The termination status of the executed subprocess - public let terminationStatus: TerminationStatus - private let _standardOutput: Data? - private let _standardError: Data? - - /// The collected standard output value for the subprocess. - /// Accessing this property will *fatalError* if the - /// corresponding `CollectedOutputMethod` is not set to - /// `.collect` or `.collect(upTo:)` - public var standardOutput: Data { - guard let output = self._standardOutput else { - fatalError("standardOutput is only available if the Subprocess was ran with .collect as output") - } - return output - } - /// The collected standard error value for the subprocess. - /// Accessing this property will *fatalError* if the - /// corresponding `CollectedOutputMethod` is not set to - /// `.collect` or `.collect(upTo:)` - public var standardError: Data { - guard let output = self._standardError else { - fatalError("standardError is only available if the Subprocess was ran with .collect as error ") - } - return output - } - - internal init( - processIdentifier: ProcessIdentifier, - terminationStatus: TerminationStatus, - standardOutput: Data?, - standardError: Data?) { - self.processIdentifier = processIdentifier - self.terminationStatus = terminationStatus - self._standardOutput = standardOutput - self._standardError = standardError - } - } -} - -extension Subprocess.ExecutionResult: Equatable where T : Equatable {} - -extension Subprocess.ExecutionResult: Hashable where T : Hashable {} - -extension Subprocess.ExecutionResult: Codable where T : Codable {} - -extension Subprocess.ExecutionResult: CustomStringConvertible where T : CustomStringConvertible { - public var description: String { - return """ -Subprocess.ExecutionResult( - terminationStatus: \(self.terminationStatus.description), - value: \(self.value.description) -) -""" - } -} - -extension Subprocess.ExecutionResult: CustomDebugStringConvertible where T : CustomDebugStringConvertible { - public var debugDescription: String { - return """ -Subprocess.ExecutionResult( - terminationStatus: \(self.terminationStatus.debugDescription), - value: \(self.value.debugDescription) -) -""" - } -} - -extension Subprocess.CollectedResult : CustomStringConvertible, CustomDebugStringConvertible { - public var description: String { - return """ -Subprocess.CollectedResult( - processIdentifier: \(self.processIdentifier.description), - terminationStatus: \(self.terminationStatus.description), - standardOutput: \(self._standardOutput?.description ?? "not captured"), - standardError: \(self._standardError?.description ?? "not captured") -) -""" - } - - public var debugDescription: String { - return """ -Subprocess.CollectedResult( - processIdentifier: \(self.processIdentifier.debugDescription), - terminationStatus: \(self.terminationStatus.debugDescription), - standardOutput: \(self._standardOutput?.debugDescription ?? "not captured"), - standardError: \(self._standardError?.debugDescription ?? "not captured") -) -""" - } -} - -// MARK: - Internal -extension Subprocess { - internal enum OutputCapturingState { - case standardOutputCaptured(Data?) - case standardErrorCaptured(Data?) - } - - internal typealias CapturedIOs = (standardOutput: Data?, standardError: Data?) - - private func capture(fileDescriptor: FileDescriptor, maxLength: Int) async throws -> Data { - return try await fileDescriptor.readUntilEOF(upToLength: maxLength) - } - - internal func captureStandardOutput() async throws -> Data? { - guard let (limit, readFd) = self.executionOutput - .consumeCollectedFileDescriptor(), - let readFd = readFd else { - return nil - } - defer { - try? readFd.close() - } - return try await self.capture(fileDescriptor: readFd, maxLength: limit) - } - - internal func captureStandardError() async throws -> Data? { - guard let (limit, readFd) = self.executionError - .consumeCollectedFileDescriptor(), - let readFd = readFd else { - return nil - } - defer { - try? readFd.close() - } - return try await self.capture(fileDescriptor: readFd, maxLength: limit) - } - - internal func captureIOs() async throws -> CapturedIOs { - return try await withThrowingTaskGroup(of: OutputCapturingState.self) { group in - group.addTask { - let stdout = try await self.captureStandardOutput() - return .standardOutputCaptured(stdout) - } - group.addTask { - let stderr = try await self.captureStandardError() - return .standardErrorCaptured(stderr) - } - - var stdout: Data? - var stderror: Data? - while let state = try await group.next() { - switch state { - case .standardOutputCaptured(let output): - stdout = output - case .standardErrorCaptured(let error): - stderror = error - } - } - return (standardOutput: stdout, standardError: stderror) - } - } -} diff --git a/Sources/_Subprocess/SubprocessFoundation/Input+Foundation.swift b/Sources/_Subprocess/SubprocessFoundation/Input+Foundation.swift new file mode 100644 index 000000000..3ec421737 --- /dev/null +++ b/Sources/_Subprocess/SubprocessFoundation/Input+Foundation.swift @@ -0,0 +1,179 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#if SubprocessFoundation + +#if canImport(Darwin) +// On Darwin always prefer system Foundation +import Foundation +#else +// On other platforms prefer FoundationEssentials +import FoundationEssentials +#endif // canImport(Darwin) + +#if canImport(System) +import System +#else +@preconcurrency import SystemPackage +#endif + +internal import Dispatch + +/// A concrete `Input` type for subprocesses that reads input +/// from a given `Data`. +public struct DataInput: InputProtocol { + private let data: Data + + public func write(with writer: StandardInputWriter) async throws { + _ = try await writer.write(self.data) + } + + internal init(data: Data) { + self.data = data + } +} + +/// A concrete `Input` type for subprocesses that accepts input +/// from a specified sequence of `Data`. +public struct DataSequenceInput< + InputSequence: Sequence & Sendable +>: InputProtocol where InputSequence.Element == Data { + private let sequence: InputSequence + + public func write(with writer: StandardInputWriter) async throws { + var buffer = Data() + for chunk in self.sequence { + buffer.append(chunk) + } + _ = try await writer.write(buffer) + } + + internal init(underlying: InputSequence) { + self.sequence = underlying + } +} + +/// A concrete `Input` type for subprocesses that reads input +/// from a given async sequence of `Data`. +public struct DataAsyncSequenceInput< + InputSequence: AsyncSequence & Sendable +>: InputProtocol where InputSequence.Element == Data { + private let sequence: InputSequence + + private func writeChunk(_ chunk: Data, with writer: StandardInputWriter) async throws { + _ = try await writer.write(chunk) + } + + public func write(with writer: StandardInputWriter) async throws { + for try await chunk in self.sequence { + try await self.writeChunk(chunk, with: writer) + } + } + + internal init(underlying: InputSequence) { + self.sequence = underlying + } +} + +extension InputProtocol { + /// Create a Subprocess input from a `Data` + public static func data(_ data: Data) -> Self where Self == DataInput { + return DataInput(data: data) + } + + /// Create a Subprocess input from a `Sequence` of `Data`. + public static func sequence( + _ sequence: InputSequence + ) -> Self where Self == DataSequenceInput { + return .init(underlying: sequence) + } + + /// Create a Subprocess input from a `AsyncSequence` of `Data`. + public static func sequence( + _ asyncSequence: InputSequence + ) -> Self where Self == DataAsyncSequenceInput { + return .init(underlying: asyncSequence) + } +} + +extension StandardInputWriter { + /// Write a `Data` to the standard input of the subprocess. + /// - Parameter data: The sequence of bytes to write. + /// - Returns number of bytes written. + public func write( + _ data: Data + ) async throws -> Int { + return try await self.fileDescriptor.wrapped.write(data) + } + + /// Write a AsyncSequence of Data to the standard input of the subprocess. + /// - Parameter sequence: The sequence of bytes to write. + /// - Returns number of bytes written. + public func write( + _ asyncSequence: AsyncSendableSequence + ) async throws -> Int where AsyncSendableSequence.Element == Data { + var buffer = Data() + for try await data in asyncSequence { + buffer.append(data) + } + return try await self.write(buffer) + } +} + +extension FileDescriptor { + #if os(Windows) + internal func write( + _ data: Data + ) async throws -> Int { + try await withCheckedThrowingContinuation { continuation in + // TODO: Figure out a better way to asynchornously write + DispatchQueue.global(qos: .userInitiated).async { + data.withUnsafeBytes { + self.write($0) { writtenLength, error in + if let error = error { + continuation.resume(throwing: error) + } else { + continuation.resume(returning: writtenLength) + } + } + } + } + } + } + #else + internal func write( + _ data: Data + ) async throws -> Int { + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + let dispatchData = data.withUnsafeBytes { + return DispatchData( + bytesNoCopy: $0, + deallocator: .custom( + nil, + { + // noop + } + ) + ) + } + self.write(dispatchData) { writtenLength, error in + if let error = error { + continuation.resume(throwing: error) + } else { + continuation.resume(returning: writtenLength) + } + } + } + } + #endif +} + +#endif // SubprocessFoundation diff --git a/Sources/_Subprocess/SubprocessFoundation/Output+Foundation.swift b/Sources/_Subprocess/SubprocessFoundation/Output+Foundation.swift new file mode 100644 index 000000000..fac0bb8f1 --- /dev/null +++ b/Sources/_Subprocess/SubprocessFoundation/Output+Foundation.swift @@ -0,0 +1,53 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#if SubprocessFoundation + +#if canImport(Darwin) +// On Darwin always prefer system Foundation +import Foundation +#else +// On other platforms prefer FoundationEssentials +import FoundationEssentials +#endif + +/// A concrete `Output` type for subprocesses that collects output +/// from the subprocess as `Data`. This option must be used with +/// the `run()` method that returns a `CollectedResult` +public struct DataOutput: OutputProtocol { + public typealias OutputType = Data + public let maxSize: Int + + public func output(from buffer: some Sequence) throws -> Data { + return Data(buffer) + } + + internal init(limit: Int) { + self.maxSize = limit + } +} + +extension OutputProtocol where Self == DataOutput { + /// Create a `Subprocess` output that collects output as `Data` + /// up to 128kb. + public static var data: Self { + return .data(limit: 128 * 1024) + } + + /// Create a `Subprocess` output that collects output as `Data` + /// with given max number of bytes to collect. + public static func data(limit: Int) -> Self { + return .init(limit: limit) + } +} + + +#endif // SubprocessFoundation diff --git a/Sources/_Subprocess/SubprocessFoundation/Span+SubprocessFoundation.swift b/Sources/_Subprocess/SubprocessFoundation/Span+SubprocessFoundation.swift new file mode 100644 index 000000000..10697091e --- /dev/null +++ b/Sources/_Subprocess/SubprocessFoundation/Span+SubprocessFoundation.swift @@ -0,0 +1,76 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#if SubprocessFoundation && SubprocessSpan + +#if canImport(Darwin) +// On Darwin always prefer system Foundation +import Foundation +#else +// On other platforms prefer FoundationEssentials +import FoundationEssentials +#endif // canImport(Darwin) + +internal import Dispatch + + +extension Data { + init(_ s: borrowing RawSpan) { + self = s.withUnsafeBytes { Data($0) } + } + + public var bytes: RawSpan { + // FIXME: For demo purpose only + let ptr = self.withUnsafeBytes { ptr in + return ptr + } + let span = RawSpan(_unsafeBytes: ptr) + return _overrideLifetime(of: span, to: self) + } +} + + +extension DataProtocol { + var bytes: RawSpan { + _read { + if self.regions.isEmpty { + let empty = UnsafeRawBufferPointer(start: nil, count: 0) + let span = RawSpan(_unsafeBytes: empty) + yield _overrideLifetime(of: span, to: self) + } else if self.regions.count == 1 { + // Easy case: there is only one region in the data + let ptr = self.regions.first!.withUnsafeBytes { ptr in + return ptr + } + let span = RawSpan(_unsafeBytes: ptr) + yield _overrideLifetime(of: span, to: self) + } else { + // This data contains discontiguous chunks. We have to + // copy and make a contiguous chunk + var contiguous: ContiguousArray? + for region in self.regions { + if contiguous != nil { + contiguous?.append(contentsOf: region) + } else { + contiguous = .init(region) + } + } + let ptr = contiguous!.withUnsafeBytes { ptr in + return ptr + } + let span = RawSpan(_unsafeBytes: ptr) + yield _overrideLifetime(of: span, to: self) + } + } + } +} + +#endif // SubprocessFoundation diff --git a/Sources/_Subprocess/Teardown.swift b/Sources/_Subprocess/Teardown.swift new file mode 100644 index 000000000..b2da85a5f --- /dev/null +++ b/Sources/_Subprocess/Teardown.swift @@ -0,0 +1,217 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import _SubprocessCShims + +#if canImport(Darwin) +import Darwin +#elseif canImport(Bionic) +import Bionic +#elseif canImport(Glibc) +import Glibc +#elseif canImport(Musl) +import Musl +#elseif canImport(WinSDK) +import WinSDK +#endif + +/// A step in the graceful shutdown teardown sequence. +/// It consists of an action to perform on the child process and the +/// duration allowed for the child process to exit before proceeding +/// to the next step. +public struct TeardownStep: Sendable, Hashable { + internal enum Storage: Sendable, Hashable { + #if !os(Windows) + case sendSignal(Signal, allowedDuration: Duration) + #endif + case gracefulShutDown(allowedDuration: Duration) + case kill + } + var storage: Storage + + #if !os(Windows) + /// Sends `signal` to the process and allows `allowedDurationToExit` + /// for the process to exit before proceeding to the next step. + /// The final step in the sequence will always send a `.kill` signal. + public static func send( + signal: Signal, + allowedDurationToNextStep: Duration + ) -> Self { + return Self( + storage: .sendSignal( + signal, + allowedDuration: allowedDurationToNextStep + ) + ) + } + #endif // !os(Windows) + + /// Attempt to perform a graceful shutdown and allows + /// `allowedDurationToNextStep` for the process to exit + /// before proceeding to the next step: + /// - On Unix: send `SIGTERM` + /// - On Windows: + /// 1. Attempt to send `VM_CLOSE` if the child process is a GUI process; + /// 2. Attempt to send `CTRL_C_EVENT` to console; + /// 3. Attempt to send `CTRL_BREAK_EVENT` to process group. + public static func gracefulShutDown( + allowedDurationToNextStep: Duration + ) -> Self { + return Self( + storage: .gracefulShutDown( + allowedDuration: allowedDurationToNextStep + ) + ) + } +} + +@available(macOS 15.0, *) // FIXME: manually added availability +extension Execution { + /// Performs a sequence of teardown steps on the Subprocess. + /// Teardown sequence always ends with a `.kill` signal + /// - Parameter sequence: The steps to perform. + public func teardown(using sequence: some Sequence & Sendable) async { + await withUncancelledTask { + await self.runTeardownSequence(sequence) + } + } +} + +internal enum TeardownStepCompletion { + case processHasExited + case processStillAlive + case killedTheProcess +} + +@available(macOS 15.0, *) // FIXME: manually added availability +extension Execution { + internal func gracefulShutDown( + allowedDurationToNextStep duration: Duration + ) async { + #if os(Windows) + guard + let processHandle = OpenProcess( + DWORD(PROCESS_QUERY_INFORMATION | SYNCHRONIZE), + false, + self.processIdentifier.value + ) + else { + // Nothing more we can do + return + } + defer { + CloseHandle(processHandle) + } + + // 1. Attempt to send WM_CLOSE to the main window + if _subprocess_windows_send_vm_close( + self.processIdentifier.value + ) { + try? await Task.sleep(for: duration) + } + + // 2. Attempt to attach to the console and send CTRL_C_EVENT + if AttachConsole(self.processIdentifier.value) { + // Disable Ctrl-C handling in this process + if SetConsoleCtrlHandler(nil, true) { + if GenerateConsoleCtrlEvent(DWORD(CTRL_C_EVENT), 0) { + // We successfully sent the event. wait for the process to exit + try? await Task.sleep(for: duration) + } + // Re-enable Ctrl-C handling + SetConsoleCtrlHandler(nil, false) + } + // Detach console + FreeConsole() + } + + // 3. Attempt to send CTRL_BREAK_EVENT to the process group + if GenerateConsoleCtrlEvent(DWORD(CTRL_BREAK_EVENT), self.processIdentifier.value) { + // Wait for process to exit + try? await Task.sleep(for: duration) + } + #else + // Send SIGTERM + try? self.send(signal: .terminate) + #endif + } + + internal func runTeardownSequence(_ sequence: some Sequence & Sendable) async { + // First insert the `.kill` step + let finalSequence = sequence + [TeardownStep(storage: .kill)] + for step in finalSequence { + let stepCompletion: TeardownStepCompletion + + switch step.storage { + case .gracefulShutDown(let allowedDuration): + stepCompletion = await withTaskGroup(of: TeardownStepCompletion.self) { group in + group.addTask { + do { + try await Task.sleep(for: allowedDuration) + return .processStillAlive + } catch { + // teardown(using:) cancells this task + // when process has exited + return .processHasExited + } + } + await self.gracefulShutDown(allowedDurationToNextStep: allowedDuration) + return await group.next()! + } + #if !os(Windows) + case .sendSignal(let signal, let allowedDuration): + stepCompletion = await withTaskGroup(of: TeardownStepCompletion.self) { group in + group.addTask { + do { + try await Task.sleep(for: allowedDuration) + return .processStillAlive + } catch { + // teardown(using:) cancells this task + // when process has exited + return .processHasExited + } + } + try? self.send(signal: signal) + return await group.next()! + } + #endif // !os(Windows) + case .kill: + #if os(Windows) + try? self.terminate(withExitCode: 0) + #else + try? self.send(signal: .kill) + #endif + stepCompletion = .killedTheProcess + } + + switch stepCompletion { + case .killedTheProcess, .processHasExited: + return + case .processStillAlive: + // Continue to next step + break + } + } + } +} + +func withUncancelledTask( + returning: Result.Type = Result.self, + _ body: @Sendable @escaping () async -> Result +) async -> Result { + // This looks unstructured but it isn't, please note that we `await` `.value` of this task. + // The reason we need this separate `Task` is that in general, we cannot assume that code performs to our + // expectations if the task we run it on is already cancelled. However, in some cases we need the code to + // run regardless -- even if our task is already cancelled. Therefore, we create a new, uncancelled task here. + await Task { + await body() + }.value +} diff --git a/Sources/_Subprocess/_nio_locks.swift b/Sources/_Subprocess/_nio_locks.swift deleted file mode 100644 index 49053d048..000000000 --- a/Sources/_Subprocess/_nio_locks.swift +++ /dev/null @@ -1,526 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftNIO open source project -// -// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftNIO project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -#if canImport(Darwin) -import Darwin -#elseif os(Windows) -import ucrt -import WinSDK -#elseif canImport(Glibc) -import Glibc -#elseif canImport(Musl) -import Musl -#else -#error("The concurrency lock module was unable to identify your C library.") -#endif - -/// A threading lock based on `libpthread` instead of `libdispatch`. -/// -/// This object provides a lock on top of a single `pthread_mutex_t`. This kind -/// of lock is safe to use with `libpthread`-based threading models, such as the -/// one used by NIO. On Windows, the lock is based on the substantially similar -/// `SRWLOCK` type. -@available(*, deprecated, renamed: "NIOLock") -public final class Lock { -#if os(Windows) - fileprivate let mutex: UnsafeMutablePointer = - UnsafeMutablePointer.allocate(capacity: 1) -#else - fileprivate let mutex: UnsafeMutablePointer = - UnsafeMutablePointer.allocate(capacity: 1) -#endif - - /// Create a new lock. - public init() { -#if os(Windows) - InitializeSRWLock(self.mutex) -#else - var attr = pthread_mutexattr_t() - pthread_mutexattr_init(&attr) - debugOnly { - pthread_mutexattr_settype(&attr, .init(PTHREAD_MUTEX_ERRORCHECK)) - } - - let err = pthread_mutex_init(self.mutex, &attr) - precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") -#endif - } - - deinit { -#if os(Windows) - // SRWLOCK does not need to be free'd -#else - let err = pthread_mutex_destroy(self.mutex) - precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") -#endif - mutex.deallocate() - } - - /// Acquire the lock. - /// - /// Whenever possible, consider using `withLock` instead of this method and - /// `unlock`, to simplify lock handling. - public func lock() { -#if os(Windows) - AcquireSRWLockExclusive(self.mutex) -#else - let err = pthread_mutex_lock(self.mutex) - precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") -#endif - } - - /// Release the lock. - /// - /// Whenever possible, consider using `withLock` instead of this method and - /// `lock`, to simplify lock handling. - public func unlock() { -#if os(Windows) - ReleaseSRWLockExclusive(self.mutex) -#else - let err = pthread_mutex_unlock(self.mutex) - precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") -#endif - } - - /// Acquire the lock for the duration of the given block. - /// - /// This convenience method should be preferred to `lock` and `unlock` in - /// most situations, as it ensures that the lock will be released regardless - /// of how `body` exits. - /// - /// - Parameter body: The block to execute while holding the lock. - /// - Returns: The value returned by the block. - @inlinable - public func withLock(_ body: () throws -> T) rethrows -> T { - self.lock() - defer { - self.unlock() - } - return try body() - } - - // specialise Void return (for performance) - @inlinable - public func withLockVoid(_ body: () throws -> Void) rethrows -> Void { - try self.withLock(body) - } -} - -/// A `Lock` with a built-in state variable. -/// -/// This class provides a convenience addition to `Lock`: it provides the ability to wait -/// until the state variable is set to a specific value to acquire the lock. -public final class ConditionLock { - private var _value: T - private let mutex: NIOLock -#if os(Windows) - private let cond: UnsafeMutablePointer = - UnsafeMutablePointer.allocate(capacity: 1) -#else - private let cond: UnsafeMutablePointer = - UnsafeMutablePointer.allocate(capacity: 1) -#endif - - /// Create the lock, and initialize the state variable to `value`. - /// - /// - Parameter value: The initial value to give the state variable. - public init(value: T) { - self._value = value - self.mutex = NIOLock() -#if os(Windows) - InitializeConditionVariable(self.cond) -#else - let err = pthread_cond_init(self.cond, nil) - precondition(err == 0, "\(#function) failed in pthread_cond with error \(err)") -#endif - } - - deinit { -#if os(Windows) - // condition variables do not need to be explicitly destroyed -#else - let err = pthread_cond_destroy(self.cond) - precondition(err == 0, "\(#function) failed in pthread_cond with error \(err)") -#endif - self.cond.deallocate() - } - - /// Acquire the lock, regardless of the value of the state variable. - public func lock() { - self.mutex.lock() - } - - /// Release the lock, regardless of the value of the state variable. - public func unlock() { - self.mutex.unlock() - } - - /// The value of the state variable. - /// - /// Obtaining the value of the state variable requires acquiring the lock. - /// This means that it is not safe to access this property while holding the - /// lock: it is only safe to use it when not holding it. - public var value: T { - self.lock() - defer { - self.unlock() - } - return self._value - } - - /// Acquire the lock when the state variable is equal to `wantedValue`. - /// - /// - Parameter wantedValue: The value to wait for the state variable - /// to have before acquiring the lock. - public func lock(whenValue wantedValue: T) { - self.lock() - while true { - if self._value == wantedValue { - break - } - self.mutex.withLockPrimitive { mutex in -#if os(Windows) - let result = SleepConditionVariableSRW(self.cond, mutex, INFINITE, 0) - precondition(result, "\(#function) failed in SleepConditionVariableSRW with error \(GetLastError())") -#else - let err = pthread_cond_wait(self.cond, mutex) - precondition(err == 0, "\(#function) failed in pthread_cond with error \(err)") -#endif - } - } - } - - /// Acquire the lock when the state variable is equal to `wantedValue`, - /// waiting no more than `timeoutSeconds` seconds. - /// - /// - Parameter wantedValue: The value to wait for the state variable - /// to have before acquiring the lock. - /// - Parameter timeoutSeconds: The number of seconds to wait to acquire - /// the lock before giving up. - /// - Returns: `true` if the lock was acquired, `false` if the wait timed out. - public func lock(whenValue wantedValue: T, timeoutSeconds: Double) -> Bool { - precondition(timeoutSeconds >= 0) - -#if os(Windows) - var dwMilliseconds: DWORD = DWORD(timeoutSeconds * 1000) - - self.lock() - while true { - if self._value == wantedValue { - return true - } - - let dwWaitStart = timeGetTime() - if !SleepConditionVariableSRW(self.cond, self.mutex._storage.mutex, - dwMilliseconds, 0) { - let dwError = GetLastError() - if (dwError == ERROR_TIMEOUT) { - self.unlock() - return false - } - fatalError("SleepConditionVariableSRW: \(dwError)") - } - - // NOTE: this may be a spurious wakeup, adjust the timeout accordingly - dwMilliseconds = dwMilliseconds - (timeGetTime() - dwWaitStart) - } -#else - let nsecPerSec: Int64 = 1000000000 - self.lock() - /* the timeout as a (seconds, nano seconds) pair */ - let timeoutNS = Int64(timeoutSeconds * Double(nsecPerSec)) - - var curTime = timeval() - gettimeofday(&curTime, nil) - - let allNSecs: Int64 = timeoutNS + Int64(curTime.tv_usec) * 1000 - var timeoutAbs = timespec(tv_sec: curTime.tv_sec + Int((allNSecs / nsecPerSec)), - tv_nsec: Int(allNSecs % nsecPerSec)) - assert(timeoutAbs.tv_nsec >= 0 && timeoutAbs.tv_nsec < Int(nsecPerSec)) - assert(timeoutAbs.tv_sec >= curTime.tv_sec) - return self.mutex.withLockPrimitive { mutex -> Bool in - while true { - if self._value == wantedValue { - return true - } - switch pthread_cond_timedwait(self.cond, mutex, &timeoutAbs) { - case 0: - continue - case ETIMEDOUT: - self.unlock() - return false - case let e: - fatalError("caught error \(e) when calling pthread_cond_timedwait") - } - } - } -#endif - } - - /// Release the lock, setting the state variable to `newValue`. - /// - /// - Parameter newValue: The value to give to the state variable when we - /// release the lock. - public func unlock(withValue newValue: T) { - self._value = newValue - self.unlock() -#if os(Windows) - WakeAllConditionVariable(self.cond) -#else - let err = pthread_cond_broadcast(self.cond) - precondition(err == 0, "\(#function) failed in pthread_cond with error \(err)") -#endif - } -} - -/// A utility function that runs the body code only in debug builds, without -/// emitting compiler warnings. -/// -/// This is currently the only way to do this in Swift: see -/// https://forums.swift.org/t/support-debug-only-code/11037 for a discussion. -@inlinable -internal func debugOnly(_ body: () -> Void) { - assert({ body(); return true }()) -} - -@available(*, deprecated) -extension Lock: @unchecked Sendable {} -extension ConditionLock: @unchecked Sendable {} - -#if os(Windows) -@usableFromInline -typealias LockPrimitive = SRWLOCK -#else -@usableFromInline -typealias LockPrimitive = pthread_mutex_t -#endif - -@usableFromInline -enum LockOperations { } - -extension LockOperations { - @inlinable - static func create(_ mutex: UnsafeMutablePointer) { - mutex.assertValidAlignment() - -#if os(Windows) - InitializeSRWLock(mutex) -#else - var attr = pthread_mutexattr_t() - pthread_mutexattr_init(&attr) - debugOnly { - pthread_mutexattr_settype(&attr, .init(PTHREAD_MUTEX_ERRORCHECK)) - } - - let err = pthread_mutex_init(mutex, &attr) - precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") -#endif - } - - @inlinable - static func destroy(_ mutex: UnsafeMutablePointer) { - mutex.assertValidAlignment() - -#if os(Windows) - // SRWLOCK does not need to be free'd -#else - let err = pthread_mutex_destroy(mutex) - precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") -#endif - } - - @inlinable - static func lock(_ mutex: UnsafeMutablePointer) { - mutex.assertValidAlignment() - -#if os(Windows) - AcquireSRWLockExclusive(mutex) -#else - let err = pthread_mutex_lock(mutex) - precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") -#endif - } - - @inlinable - static func unlock(_ mutex: UnsafeMutablePointer) { - mutex.assertValidAlignment() - -#if os(Windows) - ReleaseSRWLockExclusive(mutex) -#else - let err = pthread_mutex_unlock(mutex) - precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") -#endif - } -} - -// Tail allocate both the mutex and a generic value using ManagedBuffer. -// Both the header pointer and the elements pointer are stable for -// the class's entire lifetime. -// -// However, for safety reasons, we elect to place the lock in the "elements" -// section of the buffer instead of the head. The reasoning here is subtle, -// so buckle in. -// -// _As a practical matter_, the implementation of ManagedBuffer ensures that -// the pointer to the header is stable across the lifetime of the class, and so -// each time you call `withUnsafeMutablePointers` or `withUnsafeMutablePointerToHeader` -// the value of the header pointer will be the same. This is because ManagedBuffer uses -// `Builtin.addressOf` to load the value of the header, and that does ~magic~ to ensure -// that it does not invoke any weird Swift accessors that might copy the value. -// -// _However_, the header is also available via the `.header` field on the ManagedBuffer. -// This presents a problem! The reason there's an issue is that `Builtin.addressOf` and friends -// do not interact with Swift's exclusivity model. That is, the various `with` functions do not -// conceptually trigger a mutating access to `.header`. For elements this isn't a concern because -// there's literally no other way to perform the access, but for `.header` it's entirely possible -// to accidentally recursively read it. -// -// Our implementation is free from these issues, so we don't _really_ need to worry about it. -// However, out of an abundance of caution, we store the Value in the header, and the LockPrimitive -// in the trailing elements. We still don't use `.header`, but it's better to be safe than sorry, -// and future maintainers will be happier that we were cautious. -// -// See also: https://github.com/apple/swift/pull/40000 -@usableFromInline -final class LockStorage: ManagedBuffer { - - @inlinable - static func create(value: Value) -> Self { - let buffer = Self.create(minimumCapacity: 1) { _ in - return value - } - let storage = unsafeDowncast(buffer, to: Self.self) - - storage.withUnsafeMutablePointers { _, lockPtr in - LockOperations.create(lockPtr) - } - - return storage - } - - @inlinable - func lock() { - self.withUnsafeMutablePointerToElements { lockPtr in - LockOperations.lock(lockPtr) - } - } - - @inlinable - func unlock() { - self.withUnsafeMutablePointerToElements { lockPtr in - LockOperations.unlock(lockPtr) - } - } - - @inlinable - deinit { - self.withUnsafeMutablePointerToElements { lockPtr in - LockOperations.destroy(lockPtr) - } - } - - @inlinable - func withLockPrimitive(_ body: (UnsafeMutablePointer) throws -> T) rethrows -> T { - try self.withUnsafeMutablePointerToElements { lockPtr in - return try body(lockPtr) - } - } - - @inlinable - func withLockedValue(_ mutate: (inout Value) throws -> T) rethrows -> T { - try self.withUnsafeMutablePointers { valuePtr, lockPtr in - LockOperations.lock(lockPtr) - defer { LockOperations.unlock(lockPtr) } - return try mutate(&valuePtr.pointee) - } - } -} - -extension LockStorage: @unchecked Sendable { } - -/// A threading lock based on `libpthread` instead of `libdispatch`. -/// -/// - note: ``NIOLock`` has reference semantics. -/// -/// This object provides a lock on top of a single `pthread_mutex_t`. This kind -/// of lock is safe to use with `libpthread`-based threading models, such as the -/// one used by NIO. On Windows, the lock is based on the substantially similar -/// `SRWLOCK` type. -public struct NIOLock { - @usableFromInline - internal let _storage: LockStorage - - /// Create a new lock. - @inlinable - public init() { - self._storage = .create(value: ()) - } - - /// Acquire the lock. - /// - /// Whenever possible, consider using `withLock` instead of this method and - /// `unlock`, to simplify lock handling. - @inlinable - public func lock() { - self._storage.lock() - } - - /// Release the lock. - /// - /// Whenever possible, consider using `withLock` instead of this method and - /// `lock`, to simplify lock handling. - @inlinable - public func unlock() { - self._storage.unlock() - } - - @inlinable - internal func withLockPrimitive(_ body: (UnsafeMutablePointer) throws -> T) rethrows -> T { - return try self._storage.withLockPrimitive(body) - } -} - -extension NIOLock { - /// Acquire the lock for the duration of the given block. - /// - /// This convenience method should be preferred to `lock` and `unlock` in - /// most situations, as it ensures that the lock will be released regardless - /// of how `body` exits. - /// - /// - Parameter body: The block to execute while holding the lock. - /// - Returns: The value returned by the block. - @inlinable - public func withLock(_ body: () throws -> T) rethrows -> T { - self.lock() - defer { - self.unlock() - } - return try body() - } - - @inlinable - public func withLockVoid(_ body: () throws -> Void) rethrows -> Void { - try self.withLock(body) - } -} - -extension NIOLock: Sendable {} - -extension UnsafeMutablePointer { - @inlinable - func assertValidAlignment() { - assert(UInt(bitPattern: self) % UInt(MemoryLayout.alignment) == 0) - } -} diff --git a/Sources/_CShims/include/process_shims.h b/Sources/_SubprocessCShims/include/process_shims.h similarity index 74% rename from Sources/_CShims/include/process_shims.h rename to Sources/_SubprocessCShims/include/process_shims.h index 563b517f8..35cbd2fe3 100644 --- a/Sources/_CShims/include/process_shims.h +++ b/Sources/_SubprocessCShims/include/process_shims.h @@ -2,20 +2,17 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception // -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 +// See https://swift.org/LICENSE.txt for license information // //===----------------------------------------------------------------------===// #ifndef process_shims_h #define process_shims_h -#include "_CShimsTargetConditionals.h" +#include "target_conditionals.h" #if !TARGET_OS_WINDOWS #include @@ -24,6 +21,10 @@ #include #endif +#if __has_include() +vm_size_t _subprocess_vm_size(void); +#endif + #if TARGET_OS_MAC int _subprocess_spawn( pid_t * _Nonnull pid, @@ -60,6 +61,10 @@ int _was_process_signaled(int status); int _get_signal_code(int status); int _was_process_suspended(int status); +void _subprocess_lock_environ(void); +void _subprocess_unlock_environ(void); +char * _Nullable * _Nullable _subprocess_get_environ(void); + #if TARGET_OS_LINUX int _shims_snprintf( char * _Nonnull str, @@ -72,4 +77,15 @@ int _shims_snprintf( #endif // !TARGET_OS_WINDOWS +#if TARGET_OS_WINDOWS + +#ifndef _WINDEF_ +typedef unsigned long DWORD; +typedef int BOOL; +#endif + +BOOL _subprocess_windows_send_vm_close(DWORD pid); + +#endif + #endif /* process_shims_h */ diff --git a/Sources/_CShims/include/_CShimsTargetConditionals.h b/Sources/_SubprocessCShims/include/target_conditionals.h similarity index 76% rename from Sources/_CShims/include/_CShimsTargetConditionals.h rename to Sources/_SubprocessCShims/include/target_conditionals.h index 9e1d80cb7..fef2eaf2e 100644 --- a/Sources/_CShims/include/_CShimsTargetConditionals.h +++ b/Sources/_SubprocessCShims/include/target_conditionals.h @@ -2,13 +2,11 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 +// Copyright (c) 2021 - 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception // -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// diff --git a/Sources/_SubprocessCShims/process_shims.c b/Sources/_SubprocessCShims/process_shims.c new file mode 100644 index 000000000..287e1a8d0 --- /dev/null +++ b/Sources/_SubprocessCShims/process_shims.c @@ -0,0 +1,682 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +#include "include/target_conditionals.h" + +#if TARGET_OS_LINUX +// For posix_spawn_file_actions_addchdir_np +#define _GNU_SOURCE 1 +#endif + +#include "include/process_shims.h" + +#if TARGET_OS_WINDOWS +#include +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if __has_include() +#include +#elif defined(_WIN32) +#include +#elif __has_include() +#include +extern char **environ; +#endif + +int _was_process_exited(int status) { + return WIFEXITED(status); +} + +int _get_exit_code(int status) { + return WEXITSTATUS(status); +} + +int _was_process_signaled(int status) { + return WIFSIGNALED(status); +} + +int _get_signal_code(int status) { + return WTERMSIG(status); +} + +int _was_process_suspended(int status) { + return WIFSTOPPED(status); +} + +#if TARGET_OS_LINUX +#include + +int _shims_snprintf( + char * _Nonnull str, + int len, + const char * _Nonnull format, + char * _Nonnull str1, + char * _Nonnull str2 +) { + return snprintf(str, len, format, str1, str2); +} +#endif + +#if __has_include() +vm_size_t _subprocess_vm_size(void) { + // This shim exists because vm_page_size is not marked const, and therefore looks like global mutable state to Swift. + return vm_page_size; +} +#endif + +// MARK: - Darwin (posix_spawn) +#if TARGET_OS_MAC +static int _subprocess_spawn_prefork( + pid_t * _Nonnull pid, + const char * _Nonnull exec_path, + const posix_spawn_file_actions_t _Nullable * _Nonnull file_actions, + const posix_spawnattr_t _Nullable * _Nonnull spawn_attrs, + char * _Nullable const args[_Nonnull], + char * _Nullable const env[_Nullable], + uid_t * _Nullable uid, + gid_t * _Nullable gid, + int number_of_sgroups, const gid_t * _Nullable sgroups, + int create_session +) { + // Set `POSIX_SPAWN_SETEXEC` flag since we are forking ourselves + short flags = 0; + int rc = posix_spawnattr_getflags(spawn_attrs, &flags); + if (rc != 0) { + return rc; + } + + rc = posix_spawnattr_setflags( + (posix_spawnattr_t *)spawn_attrs, flags | POSIX_SPAWN_SETEXEC + ); + if (rc != 0) { + return rc; + } + // Setup pipe to catch exec failures from child + int pipefd[2]; + if (pipe(pipefd) != 0) { + return errno; + } + // Set FD_CLOEXEC so the pipe is automatically closed when exec succeeds + flags = fcntl(pipefd[0], F_GETFD); + if (flags == -1) { + close(pipefd[0]); + close(pipefd[1]); + return errno; + } + flags |= FD_CLOEXEC; + if (fcntl(pipefd[0], F_SETFD, flags) == -1) { + close(pipefd[0]); + close(pipefd[1]); + return errno; + } + + flags = fcntl(pipefd[1], F_GETFD); + if (flags == -1) { + close(pipefd[0]); + close(pipefd[1]); + return errno; + } + flags |= FD_CLOEXEC; + if (fcntl(pipefd[1], F_SETFD, flags) == -1) { + close(pipefd[0]); + close(pipefd[1]); + return errno; + } + + // Finally, fork +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated" + pid_t childPid = fork(); +#pragma GCC diagnostic pop + if (childPid == -1) { + close(pipefd[0]); + close(pipefd[1]); + return errno; + } + + if (childPid == 0) { + // Child process + close(pipefd[0]); // Close unused read end + + // Perform setups + if (number_of_sgroups > 0 && sgroups != NULL) { + if (setgroups(number_of_sgroups, sgroups) != 0) { + int error = errno; + write(pipefd[1], &error, sizeof(error)); + close(pipefd[1]); + _exit(EXIT_FAILURE); + } + } + + if (uid != NULL) { + if (setuid(*uid) != 0) { + int error = errno; + write(pipefd[1], &error, sizeof(error)); + close(pipefd[1]); + _exit(EXIT_FAILURE); + } + } + + if (gid != NULL) { + if (setgid(*gid) != 0) { + int error = errno; + write(pipefd[1], &error, sizeof(error)); + close(pipefd[1]); + _exit(EXIT_FAILURE); + } + } + + if (create_session != 0) { + (void)setsid(); + } + + // Use posix_spawnas exec + int error = posix_spawn(pid, exec_path, file_actions, spawn_attrs, args, env); + // If we reached this point, something went wrong + write(pipefd[1], &error, sizeof(error)); + close(pipefd[1]); + _exit(EXIT_FAILURE); + } else { + // Parent process + close(pipefd[1]); // Close unused write end + // Communicate child pid back + *pid = childPid; + // Read from the pipe until pipe is closed + // Eitehr due to exec succeeds or error is written + int childError = 0; + if (read(pipefd[0], &childError, sizeof(childError)) > 0) { + // We encountered error + close(pipefd[0]); + return childError; + } else { + // Child process exec was successful + close(pipefd[0]); + return 0; + } + } +} + +int _subprocess_spawn( + pid_t * _Nonnull pid, + const char * _Nonnull exec_path, + const posix_spawn_file_actions_t _Nullable * _Nonnull file_actions, + const posix_spawnattr_t _Nullable * _Nonnull spawn_attrs, + char * _Nullable const args[_Nonnull], + char * _Nullable const env[_Nullable], + uid_t * _Nullable uid, + gid_t * _Nullable gid, + int number_of_sgroups, const gid_t * _Nullable sgroups, + int create_session +) { + int require_pre_fork = uid != NULL || + gid != NULL || + number_of_sgroups > 0 || + create_session > 0; + + if (require_pre_fork != 0) { + int rc = _subprocess_spawn_prefork( + pid, + exec_path, + file_actions, spawn_attrs, + args, env, + uid, gid, number_of_sgroups, sgroups, create_session + ); + return rc; + } + + // Spawn + return posix_spawn(pid, exec_path, file_actions, spawn_attrs, args, env); +} + +#endif // TARGET_OS_MAC + +// MARK: - Linux (fork/exec + posix_spawn fallback) +#if TARGET_OS_LINUX + +#if _POSIX_SPAWN +static int _subprocess_is_addchdir_np_available() { +#if defined(__GLIBC__) && !__GLIBC_PREREQ(2, 29) + // Glibc versions prior to 2.29 don't support posix_spawn_file_actions_addchdir_np, impacting: + // - Amazon Linux 2 (EoL mid-2025) + return 0; +#elif defined(__OpenBSD__) || defined(__QNX__) + // Currently missing as of: + // - OpenBSD 7.5 (April 2024) + // - QNX 8 (December 2023) + return 0; +#elif defined(__GLIBC__) || TARGET_OS_DARWIN || defined(__FreeBSD__) || (defined(__ANDROID__) && __ANDROID_API__ >= 34) || defined(__musl__) + // Pre-standard posix_spawn_file_actions_addchdir_np version available in: + // - Solaris 11.3 (October 2015) + // - Glibc 2.29 (February 2019) + // - macOS 10.15 (October 2019) + // - musl 1.1.24 (October 2019) + // - FreeBSD 13.1 (May 2022) + // - Android 14 (October 2023) + return 1; +#else + // Standardized posix_spawn_file_actions_addchdir version (POSIX.1-2024, June 2024) available in: + // - Solaris 11.4 (August 2018) + // - NetBSD 10.0 (March 2024) + return 1; +#endif +} + +static int _subprocess_addchdir_np( + posix_spawn_file_actions_t *file_actions, + const char * __restrict path +) { +#if defined(__GLIBC__) && !__GLIBC_PREREQ(2, 29) + // Glibc versions prior to 2.29 don't support posix_spawn_file_actions_addchdir_np, impacting: + // - Amazon Linux 2 (EoL mid-2025) + // noop +#elif defined(__OpenBSD__) || defined(__QNX__) + // Currently missing as of: + // - OpenBSD 7.5 (April 2024) + // - QNX 8 (December 2023) + // noop +#elif defined(__GLIBC__) || TARGET_OS_DARWIN || defined(__FreeBSD__) || (defined(__ANDROID__) && __ANDROID_API__ >= 34) || defined(__musl__) + // Pre-standard posix_spawn_file_actions_addchdir_np version available in: + // - Solaris 11.3 (October 2015) + // - Glibc 2.29 (February 2019) + // - macOS 10.15 (October 2019) + // - musl 1.1.24 (October 2019) + // - FreeBSD 13.1 (May 2022) + // - Android 14 (October 2023) + return posix_spawn_file_actions_addchdir_np(file_actions, path); +#else + // Standardized posix_spawn_file_actions_addchdir version (POSIX.1-2024, June 2024) available in: + // - Solaris 11.4 (August 2018) + // - NetBSD 10.0 (March 2024) + return posix_spawn_file_actions_addchdir(file_actions, path); +#endif +} + +static int _subprocess_posix_spawn_fallback( + pid_t * _Nonnull pid, + const char * _Nonnull exec_path, + const char * _Nullable working_directory, + const int file_descriptors[_Nonnull], + char * _Nullable const args[_Nonnull], + char * _Nullable const env[_Nullable], + gid_t * _Nullable process_group_id +) { + // Setup stdin, stdout, and stderr + posix_spawn_file_actions_t file_actions; + + int rc = posix_spawn_file_actions_init(&file_actions); + if (rc != 0) { return rc; } + if (file_descriptors[0] >= 0) { + rc = posix_spawn_file_actions_adddup2( + &file_actions, file_descriptors[0], STDIN_FILENO + ); + if (rc != 0) { return rc; } + } + if (file_descriptors[2] >= 0) { + rc = posix_spawn_file_actions_adddup2( + &file_actions, file_descriptors[2], STDOUT_FILENO + ); + if (rc != 0) { return rc; } + } + if (file_descriptors[4] >= 0) { + rc = posix_spawn_file_actions_adddup2( + &file_actions, file_descriptors[4], STDERR_FILENO + ); + if (rc != 0) { return rc; } + } + // Setup working directory + rc = _subprocess_addchdir_np(&file_actions, working_directory); + if (rc != 0) { + return rc; + } + + // Close parent side + if (file_descriptors[1] >= 0) { + rc = posix_spawn_file_actions_addclose(&file_actions, file_descriptors[1]); + if (rc != 0) { return rc; } + } + if (file_descriptors[3] >= 0) { + rc = posix_spawn_file_actions_addclose(&file_actions, file_descriptors[3]); + if (rc != 0) { return rc; } + } + if (file_descriptors[5] >= 0) { + rc = posix_spawn_file_actions_addclose(&file_actions, file_descriptors[5]); + if (rc != 0) { return rc; } + } + + // Setup spawnattr + posix_spawnattr_t spawn_attr; + rc = posix_spawnattr_init(&spawn_attr); + if (rc != 0) { return rc; } + // Masks + sigset_t no_signals; + sigset_t all_signals; + sigemptyset(&no_signals); + sigfillset(&all_signals); + rc = posix_spawnattr_setsigmask(&spawn_attr, &no_signals); + if (rc != 0) { return rc; } + rc = posix_spawnattr_setsigdefault(&spawn_attr, &all_signals); + if (rc != 0) { return rc; } + // Flags + short flags = POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF; + if (process_group_id != NULL) { + flags |= POSIX_SPAWN_SETPGROUP; + rc = posix_spawnattr_setpgroup(&spawn_attr, *process_group_id); + if (rc != 0) { return rc; } + } + rc = posix_spawnattr_setflags(&spawn_attr, flags); + + // Spawn! + rc = posix_spawn( + pid, exec_path, + &file_actions, &spawn_attr, + args, env + ); + posix_spawn_file_actions_destroy(&file_actions); + posix_spawnattr_destroy(&spawn_attr); + return rc; +} +#endif // _POSIX_SPAWN + +int _subprocess_fork_exec( + pid_t * _Nonnull pid, + const char * _Nonnull exec_path, + const char * _Nullable working_directory, + const int file_descriptors[_Nonnull], + char * _Nullable const args[_Nonnull], + char * _Nullable const env[_Nullable], + uid_t * _Nullable uid, + gid_t * _Nullable gid, + gid_t * _Nullable process_group_id, + int number_of_sgroups, const gid_t * _Nullable sgroups, + int create_session, + void (* _Nullable configurator)(void) +) { + int require_pre_fork = _subprocess_is_addchdir_np_available() == 0 || + uid != NULL || + gid != NULL || + process_group_id != NULL || + (number_of_sgroups > 0 && sgroups != NULL) || + create_session || + configurator != NULL; + +#if _POSIX_SPAWN + // If posix_spawn is available on this platform and + // we do not require prefork, use posix_spawn if possible. + // + // (Glibc's posix_spawn does not support + // `POSIX_SPAWN_SETEXEC` therefore we have to keep + // using fork/exec if `require_pre_fork` is true. + if (require_pre_fork == 0) { + return _subprocess_posix_spawn_fallback( + pid, exec_path, + working_directory, + file_descriptors, + args, env, + process_group_id + ); + } +#endif + + // Setup pipe to catch exec failures from child + int pipefd[2]; + if (pipe(pipefd) != 0) { + return errno; + } + // Set FD_CLOEXEC so the pipe is automatically closed when exec succeeds + short flags = fcntl(pipefd[0], F_GETFD); + if (flags == -1) { + close(pipefd[0]); + close(pipefd[1]); + return errno; + } + flags |= FD_CLOEXEC; + if (fcntl(pipefd[0], F_SETFD, flags) == -1) { + close(pipefd[0]); + close(pipefd[1]); + return errno; + } + + flags = fcntl(pipefd[1], F_GETFD); + if (flags == -1) { + close(pipefd[0]); + close(pipefd[1]); + return errno; + } + flags |= FD_CLOEXEC; + if (fcntl(pipefd[1], F_SETFD, flags) == -1) { + close(pipefd[0]); + close(pipefd[1]); + return errno; + } + + // Finally, fork +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated" + pid_t childPid = fork(); +#pragma GCC diagnostic pop + if (childPid == -1) { + close(pipefd[0]); + close(pipefd[1]); + return errno; + } + + if (childPid == 0) { + // Child process + close(pipefd[0]); // Close unused read end + + // Perform setups + if (working_directory != NULL) { + if (chdir(working_directory) != 0) { + int error = errno; + write(pipefd[1], &error, sizeof(error)); + close(pipefd[1]); + _exit(EXIT_FAILURE); + } + } + + + if (uid != NULL) { + if (setuid(*uid) != 0) { + int error = errno; + write(pipefd[1], &error, sizeof(error)); + close(pipefd[1]); + _exit(EXIT_FAILURE); + } + } + + if (gid != NULL) { + if (setgid(*gid) != 0) { + int error = errno; + write(pipefd[1], &error, sizeof(error)); + close(pipefd[1]); + _exit(EXIT_FAILURE); + } + } + + if (number_of_sgroups > 0 && sgroups != NULL) { + if (setgroups(number_of_sgroups, sgroups) != 0) { + int error = errno; + write(pipefd[1], &error, sizeof(error)); + close(pipefd[1]); + _exit(EXIT_FAILURE); + } + } + + if (create_session != 0) { + (void)setsid(); + } + + if (process_group_id != NULL) { + (void)setpgid(0, *process_group_id); + } + + // Bind stdin, stdout, and stderr + int rc = 0; + if (file_descriptors[0] >= 0) { + rc = dup2(file_descriptors[0], STDIN_FILENO); + if (rc < 0) { + int error = errno; + write(pipefd[1], &error, sizeof(error)); + close(pipefd[1]); + _exit(EXIT_FAILURE); + } + } + if (file_descriptors[2] >= 0) { + rc = dup2(file_descriptors[2], STDOUT_FILENO); + if (rc < 0) { + int error = errno; + write(pipefd[1], &error, sizeof(error)); + close(pipefd[1]); + _exit(EXIT_FAILURE); + } + } + if (file_descriptors[4] >= 0) { + rc = dup2(file_descriptors[4], STDERR_FILENO); + if (rc < 0) { + int error = errno; + write(pipefd[1], &error, sizeof(error)); + close(pipefd[1]); + _exit(EXIT_FAILURE); + } + } + // Close parent side + if (file_descriptors[1] >= 0) { + rc = close(file_descriptors[1]); + } + if (file_descriptors[3] >= 0) { + rc = close(file_descriptors[3]); + } + if (file_descriptors[4] >= 0) { + rc = close(file_descriptors[5]); + } + if (rc != 0) { + int error = errno; + write(pipefd[1], &error, sizeof(error)); + close(pipefd[1]); + _exit(EXIT_FAILURE); + } + // Run custom configuratior + if (configurator != NULL) { + configurator(); + } + // Finally, exec + execve(exec_path, args, env); + // If we reached this point, something went wrong + int error = errno; + write(pipefd[1], &error, sizeof(error)); + close(pipefd[1]); + _exit(EXIT_FAILURE); + } else { + // Parent process + close(pipefd[1]); // Close unused write end + // Communicate child pid back + *pid = childPid; + // Read from the pipe until pipe is closed + // Eitehr due to exec succeeds or error is written + int childError = 0; + if (read(pipefd[0], &childError, sizeof(childError)) > 0) { + // We encountered error + close(pipefd[0]); + return childError; + } else { + // Child process exec was successful + close(pipefd[0]); + return 0; + } + } +} + +#endif // TARGET_OS_LINUX + +#endif // !TARGET_OS_WINDOWS + +#pragma mark - Environment Locking + +#if __has_include() +#import +void _subprocess_lock_environ(void) { + environ_lock_np(); +} + +void _subprocess_unlock_environ(void) { + environ_unlock_np(); +} +#else +void _subprocess_lock_environ(void) { /* noop */ } +void _subprocess_unlock_environ(void) { /* noop */ } +#endif + +char ** _subprocess_get_environ(void) { +#if __has_include() + return *_NSGetEnviron(); +#elif defined(_WIN32) +#include + return _environ; +#elif TARGET_OS_WASI + return __wasilibc_get_environ(); +#elif __has_include() + return environ; +#endif +} + + +#if TARGET_OS_WINDOWS + +typedef struct { + DWORD pid; + HWND mainWindow; +} CallbackContext; + +static BOOL CALLBACK enumWindowsCallback( + HWND hwnd, + LPARAM lParam +) { + CallbackContext *context = (CallbackContext *)lParam; + DWORD pid; + GetWindowThreadProcessId(hwnd, &pid); + if (pid == context->pid) { + context->mainWindow = hwnd; + return FALSE; // Stop enumeration + } + return TRUE; // Continue enumeration +} + +BOOL _subprocess_windows_send_vm_close( + DWORD pid +) { + // First attempt to find the Window associate + // with this process + CallbackContext context = {0}; + context.pid = pid; + EnumWindows(enumWindowsCallback, (LPARAM)&context); + + if (context.mainWindow != NULL) { + if (SendMessage(context.mainWindow, WM_CLOSE, 0, 0)) { + return TRUE; + } + } + + return FALSE; +} + +#endif + diff --git a/SwiftKit/build.gradle b/SwiftKit/build.gradle index e0896e668..8f3b6c51c 100644 --- a/SwiftKit/build.gradle +++ b/SwiftKit/build.gradle @@ -25,7 +25,7 @@ repositories { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(22)) + languageVersion.set(JavaLanguageVersion.of(24)) } } diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java index dea52154f..8d2d7ad25 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java +++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java @@ -41,8 +41,8 @@ public static long addressByteSize() { public static final ValueLayout.OfLong SWIFT_INT64 = ValueLayout.JAVA_LONG; public static final ValueLayout.OfFloat SWIFT_FLOAT = ValueLayout.JAVA_FLOAT; public static final ValueLayout.OfDouble SWIFT_DOUBLE = ValueLayout.JAVA_DOUBLE; - public static final AddressLayout SWIFT_POINTER = ValueLayout.ADDRESS - .withTargetLayout(MemoryLayout.sequenceLayout(Long.MAX_VALUE, JAVA_BYTE)); + public static final AddressLayout SWIFT_POINTER = ValueLayout.ADDRESS; + // .withTargetLayout(MemoryLayout.sequenceLayout(Long.MAX_VALUE, JAVA_BYTE)); public static final SequenceLayout SWIFT_BYTE_ARRAY = MemoryLayout.sequenceLayout(8, ValueLayout.JAVA_BYTE); /** diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java index 53680f5fa..2a031b7e4 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java +++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java @@ -71,7 +71,6 @@ public static MemorySegment fullTypeMetadata(MemorySegment typeMetadata) { public static MemorySegment valueWitnessTable(MemorySegment typeMetadata) { return fullTypeMetadata(typeMetadata) .get(SwiftValueLayout.SWIFT_POINTER, SwiftValueWitnessTable.fullTypeMetadata$vwt$offset); -// .get(ValueLayout.ADDRESS, SwiftValueWitnessTable.fullTypeMetadata$vwt$offset); } diff --git a/SwiftKit/src/test/java/org/swift/swiftkit/SwiftRuntimeMetadataTest.java b/SwiftKit/src/test/java/org/swift/swiftkit/SwiftRuntimeMetadataTest.java index ffefe72ed..3419405e1 100644 --- a/SwiftKit/src/test/java/org/swift/swiftkit/SwiftRuntimeMetadataTest.java +++ b/SwiftKit/src/test/java/org/swift/swiftkit/SwiftRuntimeMetadataTest.java @@ -20,42 +20,42 @@ public class SwiftRuntimeMetadataTest { - @Test - public void integer_layout_metadata() { - SwiftAnyType swiftType = SwiftKit.getTypeByMangledNameInEnvironment("Si").get(); - - if (SwiftValueLayout.addressByteSize() == 4) { - // 32-bit platform - Assertions.assertEquals(8, SwiftValueWitnessTable.sizeOfSwiftType(swiftType.$memorySegment())); - Assertions.assertEquals(8, SwiftValueWitnessTable.strideOfSwiftType(swiftType.$memorySegment())); - Assertions.assertEquals(8, SwiftValueWitnessTable.alignmentOfSwiftType(swiftType.$memorySegment())); - Assertions.assertEquals("[8%[9:b1]x7](Swift.Int)", SwiftValueWitnessTable.layoutOfSwiftType(swiftType.$memorySegment()).toString()); - } else { - // 64-bit platform - Assertions.assertEquals(8, SwiftValueWitnessTable.sizeOfSwiftType(swiftType.$memorySegment())); - Assertions.assertEquals(8, SwiftValueWitnessTable.strideOfSwiftType(swiftType.$memorySegment())); - Assertions.assertEquals(8, SwiftValueWitnessTable.alignmentOfSwiftType(swiftType.$memorySegment())); - Assertions.assertEquals("[8%[8:b1]](Swift.Int)", SwiftValueWitnessTable.layoutOfSwiftType(swiftType.$memorySegment()).toString()); - } - } - - @Test - public void optional_integer_layout_metadata() { - SwiftAnyType swiftType = SwiftKit.getTypeByMangledNameInEnvironment("SiSg").get(); - - if (SwiftValueLayout.addressByteSize() == 4) { - // 64-bit platform - Assertions.assertEquals(9, SwiftValueWitnessTable.sizeOfSwiftType(swiftType.$memorySegment())); - Assertions.assertEquals(16, SwiftValueWitnessTable.strideOfSwiftType(swiftType.$memorySegment())); - Assertions.assertEquals(8, SwiftValueWitnessTable.alignmentOfSwiftType(swiftType.$memorySegment())); - Assertions.assertEquals("[8%[9:b1]x7](Swift.Optional)", SwiftValueWitnessTable.layoutOfSwiftType(swiftType.$memorySegment()).toString()); - } else { - // 64-bit platform - Assertions.assertEquals(9, SwiftValueWitnessTable.sizeOfSwiftType(swiftType.$memorySegment())); - Assertions.assertEquals(16, SwiftValueWitnessTable.strideOfSwiftType(swiftType.$memorySegment())); - Assertions.assertEquals(8, SwiftValueWitnessTable.alignmentOfSwiftType(swiftType.$memorySegment())); - Assertions.assertEquals("[8%[9:b1]x7](Swift.Optional)", SwiftValueWitnessTable.layoutOfSwiftType(swiftType.$memorySegment()).toString()); - } - } +// @Test +// public void integer_layout_metadata() { +// SwiftAnyType swiftType = SwiftKit.getTypeByMangledNameInEnvironment("Si").get(); +// +// if (SwiftValueLayout.addressByteSize() == 4) { +// // 32-bit platform +// Assertions.assertEquals(8, SwiftValueWitnessTable.sizeOfSwiftType(swiftType.$memorySegment())); +// Assertions.assertEquals(8, SwiftValueWitnessTable.strideOfSwiftType(swiftType.$memorySegment())); +// Assertions.assertEquals(8, SwiftValueWitnessTable.alignmentOfSwiftType(swiftType.$memorySegment())); +// Assertions.assertEquals("[8%[9:b1]x7](Swift.Int)", SwiftValueWitnessTable.layoutOfSwiftType(swiftType.$memorySegment()).toString()); +// } else { +// // 64-bit platform +// Assertions.assertEquals(8, SwiftValueWitnessTable.sizeOfSwiftType(swiftType.$memorySegment())); +// Assertions.assertEquals(8, SwiftValueWitnessTable.strideOfSwiftType(swiftType.$memorySegment())); +// Assertions.assertEquals(8, SwiftValueWitnessTable.alignmentOfSwiftType(swiftType.$memorySegment())); +// Assertions.assertEquals("[8%[8:b1]](Swift.Int)", SwiftValueWitnessTable.layoutOfSwiftType(swiftType.$memorySegment()).toString()); +// } +// } +// +// @Test +// public void optional_integer_layout_metadata() { +// SwiftAnyType swiftType = SwiftKit.getTypeByMangledNameInEnvironment("SiSg").get(); +// +// if (SwiftValueLayout.addressByteSize() == 4) { +// // 64-bit platform +// Assertions.assertEquals(9, SwiftValueWitnessTable.sizeOfSwiftType(swiftType.$memorySegment())); +// Assertions.assertEquals(16, SwiftValueWitnessTable.strideOfSwiftType(swiftType.$memorySegment())); +// Assertions.assertEquals(8, SwiftValueWitnessTable.alignmentOfSwiftType(swiftType.$memorySegment())); +// Assertions.assertEquals("[8%[9:b1]x7](Swift.Optional)", SwiftValueWitnessTable.layoutOfSwiftType(swiftType.$memorySegment()).toString()); +// } else { +// // 64-bit platform +// Assertions.assertEquals(9, SwiftValueWitnessTable.sizeOfSwiftType(swiftType.$memorySegment())); +// Assertions.assertEquals(16, SwiftValueWitnessTable.strideOfSwiftType(swiftType.$memorySegment())); +// Assertions.assertEquals(8, SwiftValueWitnessTable.alignmentOfSwiftType(swiftType.$memorySegment())); +// Assertions.assertEquals("[8%[9:b1]x7](Swift.Optional)", SwiftValueWitnessTable.layoutOfSwiftType(swiftType.$memorySegment()).toString()); +// } +// } } diff --git a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift index f017bc494..053236303 100644 --- a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// @_spi(Testing) import JExtractSwiftLib +import JavaKitConfigurationShared import SwiftSyntax import Testing @@ -31,9 +32,9 @@ func assertLoweredFunction( line: Int = #line, column: Int = #column ) throws { - let translator = Swift2JavaTranslator( - swiftModuleName: swiftModuleName - ) + var config = Configuration() + config.swiftModule = swiftModuleName + let translator = Swift2JavaTranslator(config: config) if let sourceFile { translator.add(filePath: "Fake.swift", text: sourceFile) @@ -117,9 +118,9 @@ func assertLoweredVariableAccessor( line: Int = #line, column: Int = #column ) throws { - let translator = Swift2JavaTranslator( - swiftModuleName: swiftModuleName - ) + var config = Configuration() + config.swiftModule = swiftModuleName + let translator = Swift2JavaTranslator(config: config) if let sourceFile { translator.add(filePath: "Fake.swift", text: sourceFile) diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index b3a09a22b..3c4ad56ad 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -25,7 +25,7 @@ enum RenderKind { func assertOutput( dump: Bool = false, input: String, - _ mode: GenerationMode, + _ mode: JExtractGenerationMode, _ renderKind: RenderKind, swiftModuleName: String = "SwiftModule", detectChunkByInitialLines: Int = 4, @@ -35,7 +35,9 @@ func assertOutput( line: Int = #line, column: Int = #column ) throws { - let translator = Swift2JavaTranslator(swiftModuleName: swiftModuleName) + var config = Configuration() + config.swiftModule = swiftModuleName + let translator = Swift2JavaTranslator(config: config) try! translator.analyze(file: "/fake/Fake.swiftinterface", text: input) diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index c738914ac..d81dad8c5 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import JExtractSwiftLib +import JavaKitConfigurationShared import Testing final class FuncCallbackImportTests { @@ -36,9 +37,9 @@ final class FuncCallbackImportTests { @Test("Import: public func callMe(callback: () -> Void)") func func_callMeFunc_callback() throws { - let st = Swift2JavaTranslator( - swiftModuleName: "__FakeModule" - ) + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error try st.analyze(file: "Fake.swift", text: Self.class_interfaceFile) @@ -125,9 +126,9 @@ final class FuncCallbackImportTests { @Test("Import: public func callMeMore(callback: (UnsafeRawPointer, Float) -> Int, fn: () -> ())") func func_callMeMoreFunc_callback() throws { - let st = Swift2JavaTranslator( - swiftModuleName: "__FakeModule" - ) + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error try st.analyze(file: "Fake.swift", text: Self.class_interfaceFile) @@ -240,9 +241,9 @@ final class FuncCallbackImportTests { @Test("Import: public func withBuffer(body: (UnsafeRawBufferPointer) -> Int)") func func_withBuffer_body() throws { - let st = Swift2JavaTranslator( - swiftModuleName: "__FakeModule" - ) + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error try st.analyze(file: "Fake.swift", text: Self.class_interfaceFile) diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift index 2cac62184..7af3c706f 100644 --- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift +++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import JExtractSwiftLib +import JavaKitConfigurationShared import Testing @Suite @@ -233,9 +234,9 @@ extension FunctionDescriptorTests { logLevel: Logger.Level = .trace, body: (String) throws -> Void ) throws { - let st = Swift2JavaTranslator( - swiftModuleName: swiftModuleName - ) + var config = Configuration() + config.swiftModule = swiftModuleName + let st = Swift2JavaTranslator(config: config) st.log.logLevel = logLevel try st.analyze(file: "/fake/Sample.swiftinterface", text: interfaceFile) @@ -266,9 +267,9 @@ extension FunctionDescriptorTests { logLevel: Logger.Level = .trace, body: (String) throws -> Void ) throws { - let st = Swift2JavaTranslator( - swiftModuleName: swiftModuleName - ) + var config = Configuration() + config.swiftModule = swiftModuleName + let st = Swift2JavaTranslator(config: config) st.log.logLevel = logLevel try st.analyze(file: "/fake/Sample.swiftinterface", text: interfaceFile) diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift index 2405e0ac4..36610dcd9 100644 --- a/Tests/JExtractSwiftTests/MethodImportTests.swift +++ b/Tests/JExtractSwiftTests/MethodImportTests.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import JExtractSwiftLib +import JavaKitConfigurationShared import Testing final class MethodImportTests { @@ -64,9 +65,9 @@ final class MethodImportTests { @Test("Import: public func helloWorld()") func method_helloWorld() throws { - let st = Swift2JavaTranslator( - swiftModuleName: "__FakeModule" - ) + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error try st.analyze(file: "Fake.swift", text: class_interfaceFile) @@ -103,9 +104,9 @@ final class MethodImportTests { @Test("Import: public func globalTakeInt(i: Int)") func func_globalTakeInt() throws { - let st = Swift2JavaTranslator( - swiftModuleName: "__FakeModule" - ) + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error try st.analyze(file: "Fake.swift", text: class_interfaceFile) @@ -144,9 +145,9 @@ final class MethodImportTests { @Test("Import: public func globalTakeIntLongString(i32: Int32, l: Int64, s: String)") func func_globalTakeIntLongString() throws { - let st = Swift2JavaTranslator( - swiftModuleName: "__FakeModule" - ) + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error try st.analyze(file: "Fake.swift", text: class_interfaceFile) @@ -188,9 +189,9 @@ final class MethodImportTests { @Test("Import: public func globalReturnClass() -> MySwiftClass") func func_globalReturnClass() throws { - let st = Swift2JavaTranslator( - swiftModuleName: "__FakeModule" - ) + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error try st.analyze(file: "Fake.swift", text: class_interfaceFile) @@ -232,9 +233,9 @@ final class MethodImportTests { @Test("Import: func swapRawBufferPointer(buffer: _)") func func_globalSwapRawBufferPointer() throws { - let st = Swift2JavaTranslator( - swiftModuleName: "__FakeModule" - ) + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error try st.analyze(file: "Fake.swift", text: class_interfaceFile) @@ -279,9 +280,9 @@ final class MethodImportTests { @Test func method_class_helloMemberFunction() throws { - let st = Swift2JavaTranslator( - swiftModuleName: "__FakeModule" - ) + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error try st.analyze(file: "Fake.swift", text: class_interfaceFile) @@ -321,9 +322,9 @@ final class MethodImportTests { @Test func method_class_makeInt() throws { - let st = Swift2JavaTranslator( - swiftModuleName: "__FakeModule" - ) + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = Swift2JavaTranslator(config: config) st.log.logLevel = .info try st.analyze(file: "Fake.swift", text: class_interfaceFile) @@ -363,9 +364,9 @@ final class MethodImportTests { @Test func class_constructor() throws { - let st = Swift2JavaTranslator( - swiftModuleName: "__FakeModule" - ) + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = Swift2JavaTranslator(config: config) st.log.logLevel = .info try st.analyze(file: "Fake.swift", text: class_interfaceFile) @@ -406,9 +407,10 @@ final class MethodImportTests { @Test func struct_constructor() throws { - let st = Swift2JavaTranslator( - swiftModuleName: "__FakeModule" - ) + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = Swift2JavaTranslator(config: config) + st.log.logLevel = .info try st.analyze(file: "Fake.swift", text: class_interfaceFile) diff --git a/Tests/JavaKitConfigurationSharedTests/GradleDependencyParsingTests.swift b/Tests/JavaKitConfigurationSharedTests/GradleDependencyParsingTests.swift new file mode 100644 index 000000000..dbd05f66e --- /dev/null +++ b/Tests/JavaKitConfigurationSharedTests/GradleDependencyParsingTests.swift @@ -0,0 +1,45 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024-2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JavaKitConfigurationShared +import Testing + +@Suite +struct GradleDependencyParsingTests { + + @Test + func parseSingleDependency() throws { + let inputString = "com.example:thing:12.2" + let parsed: JavaDependencyDescriptor = parseDependencyDescriptor(inputString)! + + #expect(parsed.groupID == "com.example") + #expect(parsed.artifactID == "thing") + #expect(parsed.version == "12.2") + } + + @Test + func parseMultiple() throws { + let inputString = "com.example:thing:12.2,com.example:another:1.2.3-beta.1," + let parsed: [JavaDependencyDescriptor] = parseDependencyDescriptors(inputString) + + #expect(parsed.count == 2) + #expect(parsed[0].groupID == "com.example") + #expect(parsed[0].artifactID == "thing") + #expect(parsed[0].version == "12.2") + #expect(parsed[1].groupID == "com.example") + #expect(parsed[1].artifactID == "another") + #expect(parsed[1].version == "1.2.3-beta.1") + } +} + diff --git a/docker/Dockerfile b/docker/Dockerfile index c68ccc3c4..6604d091c 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,6 +1,6 @@ ARG swift_version=nightly-main ARG ubuntu_version=jammy -ARG base_image=docker.io/swiftlang/swift:$swift_version-$ubuntu_version +ARG base_image=docker.io/swiftlang/swift:${swift_version}-${ubuntu_version} FROM $base_image # needed to do again after FROM due to docker limitation ARG swift_version @@ -18,13 +18,9 @@ ENV LC_ALL=en_US.UTF-8 ENV LANG=en_US.UTF-8 ENV LANGUAGE=en_US.UTF-8 -# JDK dependency COPY install_jdk.sh . + +# JDK dependency RUN bash -xc 'JDK_VENDOR=Corretto ./install_jdk.sh' ENV JAVA_HOME="/usr/lib/jvm/default-jdk" ENV PATH="$PATH:/usr/lib/jvm/default-jdk/bin" - -# Install "untested" nightly 'main' Swift -# TODO: Only do this if the released Swift is older than what we require -#COPY install_untested_nightly_swift.sh . -RUN #bash -xc './install_untested_nightly_swift.sh' diff --git a/docker/install_jdk.sh b/docker/install_jdk.sh index 8aa295ce5..8d0ddda37 100755 --- a/docker/install_jdk.sh +++ b/docker/install_jdk.sh @@ -14,70 +14,101 @@ ##===----------------------------------------------------------------------===## set -euo pipefail -# Supported JDKs: Corretto or OpenJDK +# We need JDK 24 because that's the supported version with latest FFM +# However, we also need JDK 23 at most because Gradle does not support 24. + +# Supported JDKs: Corretto if [ "$JDK_VENDOR" = "" ]; then declare -r JDK_VENDOR="Corretto" fi -echo "Installing $JDK_VENDOR JDK..." -apt-get update && apt-get install -y wget +apt-get update && apt-get install -y wget tree echo "Download JDK for: $(uname -m)" -if [ "$JDK_VENDOR" = 'OpenJDK' ]; then - if [ "$(uname -m)" = 'aarch64' ]; then - declare -r JDK_URL="https://download.java.net/java/GA/jdk23/3c5b90190c68498b986a97f276efd28a/37/GPL/openjdk-23_linux-aarch64_bin.tar.gz" - declare -r EXPECT_JDK_SHA="076dcf7078cdf941951587bf92733abacf489a6570f1df97ee35945ffebec5b7" - else - declare -r JDK_URL="https://download.java.net/java/GA/jdk23/3c5b90190c68498b986a97f276efd28a/37/GPL/$JDK_NAME" - declare -r EXPECT_JDK_SHA="08fea92724127c6fa0f2e5ea0b07ff4951ccb1e2f22db3c21eebbd7347152a67" - fi +download_and_install_jdk() { + local jdk_version="$1" + local jdk_url="" + local expected_md5="" - wget -q -O jdk.tar.gz "$JDK_URL" + echo "Installing $JDK_VENDOR JDK (${jdk_version})..." - declare JDK_SHA # on separate lines due to: SC2155 (warning): Declare and assign separately to avoid masking return values. - JDK_SHA="$(sha256sum jdk.tar.gz | cut -d ' ' -f 1)" - if [ "$JDK_SHA" != "$EXPECT_JDK_SHA" ]; then - echo "Downloaded JDK SHA does not match expected!" - echo "Expected: $EXPECT_JDK_SHA" - echo " Was: $JDK_SHA" - exit 1; - else - echo "JDK SHA is correct."; - fi -elif [ "$JDK_VENDOR" = 'Corretto' ]; then - if [ "$(uname -m)" = 'aarch64' ]; then - declare -r JDK_URL="https://corretto.aws/downloads/latest/amazon-corretto-22-aarch64-linux-jdk.tar.gz" - declare -r EXPECT_JDK_MD5="1ebe5f5229bb18bc784a1e0f54d3fe39" - else - declare -r JDK_URL="https://corretto.aws/downloads/latest/amazon-corretto-22-x64-linux-jdk.tar.gz" - declare -r EXPECT_JDK_MD5="5bd7fe30eb063699a3b4db7a00455841" - fi + if [ "$JDK_VENDOR" = 'Corretto' ]; then + if [ "$(uname -m)" = 'aarch64' ]; then + case "$jdk_version" in + "21") + jdk_url="https://corretto.aws/downloads/latest/amazon-corretto-21-aarch64-linux-jdk.tar.gz" + expected_md5="87e458029cf9976945dfa3a22af3f850" + ;; + "24") + jdk_url="https://corretto.aws/downloads/latest/amazon-corretto-24-aarch64-linux-jdk.tar.gz" + expected_md5="3b543f4e971350b73d0ab6d8174cc030" + ;; + *) + echo "Unsupported JDK version: '$jdk_version'" + exit 1 + ;; + esac + else + case "$jdk_version" in + "21") + jdk_url="https://corretto.aws/downloads/latest/amazon-corretto-21-x64-linux-jdk.tar.gz" + expected_md5="a123e7f50807c27de521bef7378d3377" + ;; + "24") + jdk_url="https://corretto.aws/downloads/latest/amazon-corretto-24-x64-linux-jdk.tar.gz" + expected_md5="130885ded3cbfc712fbe9f7dace45a52" + ;; + *) + echo "Unsupported JDK version: '$jdk_version'" + exit 1 + ;; + esac + fi + else + echo "Unsupported JDK vendor: '$JDK_VENDOR'" + exit 1 + fi - wget -q -O jdk.tar.gz "$JDK_URL" + # Download JDK + local jdk_filename="jdk_${jdk_version}.tar.gz" + wget -q -O "$jdk_filename" "$jdk_url" - declare JDK_MD5 # on separate lines due to: SC2155 (warning): Declare and assign separately to avoid masking return values. - JDK_MD5="$(md5sum jdk.tar.gz | cut -d ' ' -f 1)" - if [ "$JDK_MD5" != "$EXPECT_JDK_MD5" ]; then - echo "Downloaded JDK MD5 does not match expected!" - echo "Expected: $EXPECT_JDK_MD5" - echo " Was: $JDK_MD5" - exit 1; - else - echo "JDK MD5 is correct."; - fi -else - echo "Unsupported JDK vendor: '$JDK_VENDOR'" - exit 1 -fi + # Verify MD5 + local jdk_md5 + jdk_md5="$(md5sum "$jdk_filename" | cut -d ' ' -f 1)" + if [ "$jdk_md5" != "$expected_md5" ]; then + echo "Downloaded JDK $jdk_version MD5 does not match expected!" + echo "Expected: $expected_md5" + echo " Was: $jdk_md5" + exit 1 + else + echo "JDK $jdk_version MD5 is correct." + fi -# Extract and verify the JDK installation + # Extract and install JDK + mkdir -p "/usr/lib/jvm/jdk-${jdk_version}" + mv "$jdk_filename" "/usr/lib/jvm/jdk-${jdk_version}/" + cd "/usr/lib/jvm/jdk-${jdk_version}/" || exit 1 + tar xzf "$jdk_filename" && rm "$jdk_filename" -mkdir -p /usr/lib/jvm/ -mv jdk.tar.gz /usr/lib/jvm/ -cd /usr/lib/jvm/ -tar xzvf jdk.tar.gz && rm jdk.tar.gz -mv "$(find . -depth -maxdepth 1 -type d | head -n1)" default-jdk + # Move extracted directory to a standard name + local extracted_dir + extracted_dir="$(find . -maxdepth 1 -type d -name '*linux*' | head -n1)" + echo "move $extracted_dir to $(pwd)..." + mv "${extracted_dir}"/* . + echo "JDK $jdk_version installed successfully in /usr/lib/jvm/jdk-${jdk_version}/" + cd "$HOME" +} + +# Usage: Install both JDK versions +download_and_install_jdk "21" +download_and_install_jdk "24" + +ls -la /usr/lib/jvm/ +cd /usr/lib/jvm/ +ln -s jdk-21 default-jdk +find . | grep java | grep bin echo "JAVA_HOME = /usr/lib/jvm/default-jdk" /usr/lib/jvm/default-jdk/bin/java -version \ No newline at end of file diff --git a/docker/install_untested_nightly_swift.sh b/docker/install_untested_nightly_swift.sh deleted file mode 100755 index 9cb920ab7..000000000 --- a/docker/install_untested_nightly_swift.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash -##===----------------------------------------------------------------------===## -## -## This source file is part of the Swift.org open source project -## -## Copyright (c) 2024 Apple Inc. and the Swift.org project authors -## Licensed under Apache License v2.0 -## -## See LICENSE.txt for license information -## See CONTRIBUTORS.txt for the list of Swift.org project authors -## -## SPDX-License-Identifier: Apache-2.0 -## -##===----------------------------------------------------------------------===## -set -euo pipefail - -echo "Download [nightly] [untested] Swift toolchain for: $(uname -m)" - -ARCH="$(arch)" -if [[ "$ARCH" = "i386" || "$ARCH" = "x86_64" ]]; then - SWIFT_UNTESTED_TOOLCHAIN_JOB_URL="https://ci.swift.org/job/oss-swift-package-ubuntu-22_04/lastSuccessfulBuild/consoleText" -else - SWIFT_UNTESTED_TOOLCHAIN_JOB_URL="https://ci.swift.org/job/oss-swift-package-ubuntu-22_04-aarch64/lastSuccessfulBuild/consoleText" -fi - -if [[ "$(grep "22.04" /etc/lsb-release)" = "" ]]; then - echo "This script specifically only supports Ubuntu 22.04 due to nightly toolchain availability" - exit 1 -fi - -UNTESTED_TOOLCHAIN_URL=$(curl -s $SWIFT_UNTESTED_TOOLCHAIN_JOB_URL | grep 'Toolchain: ' | sed 's/Toolchain: //g') -UNTESTED_TOOLCHAIN_FILENAME=$"toolchain.tar.gz" - -echo "Download toolchain: $UNTESTED_TOOLCHAIN_URL" - -cd / -curl -s "$UNTESTED_TOOLCHAIN_URL" > "$UNTESTED_TOOLCHAIN_FILENAME" - -swift -version - -echo "Extract toolchain: $UNTESTED_TOOLCHAIN_FILENAME" -tar xzf "$UNTESTED_TOOLCHAIN_FILENAME" -swift -version From e66e490f173e4cdfe7fda45147dccf85c451ddcd Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 25 Jun 2025 11:14:42 +0900 Subject: [PATCH 011/141] CI: Correct validation & bring back pointer "max" bounds workaround (#284) --- .github/scripts/validate_sample.sh | 14 +++++++------- Samples/SwiftKitSampleApp/ci-validate.sh | 6 ++++++ .../java/org/swift/swiftkit/SwiftValueLayout.java | 6 ++++-- 3 files changed, 17 insertions(+), 9 deletions(-) create mode 100755 Samples/SwiftKitSampleApp/ci-validate.sh diff --git a/.github/scripts/validate_sample.sh b/.github/scripts/validate_sample.sh index 7e0ab3d2c..b57809767 100755 --- a/.github/scripts/validate_sample.sh +++ b/.github/scripts/validate_sample.sh @@ -1,5 +1,8 @@ #!/bin/bash +set -e +set -x + # shellcheck disable=SC2034 declare -r GREEN='\033[0;32m' declare -r BOLD='\033[1m' @@ -14,14 +17,11 @@ echo "========================================================================" printf "Validate sample '${BOLD}%s${RESET}' using: " "$sampleDir" cd "$sampleDir" || exit if [[ $(find . -name ${CI_VALIDATE_SCRIPT} -maxdepth 1) ]]; then - echo -e "Custom ${BOLD}${CI_VALIDATE_SCRIPT}${RESET} script..." - ./${CI_VALIDATE_SCRIPT} || exit -elif [[ $(find . -name 'build.gradle*' -maxdepth 1) ]]; then - echo -e "${BOLD}Gradle${RESET} build..." - ./gradlew build || ./gradlew build --info # re-run to get better failure output + echo -e "Run ${BOLD}${CI_VALIDATE_SCRIPT}${RESET} script..." + ./${CI_VALIDATE_SCRIPT} else - echo -e "${BOLD}SwiftPM${RESET} build..." - swift build || exit + echo -e "${BOLD}Missing ${CI_VALIDATE_SCRIPT} file!${RESET}" + exit fi echo -e "Validated sample '${BOLD}${sampleDir}${RESET}': ${BOLD}passed${RESET}." diff --git a/Samples/SwiftKitSampleApp/ci-validate.sh b/Samples/SwiftKitSampleApp/ci-validate.sh new file mode 100755 index 000000000..07b426271 --- /dev/null +++ b/Samples/SwiftKitSampleApp/ci-validate.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +set -x +set -e + +./gradlew run \ No newline at end of file diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java index 8d2d7ad25..00215ef0b 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java +++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java @@ -41,8 +41,10 @@ public static long addressByteSize() { public static final ValueLayout.OfLong SWIFT_INT64 = ValueLayout.JAVA_LONG; public static final ValueLayout.OfFloat SWIFT_FLOAT = ValueLayout.JAVA_FLOAT; public static final ValueLayout.OfDouble SWIFT_DOUBLE = ValueLayout.JAVA_DOUBLE; - public static final AddressLayout SWIFT_POINTER = ValueLayout.ADDRESS; - // .withTargetLayout(MemoryLayout.sequenceLayout(Long.MAX_VALUE, JAVA_BYTE)); + + // FIXME: this sequence layout is a workaround, we must properly size pointers when we get them. + public static final AddressLayout SWIFT_POINTER = ValueLayout.ADDRESS + .withTargetLayout(MemoryLayout.sequenceLayout(Long.MAX_VALUE, JAVA_BYTE)); public static final SequenceLayout SWIFT_BYTE_ARRAY = MemoryLayout.sequenceLayout(8, ValueLayout.JAVA_BYTE); /** From 8d4e5cd0086300a0f8abd5bf212fdbbf9a0e589e Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Tue, 24 Jun 2025 19:26:07 -0700 Subject: [PATCH 012/141] [JavaRuntime] Add module.modulemap Fix a build error in 6.1 toolchain. Apparently some targets have both `JavaRuntime-tool.build` and `JavaRuntime.build` as the include paths. The auto generated `module.modulemap` in both directories cause the error `error: redefinition of module 'JavaRuntime'`. To avoid that, add `module.modulemap` in the source directory so it's not generated. Fixes: https://github.com/swiftlang/swift-java/issues/229 --- Sources/JavaRuntime/include/module.modulemap | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 Sources/JavaRuntime/include/module.modulemap diff --git a/Sources/JavaRuntime/include/module.modulemap b/Sources/JavaRuntime/include/module.modulemap new file mode 100644 index 000000000..2c0d4a982 --- /dev/null +++ b/Sources/JavaRuntime/include/module.modulemap @@ -0,0 +1,4 @@ +module JavaRuntime { + umbrella header "JavaRuntime.h" + export * +} From 6e53b3b4c4420a0300d6362865418324848d8d11 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Wed, 25 Jun 2025 18:51:33 -0700 Subject: [PATCH 013/141] [JExtract] Stop using SyntaxVisitor for Swift2JavaVisitor (#290) --- Sources/JExtractSwiftLib/ImportedDecls.swift | 4 +- .../Swift2JavaTranslator.swift | 2 +- .../JExtractSwiftLib/Swift2JavaVisitor.swift | 171 ++++++++---------- 3 files changed, 77 insertions(+), 100 deletions(-) diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index ee86fc983..0a5952f31 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -37,8 +37,8 @@ package class ImportedNominalType: ImportedDecl { self.swiftNominal = swiftNominal } - var javaClassName: String { - swiftNominal.name + var swiftType: SwiftType { + return .nominal(.init(nominalTypeDecl: swiftNominal)) } } diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index 07d8cafbc..89deda9a6 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -106,7 +106,7 @@ extension Swift2JavaTranslator { for input in self.inputs { log.trace("Analyzing \(input.filePath)") - visitor.walk(input.syntax) + visitor.visit(sourceFile: input.syntax) } } diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index 0b4d6bbf0..7ce982a24 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift @@ -16,98 +16,85 @@ import Foundation import SwiftParser import SwiftSyntax -final class Swift2JavaVisitor: SyntaxVisitor { +final class Swift2JavaVisitor { let translator: Swift2JavaTranslator - /// Type context stack associated with the syntax. - var typeContext: [(syntaxID: Syntax.ID, type: ImportedNominalType)] = [] - - /// Innermost type context. - var currentType: ImportedNominalType? { typeContext.last?.type } - - var currentSwiftType: SwiftType? { - guard let currentType else { return nil } - return .nominal(SwiftNominalType(nominalTypeDecl: currentType.swiftNominal)) - } - - /// The current type name as a nested name like A.B.C. - var currentTypeName: String? { self.currentType?.swiftNominal.qualifiedName } - - var log: Logger { translator.log } - init(translator: Swift2JavaTranslator) { self.translator = translator - - super.init(viewMode: .all) } - /// Push specified type to the type context associated with the syntax. - func pushTypeContext(syntax: some SyntaxProtocol, importedNominal: ImportedNominalType) { - typeContext.append((syntax.id, importedNominal)) - } + var log: Logger { translator.log } - /// Pop type context if the current context is associated with the syntax. - func popTypeContext(syntax: some SyntaxProtocol) -> Bool { - if typeContext.last?.syntaxID == syntax.id { - typeContext.removeLast() - return true - } else { - return false + func visit(sourceFile node: SourceFileSyntax) { + for codeItem in node.statements { + if let declNode = codeItem.item.as(DeclSyntax.self) { + self.visit(decl: declNode, in: nil) + } } } - override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { - log.debug("Visit \(node.kind): '\(node.qualifiedNameForDebug)'") - guard let importedNominalType = translator.importedNominalType(node, parent: self.currentType) else { - return .skipChildren + func visit(decl node: DeclSyntax, in parent: ImportedNominalType?) { + switch node.as(DeclSyntaxEnum.self) { + case .actorDecl(let node): + self.visit(nominalDecl: node, in: parent) + case .classDecl(let node): + self.visit(nominalDecl: node, in: parent) + case .structDecl(let node): + self.visit(nominalDecl: node, in: parent) + case .enumDecl(let node): + self.visit(nominalDecl: node, in: parent) + case .protocolDecl(let node): + self.visit(nominalDecl: node, in: parent) + case .extensionDecl(let node): + self.visit(extensionDecl: node, in: parent) + case .typeAliasDecl: + break // TODO: Implement + case .associatedTypeDecl: + break // TODO: Implement + + case .initializerDecl(let node): + self.visit(initializerDecl: node, in: parent) + case .functionDecl(let node): + self.visit(functionDecl: node, in: parent) + case .variableDecl(let node): + self.visit(variableDecl: node, in: parent) + case .subscriptDecl: + // TODO: Implement + break + + default: + break } - - self.pushTypeContext(syntax: node, importedNominal: importedNominalType) - return .visitChildren } - override func visitPost(_ node: ClassDeclSyntax) { - if self.popTypeContext(syntax: node) { - log.debug("Completed import: \(node.kind) \(node.name)") + func visit( + nominalDecl node: some DeclSyntaxProtocol & DeclGroupSyntax & NamedDeclSyntax & WithAttributesSyntax & WithModifiersSyntax, + in parent: ImportedNominalType? + ) { + guard let importedNominalType = translator.importedNominalType(node, parent: parent) else { + return } - } - - override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { - log.debug("Visit \(node.kind): \(node.qualifiedNameForDebug)") - guard let importedNominalType = translator.importedNominalType(node, parent: self.currentType) else { - return .skipChildren + for memberItem in node.memberBlock.members { + self.visit(decl: memberItem.decl, in: importedNominalType) } - - self.pushTypeContext(syntax: node, importedNominal: importedNominalType) - return .visitChildren } - override func visitPost(_ node: StructDeclSyntax) { - if self.popTypeContext(syntax: node) { - log.debug("Completed import: \(node.kind) \(node.qualifiedNameForDebug)") + func visit(extensionDecl node: ExtensionDeclSyntax, in parent: ImportedNominalType?) { + guard parent != nil else { + // 'extension' in a nominal type is invalid. Ignore + return } - } - - override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { - // Resolve the extended type of the extension as an imported nominal, and - // recurse if we found it. guard let importedNominalType = translator.importedNominalType(node.extendedType) else { - return .skipChildren + return } - - self.pushTypeContext(syntax: node, importedNominal: importedNominalType) - return .visitChildren - } - - override func visitPost(_ node: ExtensionDeclSyntax) { - if self.popTypeContext(syntax: node) { - log.debug("Completed import: \(node.kind) \(node.qualifiedNameForDebug)") + for memberItem in node.memberBlock.members { + self.visit(decl: memberItem.decl, in: importedNominalType) } } - override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { + func visit(functionDecl node: FunctionDeclSyntax, in typeContext: ImportedNominalType?) { guard node.shouldImport(log: log) else { - return .skipChildren + return } self.log.debug("Import function: '\(node.qualifiedNameForDebug)'") @@ -116,12 +103,12 @@ final class Swift2JavaVisitor: SyntaxVisitor { do { signature = try SwiftFunctionSignature( node, - enclosingType: self.currentSwiftType, + enclosingType: typeContext?.swiftType, symbolTable: self.translator.symbolTable ) } catch { self.log.debug("Failed to import: '\(node.qualifiedNameForDebug)'; \(error)") - return .skipChildren + return } let imported = ImportedFunc( @@ -133,22 +120,20 @@ final class Swift2JavaVisitor: SyntaxVisitor { ) log.debug("Record imported method \(node.qualifiedNameForDebug)") - if let currentType { - currentType.methods.append(imported) + if let typeContext { + typeContext.methods.append(imported) } else { translator.importedGlobalFuncs.append(imported) } - - return .skipChildren } - override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { + func visit(variableDecl node: VariableDeclSyntax, in typeContext: ImportedNominalType?) { guard node.shouldImport(log: log) else { - return .skipChildren + return } guard let binding = node.bindings.first else { - return .skipChildren + return } let varName = "\(binding.pattern.trimmed)" @@ -159,7 +144,7 @@ final class Swift2JavaVisitor: SyntaxVisitor { let signature = try SwiftFunctionSignature( node, isSet: kind == .setter, - enclosingType: self.currentSwiftType, + enclosingType: typeContext?.swiftType, symbolTable: self.translator.symbolTable ) @@ -170,10 +155,10 @@ final class Swift2JavaVisitor: SyntaxVisitor { apiKind: kind, functionSignature: signature ) - + log.debug("Record imported variable accessor \(kind == .getter ? "getter" : "setter"):\(node.qualifiedNameForDebug)") - if let currentType { - currentType.variables.append(imported) + if let typeContext { + typeContext.variables.append(imported) } else { translator.importedGlobalVariables.append(imported) } @@ -189,18 +174,16 @@ final class Swift2JavaVisitor: SyntaxVisitor { } } catch { self.log.debug("Failed to import: \(node.qualifiedNameForDebug); \(error)") - return .skipChildren } - - return .skipChildren } - override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { - guard let currentType else { - fatalError("Initializer must be within a current type, was: \(node)") + func visit(initializerDecl node: InitializerDeclSyntax, in typeContext: ImportedNominalType?) { + guard let typeContext else { + self.log.info("Initializer must be within a current type; \(node)") + return } guard node.shouldImport(log: log) else { - return .skipChildren + return } self.log.debug("Import initializer: \(node.kind) '\(node.qualifiedNameForDebug)'") @@ -209,12 +192,12 @@ final class Swift2JavaVisitor: SyntaxVisitor { do { signature = try SwiftFunctionSignature( node, - enclosingType: self.currentSwiftType, + enclosingType: typeContext.swiftType, symbolTable: self.translator.symbolTable ) } catch { self.log.debug("Failed to import: \(node.qualifiedNameForDebug); \(error)") - return .skipChildren + return } let imported = ImportedFunc( module: translator.swiftModuleName, @@ -224,13 +207,7 @@ final class Swift2JavaVisitor: SyntaxVisitor { functionSignature: signature ) - currentType.initializers.append(imported) - - return .skipChildren - } - - override func visit(_ node: DeinitializerDeclSyntax) -> SyntaxVisitorContinueKind { - return .skipChildren + typeContext.initializers.append(imported) } } From 45205e1c726804ff79491b223711af2676a85fd2 Mon Sep 17 00:00:00 2001 From: Alexander <45719053+KeoFoxy@users.noreply.github.com> Date: Fri, 27 Jun 2025 07:59:58 +0300 Subject: [PATCH 014/141] feature: Code enhancement (#267) --- .../src/test/java/org/swift/swiftkit/SwiftArenaTest.java | 4 ++-- Sources/JExtractSwiftLib/CodePrinter.swift | 4 ++-- .../JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift | 4 ++-- Sources/JExtractSwiftLib/Logger.swift | 2 +- Sources/JavaKit/JavaKitVM/LockedState.swift | 4 ++-- Sources/JavaKit/JavaValue.swift | 2 +- Sources/SwiftKitSwift/SwiftKit.swift | 2 +- .../main/java/org/swift/swiftkit/SwiftValueWitnessTable.java | 2 +- USER_GUIDE.md | 4 ++-- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java index 0d900a623..52d791a13 100644 --- a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java +++ b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java @@ -63,7 +63,7 @@ public void arena_markAsDestroyed_preventUseAfterFree_class() { try { unsafelyEscapedOutsideArenaScope.echoIntMethod(1); - fail("Expected exception to be thrown! Object was suposed to be dead."); + fail("Expected exception to be thrown! Object was supposed to be dead."); } catch (IllegalStateException ex) { return; } @@ -82,7 +82,7 @@ public void arena_markAsDestroyed_preventUseAfterFree_struct() { try { unsafelyEscapedOutsideArenaScope.echoIntMethod(1); - fail("Expected exception to be thrown! Object was suposed to be dead."); + fail("Expected exception to be thrown! Object was supposed to be dead."); } catch (IllegalStateException ex) { return; } diff --git a/Sources/JExtractSwiftLib/CodePrinter.swift b/Sources/JExtractSwiftLib/CodePrinter.swift index 449cab3da..8c22fbee1 100644 --- a/Sources/JExtractSwiftLib/CodePrinter.swift +++ b/Sources/JExtractSwiftLib/CodePrinter.swift @@ -53,14 +53,14 @@ public struct CodePrinter { self.mode = mode } - internal mutating func append(_ text: String) { + mutating func append(_ text: String) { contents.append(text) if self.verbose { Swift.print(text, terminator: "") } } - internal mutating func append(contentsOf text: S) + mutating func append(contentsOf text: S) where S: Sequence, S.Element == Character { contents.append(contentsOf: text) if self.verbose { diff --git a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift index b732b9f89..da836d45b 100644 --- a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift @@ -16,7 +16,7 @@ import SwiftDiagnostics import SwiftSyntax extension WithModifiersSyntax { - internal var accessControlModifiers: DeclModifierListSyntax { + var accessControlModifiers: DeclModifierListSyntax { modifiers.filter { modifier in modifier.isAccessControl } @@ -24,7 +24,7 @@ extension WithModifiersSyntax { } extension ImplicitlyUnwrappedOptionalTypeSyntax { - internal var asOptionalTypeSyntax: any TypeSyntaxProtocol { + var asOptionalTypeSyntax: any TypeSyntaxProtocol { OptionalTypeSyntax( leadingTrivia: leadingTrivia, unexpectedBeforeWrappedType, diff --git a/Sources/JExtractSwiftLib/Logger.swift b/Sources/JExtractSwiftLib/Logger.swift index 180ffb541..541dbae42 100644 --- a/Sources/JExtractSwiftLib/Logger.swift +++ b/Sources/JExtractSwiftLib/Logger.swift @@ -111,7 +111,7 @@ extension Logger.Level: ExpressibleByArgument { } extension Logger.Level { - internal var naturalIntegralValue: Int { + var naturalIntegralValue: Int { switch self { case .trace: return 0 diff --git a/Sources/JavaKit/JavaKitVM/LockedState.swift b/Sources/JavaKit/JavaKitVM/LockedState.swift index e095668ca..b3082efcb 100644 --- a/Sources/JavaKit/JavaKitVM/LockedState.swift +++ b/Sources/JavaKit/JavaKitVM/LockedState.swift @@ -13,9 +13,9 @@ //===----------------------------------------------------------------------===// #if canImport(os) -internal import os +import os #if FOUNDATION_FRAMEWORK && canImport(C.os.lock) -internal import C.os.lock +import C.os.lock #endif #elseif canImport(Bionic) import Bionic diff --git a/Sources/JavaKit/JavaValue.swift b/Sources/JavaKit/JavaValue.swift index 310b54df3..1bc156a07 100644 --- a/Sources/JavaKit/JavaValue.swift +++ b/Sources/JavaKit/JavaValue.swift @@ -34,7 +34,7 @@ import JavaTypes /// The protocol provides operations to bridge values in both directions: /// - `getJNIValue(in:)`: produces the JNI value (of type `JNIType`) for the /// `self` Swift value in the given JNI environment. -/// - `init(fromJNI:in:)`: intializes a Swift value from the JNI value (of +/// - `init(fromJNI:in:)`: initializes a Swift value from the JNI value (of /// type `JNIType`) in the given JNI environment. /// /// The protocol also provides hooks to tie into JNI, including operations to diff --git a/Sources/SwiftKitSwift/SwiftKit.swift b/Sources/SwiftKitSwift/SwiftKit.swift index 38b3c1c88..fb9781630 100644 --- a/Sources/SwiftKitSwift/SwiftKit.swift +++ b/Sources/SwiftKitSwift/SwiftKit.swift @@ -34,7 +34,7 @@ public func _swiftjava_swift_isUniquelyReferenced(object: UnsafeMutableRawPointe @_alwaysEmitIntoClient @_transparent - internal func _swiftjava_withHeapObject( +func _swiftjava_withHeapObject( of object: AnyObject, _ body: (UnsafeMutableRawPointer) -> R ) -> R { diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java index 2a031b7e4..8ddff1b66 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java +++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java @@ -56,7 +56,7 @@ public abstract class SwiftValueWitnessTable { MemoryLayout.PathElement.groupElement("vwt")); /** - * Given the address of Swift type metadata for a type, return the addres + * Given the address of Swift type metadata for a type, return the address * of the "full" type metadata that can be accessed via fullTypeMetadataLayout. */ public static MemorySegment fullTypeMetadata(MemorySegment typeMetadata) { diff --git a/USER_GUIDE.md b/USER_GUIDE.md index d7d796fd5..351a3d5cb 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -221,7 +221,7 @@ Note that we are passing the Jar file in the `classpath` argument when initializ ### Downcasting -All Java classes available in Swift provide `is` and `as` methods to check whether an object dynamically matches another type. The `is` operation is the equivalent of Java's `instanceof` and Swift's `is` operator, and will checkin whether a given object is of the specified type, e.g., +All Java classes available in Swift provide `is` and `as` methods to check whether an object dynamically matches another type. The `is` operation is the equivalent of Java's `instanceof` and Swift's `is` operator, and will checking whether a given object is of the specified type, e.g., ```swift if myObject.is(URL.self) { @@ -626,7 +626,7 @@ The project is still very early days, however the general outline of using this - These `.swiftinterface` files are imported by jextract-swift which generates `*.java` files - The generated Java files contain generated code for efficient native invocations. -You can then use Swift libraries in Java just by calling the apropriate methods and initializers. +You can then use Swift libraries in Java just by calling the appropriate methods and initializers. ## `jextract-swift`: Generating Java bridging files From df03e23c5ac84da6612ed6aa9cfe34f24bdbcbfb Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Mon, 30 Jun 2025 14:26:30 +0900 Subject: [PATCH 015/141] ci: cleanup samples testing using matrix (#292) --- .github/workflows/pull_request.yml | 114 +++-------------------------- 1 file changed, 12 insertions(+), 102 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 39fa7ee6c..c1af9ca76 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -62,8 +62,8 @@ jobs: - name: Swift Test run: "swift test" - verify-sample-01: - name: Verify Sample JavaDependencySampleApp (swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} os:${{ matrix.os_version }}) + verify-samples: + name: Verify Sample ${{ matrix.sample_app }} (swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} os:${{ matrix.os_version }}) runs-on: ubuntu-latest strategy: fail-fast: false @@ -71,6 +71,14 @@ jobs: swift_version: ['6.1.2'] os_version: ['jammy'] jdk_vendor: ['Corretto'] + sample_app: [ + 'JavaDependencySampleApp', + 'JavaKitSampleApp', + 'JavaProbablyPrime', + 'JavaSieve', + 'SwiftAndJavaJarSampleLib', + 'SwiftKitSampleApp', + ] container: image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} env: @@ -79,103 +87,5 @@ jobs: - uses: actions/checkout@v4 - name: Prepare CI Environment uses: ./.github/actions/prepare_env - - name: "Verify Sample: JavaDependencySampleApp" - run: .github/scripts/validate_sample.sh Samples/JavaDependencySampleApp - verify-sample-02: - name: Verify Sample JavaKitSampleApp (swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} os:${{ matrix.os_version }}) - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - swift_version: ['6.1.2'] - os_version: ['jammy'] - jdk_vendor: ['Corretto'] - container: - image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} - env: - JAVA_HOME: "/usr/lib/jvm/default-jdk" - steps: - - uses: actions/checkout@v4 - - name: Prepare CI Environment - uses: ./.github/actions/prepare_env - - name: "Verify Sample" - run: .github/scripts/validate_sample.sh Samples/JavaKitSampleApp - verify-sample-03: - name: Verify Sample JavaProbablyPrime (swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} os:${{ matrix.os_version }}) - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - swift_version: ['6.1.2'] - os_version: ['jammy'] - jdk_vendor: ['Corretto'] - container: - image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} - env: - JAVA_HOME: "/usr/lib/jvm/default-jdk" - steps: - - uses: actions/checkout@v4 - - name: Prepare CI Environment - uses: ./.github/actions/prepare_env - - name: "Verify Sample" - run: .github/scripts/validate_sample.sh Samples/JavaProbablyPrime - verify-sample-04: - name: Verify Sample JavaSieve (swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} os:${{ matrix.os_version }}) - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - swift_version: ['6.1.2'] - os_version: ['jammy'] - jdk_vendor: ['Corretto'] - container: - image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} - env: - JAVA_HOME: "/usr/lib/jvm/default-jdk" - steps: - - uses: actions/checkout@v4 - - name: Prepare CI Environment - uses: ./.github/actions/prepare_env - - name: "Verify Sample" - run: .github/scripts/validate_sample.sh Samples/JavaSieve - verify-sample-05: - name: Verify Sample SwiftAndJavaJarSampleLib (swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} os:${{ matrix.os_version }}) - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - swift_version: ['6.1.2'] - os_version: ['jammy'] - jdk_vendor: ['Corretto'] - container: - image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} - env: - JAVA_HOME: "/usr/lib/jvm/default-jdk" - steps: - - uses: actions/checkout@v4 - - name: Prepare CI Environment - uses: ./.github/actions/prepare_env - - name: "Verify Sample" - run: .github/scripts/validate_sample.sh Samples/SwiftAndJavaJarSampleLib - verify-sample-06: - name: Verify Sample SwiftKitSampleApp (swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} os:${{ matrix.os_version }}) - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - swift_version: ['6.1.2'] - os_version: ['jammy'] - jdk_vendor: ['Corretto'] - container: - image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} - env: - JAVA_HOME: "/usr/lib/jvm/default-jdk" - steps: - - uses: actions/checkout@v4 - - name: Prepare CI Environment - uses: ./.github/actions/prepare_env - - name: "Verify Sample" - run: .github/scripts/validate_sample.sh Samples/SwiftKitSampleApp - # TODO: Benchmark compile crashes in CI, enable when nightly toolchains in better shape. - # - name: Build (Swift) Benchmarks - # run: "swift package --package-path Benchmarks/ benchmark list" + - name: "Verify sample" + run: .github/scripts/validate_sample.sh Samples/${{ matrix.sample_app }} From 8c38d1918cb74b9b6c9f87059a579050f4e0893c Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Mon, 30 Jun 2025 17:20:13 +0200 Subject: [PATCH 016/141] Add JExtract JNI sample app (#295) Co-authored-by: Konrad `ktoso` Malawski --- .github/workflows/pull_request.yml | 1 + Samples/JExtractJNISampleApp/Package.swift | 79 +++++++ .../MySwiftLibrary/MySwiftLibrary.swift | 60 ++++++ .../Sources/MySwiftLibrary/swift-java.config | 4 + Samples/JExtractJNISampleApp/build.gradle | 195 ++++++++++++++++++ Samples/JExtractJNISampleApp/ci-validate.sh | 6 + Samples/JExtractJNISampleApp/gradlew | 1 + Samples/JExtractJNISampleApp/gradlew.bat | 1 + .../com/example/swift/HelloJava2SwiftJNI.java | 45 ++++ ...ift2JavaGenerator+SwiftThunkPrinting.swift | 2 +- .../JNI/JNISwift2JavaGenerator.swift | 108 +++++++--- .../JNI/JNIModuleTests.swift | 7 +- 12 files changed, 482 insertions(+), 27 deletions(-) create mode 100644 Samples/JExtractJNISampleApp/Package.swift create mode 100644 Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift create mode 100644 Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config create mode 100644 Samples/JExtractJNISampleApp/build.gradle create mode 100755 Samples/JExtractJNISampleApp/ci-validate.sh create mode 120000 Samples/JExtractJNISampleApp/gradlew create mode 120000 Samples/JExtractJNISampleApp/gradlew.bat create mode 100644 Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index c1af9ca76..c9d197e74 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -78,6 +78,7 @@ jobs: 'JavaSieve', 'SwiftAndJavaJarSampleLib', 'SwiftKitSampleApp', + 'JExtractJNISampleApp' ] container: image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} diff --git a/Samples/JExtractJNISampleApp/Package.swift b/Samples/JExtractJNISampleApp/Package.swift new file mode 100644 index 000000000..8c7ce8563 --- /dev/null +++ b/Samples/JExtractJNISampleApp/Package.swift @@ -0,0 +1,79 @@ +// swift-tools-version: 6.0 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import CompilerPluginSupport +import PackageDescription + +import class Foundation.FileManager +import class Foundation.ProcessInfo + +// Note: the JAVA_HOME environment variable must be set to point to where +// Java is installed, e.g., +// Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home. +func findJavaHome() -> String { + if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] { + return home + } + + // This is a workaround for envs (some IDEs) which have trouble with + // picking up env variables during the build process + let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home" + if let home = try? String(contentsOfFile: path, encoding: .utf8) { + if let lastChar = home.last, lastChar.isNewline { + return String(home.dropLast()) + } + + return home + } + + fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.") +} +let javaHome = findJavaHome() + +let javaIncludePath = "\(javaHome)/include" +#if os(Linux) + let javaPlatformIncludePath = "\(javaIncludePath)/linux" +#elseif os(macOS) + let javaPlatformIncludePath = "\(javaIncludePath)/darwin" +#else + // TODO: Handle windows as well + #error("Currently only macOS and Linux platforms are supported, this may change in the future.") +#endif + +let package = Package( + name: "JExtractJNISampleApp", + platforms: [ + .macOS(.v15) + ], + products: [ + .library( + name: "MySwiftLibrary", + type: .dynamic, + targets: ["MySwiftLibrary"] + ) + + ], + dependencies: [ + .package(name: "swift-java", path: "../../") + ], + targets: [ + .target( + name: "MySwiftLibrary", + dependencies: [ + .product(name: "JavaKit", package: "swift-java"), + .product(name: "JavaRuntime", package: "swift-java"), + .product(name: "SwiftKitSwift", package: "swift-java"), + ], + exclude: [ + "swift-java.config" + ], + swiftSettings: [ + .swiftLanguageMode(.v5), + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]), + ], + plugins: [ + .plugin(name: "JExtractSwiftPlugin", package: "swift-java") + ] + ) + ] +) diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift new file mode 100644 index 000000000..be04bc089 --- /dev/null +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -0,0 +1,60 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// This is a "plain Swift" file containing various types of declarations, +// that is exported to Java by using the `jextract-swift` tool. +// +// No annotations are necessary on the Swift side to perform the export. + +#if os(Linux) + import Glibc +#else + import Darwin.C +#endif + +public func helloWorld() { + p("\(#function)") +} + +public func globalTakeInt(i: Int64) { + p("i:\(i)") +} + +public func globalMakeInt() -> Int64 { + return 42 +} + +public func globalWriteString(string: String) -> Int64 { + return Int64(string.count) +} + +public func globalTakeIntInt(i: Int64, j: Int64) { + p("i:\(i), j:\(j)") +} + +// ==== Internal helpers + +func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: String = #function) { + print("[swift][\(file):\(line)](\(function)) \(msg)") + fflush(stdout) +} + +#if os(Linux) + // FIXME: why do we need this workaround? + @_silgen_name("_objc_autoreleaseReturnValue") + public func _objc_autoreleaseReturnValue(a: Any) {} + + @_silgen_name("objc_autoreleaseReturnValue") + public func objc_autoreleaseReturnValue(a: Any) {} +#endif diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config new file mode 100644 index 000000000..46bf1f1ca --- /dev/null +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config @@ -0,0 +1,4 @@ +{ + "javaPackage": "com.example.swift", + "mode": "jni" +} diff --git a/Samples/JExtractJNISampleApp/build.gradle b/Samples/JExtractJNISampleApp/build.gradle new file mode 100644 index 000000000..d0e328573 --- /dev/null +++ b/Samples/JExtractJNISampleApp/build.gradle @@ -0,0 +1,195 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import groovy.json.JsonSlurper +import org.swift.swiftkit.gradle.BuildUtils + +import java.nio.file.* +import kotlinx.serialization.json.* + +plugins { + id("build-logic.java-application-conventions") + id("me.champeau.jmh") version "0.7.2" +} + +group = "org.swift.swiftkit" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(24)) + } +} + +def swiftProductsWithJExtractPlugin() { + def stdout = new ByteArrayOutputStream() + def stderr = new ByteArrayOutputStream() + + def result = exec { + commandLine 'swift', 'package', 'describe', '--type', 'json' + standardOutput = stdout + errorOutput = stderr + ignoreExitValue = true + } + + def jsonOutput = stdout.toString() + + if (result.exitValue == 0) { + def json = new JsonSlurper().parseText(jsonOutput) + def products = json.targets + .findAll { target -> + target.product_dependencies?.contains("JExtractSwiftPlugin") + } + .collectMany { target -> + target.product_memberships ?: [] + } + return products + } else { + logger.warn("Command failed: ${stderr.toString()}") + return [] + } +} + + +def swiftCheckValid = tasks.register("swift-check-valid", Exec) { + commandLine "swift" + args("-version") +} + +def jextract = tasks.register("jextract", Exec) { + description = "Generate Java wrappers for swift target" + dependsOn swiftCheckValid + + // only because we depend on "live developing" the plugin while using this project to test it + inputs.file(new File(rootDir, "Package.swift")) + inputs.dir(new File(rootDir, "Sources")) + + // If the package description changes, we should execute jextract again, maybe we added jextract to new targets + inputs.file(new File(projectDir, "Package.swift")) + + // monitor all targets/products which depend on the JExtract plugin + swiftProductsWithJExtractPlugin().each { + logger.info("[swift-java:jextract (Gradle)] Swift input target: ${it}") + inputs.dir(new File(layout.projectDirectory.asFile, "Sources/${it}".toString())) + } + outputs.dir(layout.buildDirectory.dir("../.build/plugins/outputs/${layout.projectDirectory.asFile.getName().toLowerCase()}")) + + File baseSwiftPluginOutputsDir = layout.buildDirectory.dir("../.build/plugins/outputs/").get().asFile + if (!baseSwiftPluginOutputsDir.exists()) { + baseSwiftPluginOutputsDir.mkdirs() + } + Files.walk(layout.buildDirectory.dir("../.build/plugins/outputs/").get().asFile.toPath()).each { + // Add any Java sources generated by the plugin to our sourceSet + if (it.endsWith("JExtractSwiftPlugin/src/generated/java")) { + outputs.dir(it) + } + } + + workingDir = layout.projectDirectory + commandLine "swift" + args("build") // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build + // If we wanted to execute a specific subcommand, we can like this: + // args("run",/* + // "swift-java", "jextract", + // "--swift-module", "MySwiftLibrary", + // // java.package is obtained from the swift-java.config in the swift module + // "--output-java", "${layout.buildDirectory.dir(".build/plugins/outputs/${layout.projectDirectory.asFile.getName().toLowerCase()}/JExtractSwiftPlugin/src/generated/java").get()}", + // "--output-swift", "${layout.buildDirectory.dir(".build/plugins/outputs/${layout.projectDirectory.asFile.getName().toLowerCase()}/JExtractSwiftPlugin/Sources").get()}", + // "--log-level", (logging.level <= LogLevel.INFO ? "debug" : */"info") + // ) +} + +// Add the java-swift generated Java sources +sourceSets { + main { + java { + srcDir(jextract) + } + } + test { + java { + srcDir(jextract) + } + } + jmh { + java { + srcDir(jextract) + } + } +} + +tasks.build { + dependsOn("jextract") +} + + +def cleanSwift = tasks.register("cleanSwift", Exec) { + workingDir = layout.projectDirectory + commandLine "swift" + args("package", "clean") +} +tasks.clean { + dependsOn("cleanSwift") +} + +dependencies { + implementation(project(':SwiftKit')) + + testImplementation(platform("org.junit:junit-bom:5.10.0")) + testImplementation("org.junit.jupiter:junit-jupiter") +} + +tasks.named('test', Test) { + useJUnitPlatform() +} + +application { + mainClass = "com.example.swift.HelloJava2SwiftJNI" + + applicationDefaultJvmArgs = [ + "--enable-native-access=ALL-UNNAMED", + + // Include the library paths where our dylibs are that we want to load and call + "-Djava.library.path=" + + (BuildUtils.javaLibraryPaths(rootDir) + + BuildUtils.javaLibraryPaths(project.projectDir)).join(":"), + + + // Enable tracing downcalls (to Swift) + "-Djextract.trace.downcalls=true" + ] +} + +String jmhIncludes = findProperty("jmhIncludes") + +jmh { + if (jmhIncludes != null) { + includes = [jmhIncludes] + } + + jvmArgsAppend = [ + "--enable-native-access=ALL-UNNAMED", + + "-Djava.library.path=" + + (BuildUtils.javaLibraryPaths(rootDir) + + BuildUtils.javaLibraryPaths(project.projectDir)).join(":"), + + // Enable tracing downcalls (to Swift) + "-Djextract.trace.downcalls=false" + ] +} diff --git a/Samples/JExtractJNISampleApp/ci-validate.sh b/Samples/JExtractJNISampleApp/ci-validate.sh new file mode 100755 index 000000000..07b426271 --- /dev/null +++ b/Samples/JExtractJNISampleApp/ci-validate.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +set -x +set -e + +./gradlew run \ No newline at end of file diff --git a/Samples/JExtractJNISampleApp/gradlew b/Samples/JExtractJNISampleApp/gradlew new file mode 120000 index 000000000..343e0d2ca --- /dev/null +++ b/Samples/JExtractJNISampleApp/gradlew @@ -0,0 +1 @@ +../../gradlew \ No newline at end of file diff --git a/Samples/JExtractJNISampleApp/gradlew.bat b/Samples/JExtractJNISampleApp/gradlew.bat new file mode 120000 index 000000000..cb5a94645 --- /dev/null +++ b/Samples/JExtractJNISampleApp/gradlew.bat @@ -0,0 +1 @@ +../../gradlew.bat \ No newline at end of file diff --git a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java new file mode 100644 index 000000000..36bad93c6 --- /dev/null +++ b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java @@ -0,0 +1,45 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +// Import swift-extract generated sources + +// Import javakit/swiftkit support libraries + +import org.swift.swiftkit.SwiftKit; + +public class HelloJava2SwiftJNI { + + public static void main(String[] args) { + System.out.print("Property: java.library.path = " + SwiftKit.getJavaLibraryPath()); + + examples(); + } + + static void examples() { + MySwiftLibrary.helloWorld(); + + MySwiftLibrary.globalTakeInt(1337); + MySwiftLibrary.globalTakeIntInt(1337, 42); + + long cnt = MySwiftLibrary.globalWriteString("String from Java"); + SwiftKit.trace("count = " + cnt); + + long i = MySwiftLibrary.globalMakeInt(); + SwiftKit.trace("globalMakeInt() = " + i); + + System.out.println("DONE."); + } +} diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift index 54ce0d72e..d6b09b24f 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -27,7 +27,7 @@ extension FFMSwift2JavaGenerator { var printer = CodePrinter() printer.print("// Empty file generated on purpose") - try printer.writeContents( + _ = try printer.writeContents( outputDirectory: self.swiftOutputDirectory, javaPackagePath: nil, filename: expectedFileName) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index 8ec4b4377..0f16e697d 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -28,6 +28,10 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { var thunkNameRegistry = ThunkNameRegistry() + /// Because we need to write empty files for SwiftPM, keep track which files we didn't write yet, + /// and write an empty file for those. + var expectedOutputSwiftFiles: Set + package init( translator: Swift2JavaTranslator, javaPackage: String, @@ -40,11 +44,31 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { self.javaPackage = javaPackage self.swiftOutputDirectory = swiftOutputDirectory self.javaOutputDirectory = javaOutputDirectory + + // If we are forced to write empty files, construct the expected outputs + if translator.config.writeEmptyFiles ?? false { + self.expectedOutputSwiftFiles = Set(translator.inputs.compactMap { (input) -> String? in + guard let filePathPart = input.filePath.split(separator: "/\(translator.swiftModuleName)/").last else { + return nil + } + + return String(filePathPart.replacing(".swift", with: "+SwiftJava.swift")) + }) + self.expectedOutputSwiftFiles.insert("\(translator.swiftModuleName)Module+SwiftJava.swift") + } else { + self.expectedOutputSwiftFiles = [] + } } func generate() throws { try writeSwiftThunkSources() try writeExportedJavaSources() + + let pendingFileCount = self.expectedOutputSwiftFiles.count + if pendingFileCount > 0 { + print("[swift-java] Write empty [\(pendingFileCount)] 'expected' files in: \(swiftOutputDirectory)/") + try writeSwiftExpectedEmptySources() + } } } @@ -73,6 +97,19 @@ extension JNISwift2JavaGenerator { try writeSwiftThunkSources(&printer) } + package func writeSwiftExpectedEmptySources() throws { + for expectedFileName in self.expectedOutputSwiftFiles { + logger.trace("Write empty file: \(expectedFileName) ...") + + var printer = CodePrinter() + printer.print("// Empty file generated on purpose") + _ = try printer.writeContents( + outputDirectory: self.swiftOutputDirectory, + javaPackagePath: nil, + filename: expectedFileName) + } + } + package func writeSwiftThunkSources(_ printer: inout CodePrinter) throws { let moduleFilenameBase = "\(self.swiftModuleName)Module+SwiftJava" let moduleFilename = "\(moduleFilenameBase).swift" @@ -84,10 +121,11 @@ extension JNISwift2JavaGenerator { if let outputFile = try printer.writeContents( outputDirectory: self.swiftOutputDirectory, - javaPackagePath: javaPackagePath, + javaPackagePath: nil, filename: moduleFilename ) { print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile))") + self.expectedOutputSwiftFiles.remove(moduleFilename) } } catch { logger.warning("Failed to write to Swift thunks: \(moduleFilename)") @@ -113,18 +151,22 @@ extension JNISwift2JavaGenerator { private func printSwiftFunctionThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { // TODO: Replace swiftModuleName with class name if non-global - let cName = "Java_" + self.javaPackage.replacingOccurrences(of: ".", with: "_") + "_\(swiftModuleName)_" + decl.name + let cName = + "Java_" + self.javaPackage.replacingOccurrences(of: ".", with: "_") + "_\(swiftModuleName)_" + + decl.name let thunkName = thunkNameRegistry.functionThunkName(decl: decl) let translatedParameters = decl.functionSignature.parameters.enumerated().map { idx, param in (param.parameterName ?? "arg\(idx)", param.type.javaType) } - let thunkParameters = [ - "environment: UnsafeMutablePointer!", - "thisClass: jclass" - ] + translatedParameters.map { "\($0.0): \($0.1.jniTypeName)"} + let thunkParameters = + [ + "environment: UnsafeMutablePointer!", + "thisClass: jclass", + ] + translatedParameters.map { "\($0.0): \($0.1.jniTypeName)" } let swiftReturnType = decl.functionSignature.result.type - let thunkReturnType = !swiftReturnType.isVoid ? " -> \(swiftReturnType.javaType.jniTypeName)" : "" + let thunkReturnType = + !swiftReturnType.isVoid ? " -> \(swiftReturnType.javaType.jniTypeName)" : "" printer.printBraceBlock( """ @@ -132,24 +174,28 @@ extension JNISwift2JavaGenerator { func \(thunkName)(\(thunkParameters.joined(separator: ", ")))\(thunkReturnType) """ ) { printer in - let downcallParameters = zip(decl.functionSignature.parameters, translatedParameters).map { originalParam, translatedParam in - let label = originalParam.argumentLabel.map { "\($0): "} ?? "" + let downcallParameters = zip(decl.functionSignature.parameters, translatedParameters).map { + originalParam, translatedParam in + let label = originalParam.argumentLabel.map { "\($0): " } ?? "" return "\(label)\(originalParam.type)(fromJNI: \(translatedParam.0), in: environment!)" } let tryClause: String = decl.isThrowing ? "try " : "" - let functionDowncall = "\(tryClause)\(swiftModuleName).\(decl.name)(\(downcallParameters.joined(separator: ", ")))" + let functionDowncall = + "\(tryClause)\(swiftModuleName).\(decl.name)(\(downcallParameters.joined(separator: ", ")))" - let innerBody = if swiftReturnType.isVoid { - functionDowncall - } else { - """ - let result = \(functionDowncall) - return result.getJNIValue(in: environment)") - """ - } + let innerBody = + if swiftReturnType.isVoid { + functionDowncall + } else { + """ + let result = \(functionDowncall) + return result.getJNIValue(in: environment) + """ + } if decl.isThrowing { - let dummyReturn = !swiftReturnType.isVoid ? "return \(swiftReturnType).jniPlaceholderValue" : "" + let dummyReturn = + !swiftReturnType.isVoid ? "return \(swiftReturnType).jniPlaceholderValue" : "" printer.print( """ do { @@ -173,6 +219,16 @@ extension JNISwift2JavaGenerator { printPackage(&printer) printModuleClass(&printer) { printer in + printer.print( + """ + static final String LIB_NAME = "\(swiftModuleName)"; + + static { + System.loadLibrary(LIB_NAME); + } + """ + ) + for decl in analysis.importedGlobalFuncs { self.logger.trace("Print global function: \(decl)") printFunctionBinding(&printer, decl) @@ -223,7 +279,9 @@ extension JNISwift2JavaGenerator { */ """ ) - printer.print("public static native \(returnType) \(decl.name)(\(params.joined(separator: ", ")))\(throwsClause);") + printer.print( + "public static native \(returnType) \(decl.name)(\(params.joined(separator: ", ")))\(throwsClause);" + ) } } @@ -253,7 +311,7 @@ extension SwiftStandardLibraryTypeKind { var javaType: JavaType? { switch self { case .bool: .boolean - case .int: .long // TODO: Handle 32-bit or 64-bit + case .int: .long // TODO: Handle 32-bit or 64-bit case .int8: .byte case .uint16: .char case .int16: .short @@ -264,10 +322,10 @@ extension SwiftStandardLibraryTypeKind { case .void: .void case .string: .javaLangString case .uint, .uint8, .uint32, .uint64, - .unsafeRawPointer, .unsafeMutableRawPointer, - .unsafePointer, .unsafeMutablePointer, - .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, - .unsafeBufferPointer, .unsafeMutableBufferPointer: + .unsafeRawPointer, .unsafeMutableRawPointer, + .unsafePointer, .unsafeMutablePointer, + .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, + .unsafeBufferPointer, .unsafeMutableBufferPointer: nil } } diff --git a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift index 2f73cca44..e483a4dd3 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift @@ -44,6 +44,11 @@ struct JNIModuleTests { package com.example.swift; public final class SwiftModule { + static final String LIB_NAME = "SwiftModule"; + + static { + System.loadLibrary(LIB_NAME); + } """ ]) } @@ -104,7 +109,7 @@ struct JNIModuleTests { @_cdecl("Java_com_example_swift_SwiftModule_takeIntegers") func swiftjava_SwiftModule_takeIntegers_i1_i2_i3_i4(environment: UnsafeMutablePointer!, thisClass: jclass, i1: jbyte, i2: jshort, i3: jint, i4: jlong) -> jchar { let result = SwiftModule.takeIntegers(i1: Int8(fromJNI: i1, in: environment!), i2: Int16(fromJNI: i2, in: environment!), i3: Int32(fromJNI: i3, in: environment!), i4: Int(fromJNI: i4, in: environment!)) - return result.getJNIValue(in: environment)") + return result.getJNIValue(in: environment) } """, """ From ac562975f3e011428f8ddb34b7b23c6fb9bfde2d Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 2 Jul 2025 08:47:45 +0900 Subject: [PATCH 017/141] ci: simplistic way to get macOS running (#294) --- .github/actions/prepare_env/action.yml | 50 ++++---- .github/scripts/install_swiftly.sh | 88 ++++++++++++++ .github/scripts/validate_sample.sh | 2 + .github/workflows/pull_request.yml | 110 +++++++++++++++--- .licenseignore | 2 + .../JExtractJNISampleApp/gradle.properties | 1 + Samples/JavaDependencySampleApp/Package.swift | 3 + .../JavaDependencySampleApp/gradle.properties | 1 + Samples/JavaKitSampleApp/Package.swift | 9 +- Samples/JavaKitSampleApp/gradle.properties | 1 + Samples/JavaProbablyPrime/Package.swift | 5 +- Samples/JavaProbablyPrime/gradle.properties | 1 + Samples/JavaSieve/Package.swift | 5 +- .../SwiftAndJavaJarSampleLib/Package.swift | 5 +- .../SwiftAndJavaJarSampleLib/ci-validate.sh | 27 +++-- .../gradle.properties | 1 + Samples/SwiftKitSampleApp/Package.swift | 5 +- Samples/SwiftKitSampleApp/gradle.properties | 1 + Samples/gradle.properties | 1 + docker/Dockerfile | 2 +- docker/install_jdk.sh | 6 +- gradle.properties | 1 + 22 files changed, 266 insertions(+), 61 deletions(-) create mode 100755 .github/scripts/install_swiftly.sh create mode 120000 Samples/JExtractJNISampleApp/gradle.properties create mode 120000 Samples/JavaDependencySampleApp/gradle.properties create mode 120000 Samples/JavaKitSampleApp/gradle.properties create mode 120000 Samples/JavaProbablyPrime/gradle.properties create mode 120000 Samples/SwiftAndJavaJarSampleLib/gradle.properties create mode 120000 Samples/SwiftKitSampleApp/gradle.properties create mode 120000 Samples/gradle.properties create mode 100644 gradle.properties diff --git a/.github/actions/prepare_env/action.yml b/.github/actions/prepare_env/action.yml index dfd0a4353..33fa1f767 100644 --- a/.github/actions/prepare_env/action.yml +++ b/.github/actions/prepare_env/action.yml @@ -4,36 +4,32 @@ description: 'Prepare the CI environment by installing Swift and selected JDK et runs: using: composite steps: - - name: Install System Dependencies - run: apt-get -qq update && apt-get -qq install -y make curl wget libjemalloc2 libjemalloc-dev - shell: bash - - name: Cache JDKs - id: cache-jdk - uses: actions/cache@v4 - continue-on-error: true + - name: Set up JDK ${{ matrix.jdk_version }} + uses: actions/setup-java@v4 with: - path: /usr/lib/jvm/ - key: ${{ runner.os }}-jdk-${{ matrix.jdk_vendor }}-${{ hashFiles('/usr/lib/jvm/*') }} - restore-keys: | - ${{ runner.os }}-jdk- - - name: Install JDK - if: steps.cache-jdk.outputs.cache-hit != 'true' - run: "bash -xc 'JDK_VENDOR=${{ matrix.jdk_vendor }} ./docker/install_jdk.sh'" + distribution: ${{ matrix.jdk_vendor }} + java-version: | + 24 + 21 + cache: 'gradle' + - name: Set JAVA_HOME_{N} shell: bash - # TODO: not using setup-java since incompatible with the swiftlang/swift base image - # - name: Install Untested Nightly Swift - # run: "bash -xc './docker/install_untested_nightly_swift.sh'" - - name: Cache local Gradle repository - uses: actions/cache@v4 - continue-on-error: true - with: - path: | - /root/.gradle/caches - /root/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('*/*.gradle*', 'settings.gradle') }} - restore-keys: | - ${{ runner.os }}-gradle- + run: | + if [[ -n "$JAVA_HOME_21_X64" ]]; then + echo "JAVA_HOME_21=$JAVA_HOME_21_X64" >> $GITHUB_ENV + elif [[ -n "$JAVA_HOME_21_ARM64" ]]; then + echo "JAVA_HOME_21=$JAVA_HOME_21_ARM64" >> $GITHUB_ENV + fi + if [[ -n "$JAVA_HOME_24_X64" ]]; then + echo "JAVA_HOME_24=$JAVA_HOME_24_X64" >> $GITHUB_ENV + elif [[ -n "$JAVA_HOME_24_ARM64" ]]; then + echo "JAVA_HOME_24=$JAVA_HOME_24_ARM64" >> $GITHUB_ENV + fi + - name: Check Java environment + shell: bash + run: ./gradlew -q javaToolchains - name: Cache local SwiftPM repository + if: matrix.os_version == 'jammy' uses: actions/cache@v4 continue-on-error: true with: diff --git a/.github/scripts/install_swiftly.sh b/.github/scripts/install_swiftly.sh new file mode 100755 index 000000000..78fa3f6b8 --- /dev/null +++ b/.github/scripts/install_swiftly.sh @@ -0,0 +1,88 @@ +#!/bin/bash + +# This script is reused from Swiftly itself, see: +# https://github.com/swiftlang/swiftly/blob/main/scripts/prep-gh-action.sh +# +# This script does a bit of extra preparation of the docker containers used to run the GitHub workflows +# that are specific to this project's needs when building/testing. Note that this script runs on +# every supported Linux distribution and macOS so it must adapt to the distribution that it is running. + +if [[ "$(uname -s)" == "Linux" ]]; then + # Install the basic utilities depending on the type of Linux distribution + apt-get --help && apt-get update && TZ=Etc/UTC apt-get -y install curl make gpg tzdata + yum --help && (curl --help && yum -y install curl) && yum install make gpg +fi + +set -e + +while [ $# -ne 0 ]; do + arg="$1" + case "$arg" in + snapshot) + swiftMainSnapshot=true + ;; + *) + ;; + esac + shift +done + +echo "Installing swiftly" + +if [[ "$(uname -s)" == "Linux" ]]; then + curl -O "https://download.swift.org/swiftly/linux/swiftly-$(uname -m).tar.gz" && tar zxf swiftly-*.tar.gz && ./swiftly init -y --skip-install + # shellcheck disable=SC1091 + . "/root/.local/share/swiftly/env.sh" +else + # shellcheck disable=SC2155 + export SWIFTLY_HOME_DIR="$(pwd)/swiftly-bootstrap" + export SWIFTLY_BIN_DIR="$SWIFTLY_HOME_DIR/bin" + export SWIFTLY_TOOLCHAINS_DIR="$SWIFTLY_HOME_DIR/toolchains" + + curl -O https://download.swift.org/swiftly/darwin/swiftly.pkg && pkgutil --check-signature swiftly.pkg && pkgutil --verbose --expand swiftly.pkg "${SWIFTLY_HOME_DIR}" && tar -C "${SWIFTLY_HOME_DIR}" -xvf "${SWIFTLY_HOME_DIR}"/swiftly-*/Payload && "$SWIFTLY_HOME_DIR/bin/swiftly" init -y --skip-install + + # shellcheck disable=SC1091 + . "$SWIFTLY_HOME_DIR/env.sh" +fi + +hash -r + +if [ -n "$GITHUB_ENV" ]; then + echo "Updating GitHub environment" + echo "PATH=$PATH" >> "$GITHUB_ENV" && echo "SWIFTLY_HOME_DIR=$SWIFTLY_HOME_DIR" >> "$GITHUB_ENV" && echo "SWIFTLY_BIN_DIR=$SWIFTLY_BIN_DIR" >> "$GITHUB_ENV" && echo "SWIFTLY_TOOLCHAINS_DIR=$SWIFTLY_TOOLCHAINS_DIR" >> "$GITHUB_ENV" +fi + +selector=() +runSelector=() + +if [ "$swiftMainSnapshot" == true ]; then + echo "Installing latest main-snapshot toolchain" + selector=("main-snapshot") + runSelector=("+main-snapshot") +elif [ -n "${SWIFT_VERSION}" ]; then + echo "Installing selected swift toolchain from SWIFT_VERSION environment variable" + selector=("${SWIFT_VERSION}") + runSelector=() +elif [ -f .swift-version ]; then + echo "Installing selected swift toolchain from .swift-version file" + selector=() + runSelector=() +else + echo "Installing latest toolchain" + selector=("latest") + runSelector=("+latest") +fi + +swiftly install --post-install-file=post-install.sh "${selector[@]}" + +if [ -f post-install.sh ]; then + echo "Performing swift toolchain post-installation" + chmod u+x post-install.sh && ./post-install.sh +fi + +echo "Displaying swift version" +swiftly run "${runSelector[@]}" swift --version + +if [[ "$(uname -s)" == "Linux" ]]; then + CC=clang swiftly run "${runSelector[@]}" "$(dirname "$0")/install-libarchive.sh" +fi diff --git a/.github/scripts/validate_sample.sh b/.github/scripts/validate_sample.sh index b57809767..64265b01f 100755 --- a/.github/scripts/validate_sample.sh +++ b/.github/scripts/validate_sample.sh @@ -11,6 +11,8 @@ declare -r RESET='\033[0m' declare -r sampleDir="$1" declare -r CI_VALIDATE_SCRIPT='ci-validate.sh' +echo "Using Swift: $(which swift)" + echo "" echo "" echo "========================================================================" diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index c9d197e74..b55e04899 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -15,18 +15,37 @@ jobs: license_header_check_project_name: Swift.org test-java: - name: Java tests (swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} os:${{ matrix.os_version }}) + name: Test (Java) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) runs-on: ubuntu-latest strategy: fail-fast: true matrix: swift_version: ['6.1.2'] os_version: ['jammy'] - jdk_vendor: ['Corretto'] + jdk_vendor: ['corretto'] container: image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} env: - JAVA_HOME: "/usr/lib/jvm/default-jdk" + SWIFT_JAVA_VERBOSE: true + steps: + - uses: actions/checkout@v4 + - name: Prepare CI Environment + uses: ./.github/actions/prepare_env + - name: Gradle :SwiftKit:build + run: ./gradlew build -x test + - name: Gradle :SwiftKit:check + run: ./gradlew :SwiftKit:check --info + + test-java-macos: + name: Test (Java) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) + runs-on: [self-hosted, macos, sequoia, ARM64] + strategy: + fail-fast: true + matrix: + swift_version: ['6.1.2'] + os_version: ['macos'] + jdk_vendor: ['corretto'] + env: SWIFT_JAVA_VERBOSE: true steps: - uses: actions/checkout@v4 @@ -36,22 +55,59 @@ jobs: run: ./gradlew build -x test - name: Gradle :SwiftKit:check run: ./gradlew :SwiftKit:check --debug + + benchmark-java: + name: Benchmark (JMH) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + swift_version: ['6.1.2'] + os_version: ['jammy'] + jdk_vendor: ['corretto'] + container: + image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} + env: + SWIFT_JAVA_VERBOSE: true + steps: + - uses: actions/checkout@v4 + - name: Prepare CI Environment + uses: ./.github/actions/prepare_env - name: Gradle compile JMH benchmarks run: ./gradlew compileJmh --info test-swift: - name: Swift tests (swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} os:${{ matrix.os_version }}) + name: Test (Swift) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) runs-on: ubuntu-latest strategy: fail-fast: false matrix: swift_version: ['6.1.2'] os_version: ['jammy'] - jdk_vendor: ['Corretto'] + jdk_vendor: ['corretto'] container: image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} env: - JAVA_HOME: "/usr/lib/jvm/default-jdk" + SWIFT_JAVA_VERBOSE: true + steps: + - uses: actions/checkout@v4 + - name: Prepare CI Environment + uses: ./.github/actions/prepare_env + - name: Swift Build + run: "swift build --build-tests --disable-sandbox" + - name: Swift Test + run: "swift test" + + test-swift-macos: + name: Test (Swift) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) + runs-on: [self-hosted, macos, sequoia, ARM64] + strategy: + fail-fast: false + matrix: + swift_version: ['6.1.2'] + os_version: ['macos'] + jdk_vendor: ['corretto'] + env: SWIFT_JAVA_VERBOSE: true steps: - uses: actions/checkout@v4 @@ -63,30 +119,58 @@ jobs: run: "swift test" verify-samples: - name: Verify Sample ${{ matrix.sample_app }} (swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} os:${{ matrix.os_version }}) + name: Sample ${{ matrix.sample_app }} (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) runs-on: ubuntu-latest strategy: fail-fast: false matrix: swift_version: ['6.1.2'] os_version: ['jammy'] - jdk_vendor: ['Corretto'] - sample_app: [ + jdk_vendor: ['corretto'] + sample_app: [ # TODO: use a reusable-workflow to generate those names 'JavaDependencySampleApp', 'JavaKitSampleApp', 'JavaProbablyPrime', 'JavaSieve', 'SwiftAndJavaJarSampleLib', 'SwiftKitSampleApp', - 'JExtractJNISampleApp' + 'JExtractJNISampleApp', ] container: image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} - env: - JAVA_HOME: "/usr/lib/jvm/default-jdk" steps: - uses: actions/checkout@v4 - name: Prepare CI Environment uses: ./.github/actions/prepare_env - - name: "Verify sample" + - name: "Verify sample: ${{ matrix.sample_app }}" + run: .github/scripts/validate_sample.sh Samples/${{ matrix.sample_app }} + + + verify-samples-macos: + name: Sample ${{ matrix.sample_app }} (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) + runs-on: [self-hosted, macos, sequoia, ARM64] + strategy: + fail-fast: false + matrix: + swift_version: ['6.1.2'] + os_version: ['macos'] + jdk_vendor: ['corretto'] + sample_app: [ # TODO: use a reusable-workflow to generate those names + 'JavaDependencySampleApp', + 'JavaKitSampleApp', + 'JavaProbablyPrime', + 'JavaSieve', + 'SwiftAndJavaJarSampleLib', + 'SwiftKitSampleApp', + 'JExtractJNISampleApp', + ] + steps: + - uses: actions/checkout@v4 + - name: Prepare CI Environment + uses: ./.github/actions/prepare_env + - name: Install Swiftly # we specifically install Swiftly in macOS jobs because we want a simpler way to find the right dylib paths for libraries + run: ./.github/scripts/install_swiftly.sh + env: + SWIFT_VERSION: "${{ matrix.swift_version }}" + - name: "Verify sample ${{ matrix.sample_app }}" run: .github/scripts/validate_sample.sh Samples/${{ matrix.sample_app }} diff --git a/.licenseignore b/.licenseignore index 003cac25c..a49ab625d 100644 --- a/.licenseignore +++ b/.licenseignore @@ -35,6 +35,8 @@ Makefile **/CMakeLists.txt **/*.jar **/generated/*.java +gradle.properties +**/gradle.properties **/generated/*.swift gradle/wrapper/gradle-wrapper.properties gradlew diff --git a/Samples/JExtractJNISampleApp/gradle.properties b/Samples/JExtractJNISampleApp/gradle.properties new file mode 120000 index 000000000..7677fb73b --- /dev/null +++ b/Samples/JExtractJNISampleApp/gradle.properties @@ -0,0 +1 @@ +../gradle.properties \ No newline at end of file diff --git a/Samples/JavaDependencySampleApp/Package.swift b/Samples/JavaDependencySampleApp/Package.swift index 4fa331160..506b8c93e 100644 --- a/Samples/JavaDependencySampleApp/Package.swift +++ b/Samples/JavaDependencySampleApp/Package.swift @@ -44,6 +44,9 @@ let package = Package( name: "JavaDependencySampleApp", platforms: [ .macOS(.v15), + .iOS(.v18), + .watchOS(.v11), + .tvOS(.v18), ], products: [ diff --git a/Samples/JavaDependencySampleApp/gradle.properties b/Samples/JavaDependencySampleApp/gradle.properties new file mode 120000 index 000000000..7677fb73b --- /dev/null +++ b/Samples/JavaDependencySampleApp/gradle.properties @@ -0,0 +1 @@ +../gradle.properties \ No newline at end of file diff --git a/Samples/JavaKitSampleApp/Package.swift b/Samples/JavaKitSampleApp/Package.swift index 0956290c4..1b5458190 100644 --- a/Samples/JavaKitSampleApp/Package.swift +++ b/Samples/JavaKitSampleApp/Package.swift @@ -43,11 +43,10 @@ let javaIncludePath = "\(javaHome)/include" let package = Package( name: "JavaKitSampleApp", platforms: [ - .macOS(.v13), - .iOS(.v13), - .tvOS(.v13), - .watchOS(.v6), - .macCatalyst(.v13), + .macOS(.v15), + .iOS(.v18), + .watchOS(.v11), + .tvOS(.v18), ], products: [ diff --git a/Samples/JavaKitSampleApp/gradle.properties b/Samples/JavaKitSampleApp/gradle.properties new file mode 120000 index 000000000..7677fb73b --- /dev/null +++ b/Samples/JavaKitSampleApp/gradle.properties @@ -0,0 +1 @@ +../gradle.properties \ No newline at end of file diff --git a/Samples/JavaProbablyPrime/Package.swift b/Samples/JavaProbablyPrime/Package.swift index 4cc887f84..9f0ecff24 100644 --- a/Samples/JavaProbablyPrime/Package.swift +++ b/Samples/JavaProbablyPrime/Package.swift @@ -7,7 +7,10 @@ import PackageDescription let package = Package( name: "JavaProbablyPrime", platforms: [ - .macOS(.v10_15), + .macOS(.v15), + .iOS(.v18), + .watchOS(.v11), + .tvOS(.v18), ], products: [ diff --git a/Samples/JavaProbablyPrime/gradle.properties b/Samples/JavaProbablyPrime/gradle.properties new file mode 120000 index 000000000..7677fb73b --- /dev/null +++ b/Samples/JavaProbablyPrime/gradle.properties @@ -0,0 +1 @@ +../gradle.properties \ No newline at end of file diff --git a/Samples/JavaSieve/Package.swift b/Samples/JavaSieve/Package.swift index 65c10481e..ee61a0214 100644 --- a/Samples/JavaSieve/Package.swift +++ b/Samples/JavaSieve/Package.swift @@ -42,7 +42,10 @@ let javaIncludePath = "\(javaHome)/include" let package = Package( name: "JavaSieve", platforms: [ - .macOS(.v10_15), + .macOS(.v15), + .iOS(.v18), + .watchOS(.v11), + .tvOS(.v18), ], dependencies: [ .package(name: "swift-java", path: "../../"), diff --git a/Samples/SwiftAndJavaJarSampleLib/Package.swift b/Samples/SwiftAndJavaJarSampleLib/Package.swift index c4c604eeb..c350a0e7f 100644 --- a/Samples/SwiftAndJavaJarSampleLib/Package.swift +++ b/Samples/SwiftAndJavaJarSampleLib/Package.swift @@ -43,7 +43,10 @@ let javaIncludePath = "\(javaHome)/include" let package = Package( name: "SwiftAndJavaJarSampleLib", platforms: [ - .macOS(.v15) + .macOS(.v15), + .iOS(.v18), + .watchOS(.v11), + .tvOS(.v18), ], products: [ .library( diff --git a/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh b/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh index 1b4769e8d..f56b611ea 100755 --- a/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh +++ b/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh @@ -7,13 +7,21 @@ set -x SWIFT_VERSION="$(swift -version | awk '/Swift version/ { print $3 }')" -# we make sure to build and run with JDK 24 because the runtime needs latest JDK, unlike Gradle which needed 21. -if [ "$(uname -s)" = 'Darwin' ] -then - export OS='osx' +# This is how env variables are set by setup-java +if [ "$(uname -m)" = 'arm64' ]; then + ARCH=ARM64 + JAVAC="${JAVA_HOME_24_ARM64}/bin/javac" + JAVA="${JAVA_HOME_24_ARM64}/bin/java" +else + ARCH=X64 + JAVAC="${JAVA_HOME_24_X64}/bin/javac" + JAVA="${JAVA_HOME_24_X64}/bin/java" +fi + +if [ -n "$JAVA_HOME_24_$ARCH" ]; then + export JAVA_HOME="$JAVA_HOME_24_$ARCH" elif [ "$(uname -s)" = 'Linux' ] then - export OS='linux' export PATH="${PATH}:/usr/lib/jvm/jdk-24/bin" # we need to make sure to use the latest JDK to actually compile/run the executable fi @@ -24,8 +32,9 @@ MYLIB_CLASSPATH="$(pwd)/build/libs/*" CLASSPATH="$(pwd)/:${SWIFTKIT_CLASSPATH}:${MYLIB_CLASSPATH}" echo "CLASSPATH = ${CLASSPATH}" -javac -cp "${CLASSPATH}" Example.java +$JAVAC -cp "${CLASSPATH}" Example.java +# FIXME: move all this into Gradle or SwiftPM and make it easier to get the right classpath for running if [ "$(uname -s)" = 'Linux' ] then SWIFT_LIB_PATHS=/usr/lib/swift/linux @@ -35,6 +44,7 @@ then SWIFT_CORE_LIB=$(find "$HOME"/.local -name "libswiftCore.so" 2>/dev/null | grep "$SWIFT_VERSION" | head -n1) if [ -n "$SWIFT_CORE_LIB" ]; then SWIFT_LIB_PATHS="${SWIFT_LIB_PATHS}:$(dirname "$SWIFT_CORE_LIB")" + ls "$SWIFT_LIB_PATHS" else # maybe there is one installed system-wide in /usr/lib? SWIFT_CORE_LIB2=$(find /usr/lib -name "libswiftCore.so" 2>/dev/null | grep "$SWIFT_VERSION" | head -n1) @@ -44,13 +54,14 @@ then fi elif [ "$(uname -s)" = 'Darwin' ] then - SWIFT_LIB_PATHS=$(find "$(swiftly use --print-location)" | grep dylib$ | grep libswiftCore | grep macos | xargs dirname) + SWIFT_LIB_PATHS=$(find "$(swiftly use --print-location)" | grep dylib$ | grep libswiftCore | grep macos | head -n1 | xargs dirname) SWIFT_LIB_PATHS="${SWIFT_LIB_PATHS}:$(pwd)/$(find . | grep libMySwiftLibrary.dylib$ | sort | head -n1 | xargs dirname)" + fi echo "SWIFT_LIB_PATHS = ${SWIFT_LIB_PATHS}" # Can we run the example? -java --enable-native-access=ALL-UNNAMED \ +${JAVA} --enable-native-access=ALL-UNNAMED \ -Djava.library.path="${SWIFT_LIB_PATHS}" \ -cp "${CLASSPATH}" \ Example diff --git a/Samples/SwiftAndJavaJarSampleLib/gradle.properties b/Samples/SwiftAndJavaJarSampleLib/gradle.properties new file mode 120000 index 000000000..7677fb73b --- /dev/null +++ b/Samples/SwiftAndJavaJarSampleLib/gradle.properties @@ -0,0 +1 @@ +../gradle.properties \ No newline at end of file diff --git a/Samples/SwiftKitSampleApp/Package.swift b/Samples/SwiftKitSampleApp/Package.swift index 34d8fcd37..8501062be 100644 --- a/Samples/SwiftKitSampleApp/Package.swift +++ b/Samples/SwiftKitSampleApp/Package.swift @@ -43,7 +43,10 @@ let javaIncludePath = "\(javaHome)/include" let package = Package( name: "SwiftKitSampleApp", platforms: [ - .macOS(.v15) + .macOS(.v15), + .iOS(.v18), + .watchOS(.v11), + .tvOS(.v18), ], products: [ .library( diff --git a/Samples/SwiftKitSampleApp/gradle.properties b/Samples/SwiftKitSampleApp/gradle.properties new file mode 120000 index 000000000..7677fb73b --- /dev/null +++ b/Samples/SwiftKitSampleApp/gradle.properties @@ -0,0 +1 @@ +../gradle.properties \ No newline at end of file diff --git a/Samples/gradle.properties b/Samples/gradle.properties new file mode 120000 index 000000000..7677fb73b --- /dev/null +++ b/Samples/gradle.properties @@ -0,0 +1 @@ +../gradle.properties \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile index 6604d091c..06b17a872 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -21,6 +21,6 @@ ENV LANGUAGE=en_US.UTF-8 COPY install_jdk.sh . # JDK dependency -RUN bash -xc 'JDK_VENDOR=Corretto ./install_jdk.sh' +RUN bash -xc 'JDK_VENDOR=corretto ./install_jdk.sh' ENV JAVA_HOME="/usr/lib/jvm/default-jdk" ENV PATH="$PATH:/usr/lib/jvm/default-jdk/bin" diff --git a/docker/install_jdk.sh b/docker/install_jdk.sh index 8d0ddda37..122c0d063 100755 --- a/docker/install_jdk.sh +++ b/docker/install_jdk.sh @@ -17,9 +17,9 @@ set -euo pipefail # We need JDK 24 because that's the supported version with latest FFM # However, we also need JDK 23 at most because Gradle does not support 24. -# Supported JDKs: Corretto +# Supported JDKs: corretto if [ "$JDK_VENDOR" = "" ]; then -declare -r JDK_VENDOR="Corretto" +declare -r JDK_VENDOR="corretto" fi apt-get update && apt-get install -y wget tree @@ -33,7 +33,7 @@ download_and_install_jdk() { echo "Installing $JDK_VENDOR JDK (${jdk_version})..." - if [ "$JDK_VENDOR" = 'Corretto' ]; then + if [ "$JDK_VENDOR" = 'corretto' ]; then if [ "$(uname -m)" = 'aarch64' ]; then case "$jdk_version" in "21") diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 000000000..335b6f40a --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +org.gradle.java.installations.fromEnv=JAVA_HOME_24,JAVA_HOME_24_X64,JAVA_HOME_24_ARM64,JAVA_HOME_21,JAVA_HOME_21_X64,JAVA_HOME_21_ARM64 \ No newline at end of file From 3c3e41185c79266bf671ee8ff4ee3797a6c1c053 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 2 Jul 2025 18:54:23 +0900 Subject: [PATCH 018/141] wrap-java: add option to match java package style directory nesting (#296) --- Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift index 349144abf..05ac7651f 100644 --- a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift +++ b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift @@ -52,6 +52,9 @@ extension SwiftJava { @Option(help: "Cache directory for intermediate results and other outputs between runs") var cacheDirectory: String? + @Option(help: "Match java package directory structure with generated Swift files") + var swiftMatchPackageDirectoryStructure: Bool = false + @Argument(help: "Path to .jar file whose Java classes should be wrapped using Swift bindings") var input: String } @@ -229,11 +232,16 @@ extension SwiftJava.WrapJavaCommand { """ + var generatedFileOutputDir = self.actualOutputDirectory + if self.swiftMatchPackageDirectoryStructure { + generatedFileOutputDir?.append(path: javaClass.getPackageName().replacing(".", with: "/")) + } + let swiftFileName = try! translator.getSwiftTypeName(javaClass, preferValueTypes: false) .swiftName.replacing(".", with: "+") + ".swift" try writeContents( swiftFileText, - outputDirectory: self.actualOutputDirectory, + outputDirectory: generatedFileOutputDir, to: swiftFileName, description: "Java class '\(javaClass.getName())' translation" ) From 56dfcfa0a054fee24098bf54eea041dfe2d21729 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Thu, 3 Jul 2025 12:24:45 +0900 Subject: [PATCH 019/141] rename package to fit ecosystem pattern of lowercase and dash (#297) --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 58ee1840b..403617daf 100644 --- a/Package.swift +++ b/Package.swift @@ -84,7 +84,7 @@ let javaIncludePath = "\(javaHome)/include" #endif let package = Package( - name: "SwiftJava", + name: "swift-java", platforms: [ .macOS(.v15) ], From e887a9fdb35f66cf0cfcec38b0d50d5aaa4cda70 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 3 Jul 2025 13:10:37 +0200 Subject: [PATCH 020/141] [JExtract] Generate JNI code for classes (static methods and initializers) (#298) --- .../Sources/MySwiftLibrary/MySwiftClass.swift | 37 + Samples/JExtractJNISampleApp/build.gradle | 5 + Samples/JExtractJNISampleApp/ci-validate.sh | 3 +- .../com/example/swift/HelloJava2SwiftJNI.java | 5 + .../com/example/swift/MySwiftClassTest.java | 37 + .../com/example/swift/MySwiftLibraryTest.java | 57 ++ .../Convenience/JavaType+Extensions.swift | 38 + ...t2JavaGenerator+JavaBindingsPrinting.swift | 2 +- ...MSwift2JavaGenerator+JavaTranslation.swift | 878 +++++++++--------- Sources/JExtractSwiftLib/ImportedDecls.swift | 18 + ...t2JavaGenerator+JavaBindingsPrinting.swift | 197 ++++ ...ISwift2JavaGenerator+JavaTranslation.swift | 123 +++ ...ift2JavaGenerator+SwiftThunkPrinting.swift | 268 ++++++ .../JNI/JNISwift2JavaGenerator.swift | 262 +----- Sources/JExtractSwiftLib/JavaParameter.swift | 25 + Sources/JavaTypes/JavaType+SwiftNames.swift | 7 + .../JNI/JNIClassTests.swift | 173 ++++ .../JNI/JNIModuleTests.swift | 30 +- 18 files changed, 1444 insertions(+), 721 deletions(-) create mode 100644 Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift create mode 100644 Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java create mode 100644 Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java create mode 100644 Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift create mode 100644 Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift create mode 100644 Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift create mode 100644 Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift create mode 100644 Sources/JExtractSwiftLib/JavaParameter.swift create mode 100644 Tests/JExtractSwiftTests/JNI/JNIClassTests.swift diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift new file mode 100644 index 000000000..fca6236fe --- /dev/null +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +public class MySwiftClass { + let x: Int64 + let y: Int64 + + public static func method() { + p("Hello from static method in a class!") + } + + public init(x: Int64, y: Int64) { + self.x = x + self.y = y + p("\(self)") + } + + public init() { + self.x = 10 + self.y = 5 + } + + deinit { + p("deinit called!") + } +} diff --git a/Samples/JExtractJNISampleApp/build.gradle b/Samples/JExtractJNISampleApp/build.gradle index d0e328573..03794ff78 100644 --- a/Samples/JExtractJNISampleApp/build.gradle +++ b/Samples/JExtractJNISampleApp/build.gradle @@ -156,6 +156,11 @@ dependencies { tasks.named('test', Test) { useJUnitPlatform() + + testLogging { + events "failed" + exceptionFormat "full" + } } application { diff --git a/Samples/JExtractJNISampleApp/ci-validate.sh b/Samples/JExtractJNISampleApp/ci-validate.sh index 07b426271..c7a68d228 100755 --- a/Samples/JExtractJNISampleApp/ci-validate.sh +++ b/Samples/JExtractJNISampleApp/ci-validate.sh @@ -3,4 +3,5 @@ set -x set -e -./gradlew run \ No newline at end of file +./gradlew run +./gradlew test \ No newline at end of file diff --git a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java index 36bad93c6..7d0d77e11 100644 --- a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java +++ b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java @@ -40,6 +40,11 @@ static void examples() { long i = MySwiftLibrary.globalMakeInt(); SwiftKit.trace("globalMakeInt() = " + i); + MySwiftClass.method(); + + MySwiftClass myClass = MySwiftClass.init(10, 5); + MySwiftClass myClass2 = MySwiftClass.init(); + System.out.println("DONE."); } } diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java new file mode 100644 index 000000000..994240cb7 --- /dev/null +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import com.example.swift.MySwiftLibrary; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class MySwiftClassTest { + @Test + void init_noParameters() { + MySwiftClass c = MySwiftClass.init(); + assertNotNull(c); + } + + @Test + void init_withParameters() { + MySwiftClass c = MySwiftClass.init(1337, 42); + assertNotNull(c); + } + +} \ No newline at end of file diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java new file mode 100644 index 000000000..f1cefd199 --- /dev/null +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -0,0 +1,57 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import com.example.swift.MySwiftLibrary; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.*; + +public class MySwiftLibraryTest { + @Test + void call_helloWorld() { + MySwiftLibrary.helloWorld(); + } + + @Test + void call_globalTakeInt() { + MySwiftLibrary.globalTakeInt(12); + } + + @Test + void call_globalMakeInt() { + long i = MySwiftLibrary.globalMakeInt(); + assertEquals(42, i); + } + + @Test + void call_globalTakeIntInt() { + MySwiftLibrary.globalTakeIntInt(1337, 42); + } + + @Test + void call_writeString_jextract() { + var string = "Hello Swift!"; + long reply = MySwiftLibrary.globalWriteString(string); + + assertEquals(string.length(), reply); + } +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift new file mode 100644 index 000000000..9da3ae5bc --- /dev/null +++ b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JavaTypes + +extension JavaType { + var jniTypeSignature: String { + switch self { + case .boolean: "Z" + case .byte: "B" + case .char: "C" + case .short: "S" + case .int: "I" + case .long: "J" + case .float: "F" + case .double: "D" + case .class(let package, let name): + if let package { + "L\(package.replacingOccurrences(of: ".", with: "/"))/\(name);" + } else { + "L\(name);" + } + case .array(let javaType): "[\(javaType.jniTypeSignature)" + case .void: fatalError("There is no type signature for 'void'") + } + } +} diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index a6cc6b26d..364fd2700 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -439,7 +439,7 @@ extension FFMSwift2JavaGenerator { } } -extension JavaConversionStep { +extension FFMSwift2JavaGenerator.JavaConversionStep { /// Whether the conversion uses SwiftArena. var requiresSwiftArena: Bool { switch self { diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index af1ddd09f..16efddead 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -34,538 +34,526 @@ extension FFMSwift2JavaGenerator { translatedDecls[decl] = translated return translated } -} - -/// Represent a parameter in Java code. -struct JavaParameter { - /// The type. - var type: JavaType - /// The name. - var name: String -} + /// Represent a Swift API parameter translated to Java. + struct TranslatedParameter { + /// Java parameter(s) mapped to the Swift parameter. + /// + /// Array because one Swift parameter can be mapped to multiple parameters. + var javaParameters: [JavaParameter] -/// Represent a Swift API parameter translated to Java. -struct TranslatedParameter { - /// Java parameter(s) mapped to the Swift parameter. - /// - /// Array because one Swift parameter can be mapped to multiple parameters. - var javaParameters: [JavaParameter] + /// Describes how to convert the Java parameter to the lowered arguments for + /// the foreign function. + var conversion: JavaConversionStep + } - /// Describes how to convert the Java parameter to the lowered arguments for - /// the foreign function. - var conversion: JavaConversionStep -} + /// Represent a Swift API result translated to Java. + struct TranslatedResult { + /// Java type that represents the Swift result type. + var javaResultType: JavaType + + /// Required indirect return receivers for receiving the result. + /// + /// 'JavaParameter.name' is the suffix for the receiver variable names. For example + /// + /// var _result_pointer = MemorySegment.allocate(...) + /// var _result_count = MemroySegment.allocate(...) + /// downCall(_result_pointer, _result_count) + /// return constructResult(_result_pointer, _result_count) + /// + /// This case, there're two out parameter, named '_pointer' and '_count'. + var outParameters: [JavaParameter] + + /// Describes how to construct the Java result from the foreign function return + /// value and/or the out parameters. + var conversion: JavaConversionStep + } -/// Represent a Swift API result translated to Java. -struct TranslatedResult { - /// Java type that represents the Swift result type. - var javaResultType: JavaType - /// Required indirect return receivers for receiving the result. - /// - /// 'JavaParameter.name' is the suffix for the receiver variable names. For example - /// - /// var _result_pointer = MemorySegment.allocate(...) - /// var _result_count = MemroySegment.allocate(...) - /// downCall(_result_pointer, _result_count) - /// return constructResult(_result_pointer, _result_count) + /// Translated Java API representing a Swift API. /// - /// This case, there're two out parameter, named '_pointer' and '_count'. - var outParameters: [JavaParameter] - - /// Describes how to construct the Java result from the foreign function return - /// value and/or the out parameters. - var conversion: JavaConversionStep -} + /// Since this holds the lowered signature, and the original `SwiftFunctionSignature` + /// in it, this contains all the API information (except the name) to generate the + /// cdecl thunk, Java binding, and the Java wrapper function. + struct TranslatedFunctionDecl { + /// Java function name. + let name: String + /// Functional interfaces required for the Java method. + let functionTypes: [TranslatedFunctionType] -/// Translated Java API representing a Swift API. -/// -/// Since this holds the lowered signature, and the original `SwiftFunctionSignature` -/// in it, this contains all the API information (except the name) to generate the -/// cdecl thunk, Java binding, and the Java wrapper function. -struct TranslatedFunctionDecl { - /// Java function name. - let name: String + /// Function signature. + let translatedSignature: TranslatedFunctionSignature - /// Functional interfaces required for the Java method. - let functionTypes: [TranslatedFunctionType] - - /// Function signature. - let translatedSignature: TranslatedFunctionSignature - - /// Cdecl lowerd signature. - let loweredSignature: LoweredFunctionSignature -} - -/// Function signature for a Java API. -struct TranslatedFunctionSignature { - var selfParameter: TranslatedParameter? - var parameters: [TranslatedParameter] - var result: TranslatedResult -} - -/// Represent a Swift closure type in the user facing Java API. -/// -/// Closures are translated to named functional interfaces in Java. -struct TranslatedFunctionType { - var name: String - var parameters: [TranslatedParameter] - var result: TranslatedResult - var swiftType: SwiftFunctionType - var cdeclType: SwiftFunctionType - - /// Whether or not this functional interface with C ABI compatible. - var isCompatibleWithC: Bool { - result.conversion.isPlaceholder && parameters.allSatisfy(\.conversion.isPlaceholder) + /// Cdecl lowerd signature. + let loweredSignature: LoweredFunctionSignature } -} -extension TranslatedFunctionSignature { - /// Whether or not if the down-calling requires temporary "Arena" which is - /// only used during the down-calling. - var requiresTemporaryArena: Bool { - if self.parameters.contains(where: { $0.conversion.requiresTemporaryArena }) { - return true - } - if self.selfParameter?.conversion.requiresTemporaryArena ?? false { - return true - } - if self.result.conversion.requiresTemporaryArena { - return true - } - return false + /// Function signature for a Java API. + struct TranslatedFunctionSignature { + var selfParameter: TranslatedParameter? + var parameters: [TranslatedParameter] + var result: TranslatedResult } - /// Whether if the down-calling requires "SwiftArena" or not, which should be - /// passed-in by the API caller. This is needed if the API returns a `SwiftValue` - var requiresSwiftArena: Bool { - return self.result.conversion.requiresSwiftArena + /// Represent a Swift closure type in the user facing Java API. + /// + /// Closures are translated to named functional interfaces in Java. + struct TranslatedFunctionType { + var name: String + var parameters: [TranslatedParameter] + var result: TranslatedResult + var swiftType: SwiftFunctionType + var cdeclType: SwiftFunctionType + + /// Whether or not this functional interface with C ABI compatible. + var isCompatibleWithC: Bool { + result.conversion.isPlaceholder && parameters.allSatisfy(\.conversion.isPlaceholder) + } } -} -struct JavaTranslation { - var swiftStdlibTypes: SwiftStandardLibraryTypeDecls + struct JavaTranslation { + var swiftStdlibTypes: SwiftStandardLibraryTypeDecls - func translate( - _ decl: ImportedFunc - ) throws -> TranslatedFunctionDecl { - let lowering = CdeclLowering(swiftStdlibTypes: self.swiftStdlibTypes) - let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature) + func translate( + _ decl: ImportedFunc + ) throws -> TranslatedFunctionDecl { + let lowering = CdeclLowering(swiftStdlibTypes: self.swiftStdlibTypes) + let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature) - // Name. - let javaName = switch decl.apiKind { - case .getter: "get\(decl.name.toCamelCase)" - case .setter: "set\(decl.name.toCamelCase)" - case .function, .initializer: decl.name - } + // Name. + let javaName = switch decl.apiKind { + case .getter: "get\(decl.name.toCamelCase)" + case .setter: "set\(decl.name.toCamelCase)" + case .function, .initializer: decl.name + } - // Signature. - let translatedSignature = try translate(loweredFunctionSignature: loweredSignature, methodName: javaName) - - // Closures. - var funcTypes: [TranslatedFunctionType] = [] - for (idx, param) in decl.functionSignature.parameters.enumerated() { - switch param.type { - case .function(let funcTy): - let paramName = param.parameterName ?? "_\(idx)" - guard case .function( let cdeclTy) = loweredSignature.parameters[idx].cdeclParameters[0].type else { - preconditionFailure("closure parameter wasn't lowered to a function type; \(funcTy)") + // Signature. + let translatedSignature = try translate(loweredFunctionSignature: loweredSignature, methodName: javaName) + + // Closures. + var funcTypes: [TranslatedFunctionType] = [] + for (idx, param) in decl.functionSignature.parameters.enumerated() { + switch param.type { + case .function(let funcTy): + let paramName = param.parameterName ?? "_\(idx)" + guard case .function( let cdeclTy) = loweredSignature.parameters[idx].cdeclParameters[0].type else { + preconditionFailure("closure parameter wasn't lowered to a function type; \(funcTy)") + } + let translatedClosure = try translateFunctionType(name: paramName, swiftType: funcTy, cdeclType: cdeclTy) + funcTypes.append(translatedClosure) + case .tuple: + // TODO: Implement + break + default: + break } - let translatedClosure = try translateFunctionType(name: paramName, swiftType: funcTy, cdeclType: cdeclTy) - funcTypes.append(translatedClosure) - case .tuple: - // TODO: Implement - break - default: - break } - } - return TranslatedFunctionDecl( - name: javaName, - functionTypes: funcTypes, - translatedSignature: translatedSignature, - loweredSignature: loweredSignature - ) - } - - /// Translate Swift closure type to Java functional interface. - func translateFunctionType( - name: String, - swiftType: SwiftFunctionType, - cdeclType: SwiftFunctionType - ) throws -> TranslatedFunctionType { - var translatedParams: [TranslatedParameter] = [] - - for (i, param) in swiftType.parameters.enumerated() { - let paramName = param.parameterName ?? "_\(i)" - translatedParams.append( - try translateClosureParameter(param.type, convention: param.convention, parameterName: paramName) + return TranslatedFunctionDecl( + name: javaName, + functionTypes: funcTypes, + translatedSignature: translatedSignature, + loweredSignature: loweredSignature ) } - guard let resultCType = try? CType(cdeclType: swiftType.resultType) else { - throw JavaTranslationError.unhandledType(.function(swiftType)) - } + /// Translate Swift closure type to Java functional interface. + func translateFunctionType( + name: String, + swiftType: SwiftFunctionType, + cdeclType: SwiftFunctionType + ) throws -> TranslatedFunctionType { + var translatedParams: [TranslatedParameter] = [] + + for (i, param) in swiftType.parameters.enumerated() { + let paramName = param.parameterName ?? "_\(i)" + translatedParams.append( + try translateClosureParameter(param.type, convention: param.convention, parameterName: paramName) + ) + } - let transltedResult = TranslatedResult( - javaResultType: resultCType.javaType, - outParameters: [], - conversion: .placeholder - ) - - return TranslatedFunctionType( - name: name, - parameters: translatedParams, - result: transltedResult, - swiftType: swiftType, - cdeclType: cdeclType - ) - } + guard let resultCType = try? CType(cdeclType: swiftType.resultType) else { + throw JavaTranslationError.unhandledType(.function(swiftType)) + } - func translateClosureParameter( - _ type: SwiftType, - convention: SwiftParameterConvention, - parameterName: String - ) throws -> TranslatedParameter { - if let cType = try? CType(cdeclType: type) { - return TranslatedParameter( - javaParameters: [ - JavaParameter(type: cType.javaType, name: parameterName) - ], + let transltedResult = TranslatedResult( + javaResultType: resultCType.javaType, + outParameters: [], conversion: .placeholder ) + + return TranslatedFunctionType( + name: name, + parameters: translatedParams, + result: transltedResult, + swiftType: swiftType, + cdeclType: cdeclType + ) } - switch type { - case .nominal(let nominal): - if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType { - switch knownType { - case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: - return TranslatedParameter( - javaParameters: [ - JavaParameter(type: .javaForeignMemorySegment, name: parameterName) - ], - conversion: .method( - .explodedName(component: "pointer"), - methodName: "reinterpret", - arguments: [ - .explodedName(component: "count") + func translateClosureParameter( + _ type: SwiftType, + convention: SwiftParameterConvention, + parameterName: String + ) throws -> TranslatedParameter { + if let cType = try? CType(cdeclType: type) { + return TranslatedParameter( + javaParameters: [ + JavaParameter(name: parameterName, type: cType.javaType) + ], + conversion: .placeholder + ) + } + + switch type { + case .nominal(let nominal): + if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType { + switch knownType { + case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: + return TranslatedParameter( + javaParameters: [ + JavaParameter(name: parameterName, type: .javaForeignMemorySegment) ], - withArena: false + conversion: .method( + .explodedName(component: "pointer"), + methodName: "reinterpret", + arguments: [ + .explodedName(component: "count") + ], + withArena: false + ) ) - ) - default: - break + default: + break + } } + default: + break } - default: - break + throw JavaTranslationError.unhandledType(type) } - throw JavaTranslationError.unhandledType(type) - } - /// Translate a Swift API signature to the user-facing Java API signature. - /// - /// Note that the result signature is for the high-level Java API, not the - /// low-level FFM down-calling interface. - func translate( - loweredFunctionSignature: LoweredFunctionSignature, - methodName: String - ) throws -> TranslatedFunctionSignature { - let swiftSignature = loweredFunctionSignature.original - - // 'self' - let selfParameter: TranslatedParameter? - if case .instance(let swiftSelf) = swiftSignature.selfParameter { - selfParameter = try self.translate( - swiftParam: swiftSelf, - loweredParam: loweredFunctionSignature.selfParameter!, - methodName: methodName, - parameterName: swiftSelf.parameterName ?? "self" - ) - } else { - selfParameter = nil - } - - // Regular parameters. - let parameters: [TranslatedParameter] = try swiftSignature.parameters.enumerated() - .map { (idx, swiftParam) in - let loweredParam = loweredFunctionSignature.parameters[idx] - let parameterName = swiftParam.parameterName ?? "_\(idx)" - return try self.translate( - swiftParam: swiftParam, - loweredParam: loweredParam, + /// Translate a Swift API signature to the user-facing Java API signature. + /// + /// Note that the result signature is for the high-level Java API, not the + /// low-level FFM down-calling interface. + func translate( + loweredFunctionSignature: LoweredFunctionSignature, + methodName: String + ) throws -> TranslatedFunctionSignature { + let swiftSignature = loweredFunctionSignature.original + + // 'self' + let selfParameter: TranslatedParameter? + if case .instance(let swiftSelf) = swiftSignature.selfParameter { + selfParameter = try self.translate( + swiftParam: swiftSelf, + loweredParam: loweredFunctionSignature.selfParameter!, methodName: methodName, - parameterName: parameterName + parameterName: swiftSelf.parameterName ?? "self" ) + } else { + selfParameter = nil } - // Result. - let result = try self.translate( - swiftResult: swiftSignature.result, - loweredResult: loweredFunctionSignature.result - ) - - return TranslatedFunctionSignature( - selfParameter: selfParameter, - parameters: parameters, - result: result - ) - } - - /// Translate a Swift API parameter to the user-facing Java API parameter. - func translate( - swiftParam: SwiftParameter, - loweredParam: LoweredParameter, - methodName: String, - parameterName: String - ) throws -> TranslatedParameter { - let swiftType = swiftParam.type - - // If there is a 1:1 mapping between this Swift type and a C type, that can - // be expressed as a Java primitive type. - if let cType = try? CType(cdeclType: swiftType) { - let javaType = cType.javaType - return TranslatedParameter( - javaParameters: [ - JavaParameter( - type: javaType, - name: parameterName + // Regular parameters. + let parameters: [TranslatedParameter] = try swiftSignature.parameters.enumerated() + .map { (idx, swiftParam) in + let loweredParam = loweredFunctionSignature.parameters[idx] + let parameterName = swiftParam.parameterName ?? "_\(idx)" + return try self.translate( + swiftParam: swiftParam, + loweredParam: loweredParam, + methodName: methodName, + parameterName: parameterName ) - ], - conversion: .placeholder + } + + // Result. + let result = try self.translate( + swiftResult: swiftSignature.result, + loweredResult: loweredFunctionSignature.result ) - } - switch swiftType { - case .metatype: - // Metatype are expressed as 'org.swift.swiftkit.SwiftAnyType' - return TranslatedParameter( - javaParameters: [ - JavaParameter( - type: JavaType.class(package: "org.swift.swiftkit", name: "SwiftAnyType"), - name: parameterName) - ], - conversion: .swiftValueSelfSegment(.placeholder) + return TranslatedFunctionSignature( + selfParameter: selfParameter, + parameters: parameters, + result: result ) + } - case .nominal(let swiftNominalType): - if let knownType = swiftNominalType.nominalTypeDecl.knownStandardLibraryType { - if swiftParam.convention == .inout { - // FIXME: Support non-trivial 'inout' for builtin types. - throw JavaTranslationError.inoutNotSupported(swiftType) - } - switch knownType { - case .unsafePointer, .unsafeMutablePointer: - // FIXME: Implement - throw JavaTranslationError.unhandledType(swiftType) - case .unsafeBufferPointer, .unsafeMutableBufferPointer: - // FIXME: Implement - throw JavaTranslationError.unhandledType(swiftType) + /// Translate a Swift API parameter to the user-facing Java API parameter. + func translate( + swiftParam: SwiftParameter, + loweredParam: LoweredParameter, + methodName: String, + parameterName: String + ) throws -> TranslatedParameter { + let swiftType = swiftParam.type + + // If there is a 1:1 mapping between this Swift type and a C type, that can + // be expressed as a Java primitive type. + if let cType = try? CType(cdeclType: swiftType) { + let javaType = cType.javaType + return TranslatedParameter( + javaParameters: [ + JavaParameter( + name: parameterName, type: javaType + ) + ], + conversion: .placeholder + ) + } - case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: - return TranslatedParameter( - javaParameters: [ - JavaParameter(type: .javaForeignMemorySegment, name: parameterName), - ], - conversion: .commaSeparated([ - .placeholder, - .method(.placeholder, methodName: "byteSize", arguments: [], withArena: false) - ]) - ) + switch swiftType { + case .metatype: + // Metatype are expressed as 'org.swift.swiftkit.SwiftAnyType' + return TranslatedParameter( + javaParameters: [ + JavaParameter( + name: parameterName, type: JavaType.class(package: "org.swift.swiftkit", name: "SwiftAnyType")) + ], + conversion: .swiftValueSelfSegment(.placeholder) + ) - case .string: - return TranslatedParameter( - javaParameters: [ - JavaParameter( - type: .javaLangString, - name: parameterName - ) - ], - conversion: .call(.placeholder, function: "SwiftKit.toCString", withArena: true) - ) + case .nominal(let swiftNominalType): + if let knownType = swiftNominalType.nominalTypeDecl.knownStandardLibraryType { + if swiftParam.convention == .inout { + // FIXME: Support non-trivial 'inout' for builtin types. + throw JavaTranslationError.inoutNotSupported(swiftType) + } + switch knownType { + case .unsafePointer, .unsafeMutablePointer: + // FIXME: Implement + throw JavaTranslationError.unhandledType(swiftType) + case .unsafeBufferPointer, .unsafeMutableBufferPointer: + // FIXME: Implement + throw JavaTranslationError.unhandledType(swiftType) + + case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: + return TranslatedParameter( + javaParameters: [ + JavaParameter(name: parameterName, type: .javaForeignMemorySegment), + ], + conversion: .commaSeparated([ + .placeholder, + .method(.placeholder, methodName: "byteSize", arguments: [], withArena: false) + ]) + ) - default: + case .string: + return TranslatedParameter( + javaParameters: [ + JavaParameter( + name: parameterName, type: .javaLangString + ) + ], + conversion: .call(.placeholder, function: "SwiftKit.toCString", withArena: true) + ) + + default: + throw JavaTranslationError.unhandledType(swiftType) + } + } + + // Generic types are not supported yet. + guard swiftNominalType.genericArguments == nil else { throw JavaTranslationError.unhandledType(swiftType) } - } - // Generic types are not supported yet. - guard swiftNominalType.genericArguments == nil else { - throw JavaTranslationError.unhandledType(swiftType) - } + return TranslatedParameter( + javaParameters: [ + JavaParameter( + name: parameterName, type: try translate(swiftType: swiftType) + ) + ], + conversion: .swiftValueSelfSegment(.placeholder) + ) - return TranslatedParameter( - javaParameters: [ - JavaParameter( - type: try translate(swiftType: swiftType), - name: parameterName - ) - ], - conversion: .swiftValueSelfSegment(.placeholder) - ) + case .tuple: + // TODO: Implement. + throw JavaTranslationError.unhandledType(swiftType) - case .tuple: - // TODO: Implement. - throw JavaTranslationError.unhandledType(swiftType) - - case .function: - return TranslatedParameter( - javaParameters: [ - JavaParameter( - type: JavaType.class(package: nil, name: "\(methodName).\(parameterName)"), - name: parameterName) - ], - conversion: .call(.placeholder, function: "\(methodName).$toUpcallStub", withArena: true) - ) + case .function: + return TranslatedParameter( + javaParameters: [ + JavaParameter( + name: parameterName, type: JavaType.class(package: nil, name: "\(methodName).\(parameterName)")) + ], + conversion: .call(.placeholder, function: "\(methodName).$toUpcallStub", withArena: true) + ) - case .optional: - throw JavaTranslationError.unhandledType(swiftType) + case .optional: + throw JavaTranslationError.unhandledType(swiftType) + } } - } - /// Translate a Swift API result to the user-facing Java API result. - func translate( - swiftResult: SwiftResult, - loweredResult: LoweredResult - ) throws -> TranslatedResult { - let swiftType = swiftResult.type - - // If there is a 1:1 mapping between this Swift type and a C type, that can - // be expressed as a Java primitive type. - if let cType = try? CType(cdeclType: swiftType) { - let javaType = cType.javaType - return TranslatedResult( - javaResultType: javaType, - outParameters: [], - conversion: .placeholder - ) - } + /// Translate a Swift API result to the user-facing Java API result. + func translate( + swiftResult: SwiftResult, + loweredResult: LoweredResult + ) throws -> TranslatedResult { + let swiftType = swiftResult.type + + // If there is a 1:1 mapping between this Swift type and a C type, that can + // be expressed as a Java primitive type. + if let cType = try? CType(cdeclType: swiftType) { + let javaType = cType.javaType + return TranslatedResult( + javaResultType: javaType, + outParameters: [], + conversion: .placeholder + ) + } - switch swiftType { - case .metatype(_): - // Metatype are expressed as 'org.swift.swiftkit.SwiftAnyType' - let javaType = JavaType.class(package: "org.swift.swiftkit", name: "SwiftAnyType") - return TranslatedResult( - javaResultType: javaType, - outParameters: [], - conversion: .construct(.placeholder, javaType) - ) + switch swiftType { + case .metatype(_): + // Metatype are expressed as 'org.swift.swiftkit.SwiftAnyType' + let javaType = JavaType.class(package: "org.swift.swiftkit", name: "SwiftAnyType") + return TranslatedResult( + javaResultType: javaType, + outParameters: [], + conversion: .construct(.placeholder, javaType) + ) - case .nominal(let swiftNominalType): - if let knownType = swiftNominalType.nominalTypeDecl.knownStandardLibraryType { - switch knownType { - case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: - return TranslatedResult( - javaResultType: .javaForeignMemorySegment, - outParameters: [ - JavaParameter(type: .javaForeignMemorySegment, name: "pointer"), - JavaParameter(type: .long, name: "count"), - ], - conversion: .method( - .readMemorySegment(.explodedName(component: "pointer"), as: .javaForeignMemorySegment), - methodName: "reinterpret", - arguments: [ - .readMemorySegment(.explodedName(component: "count"), as: .long), + case .nominal(let swiftNominalType): + if let knownType = swiftNominalType.nominalTypeDecl.knownStandardLibraryType { + switch knownType { + case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: + return TranslatedResult( + javaResultType: .javaForeignMemorySegment, + outParameters: [ + JavaParameter(name: "pointer", type: .javaForeignMemorySegment), + JavaParameter(name: "count", type: .long), ], - withArena: false + conversion: .method( + .readMemorySegment(.explodedName(component: "pointer"), as: .javaForeignMemorySegment), + methodName: "reinterpret", + arguments: [ + .readMemorySegment(.explodedName(component: "count"), as: .long), + ], + withArena: false + ) ) - ) - case .unsafePointer, .unsafeMutablePointer: - // FIXME: Implement - throw JavaTranslationError.unhandledType(swiftType) - case .unsafeBufferPointer, .unsafeMutableBufferPointer: - // FIXME: Implement - throw JavaTranslationError.unhandledType(swiftType) - case .string: - // FIXME: Implement - throw JavaTranslationError.unhandledType(swiftType) - default: + case .unsafePointer, .unsafeMutablePointer: + // FIXME: Implement + throw JavaTranslationError.unhandledType(swiftType) + case .unsafeBufferPointer, .unsafeMutableBufferPointer: + // FIXME: Implement + throw JavaTranslationError.unhandledType(swiftType) + case .string: + // FIXME: Implement + throw JavaTranslationError.unhandledType(swiftType) + default: + throw JavaTranslationError.unhandledType(swiftType) + } + } + + // Generic types are not supported yet. + guard swiftNominalType.genericArguments == nil else { throw JavaTranslationError.unhandledType(swiftType) } - } - // Generic types are not supported yet. - guard swiftNominalType.genericArguments == nil else { - throw JavaTranslationError.unhandledType(swiftType) - } + let javaType: JavaType = .class(package: nil, name: swiftNominalType.nominalTypeDecl.name) + return TranslatedResult( + javaResultType: javaType, + outParameters: [ + JavaParameter(name: "", type: javaType) + ], + conversion: .constructSwiftValue(.placeholder, javaType) + ) - let javaType: JavaType = .class(package: nil, name: swiftNominalType.nominalTypeDecl.name) - return TranslatedResult( - javaResultType: javaType, - outParameters: [ - JavaParameter(type: javaType, name: "") - ], - conversion: .constructSwiftValue(.placeholder, javaType) - ) + case .tuple: + // TODO: Implement. + throw JavaTranslationError.unhandledType(swiftType) - case .tuple: - // TODO: Implement. - throw JavaTranslationError.unhandledType(swiftType) + case .optional, .function: + throw JavaTranslationError.unhandledType(swiftType) + } - case .optional, .function: - throw JavaTranslationError.unhandledType(swiftType) } - } - - func translate( - swiftType: SwiftType - ) throws -> JavaType { - guard let nominalName = swiftType.asNominalTypeDeclaration?.name else { - throw JavaTranslationError.unhandledType(swiftType) + func translate( + swiftType: SwiftType + ) throws -> JavaType { + guard let nominalName = swiftType.asNominalTypeDeclaration?.name else { + throw JavaTranslationError.unhandledType(swiftType) + } + return .class(package: nil, name: nominalName) } - return .class(package: nil, name: nominalName) } -} -/// Describes how to convert values between Java types and FFM types. -enum JavaConversionStep { - // The input - case placeholder + /// Describes how to convert values between Java types and FFM types. + enum JavaConversionStep { + // The input + case placeholder - // The input exploded into components. - case explodedName(component: String) + // The input exploded into components. + case explodedName(component: String) - // A fixed value - case constant(String) + // A fixed value + case constant(String) - // 'value.$memorySegment()' - indirect case swiftValueSelfSegment(JavaConversionStep) + // 'value.$memorySegment()' + indirect case swiftValueSelfSegment(JavaConversionStep) - // call specified function using the placeholder as arguments. - // If `withArena` is true, `arena$` argument is added. - indirect case call(JavaConversionStep, function: String, withArena: Bool) + // call specified function using the placeholder as arguments. + // If `withArena` is true, `arena$` argument is added. + indirect case call(JavaConversionStep, function: String, withArena: Bool) - // Apply a method on the placeholder. - // If `withArena` is true, `arena$` argument is added. - indirect case method(JavaConversionStep, methodName: String, arguments: [JavaConversionStep] = [], withArena: Bool) + // Apply a method on the placeholder. + // If `withArena` is true, `arena$` argument is added. + indirect case method(JavaConversionStep, methodName: String, arguments: [JavaConversionStep] = [], withArena: Bool) - // Call 'new \(Type)(\(placeholder), swiftArena$)'. - indirect case constructSwiftValue(JavaConversionStep, JavaType) + // Call 'new \(Type)(\(placeholder), swiftArena$)'. + indirect case constructSwiftValue(JavaConversionStep, JavaType) - // Construct the type using the placeholder as arguments. - indirect case construct(JavaConversionStep, JavaType) + // Construct the type using the placeholder as arguments. + indirect case construct(JavaConversionStep, JavaType) - // Casting the placeholder to the certain type. - indirect case cast(JavaConversionStep, JavaType) + // Casting the placeholder to the certain type. + indirect case cast(JavaConversionStep, JavaType) - // Convert the results of the inner steps to a comma separated list. - indirect case commaSeparated([JavaConversionStep]) + // Convert the results of the inner steps to a comma separated list. + indirect case commaSeparated([JavaConversionStep]) - // Refer an exploded argument suffixed with `_\(name)`. - indirect case readMemorySegment(JavaConversionStep, as: JavaType) + // Refer an exploded argument suffixed with `_\(name)`. + indirect case readMemorySegment(JavaConversionStep, as: JavaType) - var isPlaceholder: Bool { - return if case .placeholder = self { true } else { false } + var isPlaceholder: Bool { + return if case .placeholder = self { true } else { false } + } } } + +extension FFMSwift2JavaGenerator.TranslatedFunctionSignature { + /// Whether or not if the down-calling requires temporary "Arena" which is + /// only used during the down-calling. + var requiresTemporaryArena: Bool { + if self.parameters.contains(where: { $0.conversion.requiresTemporaryArena }) { + return true + } + if self.selfParameter?.conversion.requiresTemporaryArena ?? false { + return true + } + if self.result.conversion.requiresTemporaryArena { + return true + } + return false + } + + /// Whether if the down-calling requires "SwiftArena" or not, which should be + /// passed-in by the API caller. This is needed if the API returns a `SwiftValue` + var requiresSwiftArena: Bool { + return self.result.conversion.requiresSwiftArena + } +} + + extension CType { /// Map lowered C type to Java type for FFM binding. var javaType: JavaType { diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 0a5952f31..76d53d883 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -40,6 +40,10 @@ package class ImportedNominalType: ImportedDecl { var swiftType: SwiftType { return .nominal(.init(nominalTypeDecl: swiftNominal)) } + + var qualifiedName: String { + self.swiftNominal.qualifiedName + } } public final class ImportedFunc: ImportedDecl, CustomStringConvertible { @@ -74,6 +78,20 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible { } } + var isStatic: Bool { + if case .staticMethod = functionSignature.selfParameter { + return true + } + return false + } + + var isInitializer: Bool { + if case .initializer = functionSignature.selfParameter { + return true + } + return false + } + /// If this function/method is member of a class/struct/protocol, /// this will contain that declaration's imported name. /// diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift new file mode 100644 index 000000000..ac775638f --- /dev/null +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -0,0 +1,197 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +extension JNISwift2JavaGenerator { + func writeExportedJavaSources() throws { + var printer = CodePrinter() + try writeExportedJavaSources(&printer) + } + + package func writeExportedJavaSources(_ printer: inout CodePrinter) throws { + for (_, ty) in analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) { + let filename = "\(ty.swiftNominal.name).java" + logger.info("Printing contents: \(filename)") + printImportedNominal(&printer, ty) + + if let outputFile = try printer.writeContents( + outputDirectory: javaOutputDirectory, + javaPackagePath: javaPackagePath, + filename: filename + ) { + logger.info("[swift-java] Generated: \(ty.swiftNominal.name.bold).java (at \(outputFile))") + } + } + + let filename = "\(self.swiftModuleName).java" + logger.trace("Printing module class: \(filename)") + printModule(&printer) + + if let outputFile = try printer.writeContents( + outputDirectory: javaOutputDirectory, + javaPackagePath: javaPackagePath, + filename: filename + ) { + logger.info("[swift-java] Generated: \(self.swiftModuleName).java (at \(outputFile))") + } + } + + private func printModule(_ printer: inout CodePrinter) { + printHeader(&printer) + printPackage(&printer) + + printModuleClass(&printer) { printer in + printer.print( + """ + static final String LIB_NAME = "\(swiftModuleName)"; + + static { + System.loadLibrary(LIB_NAME); + } + """ + ) + + for decl in analysis.importedGlobalFuncs { + self.logger.trace("Print global function: \(decl)") + printFunctionBinding(&printer, decl) + printer.println() + } + } + } + + private func printImportedNominal(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + printHeader(&printer) + printPackage(&printer) + + printNominal(&printer, decl) { printer in + printer.print( + """ + static final String LIB_NAME = "\(swiftModuleName)"; + + @SuppressWarnings("unused") + private static final boolean INITIALIZED_LIBS = initializeLibs(); + static boolean initializeLibs() { + System.loadLibrary(LIB_NAME); + return true; + } + """ + ) + + printer.println() + + printer.print( + """ + private long selfPointer; + + private \(decl.swiftNominal.name)(long selfPointer) { + this.selfPointer = selfPointer; + } + """ + ) + + printer.println() + + for initializer in decl.initializers { + printInitializerBindings(&printer, initializer, type: decl) + } + + for method in decl.methods { + printFunctionBinding(&printer, method) + } + } + } + + private func printHeader(_ printer: inout CodePrinter) { + printer.print( + """ + // Generated by jextract-swift + // Swift module: \(swiftModuleName) + + """ + ) + } + + private func printPackage(_ printer: inout CodePrinter) { + printer.print( + """ + package \(javaPackage); + + """ + ) + } + + private func printNominal( + _ printer: inout CodePrinter, _ decl: ImportedNominalType, body: (inout CodePrinter) -> Void + ) { + printer.printBraceBlock("public final class \(decl.swiftNominal.name)") { printer in + body(&printer) + } + } + + private func printModuleClass(_ printer: inout CodePrinter, body: (inout CodePrinter) -> Void) { + printer.printBraceBlock("public final class \(swiftModuleName)") { printer in + body(&printer) + } + } + + private func printFunctionBinding(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + printDeclDocumentation(&printer, decl) + printer.print( + "public static native \(renderFunctionSignature(decl));" + ) + } + + private func printInitializerBindings(_ printer: inout CodePrinter, _ decl: ImportedFunc, type: ImportedNominalType) { + let translatedDecl = translatedDecl(for: decl) + + printDeclDocumentation(&printer, decl) + printer.printBraceBlock("public static \(renderFunctionSignature(decl))") { printer in + let initArguments = translatedDecl.translatedFunctionSignature.parameters.map(\.name) + printer.print( + """ + long selfPointer = \(type.qualifiedName).allocatingInit(\(initArguments.joined(separator: ", "))); + return new \(type.qualifiedName)(selfPointer); + """ + ) + } + + let parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter) + printer.print("private static native long allocatingInit(\(parameters.joined(separator: ", ")));") + } + + private func printDeclDocumentation(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + printer.print( + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * \(decl.signatureString) + * } + */ + """ + ) + } + + /// Renders a Java function signature + /// + /// `func method(x: Int, y: Int) -> Int` becomes + /// `long method(long x, long y)` + private func renderFunctionSignature(_ decl: ImportedFunc) -> String { + let translatedDecl = translatedDecl(for: decl) + let resultType = translatedDecl.translatedFunctionSignature.resultType + let parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter) + let throwsClause = decl.isThrowing ? " throws Exception" : "" + + return "\(resultType) \(translatedDecl.name)(\(parameters.joined(separator: ", ")))\(throwsClause)" + } +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift new file mode 100644 index 000000000..a5df702c2 --- /dev/null +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -0,0 +1,123 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JavaTypes + +extension JNISwift2JavaGenerator { + func translatedDecl( + for decl: ImportedFunc + ) -> TranslatedFunctionDecl { + if let cached = translatedDecls[decl] { + return cached + } + + let translation = JavaTranslation(swiftModuleName: self.swiftModuleName) + let translated = translation.translate(decl) + + translatedDecls[decl] = translated + return translated + } + + struct JavaTranslation { + let swiftModuleName: String + + func translate(_ decl: ImportedFunc) -> TranslatedFunctionDecl { + let translatedFunctionSignature = translate(functionSignature: decl.functionSignature) + // Types with no parent will be outputted inside a "module" class. + let parentName = decl.parentType?.asNominalType?.nominalTypeDecl.qualifiedName ?? swiftModuleName + + return TranslatedFunctionDecl( + name: decl.name, + parentName: parentName, + translatedFunctionSignature: translatedFunctionSignature + ) + } + + func translate(functionSignature: SwiftFunctionSignature, isInitializer: Bool = false) -> TranslatedFunctionSignature { + let parameters = functionSignature.parameters.enumerated().map { idx, param in + let parameterName = param.parameterName ?? "arg\(idx))" + return translate(swiftParam: param, parameterName: parameterName) + } + + return TranslatedFunctionSignature( + parameters: parameters, + resultType: translate(swiftType: functionSignature.result.type) + ) + } + + func translate(swiftParam: SwiftParameter, parameterName: String) -> JavaParameter { + return JavaParameter( + name: parameterName, + type: translate(swiftType: swiftParam.type) + ) + } + + func translate(swiftType: SwiftType) -> JavaType { + switch swiftType { + case .nominal(let nominalType): + if let knownType = nominalType.nominalTypeDecl.knownStandardLibraryType { + guard let javaType = translate(standardLibraryType: knownType) else { + fatalError("unsupported known type: \(knownType)") + } + return javaType + } + + return .class(package: nil, name: nominalType.nominalTypeDecl.name) + + case .tuple([]): + return .void + + case .metatype, .optional, .tuple, .function: + fatalError("unsupported type: \(self)") + } + } + + func translate(standardLibraryType: SwiftStandardLibraryTypeKind) -> JavaType? { + switch standardLibraryType { + case .bool: .boolean + case .int8: .byte + case .uint16: .char + case .int16: .short + case .int32: .int + case .int64: .long + case .float: .float + case .double: .double + case .void: .void + case .string: .javaLangString + case .int, .uint, .uint8, .uint32, .uint64, + .unsafeRawPointer, .unsafeMutableRawPointer, + .unsafePointer, .unsafeMutablePointer, + .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, + .unsafeBufferPointer, .unsafeMutableBufferPointer: + nil + } + } + } + + struct TranslatedFunctionDecl { + /// Java function name + let name: String + + /// The name of the Java parent scope this function is declared in + let parentName: String + + /// Function signature + let translatedFunctionSignature: TranslatedFunctionSignature + } + + struct TranslatedFunctionSignature { + let parameters: [JavaParameter] + let resultType: JavaType + } +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift new file mode 100644 index 000000000..c61dd929c --- /dev/null +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -0,0 +1,268 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JavaTypes + +extension JNISwift2JavaGenerator { + func writeSwiftThunkSources() throws { + var printer = CodePrinter() + try writeSwiftThunkSources(&printer) + } + + package func writeSwiftExpectedEmptySources() throws { + for expectedFileName in self.expectedOutputSwiftFiles { + logger.trace("Write empty file: \(expectedFileName) ...") + + var printer = CodePrinter() + printer.print("// Empty file generated on purpose") + _ = try printer.writeContents( + outputDirectory: self.swiftOutputDirectory, + javaPackagePath: nil, + filename: expectedFileName) + } + } + + package func writeSwiftThunkSources(_ printer: inout CodePrinter) throws { + let moduleFilenameBase = "\(self.swiftModuleName)Module+SwiftJava" + let moduleFilename = "\(moduleFilenameBase).swift" + + do { + logger.trace("Printing swift module class: \(moduleFilename)") + + try printGlobalSwiftThunkSources(&printer) + + if let outputFile = try printer.writeContents( + outputDirectory: self.swiftOutputDirectory, + javaPackagePath: nil, + filename: moduleFilename + ) { + print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile))") + self.expectedOutputSwiftFiles.remove(moduleFilename) + } + + for (_, ty) in self.analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) { + let fileNameBase = "\(ty.swiftNominal.qualifiedName)+SwiftJava" + let filename = "\(fileNameBase).swift" + logger.info("Printing contents: \(filename)") + + do { + try printNominalTypeThunks(&printer, ty) + + if let outputFile = try printer.writeContents( + outputDirectory: self.swiftOutputDirectory, + javaPackagePath: nil, + filename: filename) { + print("[swift-java] Generated: \(fileNameBase.bold).swift (at \(outputFile))") + self.expectedOutputSwiftFiles.remove(filename) + } + } catch { + logger.warning("Failed to write to Swift thunks: \(filename)") + } + } + } catch { + logger.warning("Failed to write to Swift thunks: \(moduleFilename)") + } + } + + private func printGlobalSwiftThunkSources(_ printer: inout CodePrinter) throws { + printHeader(&printer) + + for decl in analysis.importedGlobalFuncs { + printSwiftFunctionThunk(&printer, decl) + printer.println() + } + } + + private func printNominalTypeThunks(_ printer: inout CodePrinter, _ type: ImportedNominalType) throws { + printHeader(&printer) + + for initializer in type.initializers { + printInitializerThunk(&printer, initializer) + printer.println() + } + + for method in type.methods { + printSwiftFunctionThunk(&printer, method) + printer.println() + } + } + + private func printInitializerThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + let translatedDecl = translatedDecl(for: decl) + let typeName = translatedDecl.parentName + + printCDecl( + &printer, + javaMethodName: "allocatingInit", + parentName: translatedDecl.parentName, + parameters: translatedDecl.translatedFunctionSignature.parameters, + isStatic: true, + resultType: .long + ) { printer in + let downcallArguments = renderDowncallArguments( + swiftFunctionSignature: decl.functionSignature, + translatedFunctionSignature: translatedDecl.translatedFunctionSignature + ) + // TODO: Throwing initializers + printer.print( + """ + let selfPointer = UnsafeMutablePointer<\(typeName)>.allocate(capacity: 1) + selfPointer.initialize(to: \(typeName)(\(downcallArguments))) + return Int64(Int(bitPattern: selfPointer)).getJNIValue(in: environment) + """ + ) + } + } + + private func printSwiftFunctionThunk( + _ printer: inout CodePrinter, + _ decl: ImportedFunc + ) { + let translatedDecl = self.translatedDecl(for: decl) + let parentName = translatedDecl.parentName ?? swiftModuleName + let swiftReturnType = decl.functionSignature.result.type + + printCDecl(&printer, decl) { printer in + let downcallParameters = renderDowncallArguments( + swiftFunctionSignature: decl.functionSignature, + translatedFunctionSignature: translatedDecl.translatedFunctionSignature + ) + let tryClause: String = decl.isThrowing ? "try " : "" + let functionDowncall = + "\(tryClause)\(parentName).\(decl.name)(\(downcallParameters))" + + let innerBody = + if swiftReturnType.isVoid { + functionDowncall + } else { + """ + let result = \(functionDowncall) + return result.getJNIValue(in: environment) + """ + } + + if decl.isThrowing { + let dummyReturn = + !swiftReturnType.isVoid ? "return \(swiftReturnType).jniPlaceholderValue" : "" + printer.print( + """ + do { + \(innerBody) + } catch { + environment.throwAsException(error) + \(dummyReturn) + } + """ + ) + } else { + printer.print(innerBody) + } + } + } + + private func printCDecl( + _ printer: inout CodePrinter, + _ decl: ImportedFunc, + _ body: (inout CodePrinter) -> Void + ) { + let translatedDecl = translatedDecl(for: decl) + let parentName = translatedDecl.parentName + + printCDecl( + &printer, + javaMethodName: translatedDecl.name, + parentName: parentName, + parameters: translatedDecl.translatedFunctionSignature.parameters, + isStatic: decl.isStatic || decl.isInitializer || !decl.hasParent, + resultType: translatedDecl.translatedFunctionSignature.resultType, + body + ) + } + + private func printCDecl( + _ printer: inout CodePrinter, + javaMethodName: String, + parentName: String, + parameters: [JavaParameter], + isStatic: Bool, + resultType: JavaType, + _ body: (inout CodePrinter) -> Void + ) { + var jniSignature = parameters.reduce(into: "") { signature, parameter in + signature += parameter.type.jniTypeSignature + } + + // Escape signature characters + jniSignature = jniSignature + .replacingOccurrences(of: "_", with: "_1") + .replacingOccurrences(of: "/", with: "_") + .replacingOccurrences(of: ";", with: "_2") + .replacingOccurrences(of: "[", with: "_3") + + let cName = + "Java_" + + self.javaPackage.replacingOccurrences(of: ".", with: "_") + + "_\(parentName)_" + + javaMethodName + + "__" + + jniSignature + let translatedParameters = parameters.map { + "\($0.name): \($0.type.jniTypeName)" + } + let thisParameter = isStatic ? "thisClass: jclass" : "thisObject: jobject" + + let thunkParameters = + [ + "environment: UnsafeMutablePointer!", + thisParameter + ] + translatedParameters + let thunkReturnType = !resultType.isVoid ? " -> \(resultType.jniTypeName)" : "" + + // TODO: Think about function overloads + printer.printBraceBlock( + """ + @_cdecl("\(cName)") + func \(cName)(\(thunkParameters.joined(separator: ", ")))\(thunkReturnType) + """ + ) { printer in + body(&printer) + } + } + + private func printHeader(_ printer: inout CodePrinter) { + printer.print( + """ + // Generated by swift-java + + import JavaKit + + """ + ) + } + + /// Renders the arguments for making a downcall + private func renderDowncallArguments( + swiftFunctionSignature: SwiftFunctionSignature, + translatedFunctionSignature: TranslatedFunctionSignature + ) -> String { + zip( + swiftFunctionSignature.parameters, + translatedFunctionSignature.parameters + ).map { originalParam, translatedParam in + let label = originalParam.argumentLabel.map { "\($0): " } ?? "" + return "\(label)\(originalParam.type)(fromJNI: \(translatedParam.name), in: environment!)" + } + .joined(separator: ", ") + } +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index 0f16e697d..b242cf66a 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -28,6 +28,9 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { var thunkNameRegistry = ThunkNameRegistry() + /// Cached Java translation result. 'nil' indicates failed translation. + var translatedDecls: [ImportedFunc: TranslatedFunctionDecl] = [:] + /// Because we need to write empty files for SwiftPM, keep track which files we didn't write yet, /// and write an empty file for those. var expectedOutputSwiftFiles: Set @@ -71,262 +74,3 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { } } } - -extension JNISwift2JavaGenerator { - func writeExportedJavaSources() throws { - var printer = CodePrinter() - try writeExportedJavaSources(&printer) - } - - package func writeExportedJavaSources(_ printer: inout CodePrinter) throws { - let filename = "\(self.swiftModuleName).java" - logger.trace("Printing module class: \(filename)") - printModule(&printer) - - if let outputFile = try printer.writeContents( - outputDirectory: javaOutputDirectory, - javaPackagePath: javaPackagePath, - filename: filename - ) { - logger.info("[swift-java] Generated: \(self.swiftModuleName).java (at \(outputFile))") - } - } - - func writeSwiftThunkSources() throws { - var printer = CodePrinter() - try writeSwiftThunkSources(&printer) - } - - package func writeSwiftExpectedEmptySources() throws { - for expectedFileName in self.expectedOutputSwiftFiles { - logger.trace("Write empty file: \(expectedFileName) ...") - - var printer = CodePrinter() - printer.print("// Empty file generated on purpose") - _ = try printer.writeContents( - outputDirectory: self.swiftOutputDirectory, - javaPackagePath: nil, - filename: expectedFileName) - } - } - - package func writeSwiftThunkSources(_ printer: inout CodePrinter) throws { - let moduleFilenameBase = "\(self.swiftModuleName)Module+SwiftJava" - let moduleFilename = "\(moduleFilenameBase).swift" - - do { - logger.trace("Printing swift module class: \(moduleFilename)") - - try printGlobalSwiftThunkSources(&printer) - - if let outputFile = try printer.writeContents( - outputDirectory: self.swiftOutputDirectory, - javaPackagePath: nil, - filename: moduleFilename - ) { - print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile))") - self.expectedOutputSwiftFiles.remove(moduleFilename) - } - } catch { - logger.warning("Failed to write to Swift thunks: \(moduleFilename)") - } - } -} - -extension JNISwift2JavaGenerator { - private func printGlobalSwiftThunkSources(_ printer: inout CodePrinter) throws { - printer.print( - """ - // Generated by swift-java - - import JavaKit - - """) - - for decl in analysis.importedGlobalFuncs { - printSwiftFunctionThunk(&printer, decl) - printer.println() - } - } - - private func printSwiftFunctionThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - // TODO: Replace swiftModuleName with class name if non-global - let cName = - "Java_" + self.javaPackage.replacingOccurrences(of: ".", with: "_") + "_\(swiftModuleName)_" - + decl.name - let thunkName = thunkNameRegistry.functionThunkName(decl: decl) - let translatedParameters = decl.functionSignature.parameters.enumerated().map { idx, param in - (param.parameterName ?? "arg\(idx)", param.type.javaType) - } - - let thunkParameters = - [ - "environment: UnsafeMutablePointer!", - "thisClass: jclass", - ] + translatedParameters.map { "\($0.0): \($0.1.jniTypeName)" } - let swiftReturnType = decl.functionSignature.result.type - let thunkReturnType = - !swiftReturnType.isVoid ? " -> \(swiftReturnType.javaType.jniTypeName)" : "" - - printer.printBraceBlock( - """ - @_cdecl("\(cName)") - func \(thunkName)(\(thunkParameters.joined(separator: ", ")))\(thunkReturnType) - """ - ) { printer in - let downcallParameters = zip(decl.functionSignature.parameters, translatedParameters).map { - originalParam, translatedParam in - let label = originalParam.argumentLabel.map { "\($0): " } ?? "" - return "\(label)\(originalParam.type)(fromJNI: \(translatedParam.0), in: environment!)" - } - let tryClause: String = decl.isThrowing ? "try " : "" - let functionDowncall = - "\(tryClause)\(swiftModuleName).\(decl.name)(\(downcallParameters.joined(separator: ", ")))" - - let innerBody = - if swiftReturnType.isVoid { - functionDowncall - } else { - """ - let result = \(functionDowncall) - return result.getJNIValue(in: environment) - """ - } - - if decl.isThrowing { - let dummyReturn = - !swiftReturnType.isVoid ? "return \(swiftReturnType).jniPlaceholderValue" : "" - printer.print( - """ - do { - \(innerBody) - } catch { - environment.throwAsException(error) - \(dummyReturn) - } - """ - ) - } else { - printer.print(innerBody) - } - } - } -} - -extension JNISwift2JavaGenerator { - private func printModule(_ printer: inout CodePrinter) { - printHeader(&printer) - printPackage(&printer) - - printModuleClass(&printer) { printer in - printer.print( - """ - static final String LIB_NAME = "\(swiftModuleName)"; - - static { - System.loadLibrary(LIB_NAME); - } - """ - ) - - for decl in analysis.importedGlobalFuncs { - self.logger.trace("Print global function: \(decl)") - printFunctionBinding(&printer, decl) - printer.println() - } - } - } - - private func printHeader(_ printer: inout CodePrinter) { - printer.print( - """ - // Generated by jextract-swift - // Swift module: \(swiftModuleName) - - """ - ) - } - - private func printPackage(_ printer: inout CodePrinter) { - printer.print( - """ - package \(javaPackage); - - """ - ) - } - - private func printModuleClass(_ printer: inout CodePrinter, body: (inout CodePrinter) -> Void) { - printer.printBraceBlock("public final class \(swiftModuleName)") { printer in - body(&printer) - } - } - - private func printFunctionBinding(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - let returnType = decl.functionSignature.result.type.javaType - let params = decl.functionSignature.parameters.enumerated().map { idx, param in - "\(param.type.javaType) \(param.parameterName ?? "arg\(idx))")" - } - let throwsClause = decl.isThrowing ? " throws Exception" : "" - - printer.print( - """ - /** - * Downcall to Swift: - * {@snippet lang=swift : - * \(decl.signatureString) - * } - */ - """ - ) - printer.print( - "public static native \(returnType) \(decl.name)(\(params.joined(separator: ", ")))\(throwsClause);" - ) - } -} - -extension SwiftType { - var javaType: JavaType { - switch self { - case .nominal(let nominalType): - if let knownType = nominalType.nominalTypeDecl.knownStandardLibraryType { - guard let javaType = knownType.javaType else { - fatalError("unsupported known type: \(knownType)") - } - return javaType - } - - fatalError("unsupported nominal type: \(nominalType)") - - case .tuple([]): - return .void - - case .metatype, .optional, .tuple, .function: - fatalError("unsupported type: \(self)") - } - } -} - -extension SwiftStandardLibraryTypeKind { - var javaType: JavaType? { - switch self { - case .bool: .boolean - case .int: .long // TODO: Handle 32-bit or 64-bit - case .int8: .byte - case .uint16: .char - case .int16: .short - case .int32: .int - case .int64: .long - case .float: .float - case .double: .double - case .void: .void - case .string: .javaLangString - case .uint, .uint8, .uint32, .uint64, - .unsafeRawPointer, .unsafeMutableRawPointer, - .unsafePointer, .unsafeMutablePointer, - .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, - .unsafeBufferPointer, .unsafeMutableBufferPointer: - nil - } - } -} diff --git a/Sources/JExtractSwiftLib/JavaParameter.swift b/Sources/JExtractSwiftLib/JavaParameter.swift new file mode 100644 index 000000000..728964199 --- /dev/null +++ b/Sources/JExtractSwiftLib/JavaParameter.swift @@ -0,0 +1,25 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JavaTypes + +/// Represent a parameter in Java code. +struct JavaParameter { + let name: String + let type: JavaType + + var asParameter: String { + "\(type) \(name)" + } +} diff --git a/Sources/JavaTypes/JavaType+SwiftNames.swift b/Sources/JavaTypes/JavaType+SwiftNames.swift index d01398b83..492ff4595 100644 --- a/Sources/JavaTypes/JavaType+SwiftNames.swift +++ b/Sources/JavaTypes/JavaType+SwiftNames.swift @@ -45,6 +45,13 @@ extension JavaType { } } + public var isVoid: Bool { + if case .void = self { + return true + } + return false + } + public var isString: Bool { switch self { case .boolean, .byte, .char, .short, .int, .long, .float, .double, .void, diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift new file mode 100644 index 000000000..c39d5815c --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -0,0 +1,173 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import Testing + +@Suite +struct JNIClassTests { + let source = """ + public class MyClass { + let x: Int64 + let y: Int64 + + public static func method() {} + + public init(x: Int64, y: Int64) { + self.x = y + self.y = y + } + + public init() { + self.x = 0 + self.y = 0 + } + } + """ + + @Test + func generatesJavaClass() throws { + try assertOutput(input: source, .jni, .java, expectedChunks: [ + """ + // Generated by jextract-swift + // Swift module: SwiftModule + + package com.example.swift; + + public final class MyClass { + static final String LIB_NAME = "SwiftModule"; + + @SuppressWarnings("unused") + private static final boolean INITIALIZED_LIBS = initializeLibs(); + static boolean initializeLibs() { + System.loadLibrary(LIB_NAME); + return true; + } + + private long selfPointer; + + private MyClass(long selfPointer) { + this.selfPointer = selfPointer; + } + """, + ]) + } + + @Test + func staticMethod_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public static func method() + * } + */ + public static native void method(); + """ + ] + ) + } + + @Test + func staticMethod_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass_method__") + func Java_com_example_swift_MyClass_method__(environment: UnsafeMutablePointer!, thisClass: jclass) { + MyClass.method() + } + """ + ] + ) + } + + @Test + func initializer_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public init(x: Int64, y: Int64) + * } + */ + public static MyClass init(long x, long y) { + long selfPointer = MyClass.allocatingInit(x, y); + return new MyClass(selfPointer); + } + """, + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public init() + * } + */ + public static MyClass init() { + long selfPointer = MyClass.allocatingInit(); + return new MyClass(selfPointer); + } + """, + """ + private static native long allocatingInit(long x, long y); + """, + """ + private static native long allocatingInit(); + """ + ] + ) + } + + @Test + func initializer_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass_allocatingInit__") + func Java_com_example_swift_MyClass_allocatingInit__(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong { + let selfPointer = UnsafeMutablePointer.allocate(capacity: 1) + selfPointer.initialize(to: MyClass()) + return Int64(Int(bitPattern: selfPointer)).getJNIValue(in: environment) + } + """, + """ + @_cdecl("Java_com_example_swift_MyClass_allocatingInit__JJ") + func Java_com_example_swift_MyClass_allocatingInit__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jlong) -> jlong { + let selfPointer = UnsafeMutablePointer.allocate(capacity: 1) + selfPointer.initialize(to: MyClass(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) + return Int64(Int(bitPattern: selfPointer)).getJNIValue(in: environment) + } + """ + ] + ) + } +} diff --git a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift index e483a4dd3..d6a030a8c 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift @@ -19,7 +19,7 @@ import Testing struct JNIModuleTests { let globalMethodWithPrimitives = """ public func helloWorld() - public func takeIntegers(i1: Int8, i2: Int16, i3: Int32, i4: Int) -> UInt16 + public func takeIntegers(i1: Int8, i2: Int16, i3: Int32, i4: Int64) -> UInt16 public func otherPrimitives(b: Bool, f: Float, d: Double) """ @@ -73,7 +73,7 @@ struct JNIModuleTests { /** * Downcall to Swift: * {@snippet lang=swift : - * public func takeIntegers(i1: Int8, i2: Int16, i3: Int32, i4: Int) -> UInt16 + * public func takeIntegers(i1: Int8, i2: Int16, i3: Int32, i4: Int64) -> UInt16 * } */ public static native char takeIntegers(byte i1, short i2, int i3, long i4); @@ -100,21 +100,21 @@ struct JNIModuleTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_SwiftModule_helloWorld") - func swiftjava_SwiftModule_helloWorld(environment: UnsafeMutablePointer!, thisClass: jclass) { + @_cdecl("Java_com_example_swift_SwiftModule_helloWorld__") + func Java_com_example_swift_SwiftModule_helloWorld__(environment: UnsafeMutablePointer!, thisClass: jclass) { SwiftModule.helloWorld() } """, """ - @_cdecl("Java_com_example_swift_SwiftModule_takeIntegers") - func swiftjava_SwiftModule_takeIntegers_i1_i2_i3_i4(environment: UnsafeMutablePointer!, thisClass: jclass, i1: jbyte, i2: jshort, i3: jint, i4: jlong) -> jchar { - let result = SwiftModule.takeIntegers(i1: Int8(fromJNI: i1, in: environment!), i2: Int16(fromJNI: i2, in: environment!), i3: Int32(fromJNI: i3, in: environment!), i4: Int(fromJNI: i4, in: environment!)) + @_cdecl("Java_com_example_swift_SwiftModule_takeIntegers__BSIJ") + func Java_com_example_swift_SwiftModule_takeIntegers__BSIJ(environment: UnsafeMutablePointer!, thisClass: jclass, i1: jbyte, i2: jshort, i3: jint, i4: jlong) -> jchar { + let result = SwiftModule.takeIntegers(i1: Int8(fromJNI: i1, in: environment!), i2: Int16(fromJNI: i2, in: environment!), i3: Int32(fromJNI: i3, in: environment!), i4: Int64(fromJNI: i4, in: environment!)) return result.getJNIValue(in: environment) } """, """ - @_cdecl("Java_com_example_swift_SwiftModule_otherPrimitives") - func swiftjava_SwiftModule_otherPrimitives_b_f_d(environment: UnsafeMutablePointer!, thisClass: jclass, b: jboolean, f: jfloat, d: jdouble) { + @_cdecl("Java_com_example_swift_SwiftModule_otherPrimitives__ZFD") + func Java_com_example_swift_SwiftModule_otherPrimitives__ZFD(environment: UnsafeMutablePointer!, thisClass: jclass, b: jboolean, f: jfloat, d: jdouble) { SwiftModule.otherPrimitives(b: Bool(fromJNI: b, in: environment!), f: Float(fromJNI: f, in: environment!), d: Double(fromJNI: d, in: environment!)) } """ @@ -151,8 +151,8 @@ struct JNIModuleTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_SwiftModule_copy") - func swiftjava_SwiftModule_copy__(environment: UnsafeMutablePointer!, thisClass: jclass, string: jstring?) -> jstring? { + @_cdecl("Java_com_example_swift_SwiftModule_copy__Ljava_lang_String_2") + func Java_com_example_swift_SwiftModule_copy__Ljava_lang_String_2(environment: UnsafeMutablePointer!, thisClass: jclass, string: jstring?) -> jstring? { let result = SwiftModule.copy(String(fromJNI: string, in: environment!)) return result.getJNIValue(in: environment) } @@ -199,8 +199,8 @@ struct JNIModuleTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_SwiftModule_methodA") - func swiftjava_SwiftModule_methodA(environment: UnsafeMutablePointer!, thisClass: jclass) { + @_cdecl("Java_com_example_swift_SwiftModule_methodA__") + func Java_com_example_swift_SwiftModule_methodA__(environment: UnsafeMutablePointer!, thisClass: jclass) { do { try SwiftModule.methodA() } catch { @@ -209,8 +209,8 @@ struct JNIModuleTests { } """, """ - @_cdecl("Java_com_example_swift_SwiftModule_methodB") - func swiftjava_SwiftModule_methodB(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong { + @_cdecl("Java_com_example_swift_SwiftModule_methodB__") + func Java_com_example_swift_SwiftModule_methodB__(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong { do { let result = try SwiftModule.methodB() return result.getJNIValue(in: environment) From d266a4452a115f58d0d73f2e89cd921a8f611aab Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 8 Jul 2025 16:03:36 +0200 Subject: [PATCH 021/141] Separate SwiftKit into SwiftKitCore and SwiftKitFFM (#300) --- .github/actions/prepare_env/action.yml | 1 + .github/workflows/pull_request.yml | 24 +++-- ...d-logic.java-common-conventions.gradle.kts | 15 ---- Samples/JExtractJNISampleApp/build.gradle | 2 +- .../com/example/swift/HelloJava2SwiftJNI.java | 6 +- Samples/SwiftAndJavaJarSampleLib/build.gradle | 3 +- .../SwiftAndJavaJarSampleLib/ci-validate.sh | 5 +- .../{ => ffm}/JavaToSwiftBenchmark.java | 2 +- .../com/example/swift/HelloJava2Swift.java | 19 ++-- .../com/example/swift/MySwiftClassTest.java | 12 +-- .../com/example/swift/MySwiftLibraryTest.java | 4 - .../swift/swiftkit/ffm}/MySwiftClassTest.java | 14 +-- .../swiftkit/{ => ffm}/SwiftArenaTest.java | 15 ++-- Samples/SwiftKitSampleApp/build.gradle | 4 +- .../{ => ffm}/JavaToSwiftBenchmark.java | 6 +- .../{ => ffm}/StringPassingBenchmark.java | 9 +- .../com/example/swift/HelloJava2Swift.java | 33 +++---- .../com/example/swift/MySwiftClassTest.java | 12 +-- .../com/example/swift/MySwiftLibraryTest.java | 5 -- .../swift/swiftkitffm}/MySwiftClassTest.java | 16 ++-- .../MySwiftStructTest.java | 5 +- .../SwiftArenaTest.java | 23 +++-- ...t2JavaGenerator+JavaBindingsPrinting.swift | 10 +-- ...MSwift2JavaGenerator+JavaTranslation.swift | 6 +- .../FFM/FFMSwift2JavaGenerator.swift | 21 ++--- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 2 +- .../JExtractSwiftLib/SwiftKit+Printing.swift | 2 +- {SwiftKit => SwiftKitCore}/build.gradle | 8 +- .../swiftkit/core}/ClosableSwiftArena.java | 3 +- .../core}/ConfinedSwiftMemorySession.java | 29 ++----- .../swift/swiftkit/core/JNISwiftInstance.java | 56 ++++++++++++ .../core/JNISwiftInstanceCleanup.java | 31 +++++++ .../org/swift/swiftkit/core}/SwiftArena.java | 29 +------ .../swift/swiftkit/core}/SwiftInstance.java | 43 +++------ .../swiftkit/core/SwiftInstanceCleanup.java | 20 +++++ .../swift/swiftkit/core/SwiftLibraries.java | 83 ++++++++++++++++++ .../org/swift/swiftkit/core}/SwiftValue.java | 2 +- .../swiftkit/core/WrongThreadException.java | 14 ++- .../swiftkit/core}/util/PlatformUtils.java | 2 +- .../swiftkit/core}/util/StringUtils.java | 2 +- SwiftKitFFM/build.gradle | 87 +++++++++++++++++++ .../ffm/AllocatingAutoSwiftMemorySession.java | 22 ++--- .../swiftkit/ffm/AllocatingSwiftArena.java | 34 ++++++++ .../ffm/ClosableAllocatingSwiftArena.java | 22 +++++ .../ffm/FFMConfinedSwiftMemorySession.java | 40 +++++++++ .../swift/swiftkit/ffm/FFMSwiftInstance.java | 69 +++++++++++++++ .../swiftkit/ffm/FFMSwiftInstanceCleanup.java | 22 +++-- .../swiftkit/ffm}/MemorySegmentUtils.java | 2 +- .../org/swift/swiftkit/ffm}/SwiftAnyType.java | 7 +- .../swift/swiftkit/ffm}/SwiftHeapObject.java | 5 +- .../org/swift/swiftkit/ffm/SwiftRuntime.java | 58 ++----------- .../swift/swiftkit/ffm}/SwiftValueLayout.java | 2 +- .../swiftkit/ffm}/SwiftValueWitnessTable.java | 15 ++-- .../swift/swiftkit/ffm}/AutoArenaTest.java | 15 +--- .../ffm}/SwiftRuntimeMetadataTest.java | 6 +- .../ClassPrintingTests.swift | 2 +- .../FuncCallbackImportTests.swift | 20 ++--- .../FunctionDescriptorImportTests.swift | 20 ++--- .../MethodImportTests.swift | 8 +- .../StringPassingTests.swift | 6 +- .../VariableImportTests.swift | 8 +- settings.gradle | 3 +- 62 files changed, 689 insertions(+), 382 deletions(-) rename Samples/SwiftAndJavaJarSampleLib/src/jmh/java/org/swift/swiftkit/{ => ffm}/JavaToSwiftBenchmark.java (98%) rename Samples/{SwiftKitSampleApp/src/test/java/org/swift/swiftkit => SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/ffm}/MySwiftClassTest.java (72%) rename Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/{ => ffm}/SwiftArenaTest.java (77%) rename Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/{ => ffm}/JavaToSwiftBenchmark.java (92%) rename Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/{ => ffm}/StringPassingBenchmark.java (94%) rename Samples/{SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit => SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm}/MySwiftClassTest.java (66%) rename Samples/SwiftKitSampleApp/src/test/java/org/swift/{swiftkit => swiftkitffm}/MySwiftStructTest.java (86%) rename Samples/SwiftKitSampleApp/src/test/java/org/swift/{swiftkit => swiftkitffm}/SwiftArenaTest.java (84%) rename {SwiftKit => SwiftKitCore}/build.gradle (87%) rename {SwiftKit/src/main/java/org/swift/swiftkit => SwiftKitCore/src/main/java/org/swift/swiftkit/core}/ClosableSwiftArena.java (96%) rename {SwiftKit/src/main/java/org/swift/swiftkit => SwiftKitCore/src/main/java/org/swift/swiftkit/core}/ConfinedSwiftMemorySession.java (68%) create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstanceCleanup.java rename {SwiftKit/src/main/java/org/swift/swiftkit => SwiftKitCore/src/main/java/org/swift/swiftkit/core}/SwiftArena.java (54%) rename {SwiftKit/src/main/java/org/swift/swiftkit => SwiftKitCore/src/main/java/org/swift/swiftkit/core}/SwiftInstance.java (70%) create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstanceCleanup.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java rename {SwiftKit/src/main/java/org/swift/swiftkit => SwiftKitCore/src/main/java/org/swift/swiftkit/core}/SwiftValue.java (94%) rename SwiftKit/src/main/java/org/swift/swiftkit/ManagedSwiftType.java => SwiftKitCore/src/main/java/org/swift/swiftkit/core/WrongThreadException.java (67%) rename {SwiftKit/src/main/java/org/swift/swiftkit => SwiftKitCore/src/main/java/org/swift/swiftkit/core}/util/PlatformUtils.java (97%) rename {SwiftKit/src/main/java/org/swift/swiftkit => SwiftKitCore/src/main/java/org/swift/swiftkit/core}/util/StringUtils.java (96%) create mode 100644 SwiftKitFFM/build.gradle rename SwiftKit/src/main/java/org/swift/swiftkit/AutoSwiftMemorySession.java => SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingAutoSwiftMemorySession.java (78%) create mode 100644 SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingSwiftArena.java create mode 100644 SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/ClosableAllocatingSwiftArena.java create mode 100644 SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMConfinedSwiftMemorySession.java create mode 100644 SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstance.java rename SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstanceCleanup.java => SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstanceCleanup.java (63%) rename {SwiftKit/src/main/java/org/swift/swiftkit => SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm}/MemorySegmentUtils.java (97%) rename {SwiftKit/src/main/java/org/swift/swiftkit => SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm}/SwiftAnyType.java (89%) rename {SwiftKit/src/main/java/org/swift/swiftkit => SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm}/SwiftHeapObject.java (91%) rename SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java => SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java (88%) rename {SwiftKit/src/main/java/org/swift/swiftkit => SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm}/SwiftValueLayout.java (98%) rename {SwiftKit/src/main/java/org/swift/swiftkit => SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm}/SwiftValueWitnessTable.java (94%) rename {SwiftKit/src/test/java/org/swift/swiftkit => SwiftKitFFM/src/test/java/org/swift/swiftkit/ffm}/AutoArenaTest.java (78%) rename {SwiftKit/src/test/java/org/swift/swiftkit => SwiftKitFFM/src/test/java/org/swift/swiftkit/ffm}/SwiftRuntimeMetadataTest.java (95%) diff --git a/.github/actions/prepare_env/action.yml b/.github/actions/prepare_env/action.yml index 33fa1f767..c63b87cfa 100644 --- a/.github/actions/prepare_env/action.yml +++ b/.github/actions/prepare_env/action.yml @@ -11,6 +11,7 @@ runs: java-version: | 24 21 + 19 cache: 'gradle' - name: Set JAVA_HOME_{N} shell: bash diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index b55e04899..9a0b59b89 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -31,10 +31,14 @@ jobs: - uses: actions/checkout@v4 - name: Prepare CI Environment uses: ./.github/actions/prepare_env - - name: Gradle :SwiftKit:build - run: ./gradlew build -x test - - name: Gradle :SwiftKit:check - run: ./gradlew :SwiftKit:check --info + - name: Gradle :SwiftKitCore:build + run: ./gradlew :SwiftKitCore:build -x test + - name: Gradle :SwiftKitCore:check + run: ./gradlew :SwiftKitCore:check --info + - name: Gradle :SwiftKitFFM:build + run: ./gradlew :SwiftKitFFM:build -x test + - name: Gradle :SwiftKitFFM:check + run: ./gradlew :SwiftKitFFM:check --info test-java-macos: name: Test (Java) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) @@ -51,10 +55,14 @@ jobs: - uses: actions/checkout@v4 - name: Prepare CI Environment uses: ./.github/actions/prepare_env - - name: Gradle :SwiftKit:build - run: ./gradlew build -x test - - name: Gradle :SwiftKit:check - run: ./gradlew :SwiftKit:check --debug + - name: Gradle :SwiftKitCore:build + run: ./gradlew :SwiftKitCore:build -x test + - name: Gradle :SwiftKitCore:check + run: ./gradlew :SwiftKitCore:check --debug + - name: Gradle :SwiftKitFFM:build + run: ./gradlew :SwiftKitFFM:build -x test + - name: Gradle :SwiftKitFFM:check + run: ./gradlew :SwiftKitFFM:check --debug benchmark-java: name: Benchmark (JMH) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) diff --git a/BuildLogic/src/main/kotlin/build-logic.java-common-conventions.gradle.kts b/BuildLogic/src/main/kotlin/build-logic.java-common-conventions.gradle.kts index fb10bee0f..df0a6633a 100644 --- a/BuildLogic/src/main/kotlin/build-logic.java-common-conventions.gradle.kts +++ b/BuildLogic/src/main/kotlin/build-logic.java-common-conventions.gradle.kts @@ -31,21 +31,6 @@ repositories { mavenCentral() } -testing { - suites { - val test by getting(JvmTestSuite::class) { - useJUnitJupiter("5.10.3") - } - } -} - -/// Enable access to preview APIs, e.g. java.lang.foreign.* (Panama) -tasks.withType(JavaCompile::class).forEach { - it.options.compilerArgs.add("--enable-preview") - it.options.compilerArgs.add("-Xlint:preview") -} - - fun getSwiftRuntimeLibraryPaths(): List { val process = ProcessBuilder("swiftc", "-print-target-info") .redirectError(ProcessBuilder.Redirect.INHERIT) diff --git a/Samples/JExtractJNISampleApp/build.gradle b/Samples/JExtractJNISampleApp/build.gradle index 03794ff78..54bd725af 100644 --- a/Samples/JExtractJNISampleApp/build.gradle +++ b/Samples/JExtractJNISampleApp/build.gradle @@ -148,7 +148,7 @@ tasks.clean { } dependencies { - implementation(project(':SwiftKit')) + implementation(project(':SwiftKitCore')) testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") diff --git a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java index 7d0d77e11..66366a191 100644 --- a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java +++ b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java @@ -18,12 +18,12 @@ // Import javakit/swiftkit support libraries -import org.swift.swiftkit.SwiftKit; +import org.swift.swiftkit.core.SwiftLibraries; public class HelloJava2SwiftJNI { public static void main(String[] args) { - System.out.print("Property: java.library.path = " + SwiftKit.getJavaLibraryPath()); + System.out.print("Property: java.library.path = " + SwiftLibraries.getJavaLibraryPath()); examples(); } @@ -35,10 +35,8 @@ static void examples() { MySwiftLibrary.globalTakeIntInt(1337, 42); long cnt = MySwiftLibrary.globalWriteString("String from Java"); - SwiftKit.trace("count = " + cnt); long i = MySwiftLibrary.globalMakeInt(); - SwiftKit.trace("globalMakeInt() = " + i); MySwiftClass.method(); diff --git a/Samples/SwiftAndJavaJarSampleLib/build.gradle b/Samples/SwiftAndJavaJarSampleLib/build.gradle index cc4c79d40..96b25e224 100644 --- a/Samples/SwiftAndJavaJarSampleLib/build.gradle +++ b/Samples/SwiftAndJavaJarSampleLib/build.gradle @@ -43,7 +43,8 @@ java { } dependencies { - implementation(project(':SwiftKit')) + implementation(project(':SwiftKitCore')) + implementation(project(':SwiftKitFFM')) testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") diff --git a/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh b/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh index f56b611ea..4fde0ef05 100755 --- a/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh +++ b/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh @@ -27,9 +27,10 @@ fi # check if we can compile a plain Example file that uses the generated Java bindings that should be in the generated jar # The classpath MUST end with a * if it contains jar files, and must not if it directly contains class files. -SWIFTKIT_CLASSPATH="$(pwd)/../../SwiftKit/build/libs/*" +SWIFTKIT_CORE_CLASSPATH="$(pwd)/../../SwiftKitCore/build/libs/*" +SWIFTKIT_FFM_CLASSPATH="$(pwd)/../../SwiftKitFFM/build/libs/*" MYLIB_CLASSPATH="$(pwd)/build/libs/*" -CLASSPATH="$(pwd)/:${SWIFTKIT_CLASSPATH}:${MYLIB_CLASSPATH}" +CLASSPATH="$(pwd)/:${SWIFTKIT_FFM_CLASSPATH}:${SWIFTKIT_CORE_CLASSPATH}:${MYLIB_CLASSPATH}" echo "CLASSPATH = ${CLASSPATH}" $JAVAC -cp "${CLASSPATH}" Example.java diff --git a/Samples/SwiftAndJavaJarSampleLib/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java b/Samples/SwiftAndJavaJarSampleLib/src/jmh/java/org/swift/swiftkit/ffm/JavaToSwiftBenchmark.java similarity index 98% rename from Samples/SwiftAndJavaJarSampleLib/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java rename to Samples/SwiftAndJavaJarSampleLib/src/jmh/java/org/swift/swiftkit/ffm/JavaToSwiftBenchmark.java index 4e3edf03e..7d6b92dce 100644 --- a/Samples/SwiftAndJavaJarSampleLib/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java +++ b/Samples/SwiftAndJavaJarSampleLib/src/jmh/java/org/swift/swiftkit/ffm/JavaToSwiftBenchmark.java @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.ffm; import java.util.concurrent.TimeUnit; diff --git a/Samples/SwiftAndJavaJarSampleLib/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftAndJavaJarSampleLib/src/main/java/com/example/swift/HelloJava2Swift.java index cd8af700f..e41547bd8 100644 --- a/Samples/SwiftAndJavaJarSampleLib/src/main/java/com/example/swift/HelloJava2Swift.java +++ b/Samples/SwiftAndJavaJarSampleLib/src/main/java/com/example/swift/HelloJava2Swift.java @@ -16,15 +16,10 @@ // Import swift-extract generated sources -import com.example.swift.MySwiftLibrary; -import com.example.swift.MySwiftClass; - // Import javakit/swiftkit support libraries -import org.swift.swiftkit.SwiftArena; -import org.swift.swiftkit.SwiftKit; -import org.swift.swiftkit.SwiftValueWitnessTable; - -import java.util.Arrays; +import org.swift.swiftkit.core.SwiftLibraries; +import org.swift.swiftkit.ffm.AllocatingSwiftArena; +import org.swift.swiftkit.ffm.SwiftRuntime; public class HelloJava2Swift { @@ -32,7 +27,7 @@ public static void main(String[] args) { boolean traceDowncalls = Boolean.getBoolean("jextract.trace.downcalls"); System.out.println("Property: jextract.trace.downcalls = " + traceDowncalls); - System.out.print("Property: java.library.path = " +SwiftKit.getJavaLibraryPath()); + System.out.print("Property: java.library.path = " + SwiftLibraries.getJavaLibraryPath()); examples(); } @@ -43,12 +38,12 @@ static void examples() { MySwiftLibrary.globalTakeInt(1337); // Example of using an arena; MyClass.deinit is run at end of scope - try (var arena = SwiftArena.ofConfined()) { + try (var arena = AllocatingSwiftArena.ofConfined()) { MySwiftClass obj = MySwiftClass.init(2222, 7777, arena); // just checking retains/releases work - SwiftKit.retain(obj); - SwiftKit.release(obj); + SwiftRuntime.retain(obj); + SwiftRuntime.release(obj); obj.voidMethod(); obj.takeIntMethod(42); diff --git a/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftClassTest.java index bb46ef3da..6fb7651cf 100644 --- a/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftClassTest.java @@ -16,8 +16,8 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.swift.swiftkit.SwiftArena; -import org.swift.swiftkit.SwiftKit; +import org.swift.swiftkit.core.SwiftLibraries; +import org.swift.swiftkit.ffm.AllocatingSwiftArena; import java.io.File; import java.util.stream.Stream; @@ -27,7 +27,7 @@ public class MySwiftClassTest { void checkPaths(Throwable throwable) { - var paths = SwiftKit.getJavaLibraryPath().split(":"); + var paths = SwiftLibraries.getJavaLibraryPath().split(":"); for (var path : paths) { System.out.println("CHECKING PATH: " + path); Stream.of(new File(path).listFiles()) @@ -42,7 +42,7 @@ void checkPaths(Throwable throwable) { @Test void test_MySwiftClass_voidMethod() { - try(var arena = SwiftArena.ofConfined()) { + try(var arena = AllocatingSwiftArena.ofConfined()) { MySwiftClass o = MySwiftClass.init(12, 42, arena); o.voidMethod(); } catch (Throwable throwable) { @@ -52,7 +52,7 @@ void test_MySwiftClass_voidMethod() { @Test void test_MySwiftClass_makeIntMethod() { - try(var arena = SwiftArena.ofConfined()) { + try(var arena = AllocatingSwiftArena.ofConfined()) { MySwiftClass o = MySwiftClass.init(12, 42, arena); var got = o.makeIntMethod(); assertEquals(12, got); @@ -62,7 +62,7 @@ void test_MySwiftClass_makeIntMethod() { @Test @Disabled // TODO: Need var mangled names in interfaces void test_MySwiftClass_property_len() { - try(var arena = SwiftArena.ofConfined()) { + try(var arena = AllocatingSwiftArena.ofConfined()) { MySwiftClass o = MySwiftClass.init(12, 42, arena); var got = o.getLen(); assertEquals(12, got); diff --git a/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java b/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java index 74df5da89..13ebb1b01 100644 --- a/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java +++ b/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -14,14 +14,10 @@ package com.example.swift; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.swift.swiftkit.SwiftKit; -import java.util.Arrays; import java.util.concurrent.CountDownLatch; -import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.*; diff --git a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftClassTest.java b/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/ffm/MySwiftClassTest.java similarity index 72% rename from Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftClassTest.java rename to Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/ffm/MySwiftClassTest.java index 78da5a642..fb1c5d1aa 100644 --- a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftClassTest.java +++ b/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/ffm/MySwiftClassTest.java @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.ffm; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -24,16 +24,16 @@ public class MySwiftClassTest { @Test void call_retain_retainCount_release() { - var arena = SwiftArena.ofConfined(); + var arena = AllocatingSwiftArena.ofConfined(); var obj = MySwiftClass.init(1, 2, arena); - assertEquals(1, SwiftKit.retainCount(obj)); + assertEquals(1, SwiftRuntime.retainCount(obj)); // TODO: test directly on SwiftHeapObject inheriting obj - SwiftKit.retain(obj); - assertEquals(2, SwiftKit.retainCount(obj)); + SwiftRuntime.retain(obj); + assertEquals(2, SwiftRuntime.retainCount(obj)); - SwiftKit.release(obj); - assertEquals(1, SwiftKit.retainCount(obj)); + SwiftRuntime.release(obj); + assertEquals(1, SwiftRuntime.retainCount(obj)); } } diff --git a/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/SwiftArenaTest.java b/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/ffm/SwiftArenaTest.java similarity index 77% rename from Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/SwiftArenaTest.java rename to Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/ffm/SwiftArenaTest.java index e8b1ac04f..8d786d952 100644 --- a/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/SwiftArenaTest.java +++ b/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/ffm/SwiftArenaTest.java @@ -12,20 +12,15 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.ffm; import com.example.swift.MySwiftClass; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledIf; -import org.swift.swiftkit.util.PlatformUtils; +import org.swift.swiftkit.core.util.PlatformUtils; -import java.util.Arrays; -import java.util.stream.Collectors; - -import static org.junit.jupiter.api.Assertions.*; -import static org.swift.swiftkit.SwiftKit.*; -import static org.swift.swiftkit.SwiftKit.retainCount; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.swift.swiftkit.ffm.SwiftRuntime.*; public class SwiftArenaTest { @@ -38,7 +33,7 @@ static boolean isAmd64() { @Test @DisabledIf("isAmd64") public void arena_releaseClassOnClose_class_ok() { - try (var arena = SwiftArena.ofConfined()) { + try (var arena = AllocatingSwiftArena.ofConfined()) { var obj = MySwiftClass.init(1, 2, arena); retain(obj); diff --git a/Samples/SwiftKitSampleApp/build.gradle b/Samples/SwiftKitSampleApp/build.gradle index 647eaba94..048c984de 100644 --- a/Samples/SwiftKitSampleApp/build.gradle +++ b/Samples/SwiftKitSampleApp/build.gradle @@ -16,7 +16,6 @@ import groovy.json.JsonSlurper import org.swift.swiftkit.gradle.BuildUtils import java.nio.file.* -import kotlinx.serialization.json.* plugins { id("build-logic.java-application-conventions") @@ -148,7 +147,8 @@ tasks.clean { } dependencies { - implementation(project(':SwiftKit')) + implementation(project(':SwiftKitCore')) + implementation(project(':SwiftKitFFM')) testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") diff --git a/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java b/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/ffm/JavaToSwiftBenchmark.java similarity index 92% rename from Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java rename to Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/ffm/JavaToSwiftBenchmark.java index ff313fc6d..dffbd0902 100644 --- a/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java +++ b/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/ffm/JavaToSwiftBenchmark.java @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.ffm; import com.example.swift.HelloJava2Swift; import com.example.swift.MySwiftLibrary; @@ -31,12 +31,12 @@ public class JavaToSwiftBenchmark { @State(Scope.Benchmark) public static class BenchmarkState { - ClosableSwiftArena arena; + ClosableAllocatingSwiftArena arena; MySwiftClass obj; @Setup(Level.Trial) public void beforeAll() { - arena = SwiftArena.ofConfined(); + arena = AllocatingSwiftArena.ofConfined(); obj = MySwiftClass.init(1, 2, arena); } diff --git a/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/StringPassingBenchmark.java b/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/ffm/StringPassingBenchmark.java similarity index 94% rename from Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/StringPassingBenchmark.java rename to Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/ffm/StringPassingBenchmark.java index 0e686fb43..258558422 100644 --- a/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/StringPassingBenchmark.java +++ b/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/ffm/StringPassingBenchmark.java @@ -12,15 +12,14 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.ffm; import com.example.swift.HelloJava2Swift; import com.example.swift.MySwiftClass; import com.example.swift.MySwiftLibrary; import org.openjdk.jmh.annotations.*; +import org.swift.swiftkit.core.ClosableSwiftArena; -import java.lang.foreign.Arena; -import java.nio.charset.StandardCharsets; import java.util.concurrent.TimeUnit; @BenchmarkMode(Mode.AverageTime) @@ -40,12 +39,12 @@ public class StringPassingBenchmark { public int stringLen; public String string; - ClosableSwiftArena arena; + ClosableAllocatingSwiftArena arena; MySwiftClass obj; @Setup(Level.Trial) public void beforeAll() { - arena = SwiftArena.ofConfined(); + arena = AllocatingSwiftArena.ofConfined(); obj = MySwiftClass.init(1, 2, arena); string = makeString(stringLen); } diff --git a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java index c12d82dd2..668140b29 100644 --- a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java +++ b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java @@ -18,8 +18,9 @@ // Import javakit/swiftkit support libraries -import org.swift.swiftkit.SwiftArena; -import org.swift.swiftkit.SwiftKit; +import org.swift.swiftkit.core.SwiftLibraries; +import org.swift.swiftkit.ffm.AllocatingSwiftArena; +import org.swift.swiftkit.ffm.SwiftRuntime; public class HelloJava2Swift { @@ -27,7 +28,7 @@ public static void main(String[] args) { boolean traceDowncalls = Boolean.getBoolean("jextract.trace.downcalls"); System.out.println("Property: jextract.trace.downcalls = " + traceDowncalls); - System.out.print("Property: java.library.path = " + SwiftKit.getJavaLibraryPath()); + System.out.print("Property: java.library.path = " + SwiftLibraries.getJavaLibraryPath()); examples(); } @@ -39,30 +40,30 @@ static void examples() { long cnt = MySwiftLibrary.globalWriteString("String from Java"); - SwiftKit.trace("count = " + cnt); + SwiftRuntime.trace("count = " + cnt); MySwiftLibrary.globalCallMeRunnable(() -> { - SwiftKit.trace("running runnable"); + SwiftRuntime.trace("running runnable"); }); - SwiftKit.trace("getGlobalBuffer().byteSize()=" + MySwiftLibrary.getGlobalBuffer().byteSize()); + SwiftRuntime.trace("getGlobalBuffer().byteSize()=" + MySwiftLibrary.getGlobalBuffer().byteSize()); MySwiftLibrary.withBuffer((buf) -> { - SwiftKit.trace("withBuffer{$0.byteSize()}=" + buf.byteSize()); + SwiftRuntime.trace("withBuffer{$0.byteSize()}=" + buf.byteSize()); }); // Example of using an arena; MyClass.deinit is run at end of scope - try (var arena = SwiftArena.ofConfined()) { + try (var arena = AllocatingSwiftArena.ofConfined()) { MySwiftClass obj = MySwiftClass.init(2222, 7777, arena); // just checking retains/releases work - SwiftKit.trace("retainCount = " + SwiftKit.retainCount(obj)); - SwiftKit.retain(obj); - SwiftKit.trace("retainCount = " + SwiftKit.retainCount(obj)); - SwiftKit.release(obj); - SwiftKit.trace("retainCount = " + SwiftKit.retainCount(obj)); + SwiftRuntime.trace("retainCount = " + SwiftRuntime.retainCount(obj)); + SwiftRuntime.retain(obj); + SwiftRuntime.trace("retainCount = " + SwiftRuntime.retainCount(obj)); + SwiftRuntime.release(obj); + SwiftRuntime.trace("retainCount = " + SwiftRuntime.retainCount(obj)); obj.setCounter(12); - SwiftKit.trace("obj.counter = " + obj.getCounter()); + SwiftRuntime.trace("obj.counter = " + obj.getCounter()); obj.voidMethod(); obj.takeIntMethod(42); @@ -71,9 +72,9 @@ static void examples() { otherObj.voidMethod(); MySwiftStruct swiftValue = MySwiftStruct.init(2222, 1111, arena); - SwiftKit.trace("swiftValue.capacity = " + swiftValue.getCapacity()); + SwiftRuntime.trace("swiftValue.capacity = " + swiftValue.getCapacity()); swiftValue.withCapLen((cap, len) -> { - SwiftKit.trace("withCapLenCallback: cap=" + cap + ", len=" + len); + SwiftRuntime.trace("withCapLenCallback: cap=" + cap + ", len=" + len); }); } diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java index 6c0ceb1e1..1f0464eb6 100644 --- a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -16,8 +16,8 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.swift.swiftkit.SwiftArena; -import org.swift.swiftkit.SwiftKit; +import org.swift.swiftkit.core.SwiftLibraries; +import org.swift.swiftkit.ffm.AllocatingSwiftArena; import java.io.File; import java.util.stream.Stream; @@ -27,7 +27,7 @@ public class MySwiftClassTest { void checkPaths(Throwable throwable) { - var paths = SwiftKit.getJavaLibraryPath().split(":"); + var paths = SwiftLibraries.getJavaLibraryPath().split(":"); for (var path : paths) { Stream.of(new File(path).listFiles()) .filter(file -> !file.isDirectory()) @@ -41,7 +41,7 @@ void checkPaths(Throwable throwable) { @Test void test_MySwiftClass_voidMethod() { - try(var arena = SwiftArena.ofConfined()) { + try(var arena = AllocatingSwiftArena.ofConfined()) { MySwiftClass o = MySwiftClass.init(12, 42, arena); o.voidMethod(); } catch (Throwable throwable) { @@ -51,7 +51,7 @@ void test_MySwiftClass_voidMethod() { @Test void test_MySwiftClass_makeIntMethod() { - try(var arena = SwiftArena.ofConfined()) { + try(var arena = AllocatingSwiftArena.ofConfined()) { MySwiftClass o = MySwiftClass.init(12, 42, arena); var got = o.makeIntMethod(); assertEquals(12, got); @@ -61,7 +61,7 @@ void test_MySwiftClass_makeIntMethod() { @Test @Disabled // TODO: Need var mangled names in interfaces void test_MySwiftClass_property_len() { - try(var arena = SwiftArena.ofConfined()) { + try(var arena = AllocatingSwiftArena.ofConfined()) { MySwiftClass o = MySwiftClass.init(12, 42, arena); var got = o.getLen(); assertEquals(12, got); diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java index 2df53843c..a6c34b579 100644 --- a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java +++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -14,15 +14,10 @@ package com.example.swift; -import com.example.swift.MySwiftLibrary; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.swift.swiftkit.SwiftKit; -import java.util.Arrays; import java.util.concurrent.CountDownLatch; -import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.*; diff --git a/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/MySwiftClassTest.java b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftClassTest.java similarity index 66% rename from Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/MySwiftClassTest.java rename to Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftClassTest.java index 78da5a642..17db4c418 100644 --- a/Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/MySwiftClassTest.java +++ b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftClassTest.java @@ -12,28 +12,30 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkitffm; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; import com.example.swift.MySwiftClass; +import org.swift.swiftkit.ffm.AllocatingSwiftArena; +import org.swift.swiftkit.ffm.SwiftRuntime; public class MySwiftClassTest { @Test void call_retain_retainCount_release() { - var arena = SwiftArena.ofConfined(); + var arena = AllocatingSwiftArena.ofConfined(); var obj = MySwiftClass.init(1, 2, arena); - assertEquals(1, SwiftKit.retainCount(obj)); + assertEquals(1, SwiftRuntime.retainCount(obj)); // TODO: test directly on SwiftHeapObject inheriting obj - SwiftKit.retain(obj); - assertEquals(2, SwiftKit.retainCount(obj)); + SwiftRuntime.retain(obj); + assertEquals(2, SwiftRuntime.retainCount(obj)); - SwiftKit.release(obj); - assertEquals(1, SwiftKit.retainCount(obj)); + SwiftRuntime.release(obj); + assertEquals(1, SwiftRuntime.retainCount(obj)); } } diff --git a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftStructTest.java b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftStructTest.java similarity index 86% rename from Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftStructTest.java rename to Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftStructTest.java index 843551a5f..6b994137c 100644 --- a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftStructTest.java +++ b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftStructTest.java @@ -12,10 +12,11 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkitffm; import com.example.swift.MySwiftStruct; import org.junit.jupiter.api.Test; +import org.swift.swiftkit.ffm.AllocatingSwiftArena; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -23,7 +24,7 @@ public class MySwiftStructTest { @Test void create_struct() { - try (var arena = SwiftArena.ofConfined()) { + try (var arena = AllocatingSwiftArena.ofConfined()) { long cap = 12; long len = 34; var struct = MySwiftStruct.init(cap, len, arena); diff --git a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/SwiftArenaTest.java similarity index 84% rename from Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java rename to Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/SwiftArenaTest.java index 52d791a13..08dd9b1bc 100644 --- a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java +++ b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/SwiftArenaTest.java @@ -12,21 +12,18 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkitffm; import com.example.swift.MySwiftClass; import com.example.swift.MySwiftStruct; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledIf; -import org.swift.swiftkit.util.PlatformUtils; - -import java.util.Arrays; -import java.util.stream.Collectors; +import org.swift.swiftkit.core.util.PlatformUtils; +import org.swift.swiftkit.ffm.AllocatingSwiftArena; +import org.swift.swiftkit.ffm.SwiftRuntime; import static org.junit.jupiter.api.Assertions.*; -import static org.swift.swiftkit.SwiftKit.*; -import static org.swift.swiftkit.SwiftKit.retainCount; +import static org.swift.swiftkit.ffm.SwiftRuntime.retainCount; public class SwiftArenaTest { @@ -39,13 +36,13 @@ static boolean isAmd64() { @Test @DisabledIf("isAmd64") public void arena_releaseClassOnClose_class_ok() { - try (var arena = SwiftArena.ofConfined()) { + try (var arena = AllocatingSwiftArena.ofConfined()) { var obj = MySwiftClass.init(1, 2, arena); - retain(obj); + SwiftRuntime.retain(obj); assertEquals(2, retainCount(obj)); - release(obj); + SwiftRuntime.release(obj); assertEquals(1, retainCount(obj)); } } @@ -56,7 +53,7 @@ public void arena_releaseClassOnClose_class_ok() { public void arena_markAsDestroyed_preventUseAfterFree_class() { MySwiftClass unsafelyEscapedOutsideArenaScope = null; - try (var arena = SwiftArena.ofConfined()) { + try (var arena = AllocatingSwiftArena.ofConfined()) { var obj = MySwiftClass.init(1, 2, arena); unsafelyEscapedOutsideArenaScope = obj; } @@ -75,7 +72,7 @@ public void arena_markAsDestroyed_preventUseAfterFree_class() { public void arena_markAsDestroyed_preventUseAfterFree_struct() { MySwiftStruct unsafelyEscapedOutsideArenaScope = null; - try (var arena = SwiftArena.ofConfined()) { + try (var arena = AllocatingSwiftArena.ofConfined()) { var s = MySwiftStruct.init(1, 2, arena); unsafelyEscapedOutsideArenaScope = s; } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index 364fd2700..13230a321 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -116,8 +116,8 @@ extension FFMSwift2JavaGenerator { """ public static \(returnTy) call(\(paramsStr)) { try { - if (SwiftKit.TRACE_DOWNCALLS) { - SwiftKit.traceDowncall(\(argsStr)); + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(\(argsStr)); } \(maybeReturn)HANDLE.invokeExact(\(argsStr)); } catch (Throwable ex$) { @@ -156,7 +156,7 @@ extension FFMSwift2JavaGenerator { /// apply(); /// } /// static final MethodDescriptor DESC = FunctionDescriptor.of(...); - /// static final MethodHandle HANDLE = SwiftKit.upcallHandle(Function.class, "apply", DESC); + /// static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(Function.class, "apply", DESC); /// static MemorySegment toUpcallStub(Function fi, Arena arena) { /// return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); /// } @@ -197,7 +197,7 @@ extension FFMSwift2JavaGenerator { printFunctionDescriptorDefinition(&printer, cResultType, cParams) printer.print( """ - private static final MethodHandle HANDLE = SwiftKit.upcallHandle(Function.class, "apply", DESC); + private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(Function.class, "apply", DESC); private static MemorySegment toUpcallStub(Function fi, Arena arena) { return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); } @@ -320,7 +320,7 @@ extension FFMSwift2JavaGenerator { .flatMap(\.javaParameters) .map { "\($0.type) \($0.name)" } if translatedSignature.requiresSwiftArena { - paramDecls.append("SwiftArena swiftArena$") + paramDecls.append("AllocatingSwiftArena swiftArena$") } // TODO: we could copy the Swift method's documentation over here, that'd be great UX diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 16efddead..d29f6b962 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -313,7 +313,7 @@ extension FFMSwift2JavaGenerator { return TranslatedParameter( javaParameters: [ JavaParameter( - name: parameterName, type: JavaType.class(package: "org.swift.swiftkit", name: "SwiftAnyType")) + name: parameterName, type: JavaType.class(package: "org.swift.swiftkit.ffm", name: "SwiftAnyType")) ], conversion: .swiftValueSelfSegment(.placeholder) ) @@ -350,7 +350,7 @@ extension FFMSwift2JavaGenerator { name: parameterName, type: .javaLangString ) ], - conversion: .call(.placeholder, function: "SwiftKit.toCString", withArena: true) + conversion: .call(.placeholder, function: "SwiftRuntime.toCString", withArena: true) ) default: @@ -411,7 +411,7 @@ extension FFMSwift2JavaGenerator { switch swiftType { case .metatype(_): // Metatype are expressed as 'org.swift.swiftkit.SwiftAnyType' - let javaType = JavaType.class(package: "org.swift.swiftkit", name: "SwiftAnyType") + let javaType = JavaType.class(package: "org.swift.swiftkit.ffm", name: "SwiftAnyType") return TranslatedResult( javaResultType: javaType, outParameters: [], diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index 09fcf858a..9a140b94d 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -92,9 +92,10 @@ extension FFMSwift2JavaGenerator { /// Default set Java imports for every generated file static let defaultJavaImports: Array = [ - "org.swift.swiftkit.*", - "org.swift.swiftkit.SwiftKit", - "org.swift.swiftkit.util.*", + "org.swift.swiftkit.core.*", + "org.swift.swiftkit.core.util.*", + "org.swift.swiftkit.ffm.*", + "org.swift.swiftkit.ffm.SwiftRuntime", // Necessary for native calls and type mapping "java.lang.foreign.*", @@ -188,7 +189,7 @@ extension FFMSwift2JavaGenerator { @SuppressWarnings("unused") private static final boolean INITIALIZED_LIBS = initializeLibs(); static boolean initializeLibs() { - System.loadLibrary(SwiftKit.STDLIB_DYLIB_NAME); + System.loadLibrary(SwiftLibraries.STDLIB_DYLIB_NAME); System.loadLibrary("SwiftKitSwift"); System.loadLibrary(LIB_NAME); return true; @@ -210,7 +211,7 @@ extension FFMSwift2JavaGenerator { printer.print( """ - public \(decl.swiftNominal.name)(MemorySegment segment, SwiftArena arena) { + public \(decl.swiftNominal.name)(MemorySegment segment, AllocatingSwiftArena arena) { super(segment, arena); } """ @@ -272,7 +273,7 @@ extension FFMSwift2JavaGenerator { parentProtocol = "SwiftValue" } - printer.printBraceBlock("public final class \(decl.swiftNominal.name) extends SwiftInstance implements \(parentProtocol)") { + printer.printBraceBlock("public final class \(decl.swiftNominal.name) extends FFMSwiftInstance implements \(parentProtocol)") { printer in // Constants printClassConstants(printer: &printer) @@ -322,9 +323,9 @@ extension FFMSwift2JavaGenerator { static final SymbolLookup SYMBOL_LOOKUP = getSymbolLookup(); private static SymbolLookup getSymbolLookup() { // Ensure Swift and our Lib are loaded during static initialization of the class. - SwiftKit.loadLibrary("swiftCore"); - SwiftKit.loadLibrary("SwiftKitSwift"); - SwiftKit.loadLibrary(LIB_NAME); + SwiftLibraries.loadLibrary("swiftCore"); + SwiftLibraries.loadLibrary("SwiftKitSwift"); + SwiftLibraries.loadLibrary(LIB_NAME); if (PlatformUtils.isMacOS()) { return SymbolLookup.libraryLookup(System.mapLibraryName(LIB_NAME), LIBRARY_ARENA) @@ -386,7 +387,7 @@ extension FFMSwift2JavaGenerator { public String toString() { return getClass().getSimpleName() + "(" - + SwiftKit.nameOfSwiftType($swiftType().$memorySegment(), true) + + SwiftRuntime.nameOfSwiftType($swiftType().$memorySegment(), true) + ")@" + $memorySegment(); } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index c61dd929c..b4d73873c 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -130,7 +130,7 @@ extension JNISwift2JavaGenerator { _ decl: ImportedFunc ) { let translatedDecl = self.translatedDecl(for: decl) - let parentName = translatedDecl.parentName ?? swiftModuleName + let parentName = translatedDecl.parentName let swiftReturnType = decl.functionSignature.result.type printCDecl(&printer, decl) { printer in diff --git a/Sources/JExtractSwiftLib/SwiftKit+Printing.swift b/Sources/JExtractSwiftLib/SwiftKit+Printing.swift index aa01502d7..0afc96452 100644 --- a/Sources/JExtractSwiftLib/SwiftKit+Printing.swift +++ b/Sources/JExtractSwiftLib/SwiftKit+Printing.swift @@ -23,7 +23,7 @@ package struct SwiftKitPrinting { /// Forms syntax for a Java call to a swiftkit exposed function. static func renderCallGetSwiftType(module: String, nominal: ImportedNominalType) -> String { """ - SwiftKit.swiftjava.getType("\(module)", "\(nominal.swiftNominal.qualifiedName)") + SwiftRuntime.swiftjava.getType("\(module)", "\(nominal.swiftNominal.qualifiedName)") """ } } diff --git a/SwiftKit/build.gradle b/SwiftKitCore/build.gradle similarity index 87% rename from SwiftKit/build.gradle rename to SwiftKitCore/build.gradle index 8f3b6c51c..3d5907f2c 100644 --- a/SwiftKit/build.gradle +++ b/SwiftKitCore/build.gradle @@ -25,13 +25,15 @@ repositories { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(24)) + // JDK 19 is the last JDK to support Java 7 + languageVersion.set(JavaLanguageVersion.of(19)) } + // Support Android 6+ (Java 7) + sourceCompatibility = JavaVersion.VERSION_1_7 } dependencies { - testImplementation(platform("org.junit:junit-bom:5.10.0")) - testImplementation("org.junit.jupiter:junit-jupiter") + testImplementation 'junit:junit:4.13.2' } tasks.test { diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/ClosableSwiftArena.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ClosableSwiftArena.java similarity index 96% rename from SwiftKit/src/main/java/org/swift/swiftkit/ClosableSwiftArena.java rename to SwiftKitCore/src/main/java/org/swift/swiftkit/core/ClosableSwiftArena.java index c257ae578..1a0890690 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/ClosableSwiftArena.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ClosableSwiftArena.java @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.core; /** * Auto-closable version of {@link SwiftArena}. @@ -24,5 +24,4 @@ public interface ClosableSwiftArena extends SwiftArena, AutoCloseable { * Throws if unable to verify all resources have been release (e.g. over retained Swift classes) */ void close(); - } diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/ConfinedSwiftMemorySession.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java similarity index 68% rename from SwiftKit/src/main/java/org/swift/swiftkit/ConfinedSwiftMemorySession.java rename to SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java index 86725ae8e..cd8f2dd1f 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/ConfinedSwiftMemorySession.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java @@ -12,15 +12,13 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.core; -import java.lang.foreign.Arena; -import java.lang.foreign.MemorySegment; import java.util.LinkedList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; -final class ConfinedSwiftMemorySession implements ClosableSwiftArena { +public class ConfinedSwiftMemorySession implements ClosableSwiftArena { final static int CLOSED = 0; final static int ACTIVE = 1; @@ -28,21 +26,17 @@ final class ConfinedSwiftMemorySession implements ClosableSwiftArena { final Thread owner; final AtomicInteger state; - final Arena arena; final ConfinedResourceList resources; public ConfinedSwiftMemorySession(Thread owner) { this.owner = owner; this.state = new AtomicInteger(ACTIVE); this.resources = new ConfinedResourceList(); - - this.arena = Arena.ofConfined(); } public void checkValid() throws RuntimeException { if (this.owner != null && this.owner != Thread.currentThread()) { - throw new WrongThreadException("ConfinedSwift arena is confined to %s but was closed from %s!" - .formatted(this.owner, Thread.currentThread())); + throw new WrongThreadException(String.format("ConfinedSwift arena is confined to %s but was closed from %s!", this.owner, Thread.currentThread())); } else if (this.state.get() < ACTIVE) { throw new RuntimeException("SwiftArena is already closed!"); } @@ -53,32 +47,19 @@ public void close() { checkValid(); // Cleanup all resources - if (this.state.compareAndExchange(ACTIVE, CLOSED) == ACTIVE) { + if (this.state.compareAndSet(ACTIVE, CLOSED)) { this.resources.runCleanup(); } // else, was already closed; do nothing - - this.arena.close(); } @Override public void register(SwiftInstance instance) { checkValid(); - var statusDestroyedFlag = instance.$statusDestroyedFlag(); - Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true); - - var cleanup = new SwiftInstanceCleanup( - instance.$memorySegment(), - instance.$swiftType(), - markAsDestroyed); + SwiftInstanceCleanup cleanup = instance.createCleanupAction(); this.resources.add(cleanup); } - @Override - public MemorySegment allocate(long byteSize, long byteAlignment) { - return arena.allocate(byteSize, byteAlignment); - } - static final class ConfinedResourceList implements SwiftResourceList { // TODO: Could use intrusive linked list to avoid one indirection here final List resourceCleanups = new LinkedList<>(); diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java new file mode 100644 index 000000000..891745a23 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java @@ -0,0 +1,56 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core; + +import java.util.concurrent.atomic.AtomicBoolean; + +public abstract class JNISwiftInstance extends SwiftInstance { + /** + * The designated constructor of any imported Swift types. + * + * @param pointer a pointer to the memory containing the value + * @param arena the arena this object belongs to. When the arena goes out of scope, this value is destroyed. + */ + protected JNISwiftInstance(long pointer, SwiftArena arena) { + super(pointer, arena); + } + + /** + * Creates a function that will be called when the value should be destroyed. + * This will be code-generated to call a native method to do deinitialization and deallocation. + *

+ * The reason for this "indirection" is that we cannot have static methods on abstract classes, + * and we can't define the destroy method as a member method, because we assume that the wrapper + * has been released, when we destroy. + *

+ * Warning: The function must not capture {@code this}. + * + * @return a function that is called when the value should be destroyed. + */ + abstract Runnable $createDestroyFunction(); + + @Override + public SwiftInstanceCleanup createCleanupAction() { + final AtomicBoolean statusDestroyedFlag = $statusDestroyedFlag(); + Runnable markAsDestroyed = new Runnable() { + @Override + public void run() { + statusDestroyedFlag.set(true); + } + }; + + return new JNISwiftInstanceCleanup(this.$createDestroyFunction(), markAsDestroyed); + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstanceCleanup.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstanceCleanup.java new file mode 100644 index 000000000..08ddcdab0 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstanceCleanup.java @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core; + +class JNISwiftInstanceCleanup implements SwiftInstanceCleanup { + private final Runnable destroyFunction; + private final Runnable markAsDestroyed; + + public JNISwiftInstanceCleanup(Runnable destroyFunction, Runnable markAsDestroyed) { + this.destroyFunction = destroyFunction; + this.markAsDestroyed = markAsDestroyed; + } + + @Override + public void run() { + markAsDestroyed.run(); + destroyFunction.run(); + } +} diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftArena.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java similarity index 54% rename from SwiftKit/src/main/java/org/swift/swiftkit/SwiftArena.java rename to SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java index f50025cce..ed16d2501 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftArena.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java @@ -12,10 +12,7 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; - -import java.lang.foreign.SegmentAllocator; -import java.util.concurrent.ThreadFactory; +package org.swift.swiftkit.core; /** * A Swift arena manages Swift allocated memory for classes, structs, enums etc. @@ -24,39 +21,17 @@ *

A confined arena has an associated owner thread that confines some operations to * associated owner thread such as {@link ClosableSwiftArena#close()}. */ -public interface SwiftArena extends SegmentAllocator { - - static ClosableSwiftArena ofConfined() { - return new ConfinedSwiftMemorySession(Thread.currentThread()); - } - - static SwiftArena ofAuto() { - ThreadFactory cleanerThreadFactory = r -> new Thread(r, "AutoSwiftArenaCleanerThread"); - return new AutoSwiftMemorySession(cleanerThreadFactory); - } - +public interface SwiftArena { /** * Register a Swift object. * Its memory should be considered managed by this arena, and be destroyed when the arena is closed. */ void register(SwiftInstance instance); - } /** * Represents a list of resources that need a cleanup, e.g. allocated classes/structs. */ interface SwiftResourceList { - void runCleanup(); - -} - - -final class UnexpectedRetainCountException extends RuntimeException { - public UnexpectedRetainCountException(Object resource, long retainCount, int expectedRetainCount) { - super(("Attempting to cleanup managed memory segment %s, but it's retain count was different than [%d] (was %d)! " + - "This would result in destroying a swift object that is still retained by other code somewhere." - ).formatted(resource, expectedRetainCount, retainCount)); - } } diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstance.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstance.java similarity index 70% rename from SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstance.java rename to SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstance.java index 5b8ff7007..638cb8be6 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstance.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstance.java @@ -12,24 +12,28 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.core; -import java.lang.foreign.GroupLayout; -import java.lang.foreign.MemorySegment; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Supplier; public abstract class SwiftInstance { /// Pointer to the "self". - private final MemorySegment selfMemorySegment; + private final long selfPointer; /** * The pointer to the instance in memory. I.e. the {@code self} of the Swift object or value. */ - public final MemorySegment $memorySegment() { - return this.selfMemorySegment; + public final long pointer() { + return this.selfPointer; } + /** + * Called when the arena has decided the value should be destroyed. + *

+ * Warning: The cleanup action must not capture {@code this}. + */ + public abstract SwiftInstanceCleanup createCleanupAction(); + // TODO: make this a flagset integer and/or use a field updater /** Used to track additional state of the underlying object, e.g. if it was explicitly destroyed. */ private final AtomicBoolean $state$destroyed = new AtomicBoolean(false); @@ -45,24 +49,14 @@ public abstract class SwiftInstance { return this.$state$destroyed; } - /** - * The in memory layout of an instance of this Swift type. - */ - public abstract GroupLayout $layout(); - - /** - * The Swift type metadata of this type. - */ - public abstract SwiftAnyType $swiftType(); - /** * The designated constructor of any imported Swift types. * - * @param segment the memory segment. + * @param pointer a pointer to the memory containing the value * @param arena the arena this object belongs to. When the arena goes out of scope, this value is destroyed. */ - protected SwiftInstance(MemorySegment segment, SwiftArena arena) { - this.selfMemorySegment = segment; + protected SwiftInstance(long pointer, SwiftArena arena) { + this.selfPointer = pointer; arena.register(this); } @@ -78,13 +72,4 @@ protected SwiftInstance(MemorySegment segment, SwiftArena arena) { throw new IllegalStateException("Attempted to call method on already destroyed instance of " + getClass().getSimpleName() + "!"); } } - - /** - * Returns `true` if this swift instance is a reference type, i.e. a `class` or (`distributed`) `actor`. - * - * @return `true` if this instance is a reference type, `false` otherwise. - */ - public boolean isReferenceType() { - return this instanceof SwiftHeapObject; - } } diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstanceCleanup.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstanceCleanup.java new file mode 100644 index 000000000..564169687 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstanceCleanup.java @@ -0,0 +1,20 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core; + +/** + * A Swift memory instance cleanup, e.g. count-down a reference count and destroy a class, or destroy struct/enum etc. + */ +public interface SwiftInstanceCleanup extends Runnable {} \ No newline at end of file diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java new file mode 100644 index 000000000..2abdaff5e --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java @@ -0,0 +1,83 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core; + +import org.swift.swiftkit.core.util.PlatformUtils; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; + +public final class SwiftLibraries { + + public static final String STDLIB_DYLIB_NAME = "swiftCore"; + public static final String SWIFTKITSWIFT_DYLIB_NAME = "SwiftKitSwift"; + public static final boolean TRACE_DOWNCALLS = Boolean.getBoolean("jextract.trace.downcalls"); + + private static final String STDLIB_MACOS_DYLIB_PATH = "/usr/lib/swift/libswiftCore.dylib"; + + @SuppressWarnings("unused") + private static final boolean INITIALIZED_LIBS = loadLibraries(false); + + public static boolean loadLibraries(boolean loadSwiftKit) { + System.loadLibrary(STDLIB_DYLIB_NAME); + if (loadSwiftKit) { + System.loadLibrary(SWIFTKITSWIFT_DYLIB_NAME); + } + return true; + } + + // ==== ------------------------------------------------------------------------------------------------------------ + // Loading libraries + + public static void loadLibrary(String libname) { + // TODO: avoid concurrent loadResource calls; one load is enough esp since we cause File IO when we do that + try { + // try to load a dylib from our classpath, e.g. when we included it in our jar + loadResourceLibrary(libname); + } catch (UnsatisfiedLinkError | RuntimeException e) { + // fallback to plain system path loading + System.loadLibrary(libname); + } + } + + public static void loadResourceLibrary(String libname) { + String resourceName = PlatformUtils.dynamicLibraryName(libname); + if (SwiftLibraries.TRACE_DOWNCALLS) { + System.out.println("[swift-java] Loading resource library: " + resourceName); + } + + try (InputStream libInputStream = SwiftLibraries.class.getResourceAsStream("/" + resourceName)) { + if (libInputStream == null) { + throw new RuntimeException("Expected library '" + libname + "' ('" + resourceName + "') was not found as resource!"); + } + + // TODO: we could do an in memory file system here + File tempFile = File.createTempFile(libname, ""); + tempFile.deleteOnExit(); + Files.copy(libInputStream, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + + System.load(tempFile.getAbsolutePath()); + } catch (IOException e) { + throw new RuntimeException("Failed to load dynamic library '" + libname + "' ('" + resourceName + "') as resource!", e); + } + } + + public static String getJavaLibraryPath() { + return System.getProperty("java.library.path"); + } +} diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValue.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftValue.java similarity index 94% rename from SwiftKit/src/main/java/org/swift/swiftkit/SwiftValue.java rename to SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftValue.java index a364c0129..3e9c1aff0 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValue.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftValue.java @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.core; /** * Represent a wrapper around a Swift value object. e.g. {@code struct} or {@code enum}. diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/ManagedSwiftType.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/WrongThreadException.java similarity index 67% rename from SwiftKit/src/main/java/org/swift/swiftkit/ManagedSwiftType.java rename to SwiftKitCore/src/main/java/org/swift/swiftkit/core/WrongThreadException.java index da25ae4d6..d15436f95 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/ManagedSwiftType.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/WrongThreadException.java @@ -12,14 +12,10 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; - -import java.lang.foreign.MemorySegment; - -public interface ManagedSwiftType { - /** - * The memory segment of `self` of the managed Swift object/value. - */ - public MemorySegment $memorySegment(); +package org.swift.swiftkit.core; +public class WrongThreadException extends RuntimeException { + public WrongThreadException(String message) { + super(message); + } } diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/util/PlatformUtils.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/util/PlatformUtils.java similarity index 97% rename from SwiftKit/src/main/java/org/swift/swiftkit/util/PlatformUtils.java rename to SwiftKitCore/src/main/java/org/swift/swiftkit/core/util/PlatformUtils.java index 6addb31de..667bd53b0 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/util/PlatformUtils.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/util/PlatformUtils.java @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit.util; +package org.swift.swiftkit.core.util; public class PlatformUtils { public static boolean isLinux() { diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/util/StringUtils.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/util/StringUtils.java similarity index 96% rename from SwiftKit/src/main/java/org/swift/swiftkit/util/StringUtils.java rename to SwiftKitCore/src/main/java/org/swift/swiftkit/core/util/StringUtils.java index 6753ea73a..1ce4259c5 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/util/StringUtils.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/util/StringUtils.java @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit.util; +package org.swift.swiftkit.core.util; public class StringUtils { public static String stripPrefix(String mangledName, String prefix) { diff --git a/SwiftKitFFM/build.gradle b/SwiftKitFFM/build.gradle new file mode 100644 index 000000000..22f880012 --- /dev/null +++ b/SwiftKitFFM/build.gradle @@ -0,0 +1,87 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +plugins { + id("build-logic.java-application-conventions") +} + +group = "org.swift.swiftkit" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(24)) + } +} + +dependencies { + implementation(project(':SwiftKitCore')) + + testImplementation(platform("org.junit:junit-bom:5.10.0")) + testImplementation("org.junit.jupiter:junit-jupiter") +} + +testing { + suites { + test { + useJUnitJupiter('5.10.3') + } + } +} + +tasks.test { + useJUnitPlatform() + testLogging { + events("passed", "skipped", "failed") + } +} + +/// Enable access to preview APIs, e.g. java.lang.foreign.* (Panama) +tasks.withType(JavaCompile).configureEach { + options.compilerArgs.add("--enable-preview") + options.compilerArgs.add("-Xlint:preview") +} + +// SwiftKit depends on SwiftKitSwift (Swift library that this Java library calls into) + +def compileSwift = tasks.register("compileSwift", Exec) { + description "Compile the swift-java SwiftKitSwift dynamic library that SwiftKit (Java) calls into" + + inputs.file(new File(rootDir, "Package.swift")) + inputs.dir(new File(rootDir, "Sources/")) // a bit generous, but better safe than sorry, and include all Swift source changes + outputs.dir(new File(rootDir, ".build")) + + workingDir = rootDir + commandLine "swift" + args("build", "--target", "SwiftKitSwift") +} +tasks.build { + dependsOn("compileSwift") +} + + + +def cleanSwift = tasks.register("cleanSwift", Exec) { + workingDir = rootDir + commandLine "swift" + args("package", "clean") +} +tasks.clean { + dependsOn("cleanSwift") +} + diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/AutoSwiftMemorySession.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingAutoSwiftMemorySession.java similarity index 78% rename from SwiftKit/src/main/java/org/swift/swiftkit/AutoSwiftMemorySession.java rename to SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingAutoSwiftMemorySession.java index ecbe836ef..fecd52025 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/AutoSwiftMemorySession.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingAutoSwiftMemorySession.java @@ -12,7 +12,10 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.ffm; + +import org.swift.swiftkit.core.SwiftArena; +import org.swift.swiftkit.core.SwiftInstance; import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; @@ -26,7 +29,7 @@ *

When registered Java wrapper classes around native Swift instances {@link SwiftInstance}, * are eligible for collection, this will trigger the cleanup of the native resources as well. * - *

This memory session is LESS reliable than using a {@link ConfinedSwiftMemorySession} because + *

This memory session is LESS reliable than using a {@link FFMConfinedSwiftMemorySession} because * the timing of when the native resources are cleaned up is somewhat undefined, and rely on the * system GC. Meaning, that if an object nas been promoted to an old generation, there may be a * long time between the resource no longer being referenced "in Java" and its native memory being released, @@ -37,12 +40,12 @@ * *

Whenever possible, prefer using an explicitly managed {@link SwiftArena}, such as {@link SwiftArena#ofConfined()}. */ -final class AutoSwiftMemorySession implements SwiftArena { +final class AllocatingAutoSwiftMemorySession implements AllocatingSwiftArena { private final Arena arena; private final Cleaner cleaner; - public AutoSwiftMemorySession(ThreadFactory cleanerThreadFactory) { + public AllocatingAutoSwiftMemorySession(ThreadFactory cleanerThreadFactory) { this.cleaner = Cleaner.create(cleanerThreadFactory); this.arena = Arena.ofAuto(); } @@ -51,12 +54,9 @@ public AutoSwiftMemorySession(ThreadFactory cleanerThreadFactory) { public void register(SwiftInstance instance) { Objects.requireNonNull(instance, "value"); - // We're doing this dance to avoid keeping a strong reference to the value itself - var statusDestroyedFlag = instance.$statusDestroyedFlag(); - Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true); - - MemorySegment resource = instance.$memorySegment(); - var cleanupAction = new SwiftInstanceCleanup(resource, instance.$swiftType(), markAsDestroyed); + // We make sure we don't capture `instance` in the + // cleanup action, so we can ignore the warning below. + var cleanupAction = instance.createCleanupAction(); cleaner.register(instance, cleanupAction); } @@ -64,4 +64,4 @@ public void register(SwiftInstance instance) { public MemorySegment allocate(long byteSize, long byteAlignment) { return arena.allocate(byteSize, byteAlignment); } -} +} \ No newline at end of file diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingSwiftArena.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingSwiftArena.java new file mode 100644 index 000000000..08fad15bc --- /dev/null +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingSwiftArena.java @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.ffm; + +import org.swift.swiftkit.core.SwiftArena; + +import java.lang.foreign.MemorySegment; +import java.lang.foreign.SegmentAllocator; +import java.util.concurrent.ThreadFactory; + +public interface AllocatingSwiftArena extends SwiftArena, SegmentAllocator { + MemorySegment allocate(long byteSize, long byteAlignment); + + static ClosableAllocatingSwiftArena ofConfined() { + return new FFMConfinedSwiftMemorySession(Thread.currentThread()); + } + + static AllocatingSwiftArena ofAuto() { + ThreadFactory cleanerThreadFactory = r -> new Thread(r, "AutoSwiftArenaCleanerThread"); + return new AllocatingAutoSwiftMemorySession(cleanerThreadFactory); + } +} diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/ClosableAllocatingSwiftArena.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/ClosableAllocatingSwiftArena.java new file mode 100644 index 000000000..70cdcbb6f --- /dev/null +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/ClosableAllocatingSwiftArena.java @@ -0,0 +1,22 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.ffm; + +import org.swift.swiftkit.core.ClosableSwiftArena; + +/** + * Auto-closable version of {@link AllocatingSwiftArena}. + */ +public interface ClosableAllocatingSwiftArena extends ClosableSwiftArena, AllocatingSwiftArena {} diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMConfinedSwiftMemorySession.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMConfinedSwiftMemorySession.java new file mode 100644 index 000000000..424e54b68 --- /dev/null +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMConfinedSwiftMemorySession.java @@ -0,0 +1,40 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.ffm; + +import org.swift.swiftkit.core.ConfinedSwiftMemorySession; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; + +final class FFMConfinedSwiftMemorySession extends ConfinedSwiftMemorySession implements AllocatingSwiftArena, ClosableAllocatingSwiftArena { + final Arena arena; + + public FFMConfinedSwiftMemorySession(Thread owner) { + super(owner); + this.arena = Arena.ofConfined(); + } + + @Override + public void close() { + super.close(); + this.arena.close(); + } + + @Override + public MemorySegment allocate(long byteSize, long byteAlignment) { + return arena.allocate(byteSize, byteAlignment); + } +} diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstance.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstance.java new file mode 100644 index 000000000..f4a01aa47 --- /dev/null +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstance.java @@ -0,0 +1,69 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.ffm; + +import org.swift.swiftkit.core.SwiftInstance; +import org.swift.swiftkit.core.SwiftInstanceCleanup; + +import java.lang.foreign.MemorySegment; + +public abstract class FFMSwiftInstance extends SwiftInstance { + private final MemorySegment memorySegment; + + /** + * The pointer to the instance in memory. I.e. the {@code self} of the Swift object or value. + */ + public final MemorySegment $memorySegment() { + return this.memorySegment; + } + + /** + * The Swift type metadata of this type. + */ + public abstract SwiftAnyType $swiftType(); + + /** + * The designated constructor of any imported Swift types. + * + * @param segment the memory segment. + * @param arena the arena this object belongs to. When the arena goes out of scope, this value is destroyed. + */ + protected FFMSwiftInstance(MemorySegment segment, AllocatingSwiftArena arena) { + super(segment.address(), arena); + this.memorySegment = segment; + } + + @Override + public SwiftInstanceCleanup createCleanupAction() { + var statusDestroyedFlag = $statusDestroyedFlag(); + Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true); + + return new FFMSwiftInstanceCleanup( + $memorySegment(), + $swiftType(), + markAsDestroyed + ); + } + + + /** + * Returns `true` if this swift instance is a reference type, i.e. a `class` or (`distributed`) `actor`. + * + * @return `true` if this instance is a reference type, `false` otherwise. + */ + public boolean isReferenceType() { + return this instanceof SwiftHeapObject; + } +} diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstanceCleanup.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstanceCleanup.java similarity index 63% rename from SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstanceCleanup.java rename to SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstanceCleanup.java index a9fdd9c8f..a5d0829a3 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstanceCleanup.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstanceCleanup.java @@ -12,18 +12,22 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.ffm; + +import org.swift.swiftkit.core.SwiftInstanceCleanup; import java.lang.foreign.MemorySegment; -/** - * A Swift memory instance cleanup, e.g. count-down a reference count and destroy a class, or destroy struct/enum etc. - */ -record SwiftInstanceCleanup( - MemorySegment selfPointer, - SwiftAnyType selfType, - Runnable markAsDestroyed -) implements Runnable { +public class FFMSwiftInstanceCleanup implements SwiftInstanceCleanup { + private final MemorySegment selfPointer; + private final SwiftAnyType selfType; + private final Runnable markAsDestroyed; + + public FFMSwiftInstanceCleanup(MemorySegment selfPointer, SwiftAnyType selfType, Runnable markAsDestroyed) { + this.selfPointer = selfPointer; + this.selfType = selfType; + this.markAsDestroyed = markAsDestroyed; + } @Override public void run() { diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/MemorySegmentUtils.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/MemorySegmentUtils.java similarity index 97% rename from SwiftKit/src/main/java/org/swift/swiftkit/MemorySegmentUtils.java rename to SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/MemorySegmentUtils.java index 660a55008..0890b1d8f 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/MemorySegmentUtils.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/MemorySegmentUtils.java @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.ffm; import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftAnyType.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftAnyType.java similarity index 89% rename from SwiftKit/src/main/java/org/swift/swiftkit/SwiftAnyType.java rename to SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftAnyType.java index 4d13ecb77..3915725b8 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftAnyType.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftAnyType.java @@ -12,7 +12,10 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.ffm; + +import org.swift.swiftkit.ffm.SwiftRuntime; +import org.swift.swiftkit.ffm.SwiftValueLayout; import java.lang.foreign.GroupLayout; import java.lang.foreign.MemoryLayout; @@ -46,7 +49,7 @@ public SwiftAnyType(MemorySegment memorySegment) { * Get the human-readable Swift type name of this type. */ public String getSwiftName() { - return SwiftKit.nameOfSwiftType(memorySegment, true); + return SwiftRuntime.nameOfSwiftType(memorySegment, true); } @Override diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftHeapObject.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftHeapObject.java similarity index 91% rename from SwiftKit/src/main/java/org/swift/swiftkit/SwiftHeapObject.java rename to SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftHeapObject.java index 89050fb57..b506be90e 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftHeapObject.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftHeapObject.java @@ -12,7 +12,8 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.ffm; + import java.lang.foreign.MemorySegment; @@ -25,7 +26,7 @@ public interface SwiftHeapObject { /** * Pointer to the instance. */ - public default MemorySegment $instance() { + default MemorySegment $instance() { return this.$memorySegment().get(SwiftValueLayout.SWIFT_POINTER, 0); } } diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java similarity index 88% rename from SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java rename to SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java index 9c04eded9..f734bbef2 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java @@ -12,28 +12,22 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.ffm; -import org.swift.swiftkit.util.PlatformUtils; +import org.swift.swiftkit.core.util.PlatformUtils; -import java.io.File; -import java.io.IOException; import java.lang.foreign.*; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; -import java.nio.file.CopyOption; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; import java.util.Arrays; import java.util.Optional; import java.util.stream.Collectors; -import static org.swift.swiftkit.util.StringUtils.stripPrefix; -import static org.swift.swiftkit.util.StringUtils.stripSuffix; +import static org.swift.swiftkit.core.util.StringUtils.stripPrefix; +import static org.swift.swiftkit.core.util.StringUtils.stripSuffix; -public class SwiftKit { +public class SwiftRuntime { public static final String STDLIB_DYLIB_NAME = "swiftCore"; public static final String SWIFTKITSWIFT_DYLIB_NAME = "SwiftKitSwift"; @@ -68,7 +62,7 @@ private static SymbolLookup getSymbolLookup() { } } - public SwiftKit() { + public SwiftRuntime() { } public static void traceDowncall(Object... args) { @@ -103,50 +97,10 @@ static MemorySegment findOrThrow(String symbol) { .orElseThrow(() -> new UnsatisfiedLinkError("unresolved symbol: %s".formatted(symbol))); } - public static String getJavaLibraryPath() { - return System.getProperty("java.library.path"); - } - public static boolean getJextractTraceDowncalls() { return Boolean.getBoolean("jextract.trace.downcalls"); } - // ==== ------------------------------------------------------------------------------------------------------------ - // Loading libraries - - public static void loadLibrary(String libname) { - // TODO: avoid concurrent loadResource calls; one load is enough esp since we cause File IO when we do that - try { - // try to load a dylib from our classpath, e.g. when we included it in our jar - loadResourceLibrary(libname); - } catch (UnsatisfiedLinkError | RuntimeException e) { - // fallback to plain system path loading - System.loadLibrary(libname); - } - } - - public static void loadResourceLibrary(String libname) { - var resourceName = PlatformUtils.dynamicLibraryName(libname); - if (SwiftKit.TRACE_DOWNCALLS) { - System.out.println("[swift-java] Loading resource library: " + resourceName); - } - - try (var libInputStream = SwiftKit.class.getResourceAsStream("/" + resourceName)) { - if (libInputStream == null) { - throw new RuntimeException("Expected library '" + libname + "' ('" + resourceName + "') was not found as resource!"); - } - - // TODO: we could do an in memory file system here - var tempFile = File.createTempFile(libname, ""); - tempFile.deleteOnExit(); - Files.copy(libInputStream, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - - System.load(tempFile.getAbsolutePath()); - } catch (IOException e) { - throw new RuntimeException("Failed to load dynamic library '" + libname + "' ('" + resourceName + "') as resource!", e); - } - } - // ==== ------------------------------------------------------------------------------------------------------------ // free diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueLayout.java similarity index 98% rename from SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java rename to SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueLayout.java index 00215ef0b..c0a751442 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueLayout.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueLayout.java @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.ffm; import java.lang.foreign.AddressLayout; import java.lang.foreign.MemoryLayout; diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueWitnessTable.java similarity index 94% rename from SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java rename to SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueWitnessTable.java index 8ddff1b66..a0a6a79f6 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueWitnessTable.java @@ -12,13 +12,12 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.ffm; import java.lang.foreign.*; import java.lang.invoke.*; import static java.lang.foreign.ValueLayout.JAVA_BYTE; -import static org.swift.swiftkit.SwiftKit.getSwiftInt; public abstract class SwiftValueWitnessTable { @@ -86,7 +85,7 @@ public static MemorySegment valueWitnessTable(MemorySegment typeMetadata) { * @param typeMetadata the memory segment must point to a Swift metadata */ public static long sizeOfSwiftType(MemorySegment typeMetadata) { - return getSwiftInt(valueWitnessTable(typeMetadata), SwiftValueWitnessTable.$size$offset); + return SwiftRuntime.getSwiftInt(valueWitnessTable(typeMetadata), SwiftValueWitnessTable.$size$offset); } @@ -112,7 +111,7 @@ public static long sizeOfSwiftType(MemorySegment typeMetadata) { * @param typeMetadata the memory segment must point to a Swift metadata */ public static long strideOfSwiftType(MemorySegment typeMetadata) { - return getSwiftInt(valueWitnessTable(typeMetadata), SwiftValueWitnessTable.$stride$offset); + return SwiftRuntime.getSwiftInt(valueWitnessTable(typeMetadata), SwiftValueWitnessTable.$stride$offset); } @@ -122,7 +121,7 @@ public static long strideOfSwiftType(MemorySegment typeMetadata) { * @param typeMetadata the memory segment must point to a Swift metadata */ public static long alignmentOfSwiftType(MemorySegment typeMetadata) { - long flags = getSwiftInt(valueWitnessTable(typeMetadata), SwiftValueWitnessTable.$flags$offset); + long flags = SwiftRuntime.getSwiftInt(valueWitnessTable(typeMetadata), SwiftValueWitnessTable.$flags$offset); return (flags & 0xFF) + 1; } @@ -156,7 +155,7 @@ public static MemoryLayout layoutOfSwiftType(MemorySegment typeMetadata) { return MemoryLayout.structLayout( layouts - ).withName(SwiftKit.nameOfSwiftType(typeMetadata, true)); + ).withName(SwiftRuntime.nameOfSwiftType(typeMetadata, true)); } @@ -198,7 +197,7 @@ static MemorySegment addr(SwiftAnyType ty) { final var vwt = SwiftValueWitnessTable.valueWitnessTable(ty.$memorySegment()); // Get the address of the destroy function stored at the offset of the witness table - long funcAddress = getSwiftInt(vwt, destroy.$offset); + long funcAddress = SwiftRuntime.getSwiftInt(vwt, destroy.$offset); return MemorySegment.ofAddress(funcAddress); } @@ -258,7 +257,7 @@ static MemorySegment addr(SwiftAnyType ty) { final var vwt = SwiftValueWitnessTable.valueWitnessTable(ty.$memorySegment()); // Get the address of the function stored at the offset of the witness table - long funcAddress = getSwiftInt(vwt, initializeWithCopy.$offset); + long funcAddress = SwiftRuntime.getSwiftInt(vwt, initializeWithCopy.$offset); return MemorySegment.ofAddress(funcAddress); } diff --git a/SwiftKit/src/test/java/org/swift/swiftkit/AutoArenaTest.java b/SwiftKitFFM/src/test/java/org/swift/swiftkit/ffm/AutoArenaTest.java similarity index 78% rename from SwiftKit/src/test/java/org/swift/swiftkit/AutoArenaTest.java rename to SwiftKitFFM/src/test/java/org/swift/swiftkit/ffm/AutoArenaTest.java index 23365de91..b95f6c458 100644 --- a/SwiftKit/src/test/java/org/swift/swiftkit/AutoArenaTest.java +++ b/SwiftKitFFM/src/test/java/org/swift/swiftkit/ffm/AutoArenaTest.java @@ -12,21 +12,19 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; +package org.swift.swiftkit.ffm; import org.junit.jupiter.api.Test; import java.lang.foreign.GroupLayout; import java.lang.foreign.MemorySegment; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicBoolean; public class AutoArenaTest { @Test @SuppressWarnings("removal") // System.runFinalization() will be removed public void cleaner_releases_native_resource() { - SwiftArena arena = SwiftArena.ofAuto(); + AllocatingSwiftArena arena = AllocatingSwiftArena.ofAuto(); // This object is registered to the arena. var object = new FakeSwiftInstance(arena); @@ -48,16 +46,11 @@ public void cleaner_releases_native_resource() { } } - private static class FakeSwiftInstance extends SwiftInstance implements SwiftHeapObject { - public FakeSwiftInstance(SwiftArena arena) { + private static class FakeSwiftInstance extends FFMSwiftInstance implements SwiftHeapObject { + public FakeSwiftInstance(AllocatingSwiftArena arena) { super(MemorySegment.NULL, arena); } - @Override - public GroupLayout $layout() { - return null; - } - @Override public SwiftAnyType $swiftType() { return null; diff --git a/SwiftKit/src/test/java/org/swift/swiftkit/SwiftRuntimeMetadataTest.java b/SwiftKitFFM/src/test/java/org/swift/swiftkit/ffm/SwiftRuntimeMetadataTest.java similarity index 95% rename from SwiftKit/src/test/java/org/swift/swiftkit/SwiftRuntimeMetadataTest.java rename to SwiftKitFFM/src/test/java/org/swift/swiftkit/ffm/SwiftRuntimeMetadataTest.java index 3419405e1..0ec821238 100644 --- a/SwiftKit/src/test/java/org/swift/swiftkit/SwiftRuntimeMetadataTest.java +++ b/SwiftKitFFM/src/test/java/org/swift/swiftkit/ffm/SwiftRuntimeMetadataTest.java @@ -12,11 +12,7 @@ // //===----------------------------------------------------------------------===// -package org.swift.swiftkit; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +package org.swift.swiftkit.ffm; public class SwiftRuntimeMetadataTest { diff --git a/Tests/JExtractSwiftTests/ClassPrintingTests.swift b/Tests/JExtractSwiftTests/ClassPrintingTests.swift index 6c0d8d0a1..12b11d387 100644 --- a/Tests/JExtractSwiftTests/ClassPrintingTests.swift +++ b/Tests/JExtractSwiftTests/ClassPrintingTests.swift @@ -44,7 +44,7 @@ struct ClassPrintingTests { try assertOutput(input: class_interfaceFile, .ffm, .java, swiftModuleName: "__FakeModule", expectedChunks: [ """ public static final SwiftAnyType TYPE_METADATA = - new SwiftAnyType(SwiftKit.swiftjava.getType("__FakeModule", "MySwiftClass")); + new SwiftAnyType(SwiftRuntime.swiftjava.getType("__FakeModule", "MySwiftClass")); public final SwiftAnyType $swiftType() { return TYPE_METADATA; } diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index d81dad8c5..5f80dd284 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -77,8 +77,8 @@ final class FuncCallbackImportTests { private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static void call(java.lang.foreign.MemorySegment callback) { try { - if (SwiftKit.TRACE_DOWNCALLS) { - SwiftKit.traceDowncall(callback); + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(callback); } HANDLE.invokeExact(callback); } catch (Throwable ex$) { @@ -96,7 +96,7 @@ final class FuncCallbackImportTests { void apply(); } private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(); - private static final MethodHandle HANDLE = SwiftKit.upcallHandle(Function.class, "apply", DESC); + private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(Function.class, "apply", DESC); private static MemorySegment toUpcallStub(Function fi, Arena arena) { return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); } @@ -167,8 +167,8 @@ final class FuncCallbackImportTests { private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static void call(java.lang.foreign.MemorySegment callback, java.lang.foreign.MemorySegment fn) { try { - if (SwiftKit.TRACE_DOWNCALLS) { - SwiftKit.traceDowncall(callback, fn); + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(callback, fn); } HANDLE.invokeExact(callback, fn); } catch (Throwable ex$) { @@ -190,7 +190,7 @@ final class FuncCallbackImportTests { /* _0: */SwiftValueLayout.SWIFT_POINTER, /* _1: */SwiftValueLayout.SWIFT_FLOAT ); - private static final MethodHandle HANDLE = SwiftKit.upcallHandle(Function.class, "apply", DESC); + private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(Function.class, "apply", DESC); private static MemorySegment toUpcallStub(Function fi, Arena arena) { return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); } @@ -206,7 +206,7 @@ final class FuncCallbackImportTests { void apply(); } private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(); - private static final MethodHandle HANDLE = SwiftKit.upcallHandle(Function.class, "apply", DESC); + private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(Function.class, "apply", DESC); private static MemorySegment toUpcallStub(Function fi, Arena arena) { return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); } @@ -281,8 +281,8 @@ final class FuncCallbackImportTests { private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static void call(java.lang.foreign.MemorySegment body) { try { - if (SwiftKit.TRACE_DOWNCALLS) { - SwiftKit.traceDowncall(body); + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(body); } HANDLE.invokeExact(body); } catch (Throwable ex$) { @@ -304,7 +304,7 @@ final class FuncCallbackImportTests { /* _0: */SwiftValueLayout.SWIFT_POINTER, /* _1: */SwiftValueLayout.SWIFT_INT ); - private static final MethodHandle HANDLE = SwiftKit.upcallHandle(Function.class, "apply", DESC); + private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(Function.class, "apply", DESC); private static MemorySegment toUpcallStub(Function fi, Arena arena) { return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); } diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift index 7af3c706f..a714367e2 100644 --- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift +++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift @@ -66,8 +66,8 @@ final class FunctionDescriptorTests { private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static void call(long i) { try { - if (SwiftKit.TRACE_DOWNCALLS) { - SwiftKit.traceDowncall(i); + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(i); } HANDLE.invokeExact(i); } catch (Throwable ex$) { @@ -102,8 +102,8 @@ final class FunctionDescriptorTests { private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static void call(long l, int i32) { try { - if (SwiftKit.TRACE_DOWNCALLS) { - SwiftKit.traceDowncall(l, i32); + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(l, i32); } HANDLE.invokeExact(l, i32); } catch (Throwable ex$) { @@ -138,8 +138,8 @@ final class FunctionDescriptorTests { private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static long call(long i) { try { - if (SwiftKit.TRACE_DOWNCALLS) { - SwiftKit.traceDowncall(i); + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(i); } return (long) HANDLE.invokeExact(i); } catch (Throwable ex$) { @@ -174,8 +174,8 @@ final class FunctionDescriptorTests { private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static int call(java.lang.foreign.MemorySegment self) { try { - if (SwiftKit.TRACE_DOWNCALLS) { - SwiftKit.traceDowncall(self); + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(self); } return (int) HANDLE.invokeExact(self); } catch (Throwable ex$) { @@ -209,8 +209,8 @@ final class FunctionDescriptorTests { private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static void call(int newValue, java.lang.foreign.MemorySegment self) { try { - if (SwiftKit.TRACE_DOWNCALLS) { - SwiftKit.traceDowncall(newValue, self); + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(newValue, self); } HANDLE.invokeExact(newValue, self); } catch (Throwable ex$) { diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift index 36610dcd9..118ca7891 100644 --- a/Tests/JExtractSwiftTests/MethodImportTests.swift +++ b/Tests/JExtractSwiftTests/MethodImportTests.swift @@ -180,7 +180,7 @@ final class MethodImportTests { */ public static void globalTakeIntLongString(int i32, long l, java.lang.String s) { try(var arena$ = Arena.ofConfined()) { - swiftjava___FakeModule_globalTakeIntLongString_i32_l_s.call(i32, l, SwiftKit.toCString(s, arena$)); + swiftjava___FakeModule_globalTakeIntLongString_i32_l_s.call(i32, l, SwiftRuntime.toCString(s, arena$)); } } """ @@ -222,7 +222,7 @@ final class MethodImportTests { * public func globalReturnClass() -> MySwiftClass * } */ - public static MySwiftClass globalReturnClass(SwiftArena swiftArena$) { + public static MySwiftClass globalReturnClass(AllocatingSwiftArena swiftArena$) { MemorySegment _result = swiftArena$.allocate(MySwiftClass.$LAYOUT); swiftjava___FakeModule_globalReturnClass.call(_result); return new MySwiftClass(_result, swiftArena$); @@ -396,7 +396,7 @@ final class MethodImportTests { * public init(len: Swift.Int, cap: Swift.Int) * } */ - public static MySwiftClass init(long len, long cap, SwiftArena swiftArena$) { + public static MySwiftClass init(long len, long cap, AllocatingSwiftArena swiftArena$) { MemorySegment _result = swiftArena$.allocate(MySwiftClass.$LAYOUT); swiftjava___FakeModule_MySwiftClass_init_len_cap.call(len, cap, _result) return new MySwiftClass(_result, swiftArena$); @@ -440,7 +440,7 @@ final class MethodImportTests { * public init(len: Swift.Int, cap: Swift.Int) * } */ - public static MySwiftStruct init(long len, long cap, SwiftArena swiftArena$) { + public static MySwiftStruct init(long len, long cap, AllocatingSwiftArena swiftArena$) { MemorySegment _result = swiftArena$.allocate(MySwiftStruct.$LAYOUT); swiftjava___FakeModule_MySwiftStruct_init_len_cap.call(len, cap, _result) return new MySwiftStruct(_result, swiftArena$); diff --git a/Tests/JExtractSwiftTests/StringPassingTests.swift b/Tests/JExtractSwiftTests/StringPassingTests.swift index 99dfab5c1..6a5c1c804 100644 --- a/Tests/JExtractSwiftTests/StringPassingTests.swift +++ b/Tests/JExtractSwiftTests/StringPassingTests.swift @@ -45,8 +45,8 @@ final class StringPassingTests { private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static long call(java.lang.foreign.MemorySegment string) { try { - if (SwiftKit.TRACE_DOWNCALLS) { - SwiftKit.traceDowncall(string); + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(string); } return (long) HANDLE.invokeExact(string); } catch (Throwable ex$) { @@ -64,7 +64,7 @@ final class StringPassingTests { */ public static long writeString(java.lang.String string) { try(var arena$ = Arena.ofConfined()) { - return swiftjava___FakeModule_writeString_string.call(SwiftKit.toCString(string, arena$)); + return swiftjava___FakeModule_writeString_string.call(SwiftRuntime.toCString(string, arena$)); } } """ diff --git a/Tests/JExtractSwiftTests/VariableImportTests.swift b/Tests/JExtractSwiftTests/VariableImportTests.swift index 33f41e209..e0a6678c3 100644 --- a/Tests/JExtractSwiftTests/VariableImportTests.swift +++ b/Tests/JExtractSwiftTests/VariableImportTests.swift @@ -51,8 +51,8 @@ final class VariableImportTests { private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static long call(java.lang.foreign.MemorySegment self) { try { - if (SwiftKit.TRACE_DOWNCALLS) { - SwiftKit.traceDowncall(self); + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(self); } return (long) HANDLE.invokeExact(self); } catch (Throwable ex$) { @@ -84,8 +84,8 @@ final class VariableImportTests { private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static void call(long newValue, java.lang.foreign.MemorySegment self) { try { - if (SwiftKit.TRACE_DOWNCALLS) { - SwiftKit.traceDowncall(newValue, self); + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(newValue, self); } HANDLE.invokeExact(newValue, self); } catch (Throwable ex$) { diff --git a/settings.gradle b/settings.gradle index fa0fa5bd5..9bf38ba51 100644 --- a/settings.gradle +++ b/settings.gradle @@ -18,7 +18,8 @@ pluginManagement { rootProject.name = "swift-java" -include "SwiftKit" +include "SwiftKitCore" +include "SwiftKitFFM" // Include sample apps -- you can run them via `gradle Name:run` new File(rootDir, "Samples").listFiles().each { From d9699337581c6361188be9da27300eb8c50ba638 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Tue, 8 Jul 2025 11:03:20 -0700 Subject: [PATCH 022/141] [JExtract] Pass type metadata pointer to value witness functions Instead of the pointer to the value witness table. Witnesses expect type metadata. --- .../swiftkit/ffm/SwiftValueWitnessTable.java | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueWitnessTable.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueWitnessTable.java index a0a6a79f6..2b918523d 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueWitnessTable.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueWitnessTable.java @@ -185,8 +185,8 @@ private static class destroy { $LAYOUT.byteOffset(MemoryLayout.PathElement.groupElement("destroy")); static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( - ValueLayout.ADDRESS, // witness table functions expect a pointer to self pointer - ValueLayout.ADDRESS // pointer to the witness table + ValueLayout.ADDRESS, // pointer to self + ValueLayout.ADDRESS // pointer to the type metadata ); /** @@ -213,13 +213,9 @@ static MethodHandle handle(SwiftAnyType ty) { * This includes deallocating the Swift managed memory for the object. */ public static void destroy(SwiftAnyType type, MemorySegment object) { - var fullTypeMetadata = fullTypeMetadata(type.$memorySegment()); - var wtable = valueWitnessTable(fullTypeMetadata); - var mh = destroy.handle(type); - try { - mh.invokeExact(object, wtable); + mh.invokeExact(object, type.$memorySegment()); } catch (Throwable th) { throw new AssertionError("Failed to destroy '" + type + "' at " + object, th); } @@ -246,7 +242,7 @@ private static class initializeWithCopy { /* -> */ ValueLayout.ADDRESS, // returns the destination object ValueLayout.ADDRESS, // destination ValueLayout.ADDRESS, // source - ValueLayout.ADDRESS // pointer to the witness table + ValueLayout.ADDRESS // pointer to the type metadata ); /** @@ -274,13 +270,10 @@ static MethodHandle handle(SwiftAnyType ty) { * Returns the dest object. */ public static MemorySegment initializeWithCopy(SwiftAnyType type, MemorySegment dest, MemorySegment src) { - var fullTypeMetadata = fullTypeMetadata(type.$memorySegment()); - var wtable = valueWitnessTable(fullTypeMetadata); - var mh = initializeWithCopy.handle(type); try { - return (MemorySegment) mh.invokeExact(dest, src, wtable); + return (MemorySegment) mh.invokeExact(dest, src, type.$memorySegment()); } catch (Throwable th) { throw new AssertionError("Failed to initializeWithCopy '" + type + "' (" + dest + ", " + src + ")", th); } From c2f683e0c890a6044fc47c6677dad4d12d06aaa5 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Mon, 7 Jul 2025 10:08:18 -0700 Subject: [PATCH 023/141] [JExtract] Import 'Foundation.Data' if used * Introduce `KnownSwiftModules` to represent `Swift` and `Foundation` modules. * `SwiftSymbolTable.setup()` is now a factory method to create `SwiftSymbolTable`. It scans the source file for module dependency. If `import Foundation` is found, add `Foundation` from the `KnownSwiftModules` (Just `Data` for now) * In `Swift2JavaTranslator` import `Data` APIs if any of the imported APIs use `Data` * `Data` has limited hardcoded APIs `init(bytes:count:)`, `getter:count`, and `withUnsafeBytes(_:)`. * Add `import Foundation` in Swift thunk if any of the the original source files import it --- .../JExtractSwiftPlugin.swift | 7 + .../MySwiftLibrary/MySwiftLibrary.swift | 6 + .../com/example/swift/HelloJava2Swift.java | 18 + .../FFM/CDeclLowering/CRepresentation.swift | 4 +- ...Swift2JavaGenerator+FunctionLowering.swift | 28 +- ...MSwift2JavaGenerator+JavaTranslation.swift | 14 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 16 +- .../FFM/FFMSwift2JavaGenerator.swift | 5 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 8 +- .../JNI/JNISwift2JavaGenerator.swift | 3 + .../Swift2JavaTranslator.swift | 85 ++++- .../SwiftTypes/DependencyScanner.swift | 29 ++ .../SwiftTypes/SwiftKnownModules.swift | 94 +++++ .../SwiftTypes/SwiftKnownTypeDecls.swift | 61 ++++ .../SwiftTypes/SwiftKnownTypes.swift | 46 +-- .../SwiftNominalTypeDeclaration.swift | 19 +- .../SwiftParsedModuleSymbolTable.swift | 120 ------- .../SwiftParsedModuleSymbolTableBuilder.swift | 178 ++++++++++ .../SwiftStandardLibraryTypeDecls.swift | 198 ----------- .../SwiftTypes/SwiftSymbolTable.swift | 125 +++---- .../Asserts/LoweringAssertions.swift | 6 +- .../JExtractSwiftTests/DataImportTests.swift | 328 ++++++++++++++++++ .../SwiftSymbolTableTests.swift | 8 +- 23 files changed, 936 insertions(+), 470 deletions(-) create mode 100644 Sources/JExtractSwiftLib/SwiftTypes/DependencyScanner.swift create mode 100644 Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift create mode 100644 Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift delete mode 100644 Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTable.swift create mode 100644 Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift delete mode 100644 Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypeDecls.swift create mode 100644 Tests/JExtractSwiftTests/DataImportTests.swift diff --git a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift index 7bfb51cf0..83c5f0d9e 100644 --- a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift +++ b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift @@ -100,6 +100,13 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { outputSwiftDirectory.appending(path: "\(sourceModule.name)Module+SwiftJava.swift") ] + // If the module uses 'Data' type, the thunk file is emitted as if 'Data' is declared + // in that module. Declare the thunk file as the output. + // FIXME: Make this conditional. + outputSwiftFiles += [ + outputSwiftDirectory.appending(path: "Data+SwiftJava.swift") + ] + return [ .buildCommand( displayName: "Generate Java wrappers for Swift types", diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index 18b6546d7..ce949283d 100644 --- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -23,6 +23,8 @@ import Glibc import Darwin.C #endif +import Foundation + public func helloWorld() { p("\(#function)") } @@ -53,6 +55,10 @@ public func globalReceiveRawBuffer(buf: UnsafeRawBufferPointer) -> Int { public var globalBuffer: UnsafeRawBufferPointer = UnsafeRawBufferPointer(UnsafeMutableRawBufferPointer.allocate(byteCount: 124, alignment: 1)) +public func globalReceiveReturnData(data: Data) -> Data { + return Data(data) +} + public func withBuffer(body: (UnsafeRawBufferPointer) -> Void) { body(globalBuffer) } diff --git a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java index 668140b29..985820b18 100644 --- a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java +++ b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java @@ -22,6 +22,10 @@ import org.swift.swiftkit.ffm.AllocatingSwiftArena; import org.swift.swiftkit.ffm.SwiftRuntime; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; + public class HelloJava2Swift { public static void main(String[] args) { @@ -78,6 +82,20 @@ static void examples() { }); } + // Example of using 'Data'. + try (var arena = AllocatingSwiftArena.ofConfined()) { + var origBytes = arena.allocateFrom("foobar"); + var origDat = Data.init(origBytes, origBytes.byteSize(), arena); + SwiftRuntime.trace("origDat.count = " + origDat.getCount()); + + var retDat = MySwiftLibrary.globalReceiveReturnData(origDat, arena); + retDat.withUnsafeBytes((retBytes) -> { + var str = retBytes.getString(0); + SwiftRuntime.trace("retStr=" + str); + }); + } + + System.out.println("DONE."); } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift index eb27e5f60..9f4a9ac9b 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift @@ -102,7 +102,7 @@ enum CDeclToCLoweringError: Error { case invalidFunctionConvention(SwiftFunctionType) } -extension SwiftStandardLibraryTypeKind { +extension SwiftKnownTypeDeclKind { /// Determine the primitive C type that corresponds to this C standard /// library type, if there is one. var primitiveCType: CType? { @@ -125,7 +125,7 @@ extension SwiftStandardLibraryTypeKind { .qualified(const: true, volatile: false, type: .void) ) case .void: .void - case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string: + case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string, .data: nil } } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index db764e2eb..488bd4e77 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -29,7 +29,7 @@ extension FFMSwift2JavaGenerator { enclosingType: try enclosingType.map { try SwiftType($0, symbolTable: symbolTable) }, symbolTable: symbolTable ) - return try CdeclLowering(swiftStdlibTypes: swiftStdlibTypes).lowerFunctionSignature(signature) + return try CdeclLowering(symbolTable: symbolTable).lowerFunctionSignature(signature) } /// Lower the given initializer to a C-compatible entrypoint, @@ -46,7 +46,7 @@ extension FFMSwift2JavaGenerator { symbolTable: symbolTable ) - return try CdeclLowering(swiftStdlibTypes: swiftStdlibTypes).lowerFunctionSignature(signature) + return try CdeclLowering(symbolTable: symbolTable).lowerFunctionSignature(signature) } /// Lower the given variable decl to a C-compatible entrypoint, @@ -69,7 +69,7 @@ extension FFMSwift2JavaGenerator { enclosingType: try enclosingType.map { try SwiftType($0, symbolTable: symbolTable) }, symbolTable: symbolTable ) - return try CdeclLowering(swiftStdlibTypes: swiftStdlibTypes).lowerFunctionSignature(signature) + return try CdeclLowering(symbolTable: symbolTable).lowerFunctionSignature(signature) } } @@ -77,8 +77,8 @@ extension FFMSwift2JavaGenerator { struct CdeclLowering { var knownTypes: SwiftKnownTypes - init(swiftStdlibTypes: SwiftStandardLibraryTypeDecls) { - self.knownTypes = SwiftKnownTypes(decls: swiftStdlibTypes) + init(symbolTable: SwiftSymbolTable) { + self.knownTypes = SwiftKnownTypes(symbolTable: symbolTable) } /// Lower the given Swift function signature to a Swift @_cdecl function signature, @@ -266,6 +266,9 @@ struct CdeclLowering { ) } + case .data: + break + default: // Unreachable? Should be handled by `CType(cdeclType:)` lowering above. throw LoweringError.unhandledType(type) @@ -407,6 +410,9 @@ struct CdeclLowering { ]) ) + case .data: + break + default: throw LoweringError.unhandledType(type) } @@ -509,6 +515,9 @@ struct CdeclLowering { // Returning string is not supported at this point. throw LoweringError.unhandledType(type) + case .data: + break + default: // Unreachable? Should be handled by `CType(cdeclType:)` lowering above. throw LoweringError.unhandledType(type) @@ -663,8 +672,7 @@ extension LoweredFunctionSignature { package func cdeclThunk( cName: String, swiftAPIName: String, - as apiKind: SwiftAPIKind, - stdlibTypes: SwiftStandardLibraryTypeDecls + as apiKind: SwiftAPIKind ) -> FunctionDeclSyntax { let cdeclParams = allLoweredParameters.map(\.description).joined(separator: ", ") @@ -765,7 +773,7 @@ extension LoweredFunctionSignature { } enum LoweringError: Error { - case inoutNotSupported(SwiftType) - case unhandledType(SwiftType) - case effectNotSupported(SwiftEffectSpecifier) + case inoutNotSupported(SwiftType, file: String = #file, line: Int = #line) + case unhandledType(SwiftType, file: String = #file, line: Int = #line) + case effectNotSupported(SwiftEffectSpecifier, file: String = #file, line: Int = #line) } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index d29f6b962..fe1806a9e 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -24,7 +24,7 @@ extension FFMSwift2JavaGenerator { let translated: TranslatedFunctionDecl? do { - let translation = JavaTranslation(swiftStdlibTypes: self.swiftStdlibTypes) + let translation = JavaTranslation(symbolTable: self.symbolTable) translated = try translation.translate(decl) } catch { self.log.info("Failed to translate: '\(decl.swiftDecl.qualifiedNameForDebug)'; \(error)") @@ -113,12 +113,12 @@ extension FFMSwift2JavaGenerator { } struct JavaTranslation { - var swiftStdlibTypes: SwiftStandardLibraryTypeDecls + var symbolTable: SwiftSymbolTable func translate( _ decl: ImportedFunc ) throws -> TranslatedFunctionDecl { - let lowering = CdeclLowering(swiftStdlibTypes: self.swiftStdlibTypes) + let lowering = CdeclLowering(symbolTable: symbolTable) let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature) // Name. @@ -353,6 +353,9 @@ extension FFMSwift2JavaGenerator { conversion: .call(.placeholder, function: "SwiftRuntime.toCString", withArena: true) ) + case .data: + break + default: throw JavaTranslationError.unhandledType(swiftType) } @@ -438,6 +441,9 @@ extension FFMSwift2JavaGenerator { ) ) + case .data: + break + case .unsafePointer, .unsafeMutablePointer: // FIXME: Implement throw JavaTranslationError.unhandledType(swiftType) @@ -629,6 +635,6 @@ extension CType { } enum JavaTranslationError: Error { - case inoutNotSupported(SwiftType) + case inoutNotSupported(SwiftType, file: String = #file, line: Int = #line) case unhandledType(SwiftType, file: String = #file, line: Int = #line) } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift index d6b09b24f..d717255da 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -87,6 +87,8 @@ extension FFMSwift2JavaGenerator { """) + printSwiftThunkImports(&printer) + for thunk in stt.renderGlobalThunks() { printer.print(thunk) printer.println() @@ -114,11 +116,22 @@ extension FFMSwift2JavaGenerator { """ ) + printSwiftThunkImports(&printer) + for thunk in stt.renderThunks(forType: ty) { printer.print("\(thunk)") printer.print("") } } + + func printSwiftThunkImports(_ printer: inout CodePrinter) { + for module in self.symbolTable.importedModules.keys.sorted() { + guard module != "Swift" else { + continue + } + printer.print("import \(module)") + } + } } struct SwiftThunkTranslator { @@ -196,8 +209,7 @@ struct SwiftThunkTranslator { let thunkFunc = translated.loweredSignature.cdeclThunk( cName: thunkName, swiftAPIName: decl.name, - as: decl.apiKind, - stdlibTypes: st.swiftStdlibTypes + as: decl.apiKind ) return [DeclSyntax(thunkFunc)] } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index 9a140b94d..6cf80d9b8 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -24,7 +24,6 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { let javaPackage: String let swiftOutputDirectory: String let javaOutputDirectory: String - let swiftStdlibTypes: SwiftStandardLibraryTypeDecls let symbolTable: SwiftSymbolTable var javaPackagePath: String { @@ -53,7 +52,6 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { self.swiftOutputDirectory = swiftOutputDirectory self.javaOutputDirectory = javaOutputDirectory self.symbolTable = translator.symbolTable - self.swiftStdlibTypes = translator.swiftStdlibTypeDecls // If we are forced to write empty files, construct the expected outputs if translator.config.writeEmptyFiles ?? false { @@ -65,6 +63,9 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { return String(filePathPart.replacing(".swift", with: "+SwiftJava.swift")) }) self.expectedOutputSwiftFiles.insert("\(translator.swiftModuleName)Module+SwiftJava.swift") + + // FIXME: Can we avoid this? + self.expectedOutputSwiftFiles.insert("Data+SwiftJava.swift") } else { self.expectedOutputSwiftFiles = [] } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index a5df702c2..929a332c1 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -67,7 +67,7 @@ extension JNISwift2JavaGenerator { switch swiftType { case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownStandardLibraryType { - guard let javaType = translate(standardLibraryType: knownType) else { + guard let javaType = translate(knownType: knownType) else { fatalError("unsupported known type: \(knownType)") } return javaType @@ -83,8 +83,8 @@ extension JNISwift2JavaGenerator { } } - func translate(standardLibraryType: SwiftStandardLibraryTypeKind) -> JavaType? { - switch standardLibraryType { + func translate(knownType: SwiftKnownTypeDeclKind) -> JavaType? { + switch knownType { case .bool: .boolean case .int8: .byte case .uint16: .char @@ -101,6 +101,8 @@ extension JNISwift2JavaGenerator { .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer: nil + case .data: + fatalError("unimplemented") } } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index b242cf66a..0cc523b4f 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -58,6 +58,9 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { return String(filePathPart.replacing(".swift", with: "+SwiftJava.swift")) }) self.expectedOutputSwiftFiles.insert("\(translator.swiftModuleName)Module+SwiftJava.swift") + + // FIXME: Can we avoid this? + self.expectedOutputSwiftFiles.insert("Data+SwiftJava.swift") } else { self.expectedOutputSwiftFiles = [] } diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index 89deda9a6..82798c15b 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -27,6 +27,9 @@ public final class Swift2JavaTranslator { let config: Configuration + /// The name of the Swift module being translated. + let swiftModuleName: String + // ==== Input struct Input { @@ -46,14 +49,7 @@ public final class Swift2JavaTranslator { /// type representation. package var importedTypes: [String: ImportedNominalType] = [:] - package var swiftStdlibTypeDecls: SwiftStandardLibraryTypeDecls - - package let symbolTable: SwiftSymbolTable - - /// The name of the Swift module being translated. - var swiftModuleName: String { - symbolTable.moduleName - } + package var symbolTable: SwiftSymbolTable! = nil public init( config: Configuration @@ -62,12 +58,7 @@ public final class Swift2JavaTranslator { fatalError("Missing 'swiftModule' name.") // FIXME: can we make it required in config? but we shared config for many cases } self.config = config - self.symbolTable = SwiftSymbolTable(parsedModuleName: swiftModule) - - // Create a mock of the Swift standard library. - var parsedSwiftModule = SwiftParsedModuleSymbolTable(moduleName: "Swift") - self.swiftStdlibTypeDecls = SwiftStandardLibraryTypeDecls(into: &parsedSwiftModule) - self.symbolTable.importedModules.append(parsedSwiftModule.symbolTable) + self.swiftModuleName = swiftModule } } @@ -108,11 +99,69 @@ extension Swift2JavaTranslator { log.trace("Analyzing \(input.filePath)") visitor.visit(sourceFile: input.syntax) } + + // If any API uses 'Foundation.Data', import 'Data' as if it's declared in + // this module. + if let dataDecl = self.symbolTable[.data] { + if self.isUsing(dataDecl) { + visitor.visit(nominalDecl: dataDecl.syntax!.asNominal!, in: nil) + } + } } package func prepareForTranslation() { - /// Setup the symbol table. - symbolTable.setup(inputs.map({ $0.syntax })) + self.symbolTable = SwiftSymbolTable.setup( + moduleName: self.swiftModuleName, + inputs.map({ $0.syntax }), + log: self.log + ) + } + + /// Check if any of the imported decls uses the specified nominal declaration. + func isUsing(_ decl: SwiftNominalTypeDeclaration) -> Bool { + func check(_ type: SwiftType) -> Bool { + switch type { + case .nominal(let nominal): + return nominal.nominalTypeDecl == decl + case .optional(let ty): + return check(ty) + case .tuple(let tuple): + return tuple.contains(where: check) + case .function(let fn): + return check(fn.resultType) || fn.parameters.contains(where: { check($0.type) }) + case .metatype(let ty): + return check(ty) + } + } + + func check(_ fn: ImportedFunc) -> Bool { + if check(fn.functionSignature.result.type) { + return true + } + if fn.functionSignature.parameters.contains(where: { check($0.type) }) { + return true + } + return false + } + + if self.importedGlobalFuncs.contains(where: check) { + return true + } + if self.importedGlobalVariables.contains(where: check) { + return true + } + for importedType in self.importedTypes.values { + if importedType.initializers.contains(where: check) { + return true + } + if importedType.methods.contains(where: check) { + return true + } + if importedType.variables.contains(where: check) { + return true + } + } + return false } } @@ -146,10 +195,10 @@ extension Swift2JavaTranslator { } // Whether to import this extension? - guard let nominalNode = symbolTable.parsedModule.nominalTypeSyntaxNodes[swiftNominalDecl] else { + guard swiftNominalDecl.moduleName == self.swiftModuleName else { return nil } - guard nominalNode.shouldImport(log: log) else { + guard swiftNominalDecl.syntax!.shouldImport(log: log) else { return nil } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/DependencyScanner.swift b/Sources/JExtractSwiftLib/SwiftTypes/DependencyScanner.swift new file mode 100644 index 000000000..409c81a76 --- /dev/null +++ b/Sources/JExtractSwiftLib/SwiftTypes/DependencyScanner.swift @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftSyntax + +/// Scan importing modules. +func importingModuleNames(sourceFile: SourceFileSyntax) -> [String] { + var importingModuleNames: [String] = [] + for item in sourceFile.statements { + if let importDecl = item.item.as(ImportDeclSyntax.self) { + guard let moduleName = importDecl.path.first?.name.text else { + continue + } + importingModuleNames.append(moduleName) + } + } + return importingModuleNames +} diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift new file mode 100644 index 000000000..21862c169 --- /dev/null +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift @@ -0,0 +1,94 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftSyntax +import SwiftSyntaxBuilder + +enum SwiftKnownModule: String { + case swift = "Swift" + case foundation = "Foundation" + + var name: String { + return self.rawValue + } + + var symbolTable: SwiftModuleSymbolTable { + return switch self { + case .swift: swiftSymbolTable + case .foundation: foundationSymbolTable + } + } + + var sourceFile: SourceFileSyntax { + return switch self { + case .swift: swiftSourceFile + case .foundation: foundationSourceFile + } + } +} + +private var swiftSymbolTable: SwiftModuleSymbolTable { + var builder = SwiftParsedModuleSymbolTableBuilder(moduleName: "Swift", importedModules: [:]) + builder.handle(sourceFile: swiftSourceFile) + return builder.finalize() +} + +private var foundationSymbolTable: SwiftModuleSymbolTable { + var builder = SwiftParsedModuleSymbolTableBuilder(moduleName: "Foundation", importedModules: ["Swift": swiftSymbolTable]) + builder.handle(sourceFile: foundationSourceFile) + return builder.finalize() +} + +private let swiftSourceFile: SourceFileSyntax = """ + public struct Bool {} + public struct Int {} + public struct UInt {} + public struct Int8 {} + public struct UInt8 {} + public struct Int16 {} + public struct UInt16 {} + public struct Int32 {} + public struct UInt32 {} + public struct Int64 {} + public struct UInt64 {} + public struct Float {} + public struct Double {} + + public struct UnsafeRawPointer {} + public struct UnsafeMutableRawPointer {} + public struct UnsafeRawBufferPointer {} + public struct UnsafeMutableRawBufferPointer {} + + public struct UnsafePointer {} + public struct UnsafeMutablePointer {} + + public struct UnsafeBufferPointer {} + public struct UnsafeMutableBufferPointer {} + + // FIXME: Support 'typealias Void = ()' + public struct Void {} + + public struct String { + public init(cString: UnsafePointer) + public func withCString(_ body: (UnsafePointer) -> Void) + } + """ + +private let foundationSourceFile: SourceFileSyntax = """ + public struct Data { + public init(bytes: UnsafeRawPointer, count: Int) + public var count: Int { get } + public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) -> Void) + } + """ diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift new file mode 100644 index 000000000..0ac915c74 --- /dev/null +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift @@ -0,0 +1,61 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftSyntax + +enum SwiftKnownTypeDeclKind: String, Hashable { + case bool = "Swift.Bool" + case int = "Swift.Int" + case uint = "Swift.UInt" + case int8 = "Swift.Int8" + case uint8 = "Swift.UInt8" + case int16 = "Swift.Int16" + case uint16 = "Swift.UInt16" + case int32 = "Swift.Int32" + case uint32 = "Swift.UInt32" + case int64 = "Swift.Int64" + case uint64 = "Swift.UInt64" + case float = "Swift.Float" + case double = "Swift.Double" + case unsafeRawPointer = "Swift.UnsafeRawPointer" + case unsafeMutableRawPointer = "Swift.UnsafeMutableRawPointer" + case unsafeRawBufferPointer = "Swift.UnsafeRawBufferPointer" + case unsafeMutableRawBufferPointer = "Swift.UnsafeMutableRawBufferPointer" + case unsafePointer = "Swift.UnsafePointer" + case unsafeMutablePointer = "Swift.UnsafeMutablePointer" + case unsafeBufferPointer = "Swift.UnsafeBufferPointer" + case unsafeMutableBufferPointer = "Swift.UnsafeMutableBufferPointer" + case void = "Swift.Void" + case string = "Swift.String" + + case data = "Foundation.Data" + + var moduleAndName: (module: String, name: String) { + let qualified = self.rawValue + let period = qualified.firstIndex(of: ".")! + return ( + module: String(qualified[.. SwiftType { .nominal( SwiftNominalType( - nominalTypeDecl: decls.unsafePointerDecl, + nominalTypeDecl: symbolTable[.unsafePointer], genericArguments: [pointeeType] ) ) @@ -47,7 +47,7 @@ struct SwiftKnownTypes { func unsafeMutablePointer(_ pointeeType: SwiftType) -> SwiftType { .nominal( SwiftNominalType( - nominalTypeDecl: decls.unsafeMutablePointerDecl, + nominalTypeDecl: symbolTable[.unsafeMutablePointer], genericArguments: [pointeeType] ) ) @@ -56,7 +56,7 @@ struct SwiftKnownTypes { func unsafeBufferPointer(_ elementType: SwiftType) -> SwiftType { .nominal( SwiftNominalType( - nominalTypeDecl: decls.unsafeBufferPointerDecl, + nominalTypeDecl: symbolTable[.unsafeBufferPointer], genericArguments: [elementType] ) ) @@ -65,7 +65,7 @@ struct SwiftKnownTypes { func unsafeMutableBufferPointer(_ elementType: SwiftType) -> SwiftType { .nominal( SwiftNominalType( - nominalTypeDecl: decls.unsafeMutableBufferPointerDecl, + nominalTypeDecl: symbolTable[.unsafeMutableBufferPointer], genericArguments: [elementType] ) ) diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift index 29a287fec..b63f930c2 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -31,28 +31,28 @@ package class SwiftNominalTypeDeclaration { /// The syntax node this declaration is derived from. /// Can be `nil` if this is loaded from a .swiftmodule. - var syntax: NominalTypeDeclSyntaxNode? + let syntax: NominalTypeDeclSyntaxNode? /// The kind of nominal type. - var kind: Kind + let kind: Kind /// The parent nominal type when this nominal type is nested inside another type, e.g., /// MyCollection.Iterator. - var parent: SwiftNominalTypeDeclaration? + let parent: SwiftNominalTypeDeclaration? /// The module in which this nominal type is defined. If this is a nested type, the /// module might be different from that of the parent type, if this nominal type /// is defined in an extension within another module. - var moduleName: String + let moduleName: String /// The name of this nominal type, e.g., 'MyCollection'. - var name: String + let name: String // TODO: Generic parameters. /// Identify this nominal declaration as one of the known standard library /// types, like 'Swift.Int[. - lazy var knownStandardLibraryType: SwiftStandardLibraryTypeKind? = { + lazy var knownStandardLibraryType: SwiftKnownTypeDeclKind? = { self.computeKnownStandardLibraryType() }() @@ -66,6 +66,7 @@ package class SwiftNominalTypeDeclaration { self.moduleName = moduleName self.parent = parent self.name = node.name.text + self.syntax = node // Determine the kind from the syntax node. switch Syntax(node).as(SyntaxEnum.self) { @@ -80,12 +81,12 @@ package class SwiftNominalTypeDeclaration { /// Determine the known standard library type for this nominal type /// declaration. - private func computeKnownStandardLibraryType() -> SwiftStandardLibraryTypeKind? { - if parent != nil || moduleName != "Swift" { + private func computeKnownStandardLibraryType() -> SwiftKnownTypeDeclKind? { + if parent != nil { return nil } - return SwiftStandardLibraryTypeKind(typeNameInSwiftModule: name) + return SwiftKnownTypeDeclKind(rawValue: "\(moduleName).\(name)") } package var qualifiedName: String { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTable.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTable.swift deleted file mode 100644 index 1eb37eac3..000000000 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTable.swift +++ /dev/null @@ -1,120 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2025 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import SwiftSyntax - -struct SwiftParsedModuleSymbolTable { - var symbolTable: SwiftModuleSymbolTable - - /// The nominal type declarations, indexed by the nominal type declaration syntax node. - var nominalTypeDeclarations: [SyntaxIdentifier: SwiftNominalTypeDeclaration] = [:] - - /// Mapping from the nominal type declarations in this module back to the syntax - /// node. This is the reverse mapping of 'nominalTypeDeclarations'. - var nominalTypeSyntaxNodes: [SwiftNominalTypeDeclaration: NominalTypeDeclSyntaxNode] = [:] - - init(moduleName: String) { - symbolTable = .init(moduleName: moduleName) - } -} - -extension SwiftParsedModuleSymbolTable: SwiftSymbolTableProtocol { - var moduleName: String { - symbolTable.moduleName - } - - func lookupTopLevelNominalType(_ name: String) -> SwiftNominalTypeDeclaration? { - symbolTable.lookupTopLevelNominalType(name) - } - - func lookupNestedType(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftNominalTypeDeclaration? { - symbolTable.lookupNestedType(name, parent: parent) - } -} - -extension SwiftParsedModuleSymbolTable { - /// Look up a nominal type declaration based on its syntax node. - func lookup(_ node: NominalTypeDeclSyntaxNode) -> SwiftNominalTypeDeclaration? { - nominalTypeDeclarations[node.id] - } - - /// Add a nominal type declaration and all of the nested types within it to the symbol - /// table. - @discardableResult - mutating func addNominalTypeDeclaration( - _ node: NominalTypeDeclSyntaxNode, - parent: SwiftNominalTypeDeclaration? - ) -> SwiftNominalTypeDeclaration { - // If we have already recorded this nominal type declaration, we're done. - if let existingNominal = nominalTypeDeclarations[node.id] { - return existingNominal - } - - // Otherwise, create the nominal type declaration. - let nominalTypeDecl = SwiftNominalTypeDeclaration( - moduleName: moduleName, - parent: parent, - node: node - ) - - // Ensure that we can find this nominal type declaration again based on the syntax - // node, and vice versa. - nominalTypeDeclarations[node.id] = nominalTypeDecl - nominalTypeSyntaxNodes[nominalTypeDecl] = node - - if let parent { - // For nested types, make them discoverable from the parent type. - symbolTable.nestedTypes[parent, default: [:]][node.name.text] = nominalTypeDecl - } else { - // For top-level types, make them discoverable by name. - symbolTable.topLevelTypes[node.name.text] = nominalTypeDecl - } - - // Find any nested types within this nominal type and add them. - for member in node.memberBlock.members { - if let nominalMember = member.decl.asNominal { - addNominalTypeDeclaration(nominalMember, parent: nominalTypeDecl) - } - } - - return nominalTypeDecl - } - - /// Add any nested types within the given extension (with known extended nominal type - /// declaration) to the symbol table. - mutating func addExtension( - _ extensionNode: ExtensionDeclSyntax, - extending nominalTypeDecl: SwiftNominalTypeDeclaration - ) { - // Find any nested types within this extension and add them. - for member in extensionNode.memberBlock.members { - if let nominalMember = member.decl.asNominal { - addNominalTypeDeclaration(nominalMember, parent: nominalTypeDecl) - } - } - } -} - -extension DeclSyntaxProtocol { - var asNominal: NominalTypeDeclSyntaxNode? { - switch DeclSyntax(self).as(DeclSyntaxEnum.self) { - case .actorDecl(let actorDecl): actorDecl - case .classDecl(let classDecl): classDecl - case .enumDecl(let enumDecl): enumDecl - case .protocolDecl(let protocolDecl): protocolDecl - case .structDecl(let structDecl): structDecl - default: nil - } - } -} diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift new file mode 100644 index 000000000..4026e624a --- /dev/null +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift @@ -0,0 +1,178 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftSyntax + +struct SwiftParsedModuleSymbolTableBuilder { + let log: Logger? + + /// The symbol table being built. + var symbolTable: SwiftModuleSymbolTable + + /// Imported modules to resolve type syntax. + let importedModules: [String: SwiftModuleSymbolTable] + + /// Extension decls their extended type hasn't been resolved. + var unresolvedExtensions: [ExtensionDeclSyntax] + + init(moduleName: String, importedModules: [String: SwiftModuleSymbolTable], log: Logger? = nil) { + self.log = log + self.symbolTable = .init(moduleName: moduleName) + self.importedModules = importedModules + self.unresolvedExtensions = [] + } + + var moduleName: String { + symbolTable.moduleName + } +} + +extension SwiftParsedModuleSymbolTableBuilder { + + mutating func handle( + sourceFile: SourceFileSyntax + ) { + // Find top-level type declarations. + for statement in sourceFile.statements { + // We only care about declarations. + guard case .decl(let decl) = statement.item else { + continue + } + + if let nominalTypeNode = decl.asNominal { + self.handle(nominalTypeDecl: nominalTypeNode, parent: nil) + } + if let extensionNode = decl.as(ExtensionDeclSyntax.self) { + self.handle(extensionDecl: extensionNode) + } + } + } + + /// Add a nominal type declaration and all of the nested types within it to the symbol + /// table. + mutating func handle( + nominalTypeDecl node: NominalTypeDeclSyntaxNode, + parent: SwiftNominalTypeDeclaration? + ) { + // If we have already recorded a nominal type with the name in this module, + // it's an invalid redeclaration. + if let _ = symbolTable.lookupType(node.name.text, parent: parent) { + log?.debug("Failed to add a decl into symbol table: redeclaration; " + node.nameForDebug) + return + } + + // Otherwise, create the nominal type declaration. + let nominalTypeDecl = SwiftNominalTypeDeclaration( + moduleName: moduleName, + parent: parent, + node: node + ) + + if let parent { + // For nested types, make them discoverable from the parent type. + symbolTable.nestedTypes[parent, default: [:]][nominalTypeDecl.name] = nominalTypeDecl + } else { + // For top-level types, make them discoverable by name. + symbolTable.topLevelTypes[nominalTypeDecl.name] = nominalTypeDecl + } + + self.handle(memberBlock: node.memberBlock, parent: nominalTypeDecl) + } + + mutating func handle( + memberBlock node: MemberBlockSyntax, + parent: SwiftNominalTypeDeclaration + ) { + for member in node.members { + // Find any nested types within this nominal type and add them. + if let nominalMember = member.decl.asNominal { + self.handle(nominalTypeDecl: nominalMember, parent: parent) + } + } + + } + + mutating func handle( + extensionDecl node: ExtensionDeclSyntax + ) { + if !self.tryHandle(extension: node) { + self.unresolvedExtensions.append(node) + } + } + + /// Add any nested types within the given extension to the symbol table. + /// If the extended nominal type can't be resolved, returns false. + mutating func tryHandle( + extension node: ExtensionDeclSyntax + ) -> Bool { + // Try to resolve the type referenced by this extension declaration. + // If it fails, we'll try again later. + let table = SwiftSymbolTable( + parsedModule: symbolTable, + importedModules: importedModules + ) + guard let extendedType = try? SwiftType(node.extendedType, symbolTable: table) else { + return false + } + guard let extendedNominal = extendedType.asNominalTypeDeclaration else { + // Extending type was not a nominal type. Ignore it. + return true + } + + // Find any nested types within this extension and add them. + self.handle(memberBlock: node.memberBlock, parent: extendedNominal) + return true + } + + /// Finalize the symbol table and return it. + mutating func finalize() -> SwiftModuleSymbolTable { + // Handle the unresolved extensions. + // The work queue is required because, the extending type might be declared + // in another extension that hasn't been processed. E.g.: + // + // extension Outer.Inner { struct Deeper {} } + // extension Outer { struct Inner {} } + // struct Outer {} + // + while !unresolvedExtensions.isEmpty { + var extensions = self.unresolvedExtensions + extensions.removeAll(where: { + self.tryHandle(extension: $0) + }) + + // If we didn't resolve anything, we're done. + if extensions.count == unresolvedExtensions.count { + break + } + + assert(extensions.count < unresolvedExtensions.count) + self.unresolvedExtensions = extensions + } + + return symbolTable + } +} + +extension DeclSyntaxProtocol { + var asNominal: NominalTypeDeclSyntaxNode? { + switch DeclSyntax(self).as(DeclSyntaxEnum.self) { + case .actorDecl(let actorDecl): actorDecl + case .classDecl(let classDecl): classDecl + case .enumDecl(let enumDecl): enumDecl + case .protocolDecl(let protocolDecl): protocolDecl + case .structDecl(let structDecl): structDecl + default: nil + } + } +} diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypeDecls.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypeDecls.swift deleted file mode 100644 index 5678b2bb6..000000000 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypeDecls.swift +++ /dev/null @@ -1,198 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2025 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import SwiftSyntax - -enum SwiftStandardLibraryTypeKind: String, Hashable, CaseIterable { - case bool = "Bool" - case int = "Int" - case uint = "UInt" - case int8 = "Int8" - case uint8 = "UInt8" - case int16 = "Int16" - case uint16 = "UInt16" - case int32 = "Int32" - case uint32 = "UInt32" - case int64 = "Int64" - case uint64 = "UInt64" - case float = "Float" - case double = "Double" - case unsafeRawPointer = "UnsafeRawPointer" - case unsafeMutableRawPointer = "UnsafeMutableRawPointer" - case unsafeRawBufferPointer = "UnsafeRawBufferPointer" - case unsafeMutableRawBufferPointer = "UnsafeMutableRawBufferPointer" - case unsafePointer = "UnsafePointer" - case unsafeMutablePointer = "UnsafeMutablePointer" - case unsafeBufferPointer = "UnsafeBufferPointer" - case unsafeMutableBufferPointer = "UnsafeMutableBufferPointer" - case string = "String" - case void = "Void" - - var typeName: String { rawValue } - - init?(typeNameInSwiftModule: String) { - self.init(rawValue: typeNameInSwiftModule) - } - - /// Whether this declaration is generic. - var isGeneric: Bool { - switch self { - case .bool, .double, .float, .int, .int8, .int16, .int32, .int64, - .uint, .uint8, .uint16, .uint32, .uint64, .unsafeRawPointer, - .unsafeMutableRawPointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, - .string, .void: - false - - case .unsafePointer, .unsafeMutablePointer, .unsafeBufferPointer, - .unsafeMutableBufferPointer: - true - } - } - - var isPointer: Bool { - switch self { - case .unsafePointer, .unsafeMutablePointer, .unsafeRawPointer, .unsafeMutableRawPointer: - return true - default: - return false - } - } -} - -/// Captures many types from the Swift standard library in their most basic -/// forms, so that the translator can reason about them in source code. -public struct SwiftStandardLibraryTypeDecls { - // Swift.UnsafePointer - let unsafePointerDecl: SwiftNominalTypeDeclaration - - // Swift.UnsafeMutablePointer - let unsafeMutablePointerDecl: SwiftNominalTypeDeclaration - - // Swift.UnsafeBufferPointer - let unsafeBufferPointerDecl: SwiftNominalTypeDeclaration - - // Swift.UnsafeMutableBufferPointer - let unsafeMutableBufferPointerDecl: SwiftNominalTypeDeclaration - - /// Mapping from known standard library types to their nominal type declaration. - let knownTypeToNominal: [SwiftStandardLibraryTypeKind: SwiftNominalTypeDeclaration] - - /// Mapping from nominal type declarations to known types. - let nominalTypeDeclToKnownType: [SwiftNominalTypeDeclaration: SwiftStandardLibraryTypeKind] - - private static func recordKnownType( - _ type: SwiftStandardLibraryTypeKind, - _ syntax: NominalTypeDeclSyntaxNode, - knownTypeToNominal: inout [SwiftStandardLibraryTypeKind: SwiftNominalTypeDeclaration], - nominalTypeDeclToKnownType: inout [SwiftNominalTypeDeclaration: SwiftStandardLibraryTypeKind], - parsedModule: inout SwiftParsedModuleSymbolTable - ) { - let nominalDecl = parsedModule.addNominalTypeDeclaration(syntax, parent: nil) - knownTypeToNominal[type] = nominalDecl - nominalTypeDeclToKnownType[nominalDecl] = type - } - - private static func recordKnownNonGenericStruct( - _ type: SwiftStandardLibraryTypeKind, - knownTypeToNominal: inout [SwiftStandardLibraryTypeKind: SwiftNominalTypeDeclaration], - nominalTypeDeclToKnownType: inout [SwiftNominalTypeDeclaration: SwiftStandardLibraryTypeKind], - parsedModule: inout SwiftParsedModuleSymbolTable - ) { - recordKnownType( - type, - StructDeclSyntax( - name: .identifier(type.typeName), - memberBlock: .init(members: []) - ), - knownTypeToNominal: &knownTypeToNominal, - nominalTypeDeclToKnownType: &nominalTypeDeclToKnownType, - parsedModule: &parsedModule - ) - } - - init(into parsedModule: inout SwiftParsedModuleSymbolTable) { - // Pointer types - self.unsafePointerDecl = parsedModule.addNominalTypeDeclaration( - StructDeclSyntax( - name: .identifier("UnsafePointer"), - genericParameterClause: .init( - parameters: [GenericParameterSyntax(name: .identifier("Element"))] - ), - memberBlock: .init(members: []) - ), - parent: nil - ) - - self.unsafeMutablePointerDecl = parsedModule.addNominalTypeDeclaration( - StructDeclSyntax( - name: .identifier("UnsafeMutablePointer"), - genericParameterClause: .init( - parameters: [GenericParameterSyntax(name: .identifier("Element"))] - ), - memberBlock: .init(members: []) - ), - parent: nil - ) - - self.unsafeBufferPointerDecl = parsedModule.addNominalTypeDeclaration( - StructDeclSyntax( - name: .identifier("UnsafeBufferPointer"), - genericParameterClause: .init( - parameters: [GenericParameterSyntax(name: .identifier("Element"))] - ), - memberBlock: .init(members: []) - ), - parent: nil - ) - - self.unsafeMutableBufferPointerDecl = parsedModule.addNominalTypeDeclaration( - StructDeclSyntax( - name: .identifier("UnsafeMutableBufferPointer"), - genericParameterClause: .init( - parameters: [GenericParameterSyntax(name: .identifier("Element"))] - ), - memberBlock: .init(members: []) - ), - parent: nil - ) - - var knownTypeToNominal: [SwiftStandardLibraryTypeKind: SwiftNominalTypeDeclaration] = [:] - var nominalTypeDeclToKnownType: [SwiftNominalTypeDeclaration: SwiftStandardLibraryTypeKind] = [:] - - // Handle all of the non-generic types at once. - for knownType in SwiftStandardLibraryTypeKind.allCases { - guard !knownType.isGeneric else { - continue - } - - Self.recordKnownNonGenericStruct( - knownType, - knownTypeToNominal: &knownTypeToNominal, - nominalTypeDeclToKnownType: &nominalTypeDeclToKnownType, - parsedModule: &parsedModule - ) - } - - self.knownTypeToNominal = knownTypeToNominal - self.nominalTypeDeclToKnownType = nominalTypeDeclToKnownType - } - - subscript(knownType: SwiftStandardLibraryTypeKind) -> SwiftNominalTypeDeclaration { - knownTypeToNominal[knownType]! - } - - subscript(nominalType: SwiftNominalTypeDeclaration) -> SwiftStandardLibraryTypeKind? { - nominalTypeDeclToKnownType[nominalType] - } -} diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift index 87c2c80f8..239d38c4d 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift @@ -38,89 +38,50 @@ extension SwiftSymbolTableProtocol { } package class SwiftSymbolTable { - var importedModules: [SwiftModuleSymbolTable] = [] - var parsedModule: SwiftParsedModuleSymbolTable + let importedModules: [String: SwiftModuleSymbolTable] + let parsedModule:SwiftModuleSymbolTable - package init(parsedModuleName: String) { - self.parsedModule = SwiftParsedModuleSymbolTable(moduleName: parsedModuleName) - } + private var knownTypeToNominal: [SwiftKnownTypeDeclKind: SwiftNominalTypeDeclaration] = [:] - func addImportedModule(symbolTable: SwiftModuleSymbolTable) { - importedModules.append(symbolTable) + init(parsedModule: SwiftModuleSymbolTable, importedModules: [String: SwiftModuleSymbolTable]) { + self.parsedModule = parsedModule + self.importedModules = importedModules } } extension SwiftSymbolTable { - package func setup(_ sourceFiles: some Collection) { - // First, register top-level and nested nominal types to the symbol table. + package static func setup( + moduleName: String, + _ sourceFiles: some Collection, + log: Logger + ) -> SwiftSymbolTable { + + // Prepare imported modules. + // FIXME: Support arbitrary dependencies. + var moduleNames: Set = [] for sourceFile in sourceFiles { - self.addNominalTypeDeclarations(sourceFile) - } - - // Next bind the extensions. - - // The work queue is required because, the extending type might be declared - // in another extension that hasn't been processed. E.g.: - // - // extension Outer.Inner { struct Deeper {} } - // extension Outer { struct Inner {} } - // struct Outer {} - // - var unresolvedExtensions: [ExtensionDeclSyntax] = [] - for sourceFile in sourceFiles { - // Find extensions. - for statement in sourceFile.statements { - // We only care about extensions at top-level. - if case .decl(let decl) = statement.item, let extNode = decl.as(ExtensionDeclSyntax.self) { - let resolved = handleExtension(extNode) - if !resolved { - unresolvedExtensions.append(extNode) - } - } - } + moduleNames.formUnion(importingModuleNames(sourceFile: sourceFile)) } - - while !unresolvedExtensions.isEmpty { - let numExtensionsBefore = unresolvedExtensions.count - unresolvedExtensions.removeAll(where: handleExtension(_:)) - - // If we didn't resolve anything, we're done. - if numExtensionsBefore == unresolvedExtensions.count { - break + var importedModules: [String: SwiftModuleSymbolTable] = [:] + importedModules[SwiftKnownModule.swift.name] = SwiftKnownModule.swift.symbolTable + for moduleName in moduleNames.sorted() { + if + importedModules[moduleName] == nil, + let knownModule = SwiftKnownModule(rawValue: moduleName) + { + importedModules[moduleName] = knownModule.symbolTable } - assert(numExtensionsBefore > unresolvedExtensions.count) } - } - private func addNominalTypeDeclarations(_ sourceFile: SourceFileSyntax) { - // Find top-level nominal type declarations. - for statement in sourceFile.statements { - // We only care about declarations. - guard case .decl(let decl) = statement.item, - let nominalTypeNode = decl.asNominal else { - continue - } - - parsedModule.addNominalTypeDeclaration(nominalTypeNode, parent: nil) - } - } + // FIXME: Support granular lookup context (file, type context). - private func handleExtension(_ extensionDecl: ExtensionDeclSyntax) -> Bool { - // Try to resolve the type referenced by this extension declaration. - // If it fails, we'll try again later. - guard let extendedType = try? SwiftType(extensionDecl.extendedType, symbolTable: self) else { - return false - } - guard let extendedNominal = extendedType.asNominalTypeDeclaration else { - // Extending type was not a nominal type. Ignore it. - return true + var builder = SwiftParsedModuleSymbolTableBuilder(moduleName: moduleName, importedModules: importedModules, log: log) + // First, register top-level and nested nominal types to the symbol table. + for sourceFile in sourceFiles { + builder.handle(sourceFile: sourceFile) } - - // Register nested nominals in extensions to the symbol table. - parsedModule.addExtension(extensionDecl, extending: extendedNominal) - - // We have successfully resolved the extended type. Record it. - return true + let parsedModule = builder.finalize() + return SwiftSymbolTable(parsedModule: parsedModule, importedModules: importedModules) } } @@ -134,12 +95,14 @@ extension SwiftSymbolTable: SwiftSymbolTableProtocol { return parsedResult } - for importedModule in importedModules { + for importedModule in importedModules.values { if let result = importedModule.lookupTopLevelNominalType(name) { return result } } + // FIXME: Implement module qualified name lookups. E.g. 'Swift.String' + return nil } @@ -149,7 +112,7 @@ extension SwiftSymbolTable: SwiftSymbolTableProtocol { return parsedResult } - for importedModule in importedModules { + for importedModule in importedModules.values { if let result = importedModule.lookupNestedType(name, parent: parent) { return result } @@ -158,3 +121,21 @@ extension SwiftSymbolTable: SwiftSymbolTableProtocol { return nil } } + +extension SwiftSymbolTable { + /// Map 'SwiftKnownTypeDeclKind' to the declaration. + subscript(knownType: SwiftKnownTypeDeclKind) -> SwiftNominalTypeDeclaration! { + if let known = knownTypeToNominal[knownType] { + return known + } + + let (module, name) = knownType.moduleAndName + guard let moduleTable = importedModules[module] else { + return nil + } + + let found = moduleTable.lookupTopLevelNominalType(name) + knownTypeToNominal[knownType] = found + return found + } +} diff --git a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift index 053236303..eebfdf4a9 100644 --- a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift @@ -73,8 +73,7 @@ func assertLoweredFunction( let loweredCDecl = loweredFunction.cdeclThunk( cName: "c_\(swiftFunctionName)", swiftAPIName: swiftFunctionName, - as: apiKind, - stdlibTypes: translator.swiftStdlibTypeDecls + as: apiKind ) #expect( @@ -141,8 +140,7 @@ func assertLoweredVariableAccessor( let loweredCDecl = loweredFunction?.cdeclThunk( cName: "c_\(swiftVariableName)", swiftAPIName: swiftVariableName, - as: isSet ? .setter : .getter, - stdlibTypes: translator.swiftStdlibTypeDecls + as: isSet ? .setter : .getter ) #expect( diff --git a/Tests/JExtractSwiftTests/DataImportTests.swift b/Tests/JExtractSwiftTests/DataImportTests.swift new file mode 100644 index 000000000..75613407d --- /dev/null +++ b/Tests/JExtractSwiftTests/DataImportTests.swift @@ -0,0 +1,328 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import Testing + +final class DataImportTests { + let class_interfaceFile = + """ + import Foundation + + public func receiveData(dat: Data) + public func returnData() -> Data + """ + + @Test("Import Data: Swift thunks") + func swiftThunk() throws { + + try assertOutput( + input: class_interfaceFile, .ffm, .swift, + expectedChunks: [ + """ + import Foundation + """, + """ + @_cdecl("swiftjava_SwiftModule_receiveData_dat") + public func swiftjava_SwiftModule_receiveData_dat(_ dat: UnsafeRawPointer) { + receiveData(dat: dat.assumingMemoryBound(to: Data.self).pointee) + } + """, + """ + @_cdecl("swiftjava_SwiftModule_returnData") + public func swiftjava_SwiftModule_returnData(_ _result: UnsafeMutableRawPointer) { + _result.assumingMemoryBound(to: Data.self).initialize(to: returnData()) + } + """, + + """ + @_cdecl("swiftjava_getType_SwiftModule_Data") + public func swiftjava_getType_SwiftModule_Data() -> UnsafeMutableRawPointer /* Any.Type */ { + return unsafeBitCast(Data.self, to: UnsafeMutableRawPointer.self) + } + """, + + """ + @_cdecl("swiftjava_SwiftModule_Data_init_bytes_count") + public func swiftjava_SwiftModule_Data_init_bytes_count(_ bytes: UnsafeRawPointer, _ count: Int, _ _result: UnsafeMutableRawPointer) { + _result.assumingMemoryBound(to: Data.self).initialize(to: Data(bytes: bytes, count: count)) + } + """, + + """ + @_cdecl("swiftjava_SwiftModule_Data_count$get") + public func swiftjava_SwiftModule_Data_count$get(_ self: UnsafeRawPointer) -> Int { + return self.assumingMemoryBound(to: Data.self).pointee.count + } + """, + + """ + @_cdecl("swiftjava_SwiftModule_Data_withUnsafeBytes__") + public func swiftjava_SwiftModule_Data_withUnsafeBytes__(_ body: @convention(c) (UnsafeRawPointer?, Int) -> Void, _ self: UnsafeRawPointer) { + self.assumingMemoryBound(to: Data.self).pointee.withUnsafeBytes({ (_0) in + return body(_0.baseAddress, _0.count) + }) + } + """, + ] + ) + } + + @Test("Import Data: JavaBindings") + func javaBindings() throws { + + try assertOutput( + input: class_interfaceFile, .ffm, .java, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_receiveData_dat(const void *dat) + * } + */ + private static class swiftjava_SwiftModule_receiveData_dat { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* dat: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + SwiftModule.findOrThrow("swiftjava_SwiftModule_receiveData_dat"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static void call(java.lang.foreign.MemorySegment dat) { + try { + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(dat); + } + HANDLE.invokeExact(dat); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + } + """, + + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func receiveData(dat: Data) + * } + */ + public static void receiveData(Data dat) { + swiftjava_SwiftModule_receiveData_dat.call(dat.$memorySegment()); + } + """, + + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_returnData(void *_result) + * } + */ + private static class swiftjava_SwiftModule_returnData { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* _result: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + SwiftModule.findOrThrow("swiftjava_SwiftModule_returnData"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static void call(java.lang.foreign.MemorySegment _result) { + try { + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(_result); + } + HANDLE.invokeExact(_result); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + } + """, + + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func returnData() -> Data + * } + */ + public static Data returnData(AllocatingSwiftArena swiftArena$) { + MemorySegment _result = swiftArena$.allocate(Data.$LAYOUT); + swiftjava_SwiftModule_returnData.call(_result); + return new Data(_result, swiftArena$); + } + """, + + + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_Data_init_bytes_count(const void *bytes, ptrdiff_t count, void *_result) + * } + */ + private static class swiftjava_SwiftModule_Data_init_bytes_count { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* bytes: */SwiftValueLayout.SWIFT_POINTER, + /* count: */SwiftValueLayout.SWIFT_INT, + /* _result: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + SwiftModule.findOrThrow("swiftjava_SwiftModule_Data_init_bytes_count"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static void call(java.lang.foreign.MemorySegment bytes, long count, java.lang.foreign.MemorySegment _result) { + try { + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(bytes, count, _result); + } + HANDLE.invokeExact(bytes, count, _result); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + } + """, + + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public init(bytes: UnsafeRawPointer, count: Int) + * } + */ + public static Data init(java.lang.foreign.MemorySegment bytes, long count, AllocatingSwiftArena swiftArena$) { + MemorySegment _result = swiftArena$.allocate(Data.$LAYOUT); + swiftjava_SwiftModule_Data_init_bytes_count.call(bytes, count, _result); + return new Data(_result, swiftArena$); + } + """, + + """ + /** + * {@snippet lang=c : + * ptrdiff_t swiftjava_SwiftModule_Data_count$get(const void *self) + * } + */ + private static class swiftjava_SwiftModule_Data_count$get { + private static final FunctionDescriptor DESC = FunctionDescriptor.of( + /* -> */SwiftValueLayout.SWIFT_INT, + /* self: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + SwiftModule.findOrThrow("swiftjava_SwiftModule_Data_count$get"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static long call(java.lang.foreign.MemorySegment self) { + try { + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(self); + } + return (long) HANDLE.invokeExact(self); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + } + """, + + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var count: Int + * } + */ + public long getCount() { + $ensureAlive(); + return swiftjava_SwiftModule_Data_count$get.call(this.$memorySegment()); + } + """, + + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_Data_withUnsafeBytes__(void (*body)(const void *, ptrdiff_t), const void *self) + * } + */ + private static class swiftjava_SwiftModule_Data_withUnsafeBytes__ { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* body: */SwiftValueLayout.SWIFT_POINTER, + /* self: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + SwiftModule.findOrThrow("swiftjava_SwiftModule_Data_withUnsafeBytes__"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static void call(java.lang.foreign.MemorySegment body, java.lang.foreign.MemorySegment self) { + try { + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(body, self); + } + HANDLE.invokeExact(body, self); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + /** + * {snippet lang=c : + * void (*)(const void *, ptrdiff_t) + * } + */ + private static class $body { + @FunctionalInterface + public interface Function { + void apply(java.lang.foreign.MemorySegment _0, long _1); + } + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* _0: */SwiftValueLayout.SWIFT_POINTER, + /* _1: */SwiftValueLayout.SWIFT_INT + ); + private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(Function.class, "apply", DESC); + private static MemorySegment toUpcallStub(Function fi, Arena arena) { + return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); + } + } + } + """, + + """ + public static class withUnsafeBytes { + @FunctionalInterface + public interface body { + void apply(java.lang.foreign.MemorySegment _0); + } + private static MemorySegment $toUpcallStub(body fi, Arena arena) { + return swiftjava_SwiftModule_Data_withUnsafeBytes__.$body.toUpcallStub((_0_pointer, _0_count) -> { + fi.apply(_0_pointer.reinterpret(_0_count)); + }, arena); + } + } + """, + + + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) -> Void) + * } + */ + public void withUnsafeBytes(withUnsafeBytes.body body) { + $ensureAlive(); + try(var arena$ = Arena.ofConfined()) { + swiftjava_SwiftModule_Data_withUnsafeBytes__.call(withUnsafeBytes.$toUpcallStub(body, arena$), this.$memorySegment()); + } + } + """ + ] + ) + } + +} diff --git a/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift b/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift index 2bbaa913e..fdbf2d5fa 100644 --- a/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift +++ b/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift @@ -21,7 +21,6 @@ import Testing struct SwiftSymbolTableSuite { @Test func lookupBindingTests() throws { - let symbolTable = SwiftSymbolTable(parsedModuleName: "MyModule") let sourceFile1: SourceFileSyntax = """ extension X.Y { struct Z { } @@ -33,8 +32,11 @@ struct SwiftSymbolTableSuite { let sourceFile2: SourceFileSyntax = """ struct X {} """ - - symbolTable.setup([sourceFile1, sourceFile2]) + let symbolTable = SwiftSymbolTable.setup( + moduleName: "MyModule", + [sourceFile1, sourceFile2], + log: Logger(label: "swift-java", logLevel: .critical) + ) let x = try #require(symbolTable.lookupType("X", parent: nil)) let xy = try #require(symbolTable.lookupType("Y", parent: x)) From 21cff45bc012712bf93113ebc2eff791ede77ac2 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Thu, 10 Jul 2025 15:17:57 -0700 Subject: [PATCH 024/141] [JExtract/FFM] Translate 'some DataProtocol' parameters to 'Data' * Introduce `SwiftType.opaque(SwiftType)` and `.existential(SwiftType)` to represent `some` and `any` types respectively. * Introduce `SwiftKnownTypes.representativeType(of:)` to the hardcoded `DataProtocol` -> `Data` translation mapping. --- .../MySwiftLibrary/MySwiftLibrary.swift | 4 + .../com/example/swift/HelloJava2Swift.java | 10 +- .../FFM/CDeclLowering/CRepresentation.swift | 6 +- ...Swift2JavaGenerator+FunctionLowering.swift | 26 +++++- ...MSwift2JavaGenerator+JavaTranslation.swift | 66 +++++++++---- ...ISwift2JavaGenerator+JavaTranslation.swift | 8 +- .../Swift2JavaTranslator.swift | 12 ++- .../SwiftTypes/SwiftKnownModules.swift | 4 +- .../SwiftTypes/SwiftKnownTypeDecls.swift | 1 + .../SwiftTypes/SwiftKnownTypes.swift | 12 +++ .../SwiftNominalTypeDeclaration.swift | 2 +- .../SwiftTypes/SwiftType.swift | 37 ++++++-- .../JExtractSwiftTests/DataImportTests.swift | 92 ++++++++++++++++++- 13 files changed, 225 insertions(+), 55 deletions(-) diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index ce949283d..c9291c800 100644 --- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -63,6 +63,10 @@ public func withBuffer(body: (UnsafeRawBufferPointer) -> Void) { body(globalBuffer) } +public func globalReceiveSomeDataProtocol(data: some DataProtocol) { + p(Array(data).description) +} + // ==== Internal helpers func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: String = #function) { diff --git a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java index 985820b18..821b228e9 100644 --- a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java +++ b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java @@ -22,10 +22,6 @@ import org.swift.swiftkit.ffm.AllocatingSwiftArena; import org.swift.swiftkit.ffm.SwiftRuntime; -import java.lang.foreign.MemoryLayout; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.ValueLayout; - public class HelloJava2Swift { public static void main(String[] args) { @@ -95,6 +91,12 @@ static void examples() { }); } + try (var arena = AllocatingSwiftArena.ofConfined()) { + var bytes = arena.allocateFrom("hello"); + var dat = Data.init(bytes, bytes.byteSize(), arena); + MySwiftLibrary.globalReceiveSomeDataProtocol(dat); + } + System.out.println("DONE."); } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift index 9f4a9ac9b..4e44e74ba 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift @@ -24,7 +24,7 @@ extension CType { init(cdeclType: SwiftType) throws { switch cdeclType { case .nominal(let nominalType): - if let knownType = nominalType.nominalTypeDecl.knownStandardLibraryType { + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { if let primitiveCType = knownType.primitiveCType { self = primitiveCType return @@ -68,7 +68,7 @@ extension CType { case .optional(let wrapped) where wrapped.isPointer: try self.init(cdeclType: wrapped) - case .metatype, .optional, .tuple: + case .metatype, .optional, .tuple, .opaque, .existential: throw CDeclToCLoweringError.invalidCDeclType(cdeclType) } } @@ -125,7 +125,7 @@ extension SwiftKnownTypeDeclKind { .qualified(const: true, volatile: false, type: .void) ) case .void: .void - case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string, .data: + case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string, .data, .dataProtocol: nil } } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index 488bd4e77..0c6c707c9 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -77,6 +77,10 @@ extension FFMSwift2JavaGenerator { struct CdeclLowering { var knownTypes: SwiftKnownTypes + init(knownTypes: SwiftKnownTypes) { + self.knownTypes = knownTypes + } + init(symbolTable: SwiftSymbolTable) { self.knownTypes = SwiftKnownTypes(symbolTable: symbolTable) } @@ -165,7 +169,7 @@ struct CdeclLowering { ) case .nominal(let nominal): - if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType { + if let knownType = nominal.nominalTypeDecl.knownTypeKind { if convention == .inout { // FIXME: Support non-trivial 'inout' for builtin types. throw LoweringError.inoutNotSupported(type) @@ -320,6 +324,18 @@ struct CdeclLowering { conversion: conversion ) + case .opaque(let proto), .existential(let proto): + // If the protocol has a known representative implementation, e.g. `String` for `StringProtocol` + // Translate it as the concrete type. + // NOTE: This is a temporary workaround until we add support for generics. + if + let knownProtocol = proto.asNominalTypeDeclaration?.knownTypeKind, + let concreteTy = knownTypes.representativeType(of: knownProtocol) + { + return try lowerParameter(concreteTy, convention: convention, parameterName: parameterName) + } + throw LoweringError.unhandledType(type) + case .optional: throw LoweringError.unhandledType(type) } @@ -386,7 +402,7 @@ struct CdeclLowering { switch type { case .nominal(let nominal): - if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType { + if let knownType = nominal.nominalTypeDecl.knownTypeKind { switch knownType { case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: // pointer buffers are lowered to (raw-pointer, count) pair. @@ -421,7 +437,7 @@ struct CdeclLowering { // Custom types are not supported yet. throw LoweringError.unhandledType(type) - case .function, .metatype, .optional, .tuple: + case .function, .metatype, .optional, .tuple, .existential, .opaque: // TODO: Implement throw LoweringError.unhandledType(type) } @@ -454,7 +470,7 @@ struct CdeclLowering { case .nominal(let nominal): // Types from the Swift standard library that we know about. - if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType { + if let knownType = nominal.nominalTypeDecl.knownTypeKind { switch knownType { case .unsafePointer, .unsafeMutablePointer: // Typed pointers are lowered to corresponding raw forms. @@ -575,7 +591,7 @@ struct CdeclLowering { conversion: .tupleExplode(conversions, name: outParameterName) ) - case .function(_), .optional(_): + case .function, .optional, .existential, .opaque: throw LoweringError.unhandledType(type) } } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index fe1806a9e..db706a913 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -113,12 +113,16 @@ extension FFMSwift2JavaGenerator { } struct JavaTranslation { - var symbolTable: SwiftSymbolTable + var knownTypes: SwiftKnownTypes + + init(symbolTable: SwiftSymbolTable) { + self.knownTypes = SwiftKnownTypes(symbolTable: symbolTable) + } func translate( _ decl: ImportedFunc ) throws -> TranslatedFunctionDecl { - let lowering = CdeclLowering(symbolTable: symbolTable) + let lowering = CdeclLowering(knownTypes: knownTypes) let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature) // Name. @@ -208,7 +212,7 @@ extension FFMSwift2JavaGenerator { switch type { case .nominal(let nominal): - if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType { + if let knownType = nominal.nominalTypeDecl.knownTypeKind { switch knownType { case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: return TranslatedParameter( @@ -248,11 +252,12 @@ extension FFMSwift2JavaGenerator { // 'self' let selfParameter: TranslatedParameter? if case .instance(let swiftSelf) = swiftSignature.selfParameter { - selfParameter = try self.translate( - swiftParam: swiftSelf, + selfParameter = try self.translateParameter( + type: swiftSelf.type, + convention: swiftSelf.convention, + parameterName: swiftSelf.parameterName ?? "self", loweredParam: loweredFunctionSignature.selfParameter!, - methodName: methodName, - parameterName: swiftSelf.parameterName ?? "self" + methodName: methodName ) } else { selfParameter = nil @@ -263,11 +268,12 @@ extension FFMSwift2JavaGenerator { .map { (idx, swiftParam) in let loweredParam = loweredFunctionSignature.parameters[idx] let parameterName = swiftParam.parameterName ?? "_\(idx)" - return try self.translate( - swiftParam: swiftParam, + return try self.translateParameter( + type: swiftParam.type, + convention: swiftParam.convention, + parameterName: parameterName, loweredParam: loweredParam, - methodName: methodName, - parameterName: parameterName + methodName: methodName ) } @@ -285,13 +291,13 @@ extension FFMSwift2JavaGenerator { } /// Translate a Swift API parameter to the user-facing Java API parameter. - func translate( - swiftParam: SwiftParameter, + func translateParameter( + type swiftType: SwiftType, + convention: SwiftParameterConvention, + parameterName: String, loweredParam: LoweredParameter, - methodName: String, - parameterName: String + methodName: String ) throws -> TranslatedParameter { - let swiftType = swiftParam.type // If there is a 1:1 mapping between this Swift type and a C type, that can // be expressed as a Java primitive type. @@ -319,8 +325,8 @@ extension FFMSwift2JavaGenerator { ) case .nominal(let swiftNominalType): - if let knownType = swiftNominalType.nominalTypeDecl.knownStandardLibraryType { - if swiftParam.convention == .inout { + if let knownType = swiftNominalType.nominalTypeDecl.knownTypeKind { + if convention == .inout { // FIXME: Support non-trivial 'inout' for builtin types. throw JavaTranslationError.inoutNotSupported(swiftType) } @@ -388,6 +394,26 @@ extension FFMSwift2JavaGenerator { conversion: .call(.placeholder, function: "\(methodName).$toUpcallStub", withArena: true) ) + case .existential(let proto), .opaque(let proto): + // If the protocol has a known representative implementation, e.g. `String` for `StringProtocol` + // Translate it as the concrete type. + // NOTE: This is a temporary workaround until we add support for generics. + if + let knownProtocol = proto.asNominalTypeDeclaration?.knownTypeKind, + let concreteTy = knownTypes.representativeType(of: knownProtocol) + { + return try translateParameter( + type: concreteTy, + convention: convention, + parameterName: parameterName, + loweredParam: loweredParam, + methodName: methodName + ) + } + + // Otherwise, not supported yet. + throw JavaTranslationError.unhandledType(swiftType) + case .optional: throw JavaTranslationError.unhandledType(swiftType) } @@ -422,7 +448,7 @@ extension FFMSwift2JavaGenerator { ) case .nominal(let swiftNominalType): - if let knownType = swiftNominalType.nominalTypeDecl.knownStandardLibraryType { + if let knownType = swiftNominalType.nominalTypeDecl.knownTypeKind { switch knownType { case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: return TranslatedResult( @@ -476,7 +502,7 @@ extension FFMSwift2JavaGenerator { // TODO: Implement. throw JavaTranslationError.unhandledType(swiftType) - case .optional, .function: + case .optional, .function, .existential, .opaque: throw JavaTranslationError.unhandledType(swiftType) } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 929a332c1..f006b4a8b 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -66,7 +66,7 @@ extension JNISwift2JavaGenerator { func translate(swiftType: SwiftType) -> JavaType { switch swiftType { case .nominal(let nominalType): - if let knownType = nominalType.nominalTypeDecl.knownStandardLibraryType { + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { guard let javaType = translate(knownType: knownType) else { fatalError("unsupported known type: \(knownType)") } @@ -78,7 +78,7 @@ extension JNISwift2JavaGenerator { case .tuple([]): return .void - case .metatype, .optional, .tuple, .function: + case .metatype, .optional, .tuple, .function, .existential, .opaque: fatalError("unsupported type: \(self)") } } @@ -99,10 +99,8 @@ extension JNISwift2JavaGenerator { .unsafeRawPointer, .unsafeMutableRawPointer, .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, - .unsafeBufferPointer, .unsafeMutableBufferPointer: + .unsafeBufferPointer, .unsafeMutableBufferPointer, .data, .dataProtocol: nil - case .data: - fatalError("unimplemented") } } } diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index 82798c15b..07023b097 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -103,7 +103,8 @@ extension Swift2JavaTranslator { // If any API uses 'Foundation.Data', import 'Data' as if it's declared in // this module. if let dataDecl = self.symbolTable[.data] { - if self.isUsing(dataDecl) { + let dataProtocolDecl = self.symbolTable[.dataProtocol]! + if self.isUsing(where: { $0 == dataDecl || $0 == dataProtocolDecl }) { visitor.visit(nominalDecl: dataDecl.syntax!.asNominal!, in: nil) } } @@ -117,12 +118,13 @@ extension Swift2JavaTranslator { ) } - /// Check if any of the imported decls uses the specified nominal declaration. - func isUsing(_ decl: SwiftNominalTypeDeclaration) -> Bool { + /// Check if any of the imported decls uses a nominal declaration that satisfies + /// the given predicate. + func isUsing(where predicate: (SwiftNominalTypeDeclaration) -> Bool) -> Bool { func check(_ type: SwiftType) -> Bool { switch type { case .nominal(let nominal): - return nominal.nominalTypeDecl == decl + return predicate(nominal.nominalTypeDecl) case .optional(let ty): return check(ty) case .tuple(let tuple): @@ -131,6 +133,8 @@ extension Swift2JavaTranslator { return check(fn.resultType) || fn.parameters.contains(where: { check($0.type) }) case .metatype(let ty): return check(ty) + case .existential(let ty), .opaque(let ty): + return check(ty) } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift index 21862c169..47abb7f09 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift @@ -86,7 +86,9 @@ private let swiftSourceFile: SourceFileSyntax = """ """ private let foundationSourceFile: SourceFileSyntax = """ - public struct Data { + public protocol DataProtocol {} + + public struct Data: DataProtocol { public init(bytes: UnsafeRawPointer, count: Int) public var count: Int { get } public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) -> Void) diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift index 0ac915c74..d6af59208 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift @@ -39,6 +39,7 @@ enum SwiftKnownTypeDeclKind: String, Hashable { case void = "Swift.Void" case string = "Swift.String" + case dataProtocol = "Foundation.DataProtocol" case data = "Foundation.Data" var moduleAndName: (module: String, name: String) { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift index ea4371a3e..8c70f7e28 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift @@ -35,6 +35,9 @@ struct SwiftKnownTypes { var unsafeRawPointer: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.unsafeRawPointer])) } var unsafeMutableRawPointer: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.unsafeMutableRawPointer])) } + var dataProtocol: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.dataProtocol])) } + var data: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.data])) } + func unsafePointer(_ pointeeType: SwiftType) -> SwiftType { .nominal( SwiftNominalType( @@ -70,4 +73,13 @@ struct SwiftKnownTypes { ) ) } + + /// Returns the known representative concrete type if there is one for the + /// given protocol kind. E.g. `String` for `StringProtocol` + func representativeType(of knownProtocol: SwiftKnownTypeDeclKind) -> SwiftType? { + switch knownProtocol { + case .dataProtocol: return self.data + default: return nil + } + } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift index b63f930c2..8be645c8b 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -52,7 +52,7 @@ package class SwiftNominalTypeDeclaration { /// Identify this nominal declaration as one of the known standard library /// types, like 'Swift.Int[. - lazy var knownStandardLibraryType: SwiftKnownTypeDeclKind? = { + lazy var knownTypeKind: SwiftKnownTypeDeclKind? = { self.computeKnownStandardLibraryType() }() diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index c2ddd2a53..531ab45ca 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -16,12 +16,25 @@ import SwiftSyntax /// Describes a type in the Swift type system. enum SwiftType: Equatable { + case nominal(SwiftNominalType) + indirect case function(SwiftFunctionType) + + /// `.Type` indirect case metatype(SwiftType) - case nominal(SwiftNominalType) + + /// `?` indirect case optional(SwiftType) + + /// `(, )` case tuple([SwiftType]) + /// `any ` + indirect case existential(SwiftType) + + /// `some ` + indirect case opaque(SwiftType) + static var void: Self { return .tuple([]) } @@ -30,7 +43,7 @@ enum SwiftType: Equatable { switch self { case .nominal(let nominal): nominal case .tuple(let elements): elements.count == 1 ? elements[0].asNominalType : nil - case .function, .metatype, .optional: nil + case .function, .metatype, .optional, .existential, .opaque: nil } } @@ -54,7 +67,7 @@ enum SwiftType: Equatable { var isPointer: Bool { switch self { case .nominal(let nominal): - if let knownType = nominal.nominalTypeDecl.knownStandardLibraryType { + if let knownType = nominal.nominalTypeDecl.knownTypeKind { return knownType.isPointer } default: @@ -73,7 +86,7 @@ enum SwiftType: Equatable { return nominal.nominalTypeDecl.isReferenceType case .metatype, .function: return true - case .optional, .tuple: + case .optional, .tuple, .existential, .opaque: return false } } @@ -84,7 +97,7 @@ extension SwiftType: CustomStringConvertible { /// requires parentheses. private var postfixRequiresParentheses: Bool { switch self { - case .function: true + case .function, .existential, .opaque: true case .metatype, .nominal, .optional, .tuple: false } } @@ -103,6 +116,10 @@ extension SwiftType: CustomStringConvertible { return "\(wrappedType.description)?" case .tuple(let elements): return "(\(elements.map(\.description).joined(separator: ", ")))" + case .existential(let constraintType): + return "any \(constraintType)" + case .opaque(let constraintType): + return "some \(constraintType)" } } } @@ -159,8 +176,7 @@ extension SwiftType { switch type.as(TypeSyntaxEnum.self) { case .arrayType, .classRestrictionType, .compositionType, .dictionaryType, .missingType, .namedOpaqueReturnType, - .packElementType, .packExpansionType, .someOrAnyType, - .suppressedType: + .packElementType, .packExpansionType, .suppressedType: throw TypeTranslationError.unimplementedType(type) case .attributedType(let attributedType): @@ -254,6 +270,13 @@ extension SwiftType { self = try .tuple(tupleType.elements.map { element in try SwiftType(element.type, symbolTable: symbolTable) }) + + case .someOrAnyType(let someOrAntType): + if someOrAntType.someOrAnySpecifier.tokenKind == .keyword(.some) { + self = .opaque(try SwiftType(someOrAntType.constraint, symbolTable: symbolTable)) + } else { + self = .opaque(try SwiftType(someOrAntType.constraint, symbolTable: symbolTable)) + } } } diff --git a/Tests/JExtractSwiftTests/DataImportTests.swift b/Tests/JExtractSwiftTests/DataImportTests.swift index 75613407d..104f7f0e3 100644 --- a/Tests/JExtractSwiftTests/DataImportTests.swift +++ b/Tests/JExtractSwiftTests/DataImportTests.swift @@ -16,7 +16,7 @@ import JExtractSwiftLib import Testing final class DataImportTests { - let class_interfaceFile = + let data_interfaceFile = """ import Foundation @@ -24,11 +24,19 @@ final class DataImportTests { public func returnData() -> Data """ + let dataProtocol_interfaceFile = + """ + import Foundation + + public func receiveDataProtocol(dat: some DataProtocol) + """ + + @Test("Import Data: Swift thunks") - func swiftThunk() throws { + func data_swiftThunk() throws { try assertOutput( - input: class_interfaceFile, .ffm, .swift, + input: data_interfaceFile, .ffm, .swift, expectedChunks: [ """ import Foundation @@ -80,10 +88,10 @@ final class DataImportTests { } @Test("Import Data: JavaBindings") - func javaBindings() throws { + func data_javaBindings() throws { try assertOutput( - input: class_interfaceFile, .ffm, .java, + input: data_interfaceFile, .ffm, .java, expectedChunks: [ """ /** @@ -325,4 +333,78 @@ final class DataImportTests { ) } + @Test("Import DataProtocol: Swift thunks") + func dataProtocol_swiftThunk() throws { + try assertOutput( + input: dataProtocol_interfaceFile, .ffm, .swift, + expectedChunks: [ + """ + import Foundation + """, + """ + @_cdecl("swiftjava_SwiftModule_receiveDataProtocol_dat") + public func swiftjava_SwiftModule_receiveDataProtocol_dat(_ dat: UnsafeRawPointer) { + receiveDataProtocol(dat: dat.assumingMemoryBound(to: Data.self).pointee) + } + """, + + // Just to make sure 'Data' is imported. + """ + @_cdecl("swiftjava_getType_SwiftModule_Data") + """ + ] + ) + } + + @Test("Import DataProtocol: JavaBindings") + func dataProtocol_javaBindings() throws { + + try assertOutput( + input: dataProtocol_interfaceFile, .ffm, .java, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_receiveDataProtocol_dat(const void *dat) + * } + */ + private static class swiftjava_SwiftModule_receiveDataProtocol_dat { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* dat: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + SwiftModule.findOrThrow("swiftjava_SwiftModule_receiveDataProtocol_dat"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static void call(java.lang.foreign.MemorySegment dat) { + try { + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(dat); + } + HANDLE.invokeExact(dat); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + } + """, + + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func receiveDataProtocol(dat: some DataProtocol) + * } + */ + public static void receiveDataProtocol(Data dat) { + swiftjava_SwiftModule_receiveDataProtocol_dat.call(dat.$memorySegment()); + } + """, + + // Just to make sure 'Data' is imported. + """ + public final class Data + """ + ] + ) + } } From 36d05c76f352b076ff1552c704b8d24cacae35d6 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Thu, 10 Jul 2025 16:30:29 -0700 Subject: [PATCH 025/141] [JExtract/FFM] Add Data and DataProtocol tests Also, run Samples:SwiftKitSampleApp:test in CI --- .../MySwiftLibrary/MySwiftLibrary.swift | 3 +- Samples/SwiftKitSampleApp/ci-validate.sh | 3 +- .../com/example/swift/DataImportTest.java | 49 +++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 Samples/SwiftKitSampleApp/src/test/java/com/example/swift/DataImportTest.java diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index c9291c800..1bcca4fd2 100644 --- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -63,8 +63,9 @@ public func withBuffer(body: (UnsafeRawBufferPointer) -> Void) { body(globalBuffer) } -public func globalReceiveSomeDataProtocol(data: some DataProtocol) { +public func globalReceiveSomeDataProtocol(data: some DataProtocol) -> Int { p(Array(data).description) + return data.count } // ==== Internal helpers diff --git a/Samples/SwiftKitSampleApp/ci-validate.sh b/Samples/SwiftKitSampleApp/ci-validate.sh index 07b426271..c7a68d228 100755 --- a/Samples/SwiftKitSampleApp/ci-validate.sh +++ b/Samples/SwiftKitSampleApp/ci-validate.sh @@ -3,4 +3,5 @@ set -x set -e -./gradlew run \ No newline at end of file +./gradlew run +./gradlew test \ No newline at end of file diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/DataImportTest.java b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/DataImportTest.java new file mode 100644 index 000000000..52a63f815 --- /dev/null +++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/DataImportTest.java @@ -0,0 +1,49 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.ffm.AllocatingSwiftArena; + +import static org.junit.jupiter.api.Assertions.*; + +public class DataImportTest { + @Test + void test_Data_receiveAndReturn() { + try (var arena = AllocatingSwiftArena.ofConfined()) { + var origBytes = arena.allocateFrom("foobar"); + var origDat = Data.init(origBytes, origBytes.byteSize(), arena); + assertEquals(7, origDat.getCount()); + + var retDat = MySwiftLibrary.globalReceiveReturnData(origDat, arena); + assertEquals(7, retDat.getCount()); + retDat.withUnsafeBytes((retBytes) -> { + assertEquals(7, retBytes.byteSize()); + var str = retBytes.getString(0); + assertEquals("foobar", str); + }); + } + } + + @Test + void test_DataProtocol_receive() { + try (var arena = AllocatingSwiftArena.ofConfined()) { + var bytes = arena.allocateFrom("hello"); + var dat = Data.init(bytes, bytes.byteSize(), arena); + var result = MySwiftLibrary.globalReceiveSomeDataProtocol(dat); + assertEquals(6, result); + } + } +} From f69d84a5ae5a4420949d524f60d9852db523a2da Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 11 Jul 2025 20:58:09 +0900 Subject: [PATCH 026/141] Drop the java.toolchain version to 17, which has a Corretto LTS release (#304) --- .github/actions/prepare_env/action.yml | 2 +- SwiftKitCore/build.gradle | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/prepare_env/action.yml b/.github/actions/prepare_env/action.yml index c63b87cfa..8fc986e06 100644 --- a/.github/actions/prepare_env/action.yml +++ b/.github/actions/prepare_env/action.yml @@ -11,7 +11,7 @@ runs: java-version: | 24 21 - 19 + 17 cache: 'gradle' - name: Set JAVA_HOME_{N} shell: bash diff --git a/SwiftKitCore/build.gradle b/SwiftKitCore/build.gradle index 3d5907f2c..78cdbeb21 100644 --- a/SwiftKitCore/build.gradle +++ b/SwiftKitCore/build.gradle @@ -25,11 +25,11 @@ repositories { java { toolchain { - // JDK 19 is the last JDK to support Java 7 - languageVersion.set(JavaLanguageVersion.of(19)) + languageVersion.set(JavaLanguageVersion.of(17)) } // Support Android 6+ (Java 7) sourceCompatibility = JavaVersion.VERSION_1_7 + targetCompatibility = JavaVersion.VERSION_1_7 } dependencies { From 17210b73ab357d118eb565a98a64ad0fb3df8650 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 11 Jul 2025 21:09:23 +0900 Subject: [PATCH 027/141] WIP: Update readme to reflect latest work Work in progress, but it's time to update the readme --- README.md | 51 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index b7ad4defb..f9f4ca3f2 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,8 @@ This repository contains two approaches to Swift/Java interoperability. -- A Swift library (`JavaKit`) and bindings generator that allows a Swift program to make use of Java libraries by wrapping Java classes in corresponding Swift types, allowing Swift to directly call any wrapped Java API. -- The `jextract-swift` tool which is similar to the JDK's `jextract` which allows to extract Java sources which are used - to efficiently call into Swift _from Java_. +- Swift library (`JavaKit`) and bindings generator that allows a Swift program to make use of Java libraries by wrapping Java classes in corresponding Swift types, allowing Swift to directly call any wrapped Java API. +- The `swift-java` tool which which offers automated ways to import or "extract" bindings to sources or libraries in either language. The results are bindings for Swift or Java. ## :construction: :construction: :construction: Early Development :construction: :construction: :construction: @@ -16,34 +15,48 @@ The primary purpose of this repository is to create an environment for collabora ## Dependencies -### Required Swift Development Toolchains +### Required JDK versions -To build and use this project, currently, you will need to download a custom toolchain which includes some improvements in Swift that this project relies on: +This project consists of different modules which have different Swift and Java runtime requirements. -**Required toolchain download:** +## JavaKit macros -Currently this project supports Swift `6.0.x` and we are working on supporting later releases. +JavaKit is a Swift library offering macros which simplify writing JNI code "by hand" but also calling Java code from Swift. -You can use Swiftly ([macOS](https://www.swift.org/install/macos/swiftly/) / [linux](https://www.swift.org/install/linux/swiftly/)) the Swift toolchain installer to install the necessary Swift versions. +It is possible to generate Swift bindings to Java libraries using JavaKit by using the `swift-java wrap-java` command. -### Required JDK versions +Required language/runtime versions: +- **JDK 17+**, any recent JDK installation should be sufficient, as only general reflection and JNI APIs are used by this integration +- **Swift 6.0.x**, because the library uses modern Swift macros -This project consists of different modules which have different Swift and Java runtime requirements. +**swift-java jextract** -**JavaKit** – the Swift macros allowing the invocation of Java libraries from Swift +Is a source generator which will **generate Java bindings to existing Swift libraries**. +Its inputs are Swift sources or packages, and outputs are generated Swift and Java code necessary to call these functions efficiently from Java. -- **JDK 17+**, any recent JDK installation should be sufficient, as only general reflection and JNI APIs are used by this integration -- **Swift 6.0.x**, because the library uses modern Swift macros +## swift-java jextract --mode=ffm (default) + +This mode provides the most flexibility and performance, and allows to decrease the amount of data being copied between Swift and Java. +This does require the use of the relatively recent [JEP-454: Foreign Function & Memory API](https://openjdk.org/jeps/454), which is only available since JDK22, and will become part of JDK LTS releases with JDK 25 (depending on your JDK vendor). + +This is the primary way we envision calling Swift code from server-side Java libraries and applications. + +Required language/runtime versions: +- **Swift 6.1**, because of dependence on rich swift interface files +- **JDK 24+** + - We are validating the implementation using the currently supported non-LTE release, which at present means JDK-24. + +## swift-java jextract --mode=jni -**jextract-swift** – the source generator that ingests .swiftinterface files and makes them available to be called from generated Java sources +In this mode, the generated sources will use the legacy JNI approach to calling native code. -- **Swift 6.0.x development snapshots**, because of dependence on rich swift interface files -- **JDK 22+** because of dependence on [JEP-454: Foreign Function & Memory API](https://openjdk.org/jeps/454) - - We are validating the implementation using the currently supported non-LTE release, which at present means JDK-23. +This mode is more limited in some performance and flexibility that it can offer, however it is the most compatible, since even very old JVM's as well as even Android systems can be supported by this mode. +We recommend this mode when FFM is not available, or wide ranging deployment compatibility is your priority. When performance is paramaunt, we recommend the FFM mode instead. -The extract tool may become able to generate legacy compatible sources, which would not require JEP-454 and would instead rely on existing JNI facilities. Currently though, efforts are focused on the forward-looking implementation using modern foreign function and memory APIs. +Required language/runtime versions: +- **Swift 6.1**, because of dependence on rich swift interface files +- **Java 7+**, including -Support for more recent Swift versions will be provided, for now please stick to 6.0 while evaluating this early version of swift-java. ## Development and Testing From ac8040060e71162f6d184789da3e3ca64523d918 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 11 Jul 2025 23:58:34 +0200 Subject: [PATCH 028/141] [JExtract] Generate JNI code for memory management with `SwiftKitCore` (#302) --- .../com/example/swift/HelloJava2SwiftJNI.java | 7 +- .../com/example/swift/MySwiftClassTest.java | 17 +++-- ...t2JavaGenerator+JavaBindingsPrinting.swift | 62 +++++++++++++--- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 73 ++++++++++++++++--- .../swift/swiftkit/core/JNISwiftInstance.java | 2 +- .../JNI/JNIClassTests.swift | 54 +++++++++++--- 6 files changed, 175 insertions(+), 40 deletions(-) diff --git a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java index 66366a191..38860c061 100644 --- a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java +++ b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java @@ -19,6 +19,7 @@ // Import javakit/swiftkit support libraries import org.swift.swiftkit.core.SwiftLibraries; +import org.swift.swiftkit.core.ConfinedSwiftMemorySession; public class HelloJava2SwiftJNI { @@ -40,8 +41,10 @@ static void examples() { MySwiftClass.method(); - MySwiftClass myClass = MySwiftClass.init(10, 5); - MySwiftClass myClass2 = MySwiftClass.init(); + try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + MySwiftClass myClass = MySwiftClass.init(10, 5, arena); + MySwiftClass myClass2 = MySwiftClass.init(arena); + } System.out.println("DONE."); } diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java index 994240cb7..ec0e31ddd 100644 --- a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -14,24 +14,25 @@ package com.example.swift; -import com.example.swift.MySwiftLibrary; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.ConfinedSwiftMemorySession; import static org.junit.jupiter.api.Assertions.*; public class MySwiftClassTest { @Test void init_noParameters() { - MySwiftClass c = MySwiftClass.init(); - assertNotNull(c); + try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + MySwiftClass c = MySwiftClass.init(arena); + assertNotNull(c); + } } @Test void init_withParameters() { - MySwiftClass c = MySwiftClass.init(1337, 42); - assertNotNull(c); + try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + MySwiftClass c = MySwiftClass.init(1337, 42, arena); + assertNotNull(c); + } } - } \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index ac775638f..6a1c549a4 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -12,6 +12,19 @@ // //===----------------------------------------------------------------------===// + +// MARK: Defaults + +extension JNISwift2JavaGenerator { + /// Default set Java imports for every generated file + static let defaultJavaImports: Array = [ + "org.swift.swiftkit.core.*", + "org.swift.swiftkit.core.util.*", + ] +} + +// MARK: Printing + extension JNISwift2JavaGenerator { func writeExportedJavaSources() throws { var printer = CodePrinter() @@ -72,6 +85,7 @@ extension JNISwift2JavaGenerator { private func printImportedNominal(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { printHeader(&printer) printPackage(&printer) + printImports(&printer) printNominal(&printer, decl) { printer in printer.print( @@ -87,14 +101,10 @@ extension JNISwift2JavaGenerator { """ ) - printer.println() - printer.print( """ - private long selfPointer; - - private \(decl.swiftNominal.name)(long selfPointer) { - this.selfPointer = selfPointer; + public \(decl.swiftNominal.name)(long selfPointer, SwiftArena swiftArena) { + super(selfPointer, swiftArena); } """ ) @@ -108,6 +118,8 @@ extension JNISwift2JavaGenerator { for method in decl.methods { printFunctionBinding(&printer, method) } + + printDestroyFunction(&printer, decl) } } @@ -130,10 +142,17 @@ extension JNISwift2JavaGenerator { ) } + private func printImports(_ printer: inout CodePrinter) { + for i in JNISwift2JavaGenerator.defaultJavaImports { + printer.print("import \(i);") + } + printer.print("") + } + private func printNominal( _ printer: inout CodePrinter, _ decl: ImportedNominalType, body: (inout CodePrinter) -> Void ) { - printer.printBraceBlock("public final class \(decl.swiftNominal.name)") { printer in + printer.printBraceBlock("public final class \(decl.swiftNominal.name) extends JNISwiftInstance") { printer in body(&printer) } } @@ -160,7 +179,7 @@ extension JNISwift2JavaGenerator { printer.print( """ long selfPointer = \(type.qualifiedName).allocatingInit(\(initArguments.joined(separator: ", "))); - return new \(type.qualifiedName)(selfPointer); + return new \(type.qualifiedName)(selfPointer, swiftArena$); """ ) } @@ -182,6 +201,26 @@ extension JNISwift2JavaGenerator { ) } + /// Prints the destroy function for a `JNISwiftInstance` + private func printDestroyFunction(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + printer.print("private static native void $destroy(long selfPointer);") + + printer.print("@Override") + printer.printBraceBlock("protected Runnable $createDestroyFunction()") { printer in + printer.print( + """ + long $selfPointer = this.pointer(); + return new Runnable() { + @Override + public void run() { + \(type.swiftNominal.name).$destroy($selfPointer); + } + }; + """ + ) + } + } + /// Renders a Java function signature /// /// `func method(x: Int, y: Int) -> Int` becomes @@ -189,7 +228,12 @@ extension JNISwift2JavaGenerator { private func renderFunctionSignature(_ decl: ImportedFunc) -> String { let translatedDecl = translatedDecl(for: decl) let resultType = translatedDecl.translatedFunctionSignature.resultType - let parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter) + var parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter) + + if decl.isInitializer { + parameters.append("SwiftArena swiftArena$") + } + let throwsClause = decl.isThrowing ? " throws Exception" : "" return "\(resultType) \(translatedDecl.name)(\(parameters.joined(separator: ", ")))\(throwsClause)" diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index b4d73873c..a14c2078e 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -13,6 +13,11 @@ //===----------------------------------------------------------------------===// import JavaTypes +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif extension JNISwift2JavaGenerator { func writeSwiftThunkSources() throws { @@ -96,6 +101,8 @@ extension JNISwift2JavaGenerator { printSwiftFunctionThunk(&printer, method) printer.println() } + + printDestroyFunctionThunk(&printer, type) } private func printInitializerThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { @@ -199,24 +206,18 @@ extension JNISwift2JavaGenerator { resultType: JavaType, _ body: (inout CodePrinter) -> Void ) { - var jniSignature = parameters.reduce(into: "") { signature, parameter in + let jniSignature = parameters.reduce(into: "") { signature, parameter in signature += parameter.type.jniTypeSignature } - // Escape signature characters - jniSignature = jniSignature - .replacingOccurrences(of: "_", with: "_1") - .replacingOccurrences(of: "/", with: "_") - .replacingOccurrences(of: ";", with: "_2") - .replacingOccurrences(of: "[", with: "_3") - let cName = "Java_" + self.javaPackage.replacingOccurrences(of: ".", with: "_") - + "_\(parentName)_" - + javaMethodName + + "_\(parentName.escapedJNIIdentifier)_" + + javaMethodName.escapedJNIIdentifier + "__" - + jniSignature + + jniSignature.escapedJNIIdentifier + let translatedParameters = parameters.map { "\($0.name): \($0.type.jniTypeName)" } @@ -251,6 +252,30 @@ extension JNISwift2JavaGenerator { ) } + /// Prints the implementation of the destroy function. + private func printDestroyFunctionThunk(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + printCDecl( + &printer, + javaMethodName: "$destroy", + parentName: type.swiftNominal.name, + parameters: [ + JavaParameter(name: "selfPointer", type: .long) + ], + isStatic: true, + resultType: .void + ) { printer in + // Deinitialize the pointer allocated (which will call the VWT destroy method) + // then deallocate the memory. + printer.print( + """ + let pointer = UnsafeMutablePointer<\(type.qualifiedName)>(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + pointer.deinitialize(count: 1) + pointer.deallocate() + """ + ) + } + } + /// Renders the arguments for making a downcall private func renderDowncallArguments( swiftFunctionSignature: SwiftFunctionSignature, @@ -266,3 +291,29 @@ extension JNISwift2JavaGenerator { .joined(separator: ", ") } } + +extension String { + /// Returns a version of the string correctly escaped for a JNI + var escapedJNIIdentifier: String { + self.map { + if $0 == "_" { + return "_1" + } else if $0 == "/" { + return "_" + } else if $0 == ";" { + return "_2" + } else if $0 == "[" { + return "_3" + } else if $0.isASCII && ($0.isLetter || $0.isNumber) { + return String($0) + } else if let utf16 = $0.utf16.first { + // Escape any non-alphanumeric to their UTF16 hex encoding + let utf16Hex = String(format: "%04x", utf16) + return "_0\(utf16Hex)" + } else { + fatalError("Invalid JNI character: \($0)") + } + } + .joined() + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java index 891745a23..f99667930 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java @@ -39,7 +39,7 @@ protected JNISwiftInstance(long pointer, SwiftArena arena) { * * @return a function that is called when the value should be destroyed. */ - abstract Runnable $createDestroyFunction(); + protected abstract Runnable $createDestroyFunction(); @Override public SwiftInstanceCleanup createCleanupAction() { diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift index c39d5815c..0c0f4f02d 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -44,8 +44,11 @@ struct JNIClassTests { // Swift module: SwiftModule package com.example.swift; + + import org.swift.swiftkit.core.*; + import org.swift.swiftkit.core.util.*; - public final class MyClass { + public final class MyClass extends JNISwiftInstance { static final String LIB_NAME = "SwiftModule"; @SuppressWarnings("unused") @@ -55,12 +58,25 @@ struct JNIClassTests { return true; } - private long selfPointer; - - private MyClass(long selfPointer) { - this.selfPointer = selfPointer; + public MyClass(long selfPointer, SwiftArena swiftArena) { + super(selfPointer, swiftArena); } """, + """ + private static native void $destroy(long selfPointer); + """, + """ + @Override + protected Runnable $createDestroyFunction() { + long $selfPointer = this.pointer(); + return new Runnable() { + @Override + public void run() { + MyClass.$destroy($selfPointer); + } + }; + } + """ ]) } @@ -116,9 +132,9 @@ struct JNIClassTests { * public init(x: Int64, y: Int64) * } */ - public static MyClass init(long x, long y) { + public static MyClass init(long x, long y, SwiftArena swiftArena$) { long selfPointer = MyClass.allocatingInit(x, y); - return new MyClass(selfPointer); + return new MyClass(selfPointer, swiftArena$); } """, """ @@ -128,9 +144,9 @@ struct JNIClassTests { * public init() * } */ - public static MyClass init() { + public static MyClass init(SwiftArena swiftArena$) { long selfPointer = MyClass.allocatingInit(); - return new MyClass(selfPointer); + return new MyClass(selfPointer, swiftArena$); } """, """ @@ -170,4 +186,24 @@ struct JNIClassTests { ] ) } + + @Test + func destroyFunction_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024destroy__J") + func Java_com_example_swift_MyClass__00024destroy__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) { + let pointer = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + pointer.deinitialize(count: 1) + pointer.deallocate() + } + """ + ] + ) + } } From 7bec68559498cbdfc18abd288988b737f5b5d69e Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sat, 12 Jul 2025 13:08:53 +0200 Subject: [PATCH 029/141] [JExtract] Add support for JNI member methods (#305) --- .../Sources/MySwiftLibrary/MySwiftClass.swift | 16 +++ .../com/example/swift/HelloJava2SwiftJNI.java | 6 + .../com/example/swift/MySwiftClassTest.java | 26 ++++ ...t2JavaGenerator+JavaBindingsPrinting.swift | 37 ++++++ ...ift2JavaGenerator+SwiftThunkPrinting.swift | 124 +++++++++++------- .../Exceptions/ExceptionHandling.swift | 2 +- .../JNI/JNIClassTests.swift | 47 +++++++ 7 files changed, 210 insertions(+), 48 deletions(-) diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index fca6236fe..82958464c 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -34,4 +34,20 @@ public class MySwiftClass { deinit { p("deinit called!") } + + public func sum() -> Int64 { + return x + y + } + + public func xMultiplied(by z: Int64) -> Int64 { + return x * z; + } + + enum MySwiftClassError: Error { + case swiftError + } + + public func throwingFunction() throws { + throw MySwiftClassError.swiftError + } } diff --git a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java index 38860c061..575859ec0 100644 --- a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java +++ b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java @@ -44,6 +44,12 @@ static void examples() { try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { MySwiftClass myClass = MySwiftClass.init(10, 5, arena); MySwiftClass myClass2 = MySwiftClass.init(arena); + + try { + myClass.throwingFunction(); + } catch (Exception e) { + System.out.println("Caught exception: " + e.getMessage()); + } } System.out.println("DONE."); diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java index ec0e31ddd..3982b480f 100644 --- a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -35,4 +35,30 @@ void init_withParameters() { assertNotNull(c); } } + + @Test + void sum() { + try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + MySwiftClass c = MySwiftClass.init(20, 10, arena); + assertEquals(30, c.sum()); + } + } + + @Test + void xMultiplied() { + try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + MySwiftClass c = MySwiftClass.init(20, 10, arena); + assertEquals(200, c.xMultiplied(10)); + } + } + + @Test + void throwingFunction() { + try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + MySwiftClass c = MySwiftClass.init(20, 10, arena); + Exception exception = assertThrows(Exception.class, () -> c.throwingFunction()); + + assertEquals("swiftError", exception.getMessage()); + } + } } \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 6a1c549a4..dce399a97 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -164,12 +164,49 @@ extension JNISwift2JavaGenerator { } private func printFunctionBinding(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + if decl.isStatic || decl.isInitializer || !decl.hasParent { + printStaticFunctionBinding(&printer, decl) + } else { + printMemberMethodBindings(&printer, decl) + } + } + + private func printStaticFunctionBinding(_ printer: inout CodePrinter, _ decl: ImportedFunc) { printDeclDocumentation(&printer, decl) printer.print( "public static native \(renderFunctionSignature(decl));" ) } + /// Renders Java bindings for member methods + /// + /// Member methods are generated as a function that extracts the `selfPointer` + /// and passes it down to another native function along with the arguments + /// to call the Swift implementation. + private func printMemberMethodBindings(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + let translatedDecl = translatedDecl(for: decl) + + printDeclDocumentation(&printer, decl) + printer.printBraceBlock("public \(renderFunctionSignature(decl))") { printer in + var arguments = translatedDecl.translatedFunctionSignature.parameters.map(\.name) + arguments.append("selfPointer") + + let returnKeyword = translatedDecl.translatedFunctionSignature.resultType.isVoid ? "" : "return " + + printer.print( + """ + long selfPointer = this.pointer(); + \(returnKeyword)\(translatedDecl.parentName).$\(translatedDecl.name)(\(arguments.joined(separator: ", "))); + """ + ) + } + + let returnType = translatedDecl.translatedFunctionSignature.resultType + var parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter) + parameters.append("long selfPointer") + printer.print("private static native \(returnType) $\(translatedDecl.name)(\(parameters.joined(separator: ", ")));") + } + private func printInitializerBindings(_ printer: inout CodePrinter, _ decl: ImportedFunc, type: ImportedNominalType) { let translatedDecl = translatedDecl(for: decl) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index a14c2078e..24532ca1a 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -136,67 +136,97 @@ extension JNISwift2JavaGenerator { _ printer: inout CodePrinter, _ decl: ImportedFunc ) { + // Free functions does not have a parent + if decl.isStatic || !decl.hasParent { + self.printSwiftStaticFunctionThunk(&printer, decl) + } else { + self.printSwiftMemberFunctionThunk(&printer, decl) + } + } + + private func printSwiftStaticFunctionThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { let translatedDecl = self.translatedDecl(for: decl) - let parentName = translatedDecl.parentName - let swiftReturnType = decl.functionSignature.result.type - printCDecl(&printer, decl) { printer in - let downcallParameters = renderDowncallArguments( - swiftFunctionSignature: decl.functionSignature, - translatedFunctionSignature: translatedDecl.translatedFunctionSignature + printCDecl( + &printer, + javaMethodName: translatedDecl.name, + parentName: translatedDecl.parentName, + parameters: translatedDecl.translatedFunctionSignature.parameters, + isStatic: true, + resultType: translatedDecl.translatedFunctionSignature.resultType + ) { printer in + // For free functions the parent is the Swift module + let parentName = decl.parentType?.asNominalTypeDeclaration?.qualifiedName ?? swiftModuleName + self.printFunctionDowncall(&printer, decl, calleeName: parentName) + } + } + + private func printSwiftMemberFunctionThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + let translatedDecl = self.translatedDecl(for: decl) + let swiftParentName = decl.parentType!.asNominalTypeDeclaration!.qualifiedName + + printCDecl( + &printer, + javaMethodName: "$\(translatedDecl.name)", + parentName: translatedDecl.parentName, + parameters: translatedDecl.translatedFunctionSignature.parameters + [ + JavaParameter(name: "selfPointer", type: .long) + ], + isStatic: true, + resultType: translatedDecl.translatedFunctionSignature.resultType + ) { printer in + printer.print( + """ + let self$ = UnsafeMutablePointer<\(swiftParentName)>(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + """ ) - let tryClause: String = decl.isThrowing ? "try " : "" - let functionDowncall = - "\(tryClause)\(parentName).\(decl.name)(\(downcallParameters))" - - let innerBody = - if swiftReturnType.isVoid { - functionDowncall - } else { - """ - let result = \(functionDowncall) - return result.getJNIValue(in: environment) - """ - } + self.printFunctionDowncall(&printer, decl, calleeName: "self$.pointee") + } + } + + private func printFunctionDowncall( + _ printer: inout CodePrinter, + _ decl: ImportedFunc, + calleeName: String + ) { + let translatedDecl = self.translatedDecl(for: decl) + let swiftReturnType = decl.functionSignature.result.type + + let downcallParameters = renderDowncallArguments( + swiftFunctionSignature: decl.functionSignature, + translatedFunctionSignature: translatedDecl.translatedFunctionSignature + ) + let tryClause: String = decl.isThrowing ? "try " : "" + let functionDowncall = "\(tryClause)\(calleeName).\(decl.name)(\(downcallParameters))" + + let returnStatement = + if swiftReturnType.isVoid { + functionDowncall + } else { + """ + let result = \(functionDowncall) + return result.getJNIValue(in: environment) + """ + } - if decl.isThrowing { - let dummyReturn = - !swiftReturnType.isVoid ? "return \(swiftReturnType).jniPlaceholderValue" : "" - printer.print( + if decl.isThrowing { + let dummyReturn = + !swiftReturnType.isVoid ? "return \(swiftReturnType).jniPlaceholderValue" : "" + printer.print( """ do { - \(innerBody) + \(returnStatement) } catch { environment.throwAsException(error) \(dummyReturn) } """ - ) - } else { - printer.print(innerBody) - } + ) + } else { + printer.print(returnStatement) } } - private func printCDecl( - _ printer: inout CodePrinter, - _ decl: ImportedFunc, - _ body: (inout CodePrinter) -> Void - ) { - let translatedDecl = translatedDecl(for: decl) - let parentName = translatedDecl.parentName - - printCDecl( - &printer, - javaMethodName: translatedDecl.name, - parentName: parentName, - parameters: translatedDecl.translatedFunctionSignature.parameters, - isStatic: decl.isStatic || decl.isInitializer || !decl.hasParent, - resultType: translatedDecl.translatedFunctionSignature.resultType, - body - ) - } - private func printCDecl( _ printer: inout CodePrinter, javaMethodName: String, diff --git a/Sources/JavaKit/Exceptions/ExceptionHandling.swift b/Sources/JavaKit/Exceptions/ExceptionHandling.swift index 02537281b..d5ca11078 100644 --- a/Sources/JavaKit/Exceptions/ExceptionHandling.swift +++ b/Sources/JavaKit/Exceptions/ExceptionHandling.swift @@ -39,7 +39,7 @@ extension JNIEnvironment { } // Otherwise, create a exception with a message. - _ = try! JavaClass.withJNIClass(in: self) { exceptionClass in + _ = try! Exception.withJNIClass(in: self) { exceptionClass in interface.ThrowNew(self, exceptionClass, String(describing: error)) } } diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift index 0c0f4f02d..41802e556 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -33,6 +33,8 @@ struct JNIClassTests { self.x = 0 self.y = 0 } + + public func doSomething(x: Int64) {} } """ @@ -206,4 +208,49 @@ struct JNIClassTests { ] ) } + + @Test + func memberMethod_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func doSomething(x: Int64) + * } + */ + public void doSomething(long x) { + long selfPointer = this.pointer(); + MyClass.$doSomething(x, selfPointer); + } + """, + """ + private static native void $doSomething(long x, long selfPointer); + """ + ] + ) + } + + @Test + func memberMethod_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024doSomething__JJ") + func Java_com_example_swift_MyClass__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, selfPointer: jlong) { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!)) + } + """, + ] + ) + } } From 94d5f345335062bbe9833e3c84d701c5101c7d8e Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sun, 13 Jul 2025 09:45:50 +0200 Subject: [PATCH 030/141] [JExtract/JNI] Add support for variables (#306) * add support for variables * prefix boolean variables with `is` to match Java conventions * add confined arena constructor with current thread * add variable tests * PR feedback * add test for isWarm --- .../Sources/MySwiftLibrary/MySwiftClass.swift | 25 ++ .../MySwiftLibrary/MySwiftLibrary.swift | 2 + .../Sources/MySwiftLibrary/swift-java.config | 2 +- .../com/example/swift/HelloJava2SwiftJNI.java | 4 +- .../com/example/swift/MySwiftClassTest.java | 67 ++- .../com/example/swift/MySwiftLibraryTest.java | 7 + .../Convenience/String+Extensions.swift | 12 +- ...MSwift2JavaGenerator+JavaTranslation.swift | 4 +- Sources/JExtractSwiftLib/ImportedDecls.swift | 18 + ...t2JavaGenerator+JavaBindingsPrinting.swift | 12 + ...ISwift2JavaGenerator+JavaTranslation.swift | 9 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 43 +- .../SwiftTypes/SwiftFunctionSignature.swift | 11 +- .../core/ConfinedSwiftMemorySession.java | 4 + .../JNI/JNIVariablesTests.swift | 386 ++++++++++++++++++ 15 files changed, 584 insertions(+), 22 deletions(-) create mode 100644 Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index 82958464c..386a72270 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -16,6 +16,31 @@ public class MySwiftClass { let x: Int64 let y: Int64 + public let constant: Int64 = 100 + public var mutable: Int64 = 0 + public var product: Int64 { + return x * y + } + public var throwingVariable: Int64 { + get throws { + throw MySwiftClassError.swiftError + } + } + public var mutableDividedByTwo: Int64 { + get { + return mutable / 2 + } + set { + mutable = newValue * 2 + } + } + public let warm: Bool = false + public var getAsync: Int64 { + get async { + return 42 + } + } + public static func method() { p("Hello from static method in a class!") } diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index be04bc089..1dd845470 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -23,6 +23,8 @@ import Darwin.C #endif +public var globalVariable: Int64 = 0 + public func helloWorld() { p("\(#function)") } diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config index 46bf1f1ca..bb637f34f 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config @@ -1,4 +1,4 @@ { "javaPackage": "com.example.swift", - "mode": "jni" + "mode": "jni", } diff --git a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java index 575859ec0..8e7ca2844 100644 --- a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java +++ b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java @@ -41,10 +41,12 @@ static void examples() { MySwiftClass.method(); - try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + try (var arena = new ConfinedSwiftMemorySession()) { MySwiftClass myClass = MySwiftClass.init(10, 5, arena); MySwiftClass myClass2 = MySwiftClass.init(arena); + System.out.println("myClass.isWarm: " + myClass.isWarm()); + try { myClass.throwingFunction(); } catch (Exception e) { diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java index 3982b480f..bf244416c 100644 --- a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -22,7 +22,7 @@ public class MySwiftClassTest { @Test void init_noParameters() { - try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + try (var arena = new ConfinedSwiftMemorySession()) { MySwiftClass c = MySwiftClass.init(arena); assertNotNull(c); } @@ -30,7 +30,7 @@ void init_noParameters() { @Test void init_withParameters() { - try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + try (var arena = new ConfinedSwiftMemorySession()) { MySwiftClass c = MySwiftClass.init(1337, 42, arena); assertNotNull(c); } @@ -38,7 +38,7 @@ void init_withParameters() { @Test void sum() { - try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + try (var arena = new ConfinedSwiftMemorySession()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); assertEquals(30, c.sum()); } @@ -46,7 +46,7 @@ void sum() { @Test void xMultiplied() { - try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + try (var arena = new ConfinedSwiftMemorySession()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); assertEquals(200, c.xMultiplied(10)); } @@ -54,11 +54,68 @@ void xMultiplied() { @Test void throwingFunction() { - try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + try (var arena = new ConfinedSwiftMemorySession()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); Exception exception = assertThrows(Exception.class, () -> c.throwingFunction()); assertEquals("swiftError", exception.getMessage()); } } + + @Test + void constant() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftClass c = MySwiftClass.init(20, 10, arena); + assertEquals(100, c.getConstant()); + } + } + + @Test + void mutable() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftClass c = MySwiftClass.init(20, 10, arena); + assertEquals(0, c.getMutable()); + c.setMutable(42); + assertEquals(42, c.getMutable()); + } + } + + @Test + void product() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftClass c = MySwiftClass.init(20, 10, arena); + assertEquals(200, c.getProduct()); + } + } + + @Test + void throwingVariable() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftClass c = MySwiftClass.init(20, 10, arena); + + Exception exception = assertThrows(Exception.class, () -> c.getThrowingVariable()); + + assertEquals("swiftError", exception.getMessage()); + } + } + + @Test + void mutableDividedByTwo() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftClass c = MySwiftClass.init(20, 10, arena); + assertEquals(0, c.getMutableDividedByTwo()); + c.setMutable(20); + assertEquals(10, c.getMutableDividedByTwo()); + c.setMutableDividedByTwo(5); + assertEquals(10, c.getMutable()); + } + } + + @Test + void isWarm() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftClass c = MySwiftClass.init(20, 10, arena); + assertFalse(c.isWarm()); + } + } } \ No newline at end of file diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java index f1cefd199..5c9c2358d 100644 --- a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -54,4 +54,11 @@ void call_writeString_jextract() { assertEquals(string.length(), reply); } + + @Test + void globalVariable() { + assertEquals(0, MySwiftLibrary.getGlobalVariable()); + MySwiftLibrary.setGlobalVariable(100); + assertEquals(100, MySwiftLibrary.getGlobalVariable()); + } } \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift index 53781cefe..1878ba2f5 100644 --- a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift @@ -22,4 +22,14 @@ extension String { return "\(f.uppercased())\(String(dropFirst()))" } -} \ No newline at end of file + + /// Returns whether the string is of the format `isX` + var hasJavaBooleanNamingConvention: Bool { + guard self.hasPrefix("is"), self.count > 2 else { + return false + } + + let thirdCharacterIndex = self.index(self.startIndex, offsetBy: 2) + return self[thirdCharacterIndex].isUppercase + } +} diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index db706a913..e548db5c3 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -127,8 +127,8 @@ extension FFMSwift2JavaGenerator { // Name. let javaName = switch decl.apiKind { - case .getter: "get\(decl.name.toCamelCase)" - case .setter: "set\(decl.name.toCamelCase)" + case .getter: decl.javaGetterName + case .setter: decl.javaSetterName case .function, .initializer: decl.name } diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 76d53d883..4b8a478a2 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -154,3 +154,21 @@ extension ImportedFunc: Hashable { return lhs === rhs } } + +extension ImportedFunc { + var javaGetterName: String { + let returnsBoolean = self.functionSignature.result.type.asNominalTypeDeclaration?.knownTypeKind == .bool + + if !returnsBoolean { + return "get\(self.name.toCamelCase)" + } else if !self.name.hasJavaBooleanNamingConvention { + return "is\(self.name.toCamelCase)" + } else { + return self.name.toCamelCase + } + } + + var javaSetterName: String { + "set\(self.name.toCamelCase)" + } +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index dce399a97..ec0fa9c44 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -79,6 +79,11 @@ extension JNISwift2JavaGenerator { printFunctionBinding(&printer, decl) printer.println() } + + for decl in analysis.importedGlobalVariables { + printFunctionBinding(&printer, decl) + printer.println() + } } } @@ -113,10 +118,17 @@ extension JNISwift2JavaGenerator { for initializer in decl.initializers { printInitializerBindings(&printer, initializer, type: decl) + printer.println() } for method in decl.methods { printFunctionBinding(&printer, method) + printer.println() + } + + for variable in decl.variables { + printFunctionBinding(&printer, variable) + printer.println() } printDestroyFunction(&printer, decl) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index f006b4a8b..179991df6 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -37,8 +37,15 @@ extension JNISwift2JavaGenerator { // Types with no parent will be outputted inside a "module" class. let parentName = decl.parentType?.asNominalType?.nominalTypeDecl.qualifiedName ?? swiftModuleName + // Name. + let javaName = switch decl.apiKind { + case .getter: decl.javaGetterName + case .setter: decl.javaSetterName + case .function, .initializer: decl.name + } + return TranslatedFunctionDecl( - name: decl.name, + name: javaName, parentName: parentName, translatedFunctionSignature: translatedFunctionSignature ) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 24532ca1a..c32e0e27f 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -87,6 +87,11 @@ extension JNISwift2JavaGenerator { printSwiftFunctionThunk(&printer, decl) printer.println() } + + for decl in analysis.importedGlobalVariables { + printSwiftFunctionThunk(&printer, decl) + printer.println() + } } private func printNominalTypeThunks(_ printer: inout CodePrinter, _ type: ImportedNominalType) throws { @@ -102,6 +107,11 @@ extension JNISwift2JavaGenerator { printer.println() } + for variable in type.variables { + printSwiftFunctionThunk(&printer, variable) + printer.println() + } + printDestroyFunctionThunk(&printer, type) } @@ -192,19 +202,34 @@ extension JNISwift2JavaGenerator { let translatedDecl = self.translatedDecl(for: decl) let swiftReturnType = decl.functionSignature.result.type - let downcallParameters = renderDowncallArguments( - swiftFunctionSignature: decl.functionSignature, - translatedFunctionSignature: translatedDecl.translatedFunctionSignature - ) let tryClause: String = decl.isThrowing ? "try " : "" - let functionDowncall = "\(tryClause)\(calleeName).\(decl.name)(\(downcallParameters))" + + let result: String + switch decl.apiKind { + case .function, .initializer: + let downcallParameters = renderDowncallArguments( + swiftFunctionSignature: decl.functionSignature, + translatedFunctionSignature: translatedDecl.translatedFunctionSignature + ) + result = "\(tryClause)\(calleeName).\(decl.name)(\(downcallParameters))" + + case .getter: + result = "\(tryClause)\(calleeName).\(decl.name)" + + case .setter: + guard let newValueParameter = decl.functionSignature.parameters.first else { + fatalError("Setter did not contain newValue parameter: \(decl)") + } + + result = "\(calleeName).\(decl.name) = \(renderJNIToSwiftConversion("newValue", type: newValueParameter.type))" + } let returnStatement = if swiftReturnType.isVoid { - functionDowncall + result } else { """ - let result = \(functionDowncall) + let result = \(result) return result.getJNIValue(in: environment) """ } @@ -320,6 +345,10 @@ extension JNISwift2JavaGenerator { } .joined(separator: ", ") } + + private func renderJNIToSwiftConversion(_ variableName: String, type: SwiftType) -> String { + "\(type)(fromJNI: \(variableName), in: environment!)" + } } extension String { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift index 8f0b31283..5cca79bf3 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift @@ -211,11 +211,11 @@ extension SwiftFunctionSignature { switch binding.accessorBlock?.accessors { case .getter(let getter): if let getter = getter.as(AccessorDeclSyntax.self) { - effectSpecifiers = Self.effectSpecifiers(from: getter) + effectSpecifiers = try Self.effectSpecifiers(from: getter) } case .accessors(let accessors): if let getter = accessors.first(where: { $0.accessorSpecifier.tokenKind == .keyword(.get) }) { - effectSpecifiers = Self.effectSpecifiers(from: getter) + effectSpecifiers = try Self.effectSpecifiers(from: getter) } default: break @@ -232,11 +232,14 @@ extension SwiftFunctionSignature { } } - private static func effectSpecifiers(from decl: AccessorDeclSyntax) -> [SwiftEffectSpecifier] { + private static func effectSpecifiers(from decl: AccessorDeclSyntax) throws -> [SwiftEffectSpecifier] { var effectSpecifiers = [SwiftEffectSpecifier]() if decl.effectSpecifiers?.throwsClause != nil { effectSpecifiers.append(.throws) } + if let asyncSpecifier = decl.effectSpecifiers?.asyncSpecifier { + throw SwiftFunctionTranslationError.async(asyncSpecifier) + } return effectSpecifiers } } @@ -254,7 +257,7 @@ extension VariableDeclSyntax { /// - Parameters: /// - binding the pattern binding in this declaration. func supportedAccessorKinds(binding: PatternBindingSyntax) -> SupportedAccessorKinds { - if self.bindingSpecifier == .keyword(.let) { + if self.bindingSpecifier.tokenKind == .keyword(.let) { return [.get] } diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java index cd8f2dd1f..1b6821ca4 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java @@ -28,6 +28,10 @@ public class ConfinedSwiftMemorySession implements ClosableSwiftArena { final ConfinedResourceList resources; + public ConfinedSwiftMemorySession() { + this(Thread.currentThread()); + } + public ConfinedSwiftMemorySession(Thread owner) { this.owner = owner; this.state = new AtomicInteger(ACTIVE); diff --git a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift new file mode 100644 index 000000000..f09789d40 --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift @@ -0,0 +1,386 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import Testing + +@Suite +struct JNIVariablesTests { + let membersSource = + """ + public class MyClass { + public let constant: Int64 + public var mutable: Int64 + public var computed: Int64 { + return 0 + } + public var computedThrowing: Int64 { + get throws { return 0 } + } + public var getterAndSetter: Int64 { + get { return 0 } + set { } + } + public var someBoolean: Bool + public let isBoolean: Bool + } + """ + + @Test + func constant_javaBindings() throws { + try assertOutput(input: membersSource, .jni, .java, expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public let constant: Int64 + * } + */ + public long getConstant() { + long selfPointer = this.pointer(); + return MyClass.$getConstant(selfPointer); + } + """, + """ + private static native long $getConstant(long selfPointer); + """ + ]) + } + + @Test + func constant_swiftThunks() throws { + try assertOutput( + input: membersSource, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024getConstant__J") + func Java_com_example_swift_MyClass__00024getConstant__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + let result = self$.pointee.constant + return result.getJNIValue(in: environment) + } + """ + ] + ) + } + + @Test + func mutable_javaBindings() throws { + try assertOutput( + input: membersSource, + .jni, + .java, + detectChunkByInitialLines: 8, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var mutable: Int64 + * } + */ + public long getMutable() { + long selfPointer = this.pointer(); + return MyClass.$getMutable(selfPointer); + } + """, + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var mutable: Int64 + * } + */ + public void setMutable(long newValue) { + long selfPointer = this.pointer(); + MyClass.$setMutable(newValue, selfPointer); + } + """, + """ + private static native long $getMutable(long selfPointer); + """, + """ + private static native void $setMutable(long newValue, long selfPointer); + """ + ] + ) + } + + @Test + func mutable_swiftThunks() throws { + try assertOutput( + input: membersSource, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024getMutable__J") + func Java_com_example_swift_MyClass__00024getMutable__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + let result = self$.pointee.mutable + return result.getJNIValue(in: environment) + } + """, + """ + @_cdecl("Java_com_example_swift_MyClass__00024setMutable__JJ") + func Java_com_example_swift_MyClass__00024setMutable__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, selfPointer: jlong) { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + self$.pointee.mutable = Int64(fromJNI: newValue, in: environment!) + } + """ + ] + ) + } + + @Test + func computed_javaBindings() throws { + try assertOutput( + input: membersSource, + .jni, + .java, + detectChunkByInitialLines: 8, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var computed: Int64 + * } + */ + public long getComputed() { + long selfPointer = this.pointer(); + return MyClass.$getComputed(selfPointer); + } + """, + """ + private static native long $getComputed(long selfPointer); + """, + ] + ) + } + + @Test + func computed_swiftThunks() throws { + try assertOutput( + input: membersSource, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024getComputed__J") + func Java_com_example_swift_MyClass__00024getComputed__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + let result = self$.pointee.computed + return result.getJNIValue(in: environment) + } + """, + ] + ) + } + + @Test + func computedThrowing_javaBindings() throws { + try assertOutput( + input: membersSource, + .jni, + .java, + detectChunkByInitialLines: 8, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var computedThrowing: Int64 + * } + */ + public long getComputedThrowing() throws Exception { + long selfPointer = this.pointer(); + return MyClass.$getComputedThrowing(selfPointer); + } + """, + """ + private static native long $getComputedThrowing(long selfPointer); + """, + ] + ) + } + + @Test + func computedThrowing_swiftThunks() throws { + try assertOutput( + input: membersSource, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024getComputedThrowing__J") + func Java_com_example_swift_MyClass__00024getComputedThrowing__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + do { + let result = try self$.pointee.computedThrowing + return result.getJNIValue(in: environment) + } catch { + environment.throwAsException(error) + return Int64.jniPlaceholderValue + } + } + """, + ] + ) + } + + @Test + func getterAndSetter_javaBindings() throws { + try assertOutput( + input: membersSource, + .jni, + .java, + detectChunkByInitialLines: 8, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var getterAndSetter: Int64 + * } + */ + public long getGetterAndSetter() { + long selfPointer = this.pointer(); + return MyClass.$getGetterAndSetter(selfPointer); + } + """, + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var getterAndSetter: Int64 + * } + */ + public void setGetterAndSetter(long newValue) { + long selfPointer = this.pointer(); + MyClass.$setGetterAndSetter(newValue, selfPointer); + } + """, + """ + private static native long $getGetterAndSetter(long selfPointer); + """, + """ + private static native void $setGetterAndSetter(long newValue, long selfPointer); + """ + ] + ) + } + + @Test + func getterAndSetter_swiftThunks() throws { + try assertOutput( + input: membersSource, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024getGetterAndSetter__J") + func Java_com_example_swift_MyClass__00024getGetterAndSetter__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + let result = self$.pointee.getterAndSetter + return result.getJNIValue(in: environment) + } + """, + """ + @_cdecl("Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ") + func Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, selfPointer: jlong) { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + self$.pointee.getterAndSetter = Int64(fromJNI: newValue, in: environment!) + } + """ + ] + ) + } + + @Test + func someBoolean_javaBindings() throws { + try assertOutput( + input: membersSource, + .jni, + .java, + detectChunkByInitialLines: 8, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var someBoolean: Bool + * } + */ + public boolean isSomeBoolean() { + long selfPointer = this.pointer(); + return MyClass.$isSomeBoolean(selfPointer); + } + """, + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var someBoolean: Bool + * } + */ + public void setSomeBoolean(boolean newValue) { + long selfPointer = this.pointer(); + MyClass.$setSomeBoolean(newValue, selfPointer); + } + """, + """ + private static native boolean $isSomeBoolean(long selfPointer); + """, + """ + private static native void $setSomeBoolean(boolean newValue, long selfPointer); + """ + ] + ) + } + + @Test + func boolean_swiftThunks() throws { + try assertOutput( + input: membersSource, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024isSomeBoolean__J") + func Java_com_example_swift_MyClass__00024isSomeBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jboolean { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + let result = self$.pointee.someBoolean + return result.getJNIValue(in: environment) + } + """, + """ + @_cdecl("Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ") + func Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, selfPointer: jlong) { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + self$.pointee.someBoolean = Bool(fromJNI: newValue, in: environment!) + } + """ + ] + ) + } +} From 9c0d7ebe061ae3da75bcbd9e7208bb28f9d92507 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Mon, 14 Jul 2025 04:53:31 +0200 Subject: [PATCH 031/141] [JExtract/JNI] Omit unsupported types instead of crashing (#313) --- .../Sources/MySwiftLibrary/MySwiftClass.swift | 1 + .../Sources/MySwiftLibrary/MySwiftClass.swift | 1 + ...t2JavaGenerator+JavaBindingsPrinting.swift | 16 ++++++-- ...ISwift2JavaGenerator+JavaTranslation.swift | 38 ++++++++++++------- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 19 ++++++++-- 5 files changed, 54 insertions(+), 21 deletions(-) diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index 386a72270..fd0ce488a 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -16,6 +16,7 @@ public class MySwiftClass { let x: Int64 let y: Int64 + public let byte: UInt8 = 0 public let constant: Int64 = 100 public var mutable: Int64 = 0 public var product: Int64 { diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index 97f5149ec..306610456 100644 --- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -14,6 +14,7 @@ public class MySwiftClass { + public let byte: UInt8 = 0 public var len: Int public var cap: Int diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index ec0fa9c44..80b477605 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -176,6 +176,11 @@ extension JNISwift2JavaGenerator { } private func printFunctionBinding(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + guard let _ = translatedDecl(for: decl) else { + // Failed to translate. Skip. + return + } + if decl.isStatic || decl.isInitializer || !decl.hasParent { printStaticFunctionBinding(&printer, decl) } else { @@ -196,7 +201,7 @@ extension JNISwift2JavaGenerator { /// and passes it down to another native function along with the arguments /// to call the Swift implementation. private func printMemberMethodBindings(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - let translatedDecl = translatedDecl(for: decl) + let translatedDecl = translatedDecl(for: decl)! // We will only call this method if we can translate the decl. printDeclDocumentation(&printer, decl) printer.printBraceBlock("public \(renderFunctionSignature(decl))") { printer in @@ -220,7 +225,10 @@ extension JNISwift2JavaGenerator { } private func printInitializerBindings(_ printer: inout CodePrinter, _ decl: ImportedFunc, type: ImportedNominalType) { - let translatedDecl = translatedDecl(for: decl) + guard let translatedDecl = translatedDecl(for: decl) else { + // Failed to translate. Skip. + return + } printDeclDocumentation(&printer, decl) printer.printBraceBlock("public static \(renderFunctionSignature(decl))") { printer in @@ -275,7 +283,9 @@ extension JNISwift2JavaGenerator { /// `func method(x: Int, y: Int) -> Int` becomes /// `long method(long x, long y)` private func renderFunctionSignature(_ decl: ImportedFunc) -> String { - let translatedDecl = translatedDecl(for: decl) + guard let translatedDecl = translatedDecl(for: decl) else { + fatalError("Unable to render function signature for a function that cannot be translated: \(decl)") + } let resultType = translatedDecl.translatedFunctionSignature.resultType var parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 179991df6..9dc433d43 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -17,13 +17,19 @@ import JavaTypes extension JNISwift2JavaGenerator { func translatedDecl( for decl: ImportedFunc - ) -> TranslatedFunctionDecl { + ) -> TranslatedFunctionDecl? { if let cached = translatedDecls[decl] { return cached } - let translation = JavaTranslation(swiftModuleName: self.swiftModuleName) - let translated = translation.translate(decl) + let translated: TranslatedFunctionDecl? + do { + let translation = JavaTranslation(swiftModuleName: swiftModuleName) + translated = try translation.translate(decl) + } catch { + self.logger.debug("Failed to translate: '\(decl.swiftDecl.qualifiedNameForDebug)'; \(error)") + translated = nil + } translatedDecls[decl] = translated return translated @@ -32,8 +38,8 @@ extension JNISwift2JavaGenerator { struct JavaTranslation { let swiftModuleName: String - func translate(_ decl: ImportedFunc) -> TranslatedFunctionDecl { - let translatedFunctionSignature = translate(functionSignature: decl.functionSignature) + func translate(_ decl: ImportedFunc) throws -> TranslatedFunctionDecl { + let translatedFunctionSignature = try translate(functionSignature: decl.functionSignature) // Types with no parent will be outputted inside a "module" class. let parentName = decl.parentType?.asNominalType?.nominalTypeDecl.qualifiedName ?? swiftModuleName @@ -51,31 +57,31 @@ extension JNISwift2JavaGenerator { ) } - func translate(functionSignature: SwiftFunctionSignature, isInitializer: Bool = false) -> TranslatedFunctionSignature { - let parameters = functionSignature.parameters.enumerated().map { idx, param in + func translate(functionSignature: SwiftFunctionSignature, isInitializer: Bool = false) throws -> TranslatedFunctionSignature { + let parameters = try functionSignature.parameters.enumerated().map { idx, param in let parameterName = param.parameterName ?? "arg\(idx))" - return translate(swiftParam: param, parameterName: parameterName) + return try translate(swiftParam: param, parameterName: parameterName) } - return TranslatedFunctionSignature( + return try TranslatedFunctionSignature( parameters: parameters, resultType: translate(swiftType: functionSignature.result.type) ) } - func translate(swiftParam: SwiftParameter, parameterName: String) -> JavaParameter { - return JavaParameter( + func translate(swiftParam: SwiftParameter, parameterName: String) throws -> JavaParameter { + return try JavaParameter( name: parameterName, type: translate(swiftType: swiftParam.type) ) } - func translate(swiftType: SwiftType) -> JavaType { + func translate(swiftType: SwiftType) throws -> JavaType { switch swiftType { case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { guard let javaType = translate(knownType: knownType) else { - fatalError("unsupported known type: \(knownType)") + throw JavaTranslationError.unsupportedSwiftType(swiftType) } return javaType } @@ -86,7 +92,7 @@ extension JNISwift2JavaGenerator { return .void case .metatype, .optional, .tuple, .function, .existential, .opaque: - fatalError("unsupported type: \(self)") + throw JavaTranslationError.unsupportedSwiftType(swiftType) } } @@ -127,4 +133,8 @@ extension JNISwift2JavaGenerator { let parameters: [JavaParameter] let resultType: JavaType } + + enum JavaTranslationError: Error { + case unsupportedSwiftType(SwiftType) + } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index c32e0e27f..30db69d13 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -116,7 +116,11 @@ extension JNISwift2JavaGenerator { } private func printInitializerThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - let translatedDecl = translatedDecl(for: decl) + guard let translatedDecl = translatedDecl(for: decl) else { + // Failed to translate. Skip. + return + } + let typeName = translatedDecl.parentName printCDecl( @@ -146,6 +150,11 @@ extension JNISwift2JavaGenerator { _ printer: inout CodePrinter, _ decl: ImportedFunc ) { + guard let _ = translatedDecl(for: decl) else { + // Failed to translate. Skip. + return + } + // Free functions does not have a parent if decl.isStatic || !decl.hasParent { self.printSwiftStaticFunctionThunk(&printer, decl) @@ -155,7 +164,7 @@ extension JNISwift2JavaGenerator { } private func printSwiftStaticFunctionThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - let translatedDecl = self.translatedDecl(for: decl) + let translatedDecl = self.translatedDecl(for: decl)! // We will only call this method if we can translate the decl. printCDecl( &printer, @@ -172,7 +181,7 @@ extension JNISwift2JavaGenerator { } private func printSwiftMemberFunctionThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - let translatedDecl = self.translatedDecl(for: decl) + let translatedDecl = self.translatedDecl(for: decl)! // We will only call this method if can translate the decl. let swiftParentName = decl.parentType!.asNominalTypeDeclaration!.qualifiedName printCDecl( @@ -199,7 +208,9 @@ extension JNISwift2JavaGenerator { _ decl: ImportedFunc, calleeName: String ) { - let translatedDecl = self.translatedDecl(for: decl) + guard let translatedDecl = self.translatedDecl(for: decl) else { + fatalError("Cannot print function downcall for a function that can't be translated: \(decl)") + } let swiftReturnType = decl.functionSignature.result.type let tryClause: String = decl.isThrowing ? "try " : "" From 5ed029daa1a3567b662a769edf18aa74f8631822 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 14 Jul 2025 02:35:22 -0400 Subject: [PATCH 032/141] [JavaKit] Don't include JVM path for Android (#309) --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 403617daf..81d53b1d4 100644 --- a/Package.swift +++ b/Package.swift @@ -226,7 +226,7 @@ let package = Package( exclude: ["swift-java.config"], swiftSettings: [ .swiftLanguageMode(.v5), - .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"], .when(platforms: [.macOS, .linux, .windows])) ], linkerSettings: [ .unsafeFlags( From 3b63202effbb2fdc4a1705dc20b1369c2d235791 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 15 Jul 2025 08:49:47 +0200 Subject: [PATCH 033/141] add struct tests (#316) --- .../MySwiftLibrary/MySwiftStruct.swift | 33 +++ .../com/example/swift/HelloJava2SwiftJNI.java | 6 + .../com/example/swift/MySwiftStructTest.java | 50 +++++ .../JNI/JNIStructTests.swift | 188 ++++++++++++++++++ 4 files changed, 277 insertions(+) create mode 100644 Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift create mode 100644 Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java create mode 100644 Tests/JExtractSwiftTests/JNI/JNIStructTests.swift diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift new file mode 100644 index 000000000..00e2533c8 --- /dev/null +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +public struct MySwiftStruct { + private var cap: Int64 + public var len: Int64 + + public init(cap: Int64, len: Int64) { + self.cap = cap + self.len = len + } + + public func getCapacity() -> Int64 { + self.cap + } + + public mutating func increaseCap(by value: Int64) -> Int64 { + precondition(value > 0) + self.cap += value + return self.cap + } +} diff --git a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java index 8e7ca2844..21cad317a 100644 --- a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java +++ b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java @@ -52,6 +52,12 @@ static void examples() { } catch (Exception e) { System.out.println("Caught exception: " + e.getMessage()); } + + MySwiftStruct myStruct = MySwiftStruct.init(12, 34, arena); + System.out.println("myStruct.cap: " + myStruct.getCapacity()); + System.out.println("myStruct.len: " + myStruct.getLen()); + myStruct.increaseCap(10); + System.out.println("myStruct.cap after increase: " + myStruct.getCapacity()); } System.out.println("DONE."); diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java new file mode 100644 index 000000000..9eeaf0297 --- /dev/null +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java @@ -0,0 +1,50 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.ConfinedSwiftMemorySession; + +import static org.junit.jupiter.api.Assertions.*; + +public class MySwiftStructTest { + @Test + void init() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftStruct s = MySwiftStruct.init(1337, 42, arena); + assertEquals(1337, s.getCapacity()); + assertEquals(42, s.getLen()); + } + } + + @Test + void getAndSetLen() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftStruct s = MySwiftStruct.init(1337, 42, arena); + s.setLen(100); + assertEquals(100, s.getLen()); + } + } + + @Test + void increaseCap() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftStruct s = MySwiftStruct.init(1337, 42, arena); + long newCap = s.increaseCap(10); + assertEquals(1347, newCap); + assertEquals(1347, s.getCapacity()); + } + } +} \ No newline at end of file diff --git a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift new file mode 100644 index 000000000..0a883ed11 --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift @@ -0,0 +1,188 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import Testing + +@Suite +struct JNIStructTests { + let source = """ + public struct MyStruct { + let x: Int64 + let y: Int64 + + public init(x: Int64, y: Int64) { + self.x = y + self.y = y + } + + public func doSomething(x: Int64) {} + } + """ + + @Test + func generatesJavaClass() throws { + try assertOutput(input: source, .jni, .java, expectedChunks: [ + """ + // Generated by jextract-swift + // Swift module: SwiftModule + + package com.example.swift; + + import org.swift.swiftkit.core.*; + import org.swift.swiftkit.core.util.*; + + public final class MyStruct extends JNISwiftInstance { + static final String LIB_NAME = "SwiftModule"; + + @SuppressWarnings("unused") + private static final boolean INITIALIZED_LIBS = initializeLibs(); + static boolean initializeLibs() { + System.loadLibrary(LIB_NAME); + return true; + } + + public MyStruct(long selfPointer, SwiftArena swiftArena) { + super(selfPointer, swiftArena); + } + """, + """ + private static native void $destroy(long selfPointer); + """, + """ + @Override + protected Runnable $createDestroyFunction() { + long $selfPointer = this.pointer(); + return new Runnable() { + @Override + public void run() { + MyStruct.$destroy($selfPointer); + } + }; + } + """ + ]) + } + + @Test + func initializer_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public init(x: Int64, y: Int64) + * } + */ + public static MyStruct init(long x, long y, SwiftArena swiftArena$) { + long selfPointer = MyStruct.allocatingInit(x, y); + return new MyStruct(selfPointer, swiftArena$); + } + """, + """ + private static native long allocatingInit(long x, long y); + """, + ] + ) + } + + @Test + func initializer_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyStruct_allocatingInit__JJ") + func Java_com_example_swift_MyStruct_allocatingInit__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jlong) -> jlong { + let selfPointer = UnsafeMutablePointer.allocate(capacity: 1) + selfPointer.initialize(to: MyStruct(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) + return Int64(Int(bitPattern: selfPointer)).getJNIValue(in: environment) + } + """ + ] + ) + } + + @Test + func destroyFunction_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyStruct__00024destroy__J") + func Java_com_example_swift_MyStruct__00024destroy__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) { + let pointer = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + pointer.deinitialize(count: 1) + pointer.deallocate() + } + """ + ] + ) + } + + @Test + func memberMethod_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func doSomething(x: Int64) + * } + */ + public void doSomething(long x) { + long selfPointer = this.pointer(); + MyStruct.$doSomething(x, selfPointer); + } + """, + """ + private static native void $doSomething(long x, long selfPointer); + """ + ] + ) + } + + @Test + func memberMethod_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyStruct__00024doSomething__JJ") + func Java_com_example_swift_MyStruct__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, selfPointer: jlong) { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!)) + } + """, + ] + ) + } +} From 78b3e200e2946d70c840a38353c505f92fcfc960 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 15 Jul 2025 08:52:03 +0200 Subject: [PATCH 034/141] conform `Int` to `JavaValue` (#317) --- .../BridgedValues/JavaValue+Integers.swift | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/Sources/JavaKit/BridgedValues/JavaValue+Integers.swift b/Sources/JavaKit/BridgedValues/JavaValue+Integers.swift index 01a7bdbac..1af1b5494 100644 --- a/Sources/JavaKit/BridgedValues/JavaValue+Integers.swift +++ b/Sources/JavaKit/BridgedValues/JavaValue+Integers.swift @@ -285,3 +285,122 @@ extension Int64: JavaValue { 0 } } + +#if _pointerBitWidth(_32) +extension Int: JavaValue { + + public typealias JNIType = jint + + public static var jvalueKeyPath: WritableKeyPath { \.i } + + public func getJNIValue(in environment: JNIEnvironment) -> JNIType { JNIType(self) } + + public init(fromJNI value: JNIType, in environment: JNIEnvironment) { + self = Int(value) + } + + public static var javaType: JavaType { .int } + + public static func jniMethodCall( + in environment: JNIEnvironment + ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) { + environment.interface.CallIntMethodA + } + + public static func jniFieldGet(in environment: JNIEnvironment) -> JNIFieldGet { + environment.interface.GetIntField + } + + public static func jniFieldSet(in environment: JNIEnvironment) -> JNIFieldSet { + environment.interface.SetIntField + } + + public static func jniStaticMethodCall( + in environment: JNIEnvironment + ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) { + environment.interface.CallStaticIntMethodA + } + + public static func jniStaticFieldGet(in environment: JNIEnvironment) -> JNIStaticFieldGet { + environment.interface.GetStaticIntField + } + + public static func jniStaticFieldSet(in environment: JNIEnvironment) -> JNIStaticFieldSet { + environment.interface.SetStaticIntField + } + + public static func jniNewArray(in environment: JNIEnvironment) -> JNINewArray { + environment.interface.NewIntArray + } + + public static func jniGetArrayRegion(in environment: JNIEnvironment) -> JNIGetArrayRegion { + environment.interface.GetIntArrayRegion + } + + public static func jniSetArrayRegion(in environment: JNIEnvironment) -> JNISetArrayRegion { + environment.interface.SetIntArrayRegion + } + + public static var jniPlaceholderValue: jint { + 0 + } +} +#elseif _pointerBitWidth(_64) +extension Int: JavaValue { + public typealias JNIType = jlong + + public static var jvalueKeyPath: WritableKeyPath { \.j } + + public func getJNIValue(in environment: JNIEnvironment) -> JNIType { JNIType(self) } + + public init(fromJNI value: JNIType, in environment: JNIEnvironment) { + self = Int(value) + } + + public static var javaType: JavaType { .long } + + public static func jniMethodCall( + in environment: JNIEnvironment + ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) { + environment.interface.CallLongMethodA + } + + public static func jniFieldGet(in environment: JNIEnvironment) -> JNIFieldGet { + environment.interface.GetLongField + } + + public static func jniFieldSet(in environment: JNIEnvironment) -> JNIFieldSet { + environment.interface.SetLongField + } + + public static func jniStaticMethodCall( + in environment: JNIEnvironment + ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) { + environment.interface.CallStaticLongMethodA + } + + public static func jniStaticFieldGet(in environment: JNIEnvironment) -> JNIStaticFieldGet { + environment.interface.GetStaticLongField + } + + public static func jniStaticFieldSet(in environment: JNIEnvironment) -> JNIStaticFieldSet { + environment.interface.SetStaticLongField + } + + public static func jniNewArray(in environment: JNIEnvironment) -> JNINewArray { + environment.interface.NewLongArray + } + + public static func jniGetArrayRegion(in environment: JNIEnvironment) -> JNIGetArrayRegion { + environment.interface.GetLongArrayRegion + } + + public static func jniSetArrayRegion(in environment: JNIEnvironment) -> JNISetArrayRegion { + environment.interface.SetLongArrayRegion + } + + public static var jniPlaceholderValue: jlong { + 0 + } +} +#endif From 19a67851589ea61e46768e90b4ec878c28052eed Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Tue, 15 Jul 2025 17:33:07 -0700 Subject: [PATCH 035/141] [JExtract/FFM] Bridge Optional parameters (#320) --- .../MySwiftLibrary/MySwiftLibrary.swift | 17 ++ .../com/example/swift/HelloJava2Swift.java | 4 + .../com/example/swift/OptionalImportTest.java | 35 ++++ .../FFM/CDeclLowering/CRepresentation.swift | 2 +- ...Swift2JavaGenerator+FunctionLowering.swift | 91 +++++++++- .../JExtractSwiftLib/FFM/ConversionStep.swift | 8 +- ...MSwift2JavaGenerator+JavaTranslation.swift | 85 +++++++++- .../FFM/FFMSwift2JavaGenerator.swift | 4 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 2 +- .../SwiftTypes/SwiftKnownModules.swift | 2 + .../SwiftTypes/SwiftKnownTypeDecls.swift | 1 + .../org/swift/swiftkit/ffm/SwiftRuntime.java | 40 ++++- .../FunctionLoweringTests.swift | 21 +++ .../OptionalImportTests.swift | 156 ++++++++++++++++++ 14 files changed, 449 insertions(+), 19 deletions(-) create mode 100644 Samples/SwiftKitSampleApp/src/test/java/com/example/swift/OptionalImportTest.java create mode 100644 Tests/JExtractSwiftTests/OptionalImportTests.swift diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index 1bcca4fd2..cdd61c122 100644 --- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -68,6 +68,23 @@ public func globalReceiveSomeDataProtocol(data: some DataProtocol) -> Int { return data.count } +public func globalReceiveOptional(o1: Int?, o2: (some DataProtocol)?) -> Int { + switch (o1, o2) { + case (nil, nil): + p(", ") + return 0 + case (let v1?, nil): + p("\(v1), ") + return 1 + case (nil, let v2?): + p(", \(v2)") + return 2 + case (let v1?, let v2?): + p("\(v1), \(v2)") + return 3 + } +} + // ==== Internal helpers func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: String = #function) { diff --git a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java index 821b228e9..3b083cc0b 100644 --- a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java +++ b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java @@ -22,6 +22,9 @@ import org.swift.swiftkit.ffm.AllocatingSwiftArena; import org.swift.swiftkit.ffm.SwiftRuntime; +import java.util.Optional; +import java.util.OptionalLong; + public class HelloJava2Swift { public static void main(String[] args) { @@ -95,6 +98,7 @@ static void examples() { var bytes = arena.allocateFrom("hello"); var dat = Data.init(bytes, bytes.byteSize(), arena); MySwiftLibrary.globalReceiveSomeDataProtocol(dat); + MySwiftLibrary.globalReceiveOptional(OptionalLong.of(12), Optional.of(dat)); } diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/OptionalImportTest.java b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/OptionalImportTest.java new file mode 100644 index 000000000..57e8dba61 --- /dev/null +++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/OptionalImportTest.java @@ -0,0 +1,35 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.ffm.AllocatingSwiftArena; + +import java.util.Optional; +import java.util.OptionalLong; + +import static org.junit.jupiter.api.Assertions.*; + +public class OptionalImportTest { + @Test + void test_Optional_receive() { + try (var arena = AllocatingSwiftArena.ofConfined()) { + var origBytes = arena.allocateFrom("foobar"); + var data = Data.init(origBytes, origBytes.byteSize(), arena); + assertEquals(0, MySwiftLibrary.globalReceiveOptional(OptionalLong.empty(), Optional.empty())); + assertEquals(3, MySwiftLibrary.globalReceiveOptional(OptionalLong.of(12), Optional.of(data))); + } + } +} diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift index 4e44e74ba..c2bb53abd 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift @@ -125,7 +125,7 @@ extension SwiftKnownTypeDeclKind { .qualified(const: true, volatile: false, type: .void) ) case .void: .void - case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string, .data, .dataProtocol: + case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string, .data, .dataProtocol, .optional: nil } } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index 0c6c707c9..bb9941642 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -194,7 +194,7 @@ struct CdeclLowering { ) case .unsafeBufferPointer, .unsafeMutableBufferPointer: - guard let genericArgs = type.asNominalType?.genericArguments, genericArgs.count == 1 else { + guard let genericArgs = nominal.genericArguments, genericArgs.count == 1 else { throw LoweringError.unhandledType(type) } // Typed pointers are lowered to (raw-pointer, count) pair. @@ -253,6 +253,12 @@ struct CdeclLowering { ] )) + case .optional: + guard let genericArgs = nominal.genericArguments, genericArgs.count == 1 else { + throw LoweringError.unhandledType(type) + } + return try lowerOptionalParameter(genericArgs[0], convention: convention, parameterName: parameterName) + case .string: // 'String' is passed in by C string. i.e. 'UnsafePointer' ('const uint8_t *') if knownType == .string { @@ -336,8 +342,79 @@ struct CdeclLowering { } throw LoweringError.unhandledType(type) - case .optional: - throw LoweringError.unhandledType(type) + case .optional(let wrapped): + return try lowerOptionalParameter(wrapped, convention: convention, parameterName: parameterName) + } + } + + /// Lower a Swift Optional to cdecl function type. + /// + /// - Parameters: + /// - fn: the Swift function type to lower. + func lowerOptionalParameter( + _ wrappedType: SwiftType, + convention: SwiftParameterConvention, + parameterName: String + ) throws -> LoweredParameter { + // If there is a 1:1 mapping between this Swift type and a C type, lower it to 'UnsafePointer?' + if let _ = try? CType(cdeclType: wrappedType) { + return LoweredParameter( + cdeclParameters: [ + SwiftParameter(convention: .byValue, parameterName: parameterName, type: .optional(knownTypes.unsafePointer(wrappedType))) + ], + conversion: .pointee(.optionalChain(.placeholder)) + ) + } + + switch wrappedType { + case .nominal(let nominal): + if let knownType = nominal.nominalTypeDecl.knownTypeKind { + switch knownType { + case .data: + break + case .unsafeRawPointer, .unsafeMutableRawPointer: + throw LoweringError.unhandledType(.optional(wrappedType)) + case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: + throw LoweringError.unhandledType(.optional(wrappedType)) + case .unsafePointer, .unsafeMutablePointer: + throw LoweringError.unhandledType(.optional(wrappedType)) + case .unsafeBufferPointer, .unsafeMutableBufferPointer: + throw LoweringError.unhandledType(.optional(wrappedType)) + case .void, .string: + throw LoweringError.unhandledType(.optional(wrappedType)) + case .dataProtocol: + throw LoweringError.unhandledType(.optional(wrappedType)) + default: + // Unreachable? Should be handled by `CType(cdeclType:)` lowering above. + throw LoweringError.unhandledType(.optional(wrappedType)) + } + } + + // Lower arbitrary nominal to `UnsafeRawPointer?` + return LoweredParameter( + cdeclParameters: [ + SwiftParameter(convention: .byValue, parameterName: parameterName, type: .optional(knownTypes.unsafeRawPointer)) + ], + conversion: .pointee(.typedPointer(.optionalChain(.placeholder), swiftType: wrappedType)) + ) + + case .existential(let proto), .opaque(let proto): + if + let knownProtocol = proto.asNominalTypeDeclaration?.knownTypeKind, + let concreteTy = knownTypes.representativeType(of: knownProtocol) + { + return try lowerOptionalParameter(concreteTy, convention: convention, parameterName: parameterName) + } + throw LoweringError.unhandledType(.optional(wrappedType)) + + case .tuple(let tuple): + if tuple.count == 1 { + return try lowerOptionalParameter(tuple[0], convention: convention, parameterName: parameterName) + } + throw LoweringError.unhandledType(.optional(wrappedType)) + + case .function, .metatype, .optional: + throw LoweringError.unhandledType(.optional(wrappedType)) } } @@ -527,13 +604,13 @@ struct CdeclLowering { case .void: return LoweredResult(cdeclResultType: .void, cdeclOutParameters: [], conversion: .placeholder) - case .string: - // Returning string is not supported at this point. - throw LoweringError.unhandledType(type) - case .data: break + case .string, .optional: + // Not supported at this point. + throw LoweringError.unhandledType(type) + default: // Unreachable? Should be handled by `CType(cdeclType:)` lowering above. throw LoweringError.unhandledType(type) diff --git a/Sources/JExtractSwiftLib/FFM/ConversionStep.swift b/Sources/JExtractSwiftLib/FFM/ConversionStep.swift index c7fa53e39..656d9dd99 100644 --- a/Sources/JExtractSwiftLib/FFM/ConversionStep.swift +++ b/Sources/JExtractSwiftLib/FFM/ConversionStep.swift @@ -62,6 +62,8 @@ enum ConversionStep: Equatable { indirect case member(ConversionStep, member: String) + indirect case optionalChain(ConversionStep) + /// Count the number of times that the placeholder occurs within this /// conversion step. var placeholderCount: Int { @@ -71,7 +73,7 @@ enum ConversionStep: Equatable { .typedPointer(let inner, swiftType: _), .unsafeCastPointer(let inner, swiftType: _), .populatePointer(name: _, assumingType: _, to: let inner), - .member(let inner, member: _): + .member(let inner, member: _), .optionalChain(let inner): inner.placeholderCount case .initialize(_, arguments: let arguments): arguments.reduce(0) { $0 + $1.argument.placeholderCount } @@ -175,6 +177,10 @@ enum ConversionStep: Equatable { } return nil + case .optionalChain(let step): + let inner = step.asExprSyntax(placeholder: placeholder, bodyItems: &bodyItems) + return ExprSyntax(OptionalChainingExprSyntax(expression: inner!)) + case .closureLowering(let parameterSteps, let resultStep): var body: [CodeBlockItemSyntax] = [] diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index e548db5c3..9f6581888 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -238,7 +238,6 @@ extension FFMSwift2JavaGenerator { throw JavaTranslationError.unhandledType(type) } - /// Translate a Swift API signature to the user-facing Java API signature. /// /// Note that the result signature is for the high-level Java API, not the @@ -349,6 +348,12 @@ extension FFMSwift2JavaGenerator { ]) ) + case .optional: + guard let genericArgs = swiftNominalType.genericArguments, genericArgs.count == 1 else { + throw JavaTranslationError.unhandledType(swiftType) + } + return try translateOptionalParameter(wrappedType: genericArgs[0], convention: convention, parameterName: parameterName, loweredParam: loweredParam, methodName: methodName) + case .string: return TranslatedParameter( javaParameters: [ @@ -414,8 +419,81 @@ extension FFMSwift2JavaGenerator { // Otherwise, not supported yet. throw JavaTranslationError.unhandledType(swiftType) - case .optional: - throw JavaTranslationError.unhandledType(swiftType) + case .optional(let wrapped): + return try translateOptionalParameter(wrappedType: wrapped, convention: convention, parameterName: parameterName, loweredParam: loweredParam, methodName: methodName) + } + } + + /// Translate an Optional Swift API parameter to the user-facing Java API parameter. + func translateOptionalParameter( + wrappedType swiftType: SwiftType, + convention: SwiftParameterConvention, + parameterName: String, + loweredParam: LoweredParameter, + methodName: String + ) throws -> TranslatedParameter { + // If there is a 1:1 mapping between this Swift type and a C type, that can + // be expressed as a Java primitive type. + if let cType = try? CType(cdeclType: swiftType) { + var (translatedClass, lowerFunc) = switch cType.javaType { + case .int: ("OptionalInt", "toOptionalSegmentInt") + case .long: ("OptionalLong", "toOptionalSegmentLong") + case .double: ("OptionalDouble", "toOptionalSegmentDouble") + case .boolean: ("Optional", "toOptionalSegmentBoolean") + case .byte: ("Optional", "toOptionalSegmentByte") + case .char: ("Optional", "toOptionalSegmentCharacter") + case .short: ("Optional", "toOptionalSegmentShort") + case .float: ("Optional", "toOptionalSegmentFloat") + default: + throw JavaTranslationError.unhandledType(.optional(swiftType)) + } + return TranslatedParameter( + javaParameters: [ + JavaParameter(name: parameterName, type: JavaType(className: translatedClass)) + ], + conversion: .call(.placeholder, function: "SwiftRuntime.\(lowerFunc)", withArena: true) + ) + } + + switch swiftType { + case .nominal(let nominal): + if let knownType = nominal.nominalTypeDecl.knownTypeKind { + switch knownType { + case .data, .dataProtocol: + break + default: + throw JavaTranslationError.unhandledType(.optional(swiftType)) + } + } + + let translatedTy = try self.translate(swiftType: swiftType) + return TranslatedParameter( + javaParameters: [ + JavaParameter(name: parameterName, type: JavaType(className: "Optional<\(translatedTy.description)>")) + ], + conversion: .call(.placeholder, function: "SwiftRuntime.toOptionalSegmentInstance", withArena: false) + ) + case .existential(let proto), .opaque(let proto): + if + let knownProtocol = proto.asNominalTypeDeclaration?.knownTypeKind, + let concreteTy = knownTypes.representativeType(of: knownProtocol) + { + return try translateOptionalParameter( + wrappedType: concreteTy, + convention: convention, + parameterName: parameterName, + loweredParam: loweredParam, + methodName: methodName + ) + } + throw JavaTranslationError.unhandledType(.optional(swiftType)) + case .tuple(let tuple): + if tuple.count == 1 { + return try translateOptionalParameter(wrappedType: tuple[0], convention: convention, parameterName: parameterName, loweredParam: loweredParam, methodName: methodName) + } + throw JavaTranslationError.unhandledType(.optional(swiftType)) + default: + throw JavaTranslationError.unhandledType(.optional(swiftType)) } } @@ -585,7 +663,6 @@ extension FFMSwift2JavaGenerator.TranslatedFunctionSignature { } } - extension CType { /// Map lowered C type to Java type for FFM binding. var javaType: JavaType { diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index 6cf80d9b8..a24dabb1a 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -101,9 +101,7 @@ extension FFMSwift2JavaGenerator { // Necessary for native calls and type mapping "java.lang.foreign.*", "java.lang.invoke.*", - "java.util.Arrays", - "java.util.stream.Collectors", - "java.util.concurrent.atomic.*", + "java.util.*", "java.nio.charset.StandardCharsets", ] } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 9dc433d43..2e3ffff86 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -112,7 +112,7 @@ extension JNISwift2JavaGenerator { .unsafeRawPointer, .unsafeMutableRawPointer, .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, - .unsafeBufferPointer, .unsafeMutableBufferPointer, .data, .dataProtocol: + .unsafeBufferPointer, .unsafeMutableBufferPointer, .optional, .data, .dataProtocol: nil } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift index 47abb7f09..938d0c4aa 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift @@ -76,6 +76,8 @@ private let swiftSourceFile: SourceFileSyntax = """ public struct UnsafeBufferPointer {} public struct UnsafeMutableBufferPointer {} + public struct Optional {} + // FIXME: Support 'typealias Void = ()' public struct Void {} diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift index d6af59208..11ff25c4d 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift @@ -36,6 +36,7 @@ enum SwiftKnownTypeDeclKind: String, Hashable { case unsafeMutablePointer = "Swift.UnsafeMutablePointer" case unsafeBufferPointer = "Swift.UnsafeBufferPointer" case unsafeMutableBufferPointer = "Swift.UnsafeMutableBufferPointer" + case optional = "Swift.Optional" case void = "Swift.Void" case string = "Swift.String" diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java index f734bbef2..dc18b4454 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java @@ -14,14 +14,14 @@ package org.swift.swiftkit.ffm; +import org.swift.swiftkit.core.SwiftInstance; import org.swift.swiftkit.core.util.PlatformUtils; import java.lang.foreign.*; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; -import java.util.Arrays; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; import static org.swift.swiftkit.core.util.StringUtils.stripPrefix; @@ -425,6 +425,42 @@ public static MemorySegment toCString(String str, Arena arena) { return arena.allocateFrom(str); } + public static MemorySegment toOptionalSegmentInt(OptionalInt opt, Arena arena) { + return opt.isPresent() ? arena.allocateFrom(ValueLayout.JAVA_INT, opt.getAsInt()) : MemorySegment.NULL; + } + + public static MemorySegment toOptionalSegmentLong(OptionalLong opt, Arena arena) { + return opt.isPresent() ? arena.allocateFrom(ValueLayout.JAVA_LONG, opt.getAsLong()) : MemorySegment.NULL; + } + + public static MemorySegment toOptionalSegmentDouble(OptionalDouble opt, Arena arena) { + return opt.isPresent() ? arena.allocateFrom(ValueLayout.JAVA_DOUBLE, opt.getAsDouble()) : MemorySegment.NULL; + } + + public static MemorySegment toOptionalSegmentBoolean(Optional opt, Arena arena) { + return opt.map(val -> arena.allocateFrom(ValueLayout.JAVA_BYTE, (byte) (val ? 1 : 0))).orElse(MemorySegment.NULL); + } + + public static MemorySegment toOptionalSegmentByte(Optional opt, Arena arena) { + return opt.map(val -> arena.allocateFrom(ValueLayout.JAVA_BYTE, val)).orElse(MemorySegment.NULL); + } + + public static MemorySegment toOptionalSegmentCharacter(Optional opt, Arena arena) { + return opt.map(val -> arena.allocateFrom(ValueLayout.JAVA_CHAR, val)).orElse(MemorySegment.NULL); + } + + public static MemorySegment toOptionalSegmentShort(Optional opt, Arena arena) { + return opt.map(val -> arena.allocateFrom(ValueLayout.JAVA_SHORT, val)).orElse(MemorySegment.NULL); + } + + public static MemorySegment toOptionalSegmentFloat(Optional opt, Arena arena) { + return opt.map(val -> arena.allocateFrom(ValueLayout.JAVA_FLOAT, val)).orElse(MemorySegment.NULL); + } + + public static MemorySegment toOptionalSegmentInstance(Optional opt) { + return opt.map(instance -> instance.$memorySegment()).orElse(MemorySegment.NULL); + } + private static class swift_getTypeName { /** diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift index 9422bbdb0..8419ec61c 100644 --- a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift +++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift @@ -383,6 +383,27 @@ final class FunctionLoweringTests { ) } + @Test("Lowering optional primitives") + func lowerOptionalParameters() throws { + try assertLoweredFunction( + """ + func fn(a1: Optional, a2: Int?, a3: Point?, a4: (some DataProtocol)?) + """, + sourceFile: """ + import Foundation + struct Point {} + """, + expectedCDecl:""" + @_cdecl("c_fn") + public func c_fn(_ a1: UnsafePointer?, _ a2: UnsafePointer?, _ a3: UnsafeRawPointer?, _ a4: UnsafeRawPointer?) { + fn(a1: a1?.pointee, a2: a2?.pointee, a3: a3?.assumingMemoryBound(to: Point.self).pointee, a4: a4?.assumingMemoryBound(to: Data.self).pointee) + } + """, + expectedCFunction: """ + void c_fn(const ptrdiff_t *a1, const ptrdiff_t *a2, const void *a3, const void *a4) + """) + } + @Test("Lowering read accessor") func lowerGlobalReadAccessor() throws { try assertLoweredVariableAccessor( diff --git a/Tests/JExtractSwiftTests/OptionalImportTests.swift b/Tests/JExtractSwiftTests/OptionalImportTests.swift new file mode 100644 index 000000000..756bb3d97 --- /dev/null +++ b/Tests/JExtractSwiftTests/OptionalImportTests.swift @@ -0,0 +1,156 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import Testing + +final class OptionalImportTests { + let interfaceFile = + """ + import Foundation + + public func receiveOptionalIntSugar(_ arg: Int?) + public func receiveOptionalIntExplicit(_ arg: Optional) + public func receiveOptionalDataProto(_ arg: (some DataProtocol)?)) + """ + + + @Test("Import Optionals: JavaBindings") + func data_javaBindings() throws { + + try assertOutput( + input: interfaceFile, .ffm, .java, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_receiveOptionalIntSugar__(const ptrdiff_t *arg) + * } + */ + private static class swiftjava_SwiftModule_receiveOptionalIntSugar__ { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* arg: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + SwiftModule.findOrThrow("swiftjava_SwiftModule_receiveOptionalIntSugar__"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static void call(java.lang.foreign.MemorySegment arg) { + try { + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(arg); + } + HANDLE.invokeExact(arg); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + } + """, + + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func receiveOptionalIntSugar(_ arg: Int?) + * } + */ + public static void receiveOptionalIntSugar(OptionalLong arg) { + try(var arena$ = Arena.ofConfined()) { + swiftjava_SwiftModule_receiveOptionalIntSugar__.call(SwiftRuntime.toOptionalSegmentLong(arg, arena$)); + } + } + """, + + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_receiveOptionalIntExplicit__(const ptrdiff_t *arg) + * } + */ + private static class swiftjava_SwiftModule_receiveOptionalIntExplicit__ { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* arg: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + SwiftModule.findOrThrow("swiftjava_SwiftModule_receiveOptionalIntExplicit__"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static void call(java.lang.foreign.MemorySegment arg) { + try { + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(arg); + } + HANDLE.invokeExact(arg); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + } + """, + + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func receiveOptionalIntExplicit(_ arg: Optional) + * } + */ + public static void receiveOptionalIntExplicit(OptionalLong arg) { + try(var arena$ = Arena.ofConfined()) { + swiftjava_SwiftModule_receiveOptionalIntExplicit__.call(SwiftRuntime.toOptionalSegmentLong(arg, arena$)); + } + } + """, + + + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_receiveOptionalDataProto__(const void *arg) + * } + */ + private static class swiftjava_SwiftModule_receiveOptionalDataProto__ { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* arg: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + SwiftModule.findOrThrow("swiftjava_SwiftModule_receiveOptionalDataProto__"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static void call(java.lang.foreign.MemorySegment arg) { + try { + if (SwiftRuntime.TRACE_DOWNCALLS) { + SwiftRuntime.traceDowncall(arg); + } + HANDLE.invokeExact(arg); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + } + """, + + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func receiveOptionalDataProto(_ arg: (some DataProtocol)?) + * } + */ + public static void receiveOptionalDataProto(Optional arg) { + swiftjava_SwiftModule_receiveOptionalDataProto__.call(SwiftRuntime.toOptionalSegmentInstance(arg)); + } + """, + ] + ) + } +} From c0f166d9e1effafe850a098d56ebdd288e82e69e Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Thu, 17 Jul 2025 13:53:45 +0900 Subject: [PATCH 036/141] [SwiftKit] Move 'selfPointer' to 'JNISwiftInstance' & downcall trace logs (#322) Co-authored-by: Rintaro Ishizaki --- .../com/example/swift/HelloJava2Swift.java | 25 ++-- ...t2JavaGenerator+JavaBindingsPrinting.swift | 4 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 1 + ...t2JavaGenerator+JavaBindingsPrinting.swift | 25 +++- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 51 ++++++-- .../org/swift/swiftkit/core/CallTraces.java | 61 +++++++++ .../core/ConfinedSwiftMemorySession.java | 2 +- .../swift/swiftkit/core/JNISwiftInstance.java | 21 ++- .../swift/swiftkit/core/SwiftInstance.java | 35 ++--- .../swift/swiftkit/core/SwiftLibraries.java | 3 +- .../org/swift/swiftkit/core/SwiftObjects.java | 26 ++++ .../ffm/AllocatingAutoSwiftMemorySession.java | 2 +- .../swift/swiftkit/ffm/FFMSwiftInstance.java | 30 +++-- .../swiftkit/ffm/FFMSwiftInstanceCleanup.java | 16 +-- .../org/swift/swiftkit/ffm/SwiftRuntime.java | 40 +----- .../Asserts/TextAssertions.swift | 20 +-- .../JExtractSwiftTests/DataImportTests.swift | 26 ++-- .../FuncCallbackImportTests.swift | 12 +- .../FunctionDescriptorImportTests.swift | 20 +-- .../JNI/JNIClassTests.swift | 69 ++++++---- .../JNI/JNIStructTests.swift | 49 +++++-- .../JNI/JNIVariablesTests.swift | 121 ++++++++++++++---- .../MethodImportTests.swift | 3 - .../OptionalImportTests.swift | 12 +- .../StringPassingTests.swift | 4 +- .../VariableImportTests.swift | 8 +- 26 files changed, 451 insertions(+), 235 deletions(-) create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/CallTraces.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftObjects.java diff --git a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java index 3b083cc0b..cecb12311 100644 --- a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java +++ b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java @@ -18,6 +18,7 @@ // Import javakit/swiftkit support libraries +import org.swift.swiftkit.core.CallTraces; import org.swift.swiftkit.core.SwiftLibraries; import org.swift.swiftkit.ffm.AllocatingSwiftArena; import org.swift.swiftkit.ffm.SwiftRuntime; @@ -43,30 +44,30 @@ static void examples() { long cnt = MySwiftLibrary.globalWriteString("String from Java"); - SwiftRuntime.trace("count = " + cnt); + CallTraces.trace("count = " + cnt); MySwiftLibrary.globalCallMeRunnable(() -> { - SwiftRuntime.trace("running runnable"); + CallTraces.trace("running runnable"); }); - SwiftRuntime.trace("getGlobalBuffer().byteSize()=" + MySwiftLibrary.getGlobalBuffer().byteSize()); + CallTraces.trace("getGlobalBuffer().byteSize()=" + MySwiftLibrary.getGlobalBuffer().byteSize()); MySwiftLibrary.withBuffer((buf) -> { - SwiftRuntime.trace("withBuffer{$0.byteSize()}=" + buf.byteSize()); + CallTraces.trace("withBuffer{$0.byteSize()}=" + buf.byteSize()); }); // Example of using an arena; MyClass.deinit is run at end of scope try (var arena = AllocatingSwiftArena.ofConfined()) { MySwiftClass obj = MySwiftClass.init(2222, 7777, arena); // just checking retains/releases work - SwiftRuntime.trace("retainCount = " + SwiftRuntime.retainCount(obj)); + CallTraces.trace("retainCount = " + SwiftRuntime.retainCount(obj)); SwiftRuntime.retain(obj); - SwiftRuntime.trace("retainCount = " + SwiftRuntime.retainCount(obj)); + CallTraces.trace("retainCount = " + SwiftRuntime.retainCount(obj)); SwiftRuntime.release(obj); - SwiftRuntime.trace("retainCount = " + SwiftRuntime.retainCount(obj)); + CallTraces.trace("retainCount = " + SwiftRuntime.retainCount(obj)); obj.setCounter(12); - SwiftRuntime.trace("obj.counter = " + obj.getCounter()); + CallTraces.trace("obj.counter = " + obj.getCounter()); obj.voidMethod(); obj.takeIntMethod(42); @@ -75,9 +76,9 @@ static void examples() { otherObj.voidMethod(); MySwiftStruct swiftValue = MySwiftStruct.init(2222, 1111, arena); - SwiftRuntime.trace("swiftValue.capacity = " + swiftValue.getCapacity()); + CallTraces.trace("swiftValue.capacity = " + swiftValue.getCapacity()); swiftValue.withCapLen((cap, len) -> { - SwiftRuntime.trace("withCapLenCallback: cap=" + cap + ", len=" + len); + CallTraces.trace("withCapLenCallback: cap=" + cap + ", len=" + len); }); } @@ -85,12 +86,12 @@ static void examples() { try (var arena = AllocatingSwiftArena.ofConfined()) { var origBytes = arena.allocateFrom("foobar"); var origDat = Data.init(origBytes, origBytes.byteSize(), arena); - SwiftRuntime.trace("origDat.count = " + origDat.getCount()); + CallTraces.trace("origDat.count = " + origDat.getCount()); var retDat = MySwiftLibrary.globalReceiveReturnData(origDat, arena); retDat.withUnsafeBytes((retBytes) -> { var str = retBytes.getString(0); - SwiftRuntime.trace("retStr=" + str); + CallTraces.trace("retStr=" + str); }); } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index 13230a321..28b3aba12 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -116,8 +116,8 @@ extension FFMSwift2JavaGenerator { """ public static \(returnTy) call(\(paramsStr)) { try { - if (SwiftRuntime.TRACE_DOWNCALLS) { - SwiftRuntime.traceDowncall(\(argsStr)); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall(\(argsStr)); } \(maybeReturn)HANDLE.invokeExact(\(argsStr)); } catch (Throwable ex$) { diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift index d717255da..10dab4b9b 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -131,6 +131,7 @@ extension FFMSwift2JavaGenerator { } printer.print("import \(module)") } + printer.println() } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 80b477605..4f4ad4d81 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -206,13 +206,15 @@ extension JNISwift2JavaGenerator { printDeclDocumentation(&printer, decl) printer.printBraceBlock("public \(renderFunctionSignature(decl))") { printer in var arguments = translatedDecl.translatedFunctionSignature.parameters.map(\.name) - arguments.append("selfPointer") + + let selfVarName = "self$" + arguments.append(selfVarName) let returnKeyword = translatedDecl.translatedFunctionSignature.resultType.isVoid ? "" : "return " printer.print( """ - long selfPointer = this.pointer(); + long \(selfVarName) = this.$memoryAddress(); \(returnKeyword)\(translatedDecl.parentName).$\(translatedDecl.name)(\(arguments.joined(separator: ", "))); """ ) @@ -235,8 +237,8 @@ extension JNISwift2JavaGenerator { let initArguments = translatedDecl.translatedFunctionSignature.parameters.map(\.name) printer.print( """ - long selfPointer = \(type.qualifiedName).allocatingInit(\(initArguments.joined(separator: ", "))); - return new \(type.qualifiedName)(selfPointer, swiftArena$); + long self$ = \(type.qualifiedName).allocatingInit(\(initArguments.joined(separator: ", "))); + return new \(type.qualifiedName)(self$, swiftArena$); """ ) } @@ -262,15 +264,24 @@ extension JNISwift2JavaGenerator { private func printDestroyFunction(_ printer: inout CodePrinter, _ type: ImportedNominalType) { printer.print("private static native void $destroy(long selfPointer);") + let funcName = "$createDestroyFunction" printer.print("@Override") - printer.printBraceBlock("protected Runnable $createDestroyFunction()") { printer in + printer.printBraceBlock("protected Runnable \(funcName)()") { printer in printer.print( """ - long $selfPointer = this.pointer(); + long self$ = this.$memoryAddress(); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("\(type.swiftNominal.name).\(funcName)", + "this", this, + "self", self$); + } return new Runnable() { @Override public void run() { - \(type.swiftNominal.name).$destroy($selfPointer); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("\(type.swiftNominal.name).$destroy", "self", self$); + } + \(type.swiftNominal.name).$destroy(self$); } }; """ diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 30db69d13..38d4ff790 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -138,9 +138,9 @@ extension JNISwift2JavaGenerator { // TODO: Throwing initializers printer.print( """ - let selfPointer = UnsafeMutablePointer<\(typeName)>.allocate(capacity: 1) - selfPointer.initialize(to: \(typeName)(\(downcallArguments))) - return Int64(Int(bitPattern: selfPointer)).getJNIValue(in: environment) + let self$ = UnsafeMutablePointer<\(typeName)>.allocate(capacity: 1) + self$.initialize(to: \(typeName)(\(downcallArguments))) + return Int64(Int(bitPattern: self$)).getJNIValue(in: environment) """ ) } @@ -184,22 +184,20 @@ extension JNISwift2JavaGenerator { let translatedDecl = self.translatedDecl(for: decl)! // We will only call this method if can translate the decl. let swiftParentName = decl.parentType!.asNominalTypeDeclaration!.qualifiedName + let selfPointerParam = JavaParameter(name: "selfPointer", type: .long) printCDecl( &printer, javaMethodName: "$\(translatedDecl.name)", parentName: translatedDecl.parentName, parameters: translatedDecl.translatedFunctionSignature.parameters + [ - JavaParameter(name: "selfPointer", type: .long) + selfPointerParam ], isStatic: true, resultType: translatedDecl.translatedFunctionSignature.resultType ) { printer in - printer.print( - """ - let self$ = UnsafeMutablePointer<\(swiftParentName)>(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! - """ - ) - self.printFunctionDowncall(&printer, decl, calleeName: "self$.pointee") + let selfVar = self.printSelfJLongToUnsafeMutablePointer(&printer, + swiftParentName: swiftParentName, selfPointerParam) + self.printFunctionDowncall(&printer, decl, calleeName: "\(selfVar).pointee") } } @@ -320,28 +318,53 @@ extension JNISwift2JavaGenerator { /// Prints the implementation of the destroy function. private func printDestroyFunctionThunk(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + let selfPointerParam = JavaParameter(name: "selfPointer", type: .long) printCDecl( &printer, javaMethodName: "$destroy", parentName: type.swiftNominal.name, parameters: [ - JavaParameter(name: "selfPointer", type: .long) + selfPointerParam ], isStatic: true, resultType: .void ) { printer in + let parentName = type.qualifiedName + let selfVar = self.printSelfJLongToUnsafeMutablePointer(&printer, swiftParentName: parentName, selfPointerParam) // Deinitialize the pointer allocated (which will call the VWT destroy method) // then deallocate the memory. printer.print( """ - let pointer = UnsafeMutablePointer<\(type.qualifiedName)>(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! - pointer.deinitialize(count: 1) - pointer.deallocate() + \(selfVar).deinitialize(count: 1) + \(selfVar).deallocate() """ ) } } + /// Print the necessary conversion logic to go from a `jlong` to a `UnsafeMutablePointer` + /// + /// - Returns: name of the created "self" variable + private func printSelfJLongToUnsafeMutablePointer( + _ printer: inout CodePrinter, + swiftParentName: String, _ selfPointerParam: JavaParameter) -> String { + let newSelfParamName = "self$" + printer.print( + """ + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(\(selfPointerParam.name) != 0, "\(selfPointerParam.name) memory address was null") + let selfBits$ = Int(Int64(fromJNI: \(selfPointerParam.name), in: env$)) + guard let \(newSelfParamName) = UnsafeMutablePointer<\(swiftParentName)>(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } + """ + ) + return newSelfParamName + } + + /// Renders the arguments for making a downcall private func renderDowncallArguments( swiftFunctionSignature: SwiftFunctionSignature, diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/CallTraces.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/CallTraces.java new file mode 100644 index 000000000..358205ffd --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/CallTraces.java @@ -0,0 +1,61 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core; + +public class CallTraces { + public static final boolean TRACE_DOWNCALLS = + Boolean.getBoolean("jextract.trace.downcalls"); + + // Used to manually debug with complete backtraces on every traceDowncall + public static final boolean TRACE_DOWNCALLS_FULL = false; + + public static void traceDowncall(Object... args) { + RuntimeException ex = new RuntimeException(); + + String traceArgs = joinArgs(args); + System.err.printf("[java][%s:%d] Downcall: %s.%s(%s)\n", + ex.getStackTrace()[1].getFileName(), + ex.getStackTrace()[1].getLineNumber(), + ex.getStackTrace()[1].getClassName(), + ex.getStackTrace()[1].getMethodName(), + traceArgs); + if (TRACE_DOWNCALLS_FULL) { + ex.printStackTrace(); + } + } + + public static void trace(Object... args) { + RuntimeException ex = new RuntimeException(); + + String traceArgs = joinArgs(args); + System.err.printf("[java][%s:%d] %s: %s\n", + ex.getStackTrace()[1].getFileName(), + ex.getStackTrace()[1].getLineNumber(), + ex.getStackTrace()[1].getMethodName(), + traceArgs); + } + + private static String joinArgs(Object[] args) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < args.length; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(args[i].toString()); + } + return sb.toString(); + } + +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java index 1b6821ca4..7c6e80fb9 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java @@ -60,7 +60,7 @@ public void close() { public void register(SwiftInstance instance) { checkValid(); - SwiftInstanceCleanup cleanup = instance.createCleanupAction(); + SwiftInstanceCleanup cleanup = instance.$createCleanup(); this.resources.add(cleanup); } diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java index f99667930..6b30ed2fc 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java @@ -14,17 +14,30 @@ package org.swift.swiftkit.core; +import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; public abstract class JNISwiftInstance extends SwiftInstance { + // Pointer to the "self". + protected final long selfPointer; + /** * The designated constructor of any imported Swift types. * - * @param pointer a pointer to the memory containing the value + * @param selfPointer a pointer to the memory containing the value * @param arena the arena this object belongs to. When the arena goes out of scope, this value is destroyed. */ - protected JNISwiftInstance(long pointer, SwiftArena arena) { - super(pointer, arena); + protected JNISwiftInstance(long selfPointer, SwiftArena arena) { + SwiftObjects.requireNonZero(selfPointer, "selfPointer"); + this.selfPointer = selfPointer; + + // Only register once we have fully initialized the object since this will need the object pointer. + arena.register(this); + } + + @Override + public long $memoryAddress() { + return this.selfPointer; } /** @@ -42,7 +55,7 @@ protected JNISwiftInstance(long pointer, SwiftArena arena) { protected abstract Runnable $createDestroyFunction(); @Override - public SwiftInstanceCleanup createCleanupAction() { + public SwiftInstanceCleanup $createCleanup() { final AtomicBoolean statusDestroyedFlag = $statusDestroyedFlag(); Runnable markAsDestroyed = new Runnable() { @Override diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstance.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstance.java index 638cb8be6..44955cc6a 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstance.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstance.java @@ -17,49 +17,38 @@ import java.util.concurrent.atomic.AtomicBoolean; public abstract class SwiftInstance { - /// Pointer to the "self". - private final long selfPointer; + + // TODO: make this a flagset integer and/or use a field updater + /** Used to track additional state of the underlying object, e.g. if it was explicitly destroyed. */ + private final AtomicBoolean $state$destroyed = new AtomicBoolean(false); /** - * The pointer to the instance in memory. I.e. the {@code self} of the Swift object or value. + * Pointer to the {@code self} of the underlying Swift object or value. + * + * @apiNote When using this pointer one must ensure that the underlying object + * is kept alive using some means (e.g. a class remains retained), as + * this function does not ensure safety of the address in any way. */ - public final long pointer() { - return this.selfPointer; - } + public abstract long $memoryAddress(); /** * Called when the arena has decided the value should be destroyed. *

* Warning: The cleanup action must not capture {@code this}. */ - public abstract SwiftInstanceCleanup createCleanupAction(); - - // TODO: make this a flagset integer and/or use a field updater - /** Used to track additional state of the underlying object, e.g. if it was explicitly destroyed. */ - private final AtomicBoolean $state$destroyed = new AtomicBoolean(false); + public abstract SwiftInstanceCleanup $createCleanup(); /** * Exposes a boolean value which can be used to indicate if the object was destroyed. *

* This is exposing the object, rather than performing the action because we don't want to accidentally * form a strong reference to the {@code SwiftInstance} which could prevent the cleanup from running, - * if using an GC managed instance (e.g. using an {@link AutoSwiftMemorySession}. + * if using an GC managed instance (e.g. using an {@code AutoSwiftMemorySession}. */ public final AtomicBoolean $statusDestroyedFlag() { return this.$state$destroyed; } - /** - * The designated constructor of any imported Swift types. - * - * @param pointer a pointer to the memory containing the value - * @param arena the arena this object belongs to. When the arena goes out of scope, this value is destroyed. - */ - protected SwiftInstance(long pointer, SwiftArena arena) { - this.selfPointer = pointer; - arena.register(this); - } - /** * Ensures that this instance has not been destroyed. *

diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java index 2abdaff5e..3230e52a7 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java @@ -26,7 +26,6 @@ public final class SwiftLibraries { public static final String STDLIB_DYLIB_NAME = "swiftCore"; public static final String SWIFTKITSWIFT_DYLIB_NAME = "SwiftKitSwift"; - public static final boolean TRACE_DOWNCALLS = Boolean.getBoolean("jextract.trace.downcalls"); private static final String STDLIB_MACOS_DYLIB_PATH = "/usr/lib/swift/libswiftCore.dylib"; @@ -57,7 +56,7 @@ public static void loadLibrary(String libname) { public static void loadResourceLibrary(String libname) { String resourceName = PlatformUtils.dynamicLibraryName(libname); - if (SwiftLibraries.TRACE_DOWNCALLS) { + if (CallTraces.TRACE_DOWNCALLS) { System.out.println("[swift-java] Loading resource library: " + resourceName); } diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftObjects.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftObjects.java new file mode 100644 index 000000000..c508e90e4 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftObjects.java @@ -0,0 +1,26 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core; + +/** + * Utility functions, similar to @{link java.util.Objects} + */ +public class SwiftObjects { + public static void requireNonZero(long number, String name) { + if (number == 0) { + throw new IllegalArgumentException(String.format("'%s' must not be zero!", name)); + } + } +} diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingAutoSwiftMemorySession.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingAutoSwiftMemorySession.java index fecd52025..7063fefb6 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingAutoSwiftMemorySession.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingAutoSwiftMemorySession.java @@ -56,7 +56,7 @@ public void register(SwiftInstance instance) { // We make sure we don't capture `instance` in the // cleanup action, so we can ignore the warning below. - var cleanupAction = instance.createCleanupAction(); + var cleanupAction = instance.$createCleanup(); cleaner.register(instance, cleanupAction); } diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstance.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstance.java index f4a01aa47..1236bad2a 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstance.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstance.java @@ -22,6 +22,19 @@ public abstract class FFMSwiftInstance extends SwiftInstance { private final MemorySegment memorySegment; + /** + * The designated constructor of any imported Swift types. + * + * @param segment the memory segment. + * @param arena the arena this object belongs to. When the arena goes out of scope, this value is destroyed. + */ + protected FFMSwiftInstance(MemorySegment segment, AllocatingSwiftArena arena) { + this.memorySegment = segment; + + // Only register once we have fully initialized the object since this will need the object pointer. + arena.register(this); + } + /** * The pointer to the instance in memory. I.e. the {@code self} of the Swift object or value. */ @@ -29,24 +42,19 @@ public abstract class FFMSwiftInstance extends SwiftInstance { return this.memorySegment; } + @Override + public long $memoryAddress() { + return $memorySegment().address(); + } + /** * The Swift type metadata of this type. */ public abstract SwiftAnyType $swiftType(); - /** - * The designated constructor of any imported Swift types. - * - * @param segment the memory segment. - * @param arena the arena this object belongs to. When the arena goes out of scope, this value is destroyed. - */ - protected FFMSwiftInstance(MemorySegment segment, AllocatingSwiftArena arena) { - super(segment.address(), arena); - this.memorySegment = segment; - } @Override - public SwiftInstanceCleanup createCleanupAction() { + public SwiftInstanceCleanup $createCleanup() { var statusDestroyedFlag = $statusDestroyedFlag(); Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true); diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstanceCleanup.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstanceCleanup.java index a5d0829a3..c3a9beb6c 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstanceCleanup.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstanceCleanup.java @@ -19,13 +19,13 @@ import java.lang.foreign.MemorySegment; public class FFMSwiftInstanceCleanup implements SwiftInstanceCleanup { - private final MemorySegment selfPointer; - private final SwiftAnyType selfType; + private final MemorySegment memoryAddress; + private final SwiftAnyType type; private final Runnable markAsDestroyed; - public FFMSwiftInstanceCleanup(MemorySegment selfPointer, SwiftAnyType selfType, Runnable markAsDestroyed) { - this.selfPointer = selfPointer; - this.selfType = selfType; + public FFMSwiftInstanceCleanup(MemorySegment memoryAddress, SwiftAnyType type, Runnable markAsDestroyed) { + this.memoryAddress = memoryAddress; + this.type = type; this.markAsDestroyed = markAsDestroyed; } @@ -34,9 +34,9 @@ public void run() { markAsDestroyed.run(); // Allow null pointers just for AutoArena tests. - if (selfType != null && selfPointer != null) { - System.out.println("[debug] Destroy swift value [" + selfType.getSwiftName() + "]: " + selfPointer); - SwiftValueWitnessTable.destroy(selfType, selfPointer); + if (type != null && memoryAddress != null) { + System.out.println("[debug] Destroy swift value [" + type.getSwiftName() + "]: " + memoryAddress); + SwiftValueWitnessTable.destroy(type, memoryAddress); } } } diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java index dc18b4454..eca6be82d 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java @@ -15,6 +15,7 @@ package org.swift.swiftkit.ffm; import org.swift.swiftkit.core.SwiftInstance; +import org.swift.swiftkit.core.CallTraces; import org.swift.swiftkit.core.util.PlatformUtils; import java.lang.foreign.*; @@ -24,6 +25,7 @@ import java.util.*; import java.util.stream.Collectors; +import static org.swift.swiftkit.core.CallTraces.traceDowncall; import static org.swift.swiftkit.core.util.StringUtils.stripPrefix; import static org.swift.swiftkit.core.util.StringUtils.stripSuffix; @@ -31,7 +33,6 @@ public class SwiftRuntime { public static final String STDLIB_DYLIB_NAME = "swiftCore"; public static final String SWIFTKITSWIFT_DYLIB_NAME = "SwiftKitSwift"; - public static final boolean TRACE_DOWNCALLS = Boolean.getBoolean("jextract.trace.downcalls"); private static final String STDLIB_MACOS_DYLIB_PATH = "/usr/lib/swift/libswiftCore.dylib"; @@ -65,33 +66,6 @@ private static SymbolLookup getSymbolLookup() { public SwiftRuntime() { } - public static void traceDowncall(Object... args) { - var ex = new RuntimeException(); - - String traceArgs = Arrays.stream(args) - .map(Object::toString) - .collect(Collectors.joining(", ")); - System.out.printf("[java][%s:%d] Downcall: %s.%s(%s)\n", - ex.getStackTrace()[1].getFileName(), - ex.getStackTrace()[1].getLineNumber(), - ex.getStackTrace()[1].getClassName(), - ex.getStackTrace()[1].getMethodName(), - traceArgs); - } - - public static void trace(Object... args) { - var ex = new RuntimeException(); - - String traceArgs = Arrays.stream(args) - .map(Object::toString) - .collect(Collectors.joining(", ")); - System.out.printf("[java][%s:%d] %s: %s\n", - ex.getStackTrace()[1].getFileName(), - ex.getStackTrace()[1].getLineNumber(), - ex.getStackTrace()[1].getMethodName(), - traceArgs); - } - static MemorySegment findOrThrow(String symbol) { return SYMBOL_LOOKUP.find(symbol) .orElseThrow(() -> new UnsatisfiedLinkError("unresolved symbol: %s".formatted(symbol))); @@ -152,7 +126,7 @@ private static class swift_retainCount { public static long retainCount(MemorySegment object) { var mh$ = swift_retainCount.HANDLE; try { - if (TRACE_DOWNCALLS) { + if (CallTraces.TRACE_DOWNCALLS) { traceDowncall("swift_retainCount", object); } return (long) mh$.invokeExact(object); @@ -182,7 +156,7 @@ private static class swift_retain { public static void retain(MemorySegment object) { var mh$ = swift_retain.HANDLE; try { - if (TRACE_DOWNCALLS) { + if (CallTraces.TRACE_DOWNCALLS) { traceDowncall("swift_retain", object); } mh$.invokeExact(object); @@ -212,7 +186,7 @@ private static class swift_release { public static void release(MemorySegment object) { var mh$ = swift_release.HANDLE; try { - if (TRACE_DOWNCALLS) { + if (CallTraces.TRACE_DOWNCALLS) { traceDowncall("swift_release", object); } mh$.invokeExact(object); @@ -248,7 +222,7 @@ private static class swift_getTypeByName { public static MemorySegment getTypeByName(String string) { var mh$ = swift_getTypeByName.HANDLE; try { - if (TRACE_DOWNCALLS) { + if (CallTraces.TRACE_DOWNCALLS) { traceDowncall("_typeByName"); } // TODO: A bit annoying to generate, we need an arena for the conversion... @@ -303,7 +277,7 @@ public static Optional getTypeByMangledNameInEnvironment(String ma // contain this, but we don't need it for type lookup mangledName = stripSuffix(mangledName, "Ma"); mangledName = stripSuffix(mangledName, "CN"); - if (TRACE_DOWNCALLS) { + if (CallTraces.TRACE_DOWNCALLS) { traceDowncall("swift_getTypeByMangledNameInEnvironment", mangledName); } try (Arena arena = Arena.ofConfined()) { diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index 3c4ad56ad..5b70f68ab 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -28,7 +28,7 @@ func assertOutput( _ mode: JExtractGenerationMode, _ renderKind: RenderKind, swiftModuleName: String = "SwiftModule", - detectChunkByInitialLines: Int = 4, + detectChunkByInitialLines _detectChunkByInitialLines: Int = 4, expectedChunks: [String], fileID: String = #fileID, filePath: String = #filePath, @@ -79,19 +79,22 @@ func assertOutput( let gotLines = output.split(separator: "\n").filter { l in l.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).count > 0 } - for expected in expectedChunks { - let expectedLines = expected.split(separator: "\n") + for expectedChunk in expectedChunks { + let expectedLines = expectedChunk.split(separator: "\n") + let detectChunkByInitialLines = min(expectedLines.count, _detectChunkByInitialLines) + precondition(detectChunkByInitialLines > 0, "Chunk size to detect cannot be zero lines!") var matchingOutputOffset: Int? = nil let expectedInitialMatchingLines = expectedLines[0.. (offset+detectChunkByInitialLines) { - let textLinesAtOffset = gotLines[offset.. (lineOffset+detectChunkByInitialLines) { + let textLinesAtOffset = gotLines[lineOffset..!, thisClass: jclass) -> jlong { - let selfPointer = UnsafeMutablePointer.allocate(capacity: 1) - selfPointer.initialize(to: MyClass()) - return Int64(Int(bitPattern: selfPointer)).getJNIValue(in: environment) + let self$ = UnsafeMutablePointer.allocate(capacity: 1) + self$.initialize(to: MyClass()) + return Int64(Int(bitPattern: self$)).getJNIValue(in: environment) } """, """ @_cdecl("Java_com_example_swift_MyClass_allocatingInit__JJ") func Java_com_example_swift_MyClass_allocatingInit__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jlong) -> jlong { - let selfPointer = UnsafeMutablePointer.allocate(capacity: 1) - selfPointer.initialize(to: MyClass(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) - return Int64(Int(bitPattern: selfPointer)).getJNIValue(in: environment) + let self$ = UnsafeMutablePointer.allocate(capacity: 1) + self$.initialize(to: MyClass(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) + return Int64(Int(bitPattern: self$)).getJNIValue(in: environment) } """ ] @@ -200,9 +207,16 @@ struct JNIClassTests { """ @_cdecl("Java_com_example_swift_MyClass__00024destroy__J") func Java_com_example_swift_MyClass__00024destroy__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) { - let pointer = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! - pointer.deinitialize(count: 1) - pointer.deallocate() + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } + self$.deinitialize(count: 1) + self$.deallocate() } """ ] @@ -218,14 +232,14 @@ struct JNIClassTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func doSomething(x: Int64) - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func doSomething(x: Int64) + * } + */ public void doSomething(long x) { - long selfPointer = this.pointer(); - MyClass.$doSomething(x, selfPointer); + long self$ = this.$memoryAddress(); + MyClass.$doSomething(x, self$); } """, """ @@ -246,7 +260,14 @@ struct JNIClassTests { """ @_cdecl("Java_com_example_swift_MyClass__00024doSomething__JJ") func Java_com_example_swift_MyClass__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, selfPointer: jlong) { - let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!)) } """, diff --git a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift index 0a883ed11..47b1c4dc1 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift @@ -63,11 +63,19 @@ struct JNIStructTests { """ @Override protected Runnable $createDestroyFunction() { - long $selfPointer = this.pointer(); + long self$ = this.$memoryAddress(); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("MyStruct.$createDestroyFunction", + "this", this, + "self", self$); + } return new Runnable() { @Override public void run() { - MyStruct.$destroy($selfPointer); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("MyStruct.$destroy", "self", self$); + } + MyStruct.$destroy(self$); } }; } @@ -90,8 +98,8 @@ struct JNIStructTests { * } */ public static MyStruct init(long x, long y, SwiftArena swiftArena$) { - long selfPointer = MyStruct.allocatingInit(x, y); - return new MyStruct(selfPointer, swiftArena$); + long self$ = MyStruct.allocatingInit(x, y); + return new MyStruct(self$, swiftArena$); } """, """ @@ -112,9 +120,9 @@ struct JNIStructTests { """ @_cdecl("Java_com_example_swift_MyStruct_allocatingInit__JJ") func Java_com_example_swift_MyStruct_allocatingInit__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jlong) -> jlong { - let selfPointer = UnsafeMutablePointer.allocate(capacity: 1) - selfPointer.initialize(to: MyStruct(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) - return Int64(Int(bitPattern: selfPointer)).getJNIValue(in: environment) + let self$ = UnsafeMutablePointer.allocate(capacity: 1) + self$.initialize(to: MyStruct(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) + return Int64(Int(bitPattern: self$)).getJNIValue(in: environment) } """ ] @@ -127,14 +135,20 @@ struct JNIStructTests { input: source, .jni, .swift, - detectChunkByInitialLines: 1, expectedChunks: [ """ @_cdecl("Java_com_example_swift_MyStruct__00024destroy__J") func Java_com_example_swift_MyStruct__00024destroy__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) { - let pointer = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! - pointer.deinitialize(count: 1) - pointer.deallocate() + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } + self$.deinitialize(count: 1) + self$.deallocate() } """ ] @@ -156,8 +170,8 @@ struct JNIStructTests { * } */ public void doSomething(long x) { - long selfPointer = this.pointer(); - MyStruct.$doSomething(x, selfPointer); + long self$ = this.$memoryAddress(); + MyStruct.$doSomething(x, self$); } """, """ @@ -178,7 +192,14 @@ struct JNIStructTests { """ @_cdecl("Java_com_example_swift_MyStruct__00024doSomething__JJ") func Java_com_example_swift_MyStruct__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, selfPointer: jlong) { - let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!)) } """, diff --git a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift index f09789d40..3f65bd973 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift @@ -48,8 +48,8 @@ struct JNIVariablesTests { * } */ public long getConstant() { - long selfPointer = this.pointer(); - return MyClass.$getConstant(selfPointer); + long self$ = this.$memoryAddress(); + return MyClass.$getConstant(self$); } """, """ @@ -69,7 +69,14 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024getConstant__J") func Java_com_example_swift_MyClass__00024getConstant__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { - let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } let result = self$.pointee.constant return result.getJNIValue(in: environment) } @@ -94,8 +101,8 @@ struct JNIVariablesTests { * } */ public long getMutable() { - long selfPointer = this.pointer(); - return MyClass.$getMutable(selfPointer); + long self$ = this.$memoryAddress(); + return MyClass.$getMutable(self$); } """, """ @@ -106,8 +113,8 @@ struct JNIVariablesTests { * } */ public void setMutable(long newValue) { - long selfPointer = this.pointer(); - MyClass.$setMutable(newValue, selfPointer); + long self$ = this.$memoryAddress(); + MyClass.$setMutable(newValue, self$); } """, """ @@ -131,7 +138,14 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024getMutable__J") func Java_com_example_swift_MyClass__00024getMutable__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { - let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } let result = self$.pointee.mutable return result.getJNIValue(in: environment) } @@ -139,7 +153,14 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024setMutable__JJ") func Java_com_example_swift_MyClass__00024setMutable__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, selfPointer: jlong) { - let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } self$.pointee.mutable = Int64(fromJNI: newValue, in: environment!) } """ @@ -163,8 +184,8 @@ struct JNIVariablesTests { * } */ public long getComputed() { - long selfPointer = this.pointer(); - return MyClass.$getComputed(selfPointer); + long self$ = this.$memoryAddress(); + return MyClass.$getComputed(self$); } """, """ @@ -185,7 +206,15 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024getComputed__J") func Java_com_example_swift_MyClass__00024getComputed__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { - let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } + let result = self$.pointee.computed return result.getJNIValue(in: environment) } @@ -210,8 +239,8 @@ struct JNIVariablesTests { * } */ public long getComputedThrowing() throws Exception { - long selfPointer = this.pointer(); - return MyClass.$getComputedThrowing(selfPointer); + long self$ = this.$memoryAddress(); + return MyClass.$getComputedThrowing(self$); } """, """ @@ -232,7 +261,15 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024getComputedThrowing__J") func Java_com_example_swift_MyClass__00024getComputedThrowing__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { - let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } + do { let result = try self$.pointee.computedThrowing return result.getJNIValue(in: environment) @@ -262,8 +299,8 @@ struct JNIVariablesTests { * } */ public long getGetterAndSetter() { - long selfPointer = this.pointer(); - return MyClass.$getGetterAndSetter(selfPointer); + long self$ = this.$memoryAddress(); + return MyClass.$getGetterAndSetter(self$); } """, """ @@ -274,8 +311,8 @@ struct JNIVariablesTests { * } */ public void setGetterAndSetter(long newValue) { - long selfPointer = this.pointer(); - MyClass.$setGetterAndSetter(newValue, selfPointer); + long self$ = this.$memoryAddress(); + MyClass.$setGetterAndSetter(newValue, self$); } """, """ @@ -299,7 +336,15 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024getGetterAndSetter__J") func Java_com_example_swift_MyClass__00024getGetterAndSetter__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { - let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } + let result = self$.pointee.getterAndSetter return result.getJNIValue(in: environment) } @@ -307,7 +352,15 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ") func Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, selfPointer: jlong) { - let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } + self$.pointee.getterAndSetter = Int64(fromJNI: newValue, in: environment!) } """ @@ -331,8 +384,8 @@ struct JNIVariablesTests { * } */ public boolean isSomeBoolean() { - long selfPointer = this.pointer(); - return MyClass.$isSomeBoolean(selfPointer); + long self$ = this.$memoryAddress(); + return MyClass.$isSomeBoolean(self$); } """, """ @@ -343,8 +396,8 @@ struct JNIVariablesTests { * } */ public void setSomeBoolean(boolean newValue) { - long selfPointer = this.pointer(); - MyClass.$setSomeBoolean(newValue, selfPointer); + long self$ = this.$memoryAddress(); + MyClass.$setSomeBoolean(newValue, self$); } """, """ @@ -368,7 +421,14 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024isSomeBoolean__J") func Java_com_example_swift_MyClass__00024isSomeBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jboolean { - let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } let result = self$.pointee.someBoolean return result.getJNIValue(in: environment) } @@ -376,7 +436,14 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ") func Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, selfPointer: jlong) { - let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } self$.pointee.someBoolean = Bool(fromJNI: newValue, in: environment!) } """ diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift index 118ca7891..73357d6bd 100644 --- a/Tests/JExtractSwiftTests/MethodImportTests.swift +++ b/Tests/JExtractSwiftTests/MethodImportTests.swift @@ -168,7 +168,6 @@ final class MethodImportTests { } assertOutput( - dump: true, output, expected: """ @@ -212,7 +211,6 @@ final class MethodImportTests { } assertOutput( - dump: true, output, expected: """ @@ -256,7 +254,6 @@ final class MethodImportTests { } assertOutput( - dump: true, output, expected: """ diff --git a/Tests/JExtractSwiftTests/OptionalImportTests.swift b/Tests/JExtractSwiftTests/OptionalImportTests.swift index 756bb3d97..daa9fe664 100644 --- a/Tests/JExtractSwiftTests/OptionalImportTests.swift +++ b/Tests/JExtractSwiftTests/OptionalImportTests.swift @@ -47,8 +47,8 @@ final class OptionalImportTests { private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static void call(java.lang.foreign.MemorySegment arg) { try { - if (SwiftRuntime.TRACE_DOWNCALLS) { - SwiftRuntime.traceDowncall(arg); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall(arg); } HANDLE.invokeExact(arg); } catch (Throwable ex$) { @@ -87,8 +87,8 @@ final class OptionalImportTests { private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static void call(java.lang.foreign.MemorySegment arg) { try { - if (SwiftRuntime.TRACE_DOWNCALLS) { - SwiftRuntime.traceDowncall(arg); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall(arg); } HANDLE.invokeExact(arg); } catch (Throwable ex$) { @@ -128,8 +128,8 @@ final class OptionalImportTests { private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static void call(java.lang.foreign.MemorySegment arg) { try { - if (SwiftRuntime.TRACE_DOWNCALLS) { - SwiftRuntime.traceDowncall(arg); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall(arg); } HANDLE.invokeExact(arg); } catch (Throwable ex$) { diff --git a/Tests/JExtractSwiftTests/StringPassingTests.swift b/Tests/JExtractSwiftTests/StringPassingTests.swift index 6a5c1c804..ea81ac847 100644 --- a/Tests/JExtractSwiftTests/StringPassingTests.swift +++ b/Tests/JExtractSwiftTests/StringPassingTests.swift @@ -45,8 +45,8 @@ final class StringPassingTests { private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static long call(java.lang.foreign.MemorySegment string) { try { - if (SwiftRuntime.TRACE_DOWNCALLS) { - SwiftRuntime.traceDowncall(string); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall(string); } return (long) HANDLE.invokeExact(string); } catch (Throwable ex$) { diff --git a/Tests/JExtractSwiftTests/VariableImportTests.swift b/Tests/JExtractSwiftTests/VariableImportTests.swift index e0a6678c3..da0c1afaf 100644 --- a/Tests/JExtractSwiftTests/VariableImportTests.swift +++ b/Tests/JExtractSwiftTests/VariableImportTests.swift @@ -51,8 +51,8 @@ final class VariableImportTests { private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static long call(java.lang.foreign.MemorySegment self) { try { - if (SwiftRuntime.TRACE_DOWNCALLS) { - SwiftRuntime.traceDowncall(self); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall(self); } return (long) HANDLE.invokeExact(self); } catch (Throwable ex$) { @@ -84,8 +84,8 @@ final class VariableImportTests { private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static void call(long newValue, java.lang.foreign.MemorySegment self) { try { - if (SwiftRuntime.TRACE_DOWNCALLS) { - SwiftRuntime.traceDowncall(newValue, self); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall(newValue, self); } HANDLE.invokeExact(newValue, self); } catch (Throwable ex$) { From a82196e91377f09a7919a27809acffa25b217695 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Thu, 17 Jul 2025 15:51:01 +0900 Subject: [PATCH 037/141] Build: simple local maven publishing (#324) --- SwiftKitCore/build.gradle | 17 +++++++++++++++++ SwiftKitFFM/build.gradle | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/SwiftKitCore/build.gradle b/SwiftKitCore/build.gradle index 78cdbeb21..7bab76e03 100644 --- a/SwiftKitCore/build.gradle +++ b/SwiftKitCore/build.gradle @@ -14,15 +14,32 @@ plugins { id("build-logic.java-application-conventions") + id("maven-publish") } group = "org.swift.swiftkit" version = "1.0-SNAPSHOT" +base { + archivesName = "swiftkit-core" +} repositories { + mavenLocal() mavenCentral() } +publishing { + publications { + maven(MavenPublication) { + groupId = group + artifactId = 'swiftkit-core' + version = '1.0-SNAPSHOT' + + from components.java + } + } +} + java { toolchain { languageVersion.set(JavaLanguageVersion.of(17)) diff --git a/SwiftKitFFM/build.gradle b/SwiftKitFFM/build.gradle index 22f880012..d818586d5 100644 --- a/SwiftKitFFM/build.gradle +++ b/SwiftKitFFM/build.gradle @@ -14,15 +14,32 @@ plugins { id("build-logic.java-application-conventions") + id("maven-publish") } group = "org.swift.swiftkit" version = "1.0-SNAPSHOT" +base { + archivesName = "swiftkit-ffm" +} repositories { + mavenLocal() mavenCentral() } +publishing { + publications { + maven(MavenPublication) { + groupId = group + artifactId = 'swiftkit-ffm' + version = '1.0-SNAPSHOT' + + from components.java + } + } +} + java { toolchain { languageVersion.set(JavaLanguageVersion.of(24)) From 86da9eec8711415f142e5967d4352bb1352c77e1 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 22 Jul 2025 10:31:20 +0200 Subject: [PATCH 038/141] [JExtract/JNI] Fix boolean naming for variables already called `isX` (#325) --- .../Convenience/String+Extensions.swift | 3 +- Sources/JExtractSwiftLib/ImportedDecls.swift | 18 +++- .../Asserts/TextAssertions.swift | 1 - .../JNI/JNIVariablesTests.swift | 87 ++++++++++++++++++- 4 files changed, 100 insertions(+), 9 deletions(-) diff --git a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift index 1878ba2f5..1851e1549 100644 --- a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift @@ -14,8 +14,7 @@ extension String { - // TODO: naive implementation good enough for our simple case `methodMethodSomething` -> `MethodSomething` - var toCamelCase: String { + var firstCharacterUppercased: String { guard let f = first else { return self } diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 4b8a478a2..dd7e9c10e 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -160,15 +160,25 @@ extension ImportedFunc { let returnsBoolean = self.functionSignature.result.type.asNominalTypeDeclaration?.knownTypeKind == .bool if !returnsBoolean { - return "get\(self.name.toCamelCase)" + return "get\(self.name.firstCharacterUppercased)" } else if !self.name.hasJavaBooleanNamingConvention { - return "is\(self.name.toCamelCase)" + return "is\(self.name.firstCharacterUppercased)" } else { - return self.name.toCamelCase + return self.name } } var javaSetterName: String { - "set\(self.name.toCamelCase)" + let isBooleanSetter = self.functionSignature.parameters.first?.type.asNominalTypeDeclaration?.knownTypeKind == .bool + + // If the variable is already named "isX", then we make + // the setter "setX" to match beans spec. + if isBooleanSetter && self.name.hasJavaBooleanNamingConvention { + // Safe to force unwrap due to `hasJavaBooleanNamingConvention` check. + let propertyName = self.name.split(separator: "is", maxSplits: 1).last! + return "set\(propertyName)" + } else { + return "set\(self.name.firstCharacterUppercased)" + } } } diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index 5b70f68ab..47397c630 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -115,7 +115,6 @@ func assertOutput( print("==== ---------------------------------------------------------------") #expect(output.contains(expectedChunk), sourceLocation: sourceLocation) - fatalError("Failed: \(filePath):\(line)") continue } diff --git a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift index 3f65bd973..272d27b5a 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift @@ -33,7 +33,7 @@ struct JNIVariablesTests { set { } } public var someBoolean: Bool - public let isBoolean: Bool + public var isBoolean: Bool } """ @@ -411,7 +411,7 @@ struct JNIVariablesTests { } @Test - func boolean_swiftThunks() throws { + func someBoolean_swiftThunks() throws { try assertOutput( input: membersSource, .jni, @@ -450,4 +450,87 @@ struct JNIVariablesTests { ] ) } + + @Test + func isBoolean_javaBindings() throws { + try assertOutput( + input: membersSource, + .jni, + .java, + detectChunkByInitialLines: 8, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var isBoolean: Bool + * } + */ + public boolean isBoolean() { + long self$ = this.$memoryAddress(); + return MyClass.$isBoolean(self$); + } + """, + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var isBoolean: Bool + * } + */ + public void setBoolean(boolean newValue) { + long self$ = this.$memoryAddress(); + MyClass.$setBoolean(newValue, self$); + } + """, + """ + private static native boolean $isBoolean(long selfPointer); + """, + """ + private static native void $setBoolean(boolean newValue, long selfPointer); + """ + ] + ) + } + + @Test + func isBoolean_swiftThunks() throws { + try assertOutput( + input: membersSource, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024isBoolean__J") + func Java_com_example_swift_MyClass__00024isBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jboolean { + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } + let result = self$.pointee.isBoolean + return result.getJNIValue(in: environment) + } + """, + """ + @_cdecl("Java_com_example_swift_MyClass__00024setBoolean__ZJ") + func Java_com_example_swift_MyClass__00024setBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, selfPointer: jlong) { + guard let env$ = environment else { + fatalError("Missing JNIEnv in downcall to \\(#function)") + } + assert(selfPointer != 0, "selfPointer memory address was null") + let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } + self$.pointee.isBoolean = Bool(fromJNI: newValue, in: environment!) + } + """ + ] + ) + } } From a25d2166c2d9d21638166ae997aec60542d9d6ee Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 22 Jul 2025 12:40:04 +0200 Subject: [PATCH 039/141] [JExtract/JNI] Add support for classes/structs as parameters and return values (#326) --- .../Sources/MySwiftLibrary/MySwiftClass.swift | 12 +- .../com/example/swift/MySwiftClassTest.java | 21 ++ ...t2JavaGenerator+JavaBindingsPrinting.swift | 127 +++++------ ...ISwift2JavaGenerator+JavaTranslation.swift | 204 +++++++++++++---- ...wift2JavaGenerator+NativeTranslation.swift | 209 ++++++++++++++++++ ...ift2JavaGenerator+SwiftThunkPrinting.swift | 181 ++++++--------- Sources/JExtractSwiftLib/JNI/JNIType.swift | 82 +++++++ .../JavaConstants/JavaTypes.swift | 10 + .../JNI/JNIClassTests.swift | 164 +++++++++++--- .../JNI/JNIModuleTests.swift | 81 ++++--- .../JNI/JNIStructTests.swift | 30 ++- .../JNI/JNIVariablesTests.swift | 179 ++++++--------- 12 files changed, 883 insertions(+), 417 deletions(-) create mode 100644 Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift create mode 100644 Sources/JExtractSwiftLib/JNI/JNIType.swift diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index fd0ce488a..b4447f4ff 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -13,8 +13,8 @@ //===----------------------------------------------------------------------===// public class MySwiftClass { - let x: Int64 - let y: Int64 + public let x: Int64 + public let y: Int64 public let byte: UInt8 = 0 public let constant: Int64 = 100 @@ -76,4 +76,12 @@ public class MySwiftClass { public func throwingFunction() throws { throw MySwiftClassError.swiftError } + + public func sumX(with other: MySwiftClass) -> Int64 { + return self.x + other.x + } + + public func copy() -> MySwiftClass { + return MySwiftClass(x: self.x, y: self.y) + } } diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java index bf244416c..4425d6b21 100644 --- a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -118,4 +118,25 @@ void isWarm() { assertFalse(c.isWarm()); } } + + @Test + void sumWithX() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftClass c1 = MySwiftClass.init(20, 10, arena); + MySwiftClass c2 = MySwiftClass.init(50, 10, arena); + assertEquals(70, c1.sumX(c2)); + } + } + + @Test + void copy() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftClass c1 = MySwiftClass.init(20, 10, arena); + MySwiftClass c2 = c1.copy(arena); + + assertEquals(20, c2.getX()); + assertEquals(10, c2.getY()); + assertNotEquals(c1.$memoryAddress(), c2.$memoryAddress()); + } + } } \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 4f4ad4d81..9c3d14b8d 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -117,7 +117,7 @@ extension JNISwift2JavaGenerator { printer.println() for initializer in decl.initializers { - printInitializerBindings(&printer, initializer, type: decl) + printFunctionBinding(&printer, initializer) printer.println() } @@ -176,75 +176,82 @@ extension JNISwift2JavaGenerator { } private func printFunctionBinding(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - guard let _ = translatedDecl(for: decl) else { + guard let translatedDecl = translatedDecl(for: decl) else { // Failed to translate. Skip. return } + var modifiers = ["public"] if decl.isStatic || decl.isInitializer || !decl.hasParent { - printStaticFunctionBinding(&printer, decl) - } else { - printMemberMethodBindings(&printer, decl) + modifiers.append("static") } - } - private func printStaticFunctionBinding(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - printDeclDocumentation(&printer, decl) - printer.print( - "public static native \(renderFunctionSignature(decl));" - ) - } + let translatedSignature = translatedDecl.translatedFunctionSignature + let resultType = translatedSignature.resultType.javaType + var parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.parameter.asParameter) + if translatedSignature.requiresSwiftArena { + parameters.append("SwiftArena swiftArena$") + } + let throwsClause = decl.isThrowing ? " throws Exception" : "" - /// Renders Java bindings for member methods - /// - /// Member methods are generated as a function that extracts the `selfPointer` - /// and passes it down to another native function along with the arguments - /// to call the Swift implementation. - private func printMemberMethodBindings(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - let translatedDecl = translatedDecl(for: decl)! // We will only call this method if we can translate the decl. + let modifiersStr = modifiers.joined(separator: " ") + let parametersStr = parameters.joined(separator: ", ") printDeclDocumentation(&printer, decl) - printer.printBraceBlock("public \(renderFunctionSignature(decl))") { printer in - var arguments = translatedDecl.translatedFunctionSignature.parameters.map(\.name) - - let selfVarName = "self$" - arguments.append(selfVarName) + printer.printBraceBlock( + "\(modifiersStr) \(resultType) \(translatedDecl.name)(\(parametersStr))\(throwsClause)" + ) { printer in + printDowncall(&printer, decl) + } - let returnKeyword = translatedDecl.translatedFunctionSignature.resultType.isVoid ? "" : "return " + printNativeFunction(&printer, decl) + } - printer.print( - """ - long \(selfVarName) = this.$memoryAddress(); - \(returnKeyword)\(translatedDecl.parentName).$\(translatedDecl.name)(\(arguments.joined(separator: ", "))); - """ - ) + private func printNativeFunction(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + let translatedDecl = translatedDecl(for: decl)! // Will always call with valid decl + let nativeSignature = translatedDecl.nativeFunctionSignature + let resultType = nativeSignature.result.javaType + var parameters = nativeSignature.parameters + if let selfParameter = nativeSignature.selfParameter { + parameters.append(selfParameter) } + let renderedParameters = parameters.map { "\($0.javaParameter.type) \($0.javaParameter.name)"}.joined(separator: ", ") - let returnType = translatedDecl.translatedFunctionSignature.resultType - var parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter) - parameters.append("long selfPointer") - printer.print("private static native \(returnType) $\(translatedDecl.name)(\(parameters.joined(separator: ", ")));") + printer.print("private static native \(resultType) \(translatedDecl.nativeFunctionName)(\(renderedParameters));") } - private func printInitializerBindings(_ printer: inout CodePrinter, _ decl: ImportedFunc, type: ImportedNominalType) { - guard let translatedDecl = translatedDecl(for: decl) else { - // Failed to translate. Skip. - return + private func printDowncall( + _ printer: inout CodePrinter, + _ decl: ImportedFunc + ) { + let translatedDecl = translatedDecl(for: decl)! // We will only call this method if we can translate the decl. + let translatedFunctionSignature = translatedDecl.translatedFunctionSignature + + // Regular parameters. + var arguments = [String]() + for parameter in translatedFunctionSignature.parameters { + let lowered = parameter.conversion.render(&printer, parameter.parameter.name) + arguments.append(lowered) } - printDeclDocumentation(&printer, decl) - printer.printBraceBlock("public static \(renderFunctionSignature(decl))") { printer in - let initArguments = translatedDecl.translatedFunctionSignature.parameters.map(\.name) - printer.print( - """ - long self$ = \(type.qualifiedName).allocatingInit(\(initArguments.joined(separator: ", "))); - return new \(type.qualifiedName)(self$, swiftArena$); - """ - ) + // 'self' parameter. + if let selfParameter = translatedFunctionSignature.selfParameter { + let lowered = selfParameter.conversion.render(&printer, "this") + arguments.append(lowered) } - let parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter) - printer.print("private static native long allocatingInit(\(parameters.joined(separator: ", ")));") + //=== Part 3: Downcall. + // TODO: If we always generate a native method and a "public" method, we can actually choose our own thunk names + // using the registry? + let downcall = "\(translatedDecl.parentName).\(translatedDecl.nativeFunctionName)(\(arguments.joined(separator: ", ")))" + + //=== Part 4: Convert the return value. + if translatedFunctionSignature.resultType.javaType.isVoid { + printer.print("\(downcall);") + } else { + let result = translatedFunctionSignature.resultType.conversion.render(&printer, downcall) + printer.print("return \(result);") + } } private func printDeclDocumentation(_ printer: inout CodePrinter, _ decl: ImportedFunc) { @@ -288,24 +295,4 @@ extension JNISwift2JavaGenerator { ) } } - - /// Renders a Java function signature - /// - /// `func method(x: Int, y: Int) -> Int` becomes - /// `long method(long x, long y)` - private func renderFunctionSignature(_ decl: ImportedFunc) -> String { - guard let translatedDecl = translatedDecl(for: decl) else { - fatalError("Unable to render function signature for a function that cannot be translated: \(decl)") - } - let resultType = translatedDecl.translatedFunctionSignature.resultType - var parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter) - - if decl.isInitializer { - parameters.append("SwiftArena swiftArena$") - } - - let throwsClause = decl.isThrowing ? " throws Exception" : "" - - return "\(resultType) \(translatedDecl.name)(\(parameters.joined(separator: ", ")))\(throwsClause)" - } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 2e3ffff86..e0253624c 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -39,7 +39,16 @@ extension JNISwift2JavaGenerator { let swiftModuleName: String func translate(_ decl: ImportedFunc) throws -> TranslatedFunctionDecl { + let nativeTranslation = NativeJavaTranslation() + + // Swift -> Java let translatedFunctionSignature = try translate(functionSignature: decl.functionSignature) + // Java -> Java (native) + let nativeFunctionSignature = try nativeTranslation.translate( + functionSignature: decl.functionSignature, + translatedFunctionSignature: translatedFunctionSignature + ) + // Types with no parent will be outputted inside a "module" class. let parentName = decl.parentType?.asNominalType?.nominalTypeDecl.qualifiedName ?? swiftModuleName @@ -52,68 +61,99 @@ extension JNISwift2JavaGenerator { return TranslatedFunctionDecl( name: javaName, + nativeFunctionName: "$\(javaName)", parentName: parentName, - translatedFunctionSignature: translatedFunctionSignature + translatedFunctionSignature: translatedFunctionSignature, + nativeFunctionSignature: nativeFunctionSignature ) } - func translate(functionSignature: SwiftFunctionSignature, isInitializer: Bool = false) throws -> TranslatedFunctionSignature { + func translate(functionSignature: SwiftFunctionSignature) throws -> TranslatedFunctionSignature { let parameters = try functionSignature.parameters.enumerated().map { idx, param in let parameterName = param.parameterName ?? "arg\(idx))" - return try translate(swiftParam: param, parameterName: parameterName) + return try translateParameter(swiftType: param.type, parameterName: parameterName) + } + + // 'self' + let selfParameter: TranslatedParameter? + if case .instance(let swiftSelf) = functionSignature.selfParameter { + selfParameter = try self.translateParameter( + swiftType: swiftSelf.type, + parameterName: swiftSelf.parameterName ?? "self" + ) + } else { + selfParameter = nil } return try TranslatedFunctionSignature( + selfParameter: selfParameter, parameters: parameters, - resultType: translate(swiftType: functionSignature.result.type) - ) - } - - func translate(swiftParam: SwiftParameter, parameterName: String) throws -> JavaParameter { - return try JavaParameter( - name: parameterName, - type: translate(swiftType: swiftParam.type) + resultType: translate(swiftResult: functionSignature.result) ) } - func translate(swiftType: SwiftType) throws -> JavaType { + func translateParameter(swiftType: SwiftType, parameterName: String) throws -> TranslatedParameter { switch swiftType { case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { - guard let javaType = translate(knownType: knownType) else { + guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType) else { throw JavaTranslationError.unsupportedSwiftType(swiftType) } - return javaType + + return TranslatedParameter( + parameter: JavaParameter(name: parameterName, type: javaType), + conversion: .placeholder + ) } - return .class(package: nil, name: nominalType.nominalTypeDecl.name) + // For now, we assume this is a JExtract class. + return TranslatedParameter( + parameter: JavaParameter( + name: parameterName, + type: .class(package: nil, name: nominalType.nominalTypeDecl.name) + ), + conversion: .valueMemoryAddress(.placeholder) + ) case .tuple([]): - return .void + return TranslatedParameter( + parameter: JavaParameter(name: parameterName, type: .void), + conversion: .placeholder + ) case .metatype, .optional, .tuple, .function, .existential, .opaque: throw JavaTranslationError.unsupportedSwiftType(swiftType) } } - func translate(knownType: SwiftKnownTypeDeclKind) -> JavaType? { - switch knownType { - case .bool: .boolean - case .int8: .byte - case .uint16: .char - case .int16: .short - case .int32: .int - case .int64: .long - case .float: .float - case .double: .double - case .void: .void - case .string: .javaLangString - case .int, .uint, .uint8, .uint32, .uint64, - .unsafeRawPointer, .unsafeMutableRawPointer, - .unsafePointer, .unsafeMutablePointer, - .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, - .unsafeBufferPointer, .unsafeMutableBufferPointer, .optional, .data, .dataProtocol: - nil + func translate( + swiftResult: SwiftResult + ) throws -> TranslatedResult { + switch swiftResult.type { + case .nominal(let nominalType): + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType) else { + throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + } + + return TranslatedResult( + javaType: javaType, + conversion: .placeholder + ) + } + + // For now, we assume this is a JExtract class. + let javaType = JavaType.class(package: nil, name: nominalType.nominalTypeDecl.name) + return TranslatedResult( + javaType: javaType, + conversion: .constructSwiftValue(.placeholder, javaType) + ) + + case .tuple([]): + return TranslatedResult(javaType: .void, conversion: .placeholder) + + case .metatype, .optional, .tuple, .function, .existential, .opaque: + throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) } } } @@ -122,16 +162,106 @@ extension JNISwift2JavaGenerator { /// Java function name let name: String + /// The name of the native function + let nativeFunctionName: String + /// The name of the Java parent scope this function is declared in let parentName: String - /// Function signature + /// Function signature of the Java function the user will call let translatedFunctionSignature: TranslatedFunctionSignature + + /// Function signature of the native function that will be implemented by Swift + let nativeFunctionSignature: NativeFunctionSignature + } + + static func translate(knownType: SwiftKnownTypeDeclKind) -> JavaType? { + switch knownType { + case .bool: .boolean + case .int8: .byte + case .uint16: .char + case .int16: .short + case .int32: .int + case .int64: .long + case .float: .float + case .double: .double + case .void: .void + case .string: .javaLangString + case .int, .uint, .uint8, .uint32, .uint64, + .unsafeRawPointer, .unsafeMutableRawPointer, + .unsafePointer, .unsafeMutablePointer, + .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, + .unsafeBufferPointer, .unsafeMutableBufferPointer, .optional, .data, .dataProtocol: + nil + } } struct TranslatedFunctionSignature { - let parameters: [JavaParameter] - let resultType: JavaType + let selfParameter: TranslatedParameter? + let parameters: [TranslatedParameter] + let resultType: TranslatedResult + + var requiresSwiftArena: Bool { + return self.resultType.conversion.requiresSwiftArena + } + } + + /// Represent a Swift API parameter translated to Java. + struct TranslatedParameter { + let parameter: JavaParameter + let conversion: JavaNativeConversionStep + } + + /// Represent a Swift API result translated to Java. + struct TranslatedResult { + let javaType: JavaType + + /// Represents how to convert the Java native result into a user-facing result. + let conversion: JavaNativeConversionStep + } + + /// Describes how to convert values between Java types and the native Java function + enum JavaNativeConversionStep { + /// The value being converted + case placeholder + + /// `value.$memoryAddress()` + indirect case valueMemoryAddress(JavaNativeConversionStep) + + /// Call `new \(Type)(\(placeholder), swiftArena$)` + indirect case constructSwiftValue(JavaNativeConversionStep, JavaType) + + /// Returns the conversion string applied to the placeholder. + func render(_ printer: inout CodePrinter, _ placeholder: String) -> String { + // NOTE: 'printer' is used if the conversion wants to cause side-effects. + // E.g. storing a temporary values into a variable. + switch self { + case .placeholder: + return placeholder + + case .valueMemoryAddress: + return "\(placeholder).$memoryAddress()" + + case .constructSwiftValue(let inner, let javaType): + let inner = inner.render(&printer, placeholder) + return "new \(javaType.className!)(\(inner), swiftArena$)" + + } + } + + /// Whether the conversion uses SwiftArena. + var requiresSwiftArena: Bool { + switch self { + case .placeholder: + return false + + case .constructSwiftValue: + return true + + case .valueMemoryAddress(let inner): + return inner.requiresSwiftArena + } + } } enum JavaTranslationError: Error { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift new file mode 100644 index 000000000..461c73017 --- /dev/null +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -0,0 +1,209 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JavaTypes + +extension JNISwift2JavaGenerator { + + struct NativeJavaTranslation { + /// Translates a Swift function into the native JNI method signature. + func translate( + functionSignature: SwiftFunctionSignature, + translatedFunctionSignature: TranslatedFunctionSignature + ) throws -> NativeFunctionSignature { + let parameters = try zip(translatedFunctionSignature.parameters, functionSignature.parameters).map { translatedParameter, swiftParameter in + let parameterName = translatedParameter.parameter.name + return try translate(swiftParameter: swiftParameter, parameterName: parameterName) + } + + // Lower the self parameter. + let nativeSelf: NativeParameter? = switch functionSignature.selfParameter { + case .instance(let selfParameter): + try translate( + swiftParameter: selfParameter, + parameterName: selfParameter.parameterName ?? "self" + ) + case nil, .initializer(_), .staticMethod(_): + nil + } + + return try NativeFunctionSignature( + selfParameter: nativeSelf, + parameters: parameters, + result: translate(swiftResult: functionSignature.result) + ) + } + + func translate( + swiftParameter: SwiftParameter, + parameterName: String + ) throws -> NativeParameter { + switch swiftParameter.type { + case .nominal(let nominalType): + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType), javaType.implementsJavaValue else { + throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) + } + + return NativeParameter( + javaParameter: JavaParameter(name: parameterName, type: javaType), + conversion: .initFromJNI(.placeholder, swiftType: swiftParameter.type) + ) + } + + case .tuple([]): + return NativeParameter( + javaParameter: JavaParameter(name: parameterName, type: .void), + conversion: .placeholder + ) + + case .metatype, .optional, .tuple, .function, .existential, .opaque: + throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) + } + + // Classes are passed as the pointer. + return NativeParameter( + javaParameter: JavaParameter(name: parameterName, type: .long), + conversion: .pointee(.extractSwiftValue(.placeholder, swiftType: swiftParameter.type)) + ) + } + + func translate( + swiftResult: SwiftResult + ) throws -> NativeResult { + switch swiftResult.type { + case .nominal(let nominalType): + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType), javaType.implementsJavaValue else { + throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + } + + return NativeResult( + javaType: javaType, + conversion: .getJNIValue(.placeholder) + ) + } + + case .tuple([]): + return NativeResult( + javaType: .void, + conversion: .placeholder + ) + + case .metatype, .optional, .tuple, .function, .existential, .opaque: + throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + } + + // TODO: Handle other classes, for example from JavaKit macros. + // for now we assume all passed in classes are JExtract generated + // so we pass the pointer. + return NativeResult( + javaType: .long, + conversion: .getJNIValue(.allocateSwiftValue(name: "result", swiftType: swiftResult.type)) + ) + } + } + + struct NativeFunctionSignature { + let selfParameter: NativeParameter? + let parameters: [NativeParameter] + let result: NativeResult + } + + struct NativeParameter { + let javaParameter: JavaParameter + + var jniType: JNIType { + javaParameter.type.jniType + } + + /// Represents how to convert the JNI parameter to a Swift parameter + let conversion: NativeSwiftConversionStep + } + + struct NativeResult { + let javaType: JavaType + let conversion: NativeSwiftConversionStep + } + + /// Describes how to convert values between Java types and Swift through JNI + enum NativeSwiftConversionStep { + /// The value being converted + case placeholder + + /// `value.getJNIValue(in:)` + indirect case getJNIValue(NativeSwiftConversionStep) + + /// `SwiftType(from: value, in: environment!)` + indirect case initFromJNI(NativeSwiftConversionStep, swiftType: SwiftType) + + /// Extracts a swift type at a pointer given by a long. + indirect case extractSwiftValue(NativeSwiftConversionStep, swiftType: SwiftType) + + /// Allocate memory for a Swift value and outputs the pointer + indirect case allocateSwiftValue(name: String, swiftType: SwiftType) + + /// The thing to which the pointer typed, which is the `pointee` property + /// of the `Unsafe(Mutable)Pointer` types in Swift. + indirect case pointee(NativeSwiftConversionStep) + + + /// Returns the conversion string applied to the placeholder. + func render(_ printer: inout CodePrinter, _ placeholder: String) -> String { + // NOTE: 'printer' is used if the conversion wants to cause side-effects. + // E.g. storing a temporary values into a variable. + switch self { + case .placeholder: + return placeholder + + case .getJNIValue(let inner): + let inner = inner.render(&printer, placeholder) + return "\(inner).getJNIValue(in: environment!)" + + case .initFromJNI(let inner, let swiftType): + let inner = inner.render(&printer, placeholder) + return "\(swiftType)(fromJNI: \(inner), in: environment!)" + + case .extractSwiftValue(let inner, let swiftType): + let inner = inner.render(&printer, placeholder) + printer.print( + """ + assert(\(inner) != 0, "\(inner) memory address was null") + let \(inner)Bits$ = Int(Int64(fromJNI: \(inner), in: environment!)) + guard let \(inner)$ = UnsafeMutablePointer<\(swiftType)>(bitPattern: \(inner)Bits$) else { + fatalError("\(inner) memory address was null in call to \\(#function)!") + } + """ + ) + return "\(inner)$" + + case .allocateSwiftValue(let name, let swiftType): + let pointerName = "\(name)$" + let bitsName = "\(name)Bits$" + printer.print( + """ + let \(pointerName) = UnsafeMutablePointer<\(swiftType)>.allocate(capacity: 1) + \(pointerName).initialize(to: \(placeholder)) + let \(bitsName) = Int64(Int(bitPattern: \(pointerName))) + """ + ) + return bitsName + + case .pointee(let inner): + let inner = inner.render(&printer, placeholder) + return "\(inner).pointee" + } + } + } +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 38d4ff790..d8b6b2750 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -98,7 +98,7 @@ extension JNISwift2JavaGenerator { printHeader(&printer) for initializer in type.initializers { - printInitializerThunk(&printer, initializer) + printSwiftFunctionThunk(&printer, initializer) printer.println() } @@ -115,141 +115,105 @@ extension JNISwift2JavaGenerator { printDestroyFunctionThunk(&printer, type) } - private func printInitializerThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - guard let translatedDecl = translatedDecl(for: decl) else { - // Failed to translate. Skip. - return - } - - let typeName = translatedDecl.parentName - - printCDecl( - &printer, - javaMethodName: "allocatingInit", - parentName: translatedDecl.parentName, - parameters: translatedDecl.translatedFunctionSignature.parameters, - isStatic: true, - resultType: .long - ) { printer in - let downcallArguments = renderDowncallArguments( - swiftFunctionSignature: decl.functionSignature, - translatedFunctionSignature: translatedDecl.translatedFunctionSignature - ) - // TODO: Throwing initializers - printer.print( - """ - let self$ = UnsafeMutablePointer<\(typeName)>.allocate(capacity: 1) - self$.initialize(to: \(typeName)(\(downcallArguments))) - return Int64(Int(bitPattern: self$)).getJNIValue(in: environment) - """ - ) - } - } - private func printSwiftFunctionThunk( _ printer: inout CodePrinter, _ decl: ImportedFunc ) { - guard let _ = translatedDecl(for: decl) else { + guard let translatedDecl = translatedDecl(for: decl) else { // Failed to translate. Skip. return } - // Free functions does not have a parent - if decl.isStatic || !decl.hasParent { - self.printSwiftStaticFunctionThunk(&printer, decl) - } else { - self.printSwiftMemberFunctionThunk(&printer, decl) - } - } + let nativeSignature = translatedDecl.nativeFunctionSignature + var parameters = nativeSignature.parameters - private func printSwiftStaticFunctionThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - let translatedDecl = self.translatedDecl(for: decl)! // We will only call this method if we can translate the decl. - - printCDecl( - &printer, - javaMethodName: translatedDecl.name, - parentName: translatedDecl.parentName, - parameters: translatedDecl.translatedFunctionSignature.parameters, - isStatic: true, - resultType: translatedDecl.translatedFunctionSignature.resultType - ) { printer in - // For free functions the parent is the Swift module - let parentName = decl.parentType?.asNominalTypeDeclaration?.qualifiedName ?? swiftModuleName - self.printFunctionDowncall(&printer, decl, calleeName: parentName) + if let selfParameter = nativeSignature.selfParameter { + parameters.append(selfParameter) } - } - - private func printSwiftMemberFunctionThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - let translatedDecl = self.translatedDecl(for: decl)! // We will only call this method if can translate the decl. - let swiftParentName = decl.parentType!.asNominalTypeDeclaration!.qualifiedName - let selfPointerParam = JavaParameter(name: "selfPointer", type: .long) printCDecl( &printer, - javaMethodName: "$\(translatedDecl.name)", + javaMethodName: translatedDecl.nativeFunctionName, parentName: translatedDecl.parentName, - parameters: translatedDecl.translatedFunctionSignature.parameters + [ - selfPointerParam - ], - isStatic: true, - resultType: translatedDecl.translatedFunctionSignature.resultType + parameters: parameters.map(\.javaParameter), + resultType: nativeSignature.result.javaType.jniType ) { printer in - let selfVar = self.printSelfJLongToUnsafeMutablePointer(&printer, - swiftParentName: swiftParentName, selfPointerParam) - self.printFunctionDowncall(&printer, decl, calleeName: "\(selfVar).pointee") + self.printFunctionDowncall(&printer, decl) } } private func printFunctionDowncall( _ printer: inout CodePrinter, - _ decl: ImportedFunc, - calleeName: String + _ decl: ImportedFunc ) { guard let translatedDecl = self.translatedDecl(for: decl) else { fatalError("Cannot print function downcall for a function that can't be translated: \(decl)") } - let swiftReturnType = decl.functionSignature.result.type + let nativeSignature = translatedDecl.nativeFunctionSignature let tryClause: String = decl.isThrowing ? "try " : "" + // Regular parameters. + var arguments = [String]() + for parameter in nativeSignature.parameters { + let lowered = parameter.conversion.render(&printer, parameter.javaParameter.name) + arguments.append(lowered) + } + + // Callee + let callee: String = switch decl.functionSignature.selfParameter { + case .instance(let swiftSelf): + nativeSignature.selfParameter!.conversion.render( + &printer, + swiftSelf.parameterName ?? "self" + ) + case .staticMethod(let selfType), .initializer(let selfType): + "\(selfType)" + case .none: + swiftModuleName + } + + // Build the result let result: String switch decl.apiKind { case .function, .initializer: - let downcallParameters = renderDowncallArguments( - swiftFunctionSignature: decl.functionSignature, - translatedFunctionSignature: translatedDecl.translatedFunctionSignature - ) - result = "\(tryClause)\(calleeName).\(decl.name)(\(downcallParameters))" + let downcallArguments = zip( + decl.functionSignature.parameters, + arguments + ).map { originalParam, argument in + let label = originalParam.argumentLabel.map { "\($0): " } ?? "" + return "\(label)\(argument)" + } + .joined(separator: ", ") + result = "\(tryClause)\(callee).\(decl.name)(\(downcallArguments))" case .getter: - result = "\(tryClause)\(calleeName).\(decl.name)" + result = "\(tryClause)\(callee).\(decl.name)" case .setter: - guard let newValueParameter = decl.functionSignature.parameters.first else { + guard let newValueArgument = arguments.first else { fatalError("Setter did not contain newValue parameter: \(decl)") } - result = "\(calleeName).\(decl.name) = \(renderJNIToSwiftConversion("newValue", type: newValueParameter.type))" + result = "\(callee).\(decl.name) = \(newValueArgument)" } - let returnStatement = - if swiftReturnType.isVoid { - result + // Lower the result. + let innerBody: String + if !decl.functionSignature.result.type.isVoid { + let loweredResult = nativeSignature.result.conversion.render(&printer, result) + innerBody = "return \(loweredResult)" } else { - """ - let result = \(result) - return result.getJNIValue(in: environment) - """ + innerBody = result } if decl.isThrowing { - let dummyReturn = - !swiftReturnType.isVoid ? "return \(swiftReturnType).jniPlaceholderValue" : "" + // TODO: Handle classes for dummy value + let dummyReturn = !nativeSignature.result.javaType.isVoid ? "return \(decl.functionSignature.result.type).jniPlaceholderValue" : "" printer.print( """ do { - \(returnStatement) + \(innerBody) } catch { environment.throwAsException(error) \(dummyReturn) @@ -257,7 +221,7 @@ extension JNISwift2JavaGenerator { """ ) } else { - printer.print(returnStatement) + printer.print(innerBody) } } @@ -266,8 +230,7 @@ extension JNISwift2JavaGenerator { javaMethodName: String, parentName: String, parameters: [JavaParameter], - isStatic: Bool, - resultType: JavaType, + resultType: JNIType, _ body: (inout CodePrinter) -> Void ) { let jniSignature = parameters.reduce(into: "") { signature, parameter in @@ -283,16 +246,15 @@ extension JNISwift2JavaGenerator { + jniSignature.escapedJNIIdentifier let translatedParameters = parameters.map { - "\($0.name): \($0.type.jniTypeName)" + "\($0.name): \($0.type.jniType)" } - let thisParameter = isStatic ? "thisClass: jclass" : "thisObject: jobject" let thunkParameters = [ "environment: UnsafeMutablePointer!", - thisParameter + "thisClass: jclass" ] + translatedParameters - let thunkReturnType = !resultType.isVoid ? " -> \(resultType.jniTypeName)" : "" + let thunkReturnType = resultType != .void ? " -> \(resultType)" : "" // TODO: Think about function overloads printer.printBraceBlock( @@ -326,7 +288,6 @@ extension JNISwift2JavaGenerator { parameters: [ selfPointerParam ], - isStatic: true, resultType: .void ) { printer in let parentName = type.qualifiedName @@ -347,7 +308,9 @@ extension JNISwift2JavaGenerator { /// - Returns: name of the created "self" variable private func printSelfJLongToUnsafeMutablePointer( _ printer: inout CodePrinter, - swiftParentName: String, _ selfPointerParam: JavaParameter) -> String { + swiftParentName: String, + _ selfPointerParam: JavaParameter + ) -> String { let newSelfParamName = "self$" printer.print( """ @@ -363,26 +326,6 @@ extension JNISwift2JavaGenerator { ) return newSelfParamName } - - - /// Renders the arguments for making a downcall - private func renderDowncallArguments( - swiftFunctionSignature: SwiftFunctionSignature, - translatedFunctionSignature: TranslatedFunctionSignature - ) -> String { - zip( - swiftFunctionSignature.parameters, - translatedFunctionSignature.parameters - ).map { originalParam, translatedParam in - let label = originalParam.argumentLabel.map { "\($0): " } ?? "" - return "\(label)\(originalParam.type)(fromJNI: \(translatedParam.name), in: environment!)" - } - .joined(separator: ", ") - } - - private func renderJNIToSwiftConversion(_ variableName: String, type: SwiftType) -> String { - "\(type)(fromJNI: \(variableName), in: environment!)" - } } extension String { diff --git a/Sources/JExtractSwiftLib/JNI/JNIType.swift b/Sources/JExtractSwiftLib/JNI/JNIType.swift new file mode 100644 index 000000000..d152942a2 --- /dev/null +++ b/Sources/JExtractSwiftLib/JNI/JNIType.swift @@ -0,0 +1,82 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JavaTypes + +/// Represents types that are able to be passed over a JNI boundary. +/// +/// - SeeAlso: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/types.html +enum JNIType { + case jboolean + case jfloat + case jdouble + case jbyte + case jchar + case jshort + case jint + case jlong + case void + case jstring + case jclass + case jthrowable + case jobject + case jbooleanArray + case jbyteArray + case jcharArray + case jshortArray + case jintArray + case jlongArray + case jfloatArray + case jdoubleArray + case jobjectArray +} + +extension JavaType { + var jniType: JNIType { + switch self { + case .boolean: .jboolean + case .byte: .jbyte + case .char: .jchar + case .short: .jshort + case .int: .jint + case .long: .jlong + case .float: .jfloat + case .double: .jdouble + case .void: .void + case .array(.boolean): .jbooleanArray + case .array(.byte): .jbyteArray + case .array(.char): .jcharArray + case .array(.short): .jshortArray + case .array(.int): .jintArray + case .array(.long): .jlongArray + case .array(.float): .jfloatArray + case .array(.double): .jdoubleArray + case .array: .jobjectArray + case .javaLangString: .jstring + case .javaLangClass: .jclass + case .javaLangThrowable: .jthrowable + case .class: .jobject + } + } + + /// Returns whether this type returns `JavaValue` from JavaKit + var implementsJavaValue: Bool { + switch self { + case .boolean, .byte, .char, .short, .int, .long, .float, .double, .void, .javaLangString: + true + default: + false + } + } +} diff --git a/Sources/JExtractSwiftLib/JavaConstants/JavaTypes.swift b/Sources/JExtractSwiftLib/JavaConstants/JavaTypes.swift index 633943efd..028508014 100644 --- a/Sources/JExtractSwiftLib/JavaConstants/JavaTypes.swift +++ b/Sources/JExtractSwiftLib/JavaConstants/JavaTypes.swift @@ -29,4 +29,14 @@ extension JavaType { static var javaLangRunnable: JavaType { .class(package: "java.lang", name: "Runnable") } + + /// The description of the type java.lang.Class. + static var javaLangClass: JavaType { + .class(package: "java.lang", name: "Class") + } + + /// The description of the type java.lang.Throwable. + static var javaLangThrowable: JavaType { + .class(package: "java.lang", name: "Throwable") + } } diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift index 5970e7a05..cfe78fbd6 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -35,6 +35,9 @@ struct JNIClassTests { } public func doSomething(x: Int64) {} + + public func copy() -> MyClass {} + public func isEqual(to other: MyClass) -> Bool {} } """ @@ -103,7 +106,12 @@ struct JNIClassTests { * public static func method() * } */ - public static native void method(); + public static void method() { + MyClass.$method(); + } + """, + """ + private static native void $method(); """ ] ) @@ -118,8 +126,8 @@ struct JNIClassTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_MyClass_method__") - func Java_com_example_swift_MyClass_method__(environment: UnsafeMutablePointer!, thisClass: jclass) { + @_cdecl("Java_com_example_swift_MyClass__00024method__") + func Java_com_example_swift_MyClass__00024method__(environment: UnsafeMutablePointer!, thisClass: jclass) { MyClass.method() } """ @@ -142,8 +150,7 @@ struct JNIClassTests { * } */ public static MyClass init(long x, long y, SwiftArena swiftArena$) { - long self$ = MyClass.allocatingInit(x, y); - return new MyClass(self$, swiftArena$); + return new MyClass(MyClass.$init(x, y), swiftArena$); } """, """ @@ -154,15 +161,14 @@ struct JNIClassTests { * } */ public static MyClass init(SwiftArena swiftArena$) { - long self$ = MyClass.allocatingInit(); - return new MyClass(self$, swiftArena$); + return new MyClass(MyClass.$init(), swiftArena$); } """, """ - private static native long allocatingInit(long x, long y); + private static native long $init(long x, long y); """, """ - private static native long allocatingInit(); + private static native long $init(); """ ] ) @@ -177,19 +183,21 @@ struct JNIClassTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_MyClass_allocatingInit__") - func Java_com_example_swift_MyClass_allocatingInit__(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong { - let self$ = UnsafeMutablePointer.allocate(capacity: 1) - self$.initialize(to: MyClass()) - return Int64(Int(bitPattern: self$)).getJNIValue(in: environment) + @_cdecl("Java_com_example_swift_MyClass__00024init__JJ") + func Java_com_example_swift_MyClass__00024init__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jlong) -> jlong { + let result$ = UnsafeMutablePointer.allocate(capacity: 1) + result$.initialize(to: MyClass.init(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) + let resultBits$ = Int64(Int(bitPattern: result$)) + return resultBits$.getJNIValue(in: environment!) } """, """ - @_cdecl("Java_com_example_swift_MyClass_allocatingInit__JJ") - func Java_com_example_swift_MyClass_allocatingInit__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jlong) -> jlong { - let self$ = UnsafeMutablePointer.allocate(capacity: 1) - self$.initialize(to: MyClass(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) - return Int64(Int(bitPattern: self$)).getJNIValue(in: environment) + @_cdecl("Java_com_example_swift_MyClass__00024init__") + func Java_com_example_swift_MyClass__00024init__(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong { + let result$ = UnsafeMutablePointer.allocate(capacity: 1) + result$.initialize(to: MyClass.init()) + let resultBits$ = Int64(Int(bitPattern: result$)) + return resultBits$.getJNIValue(in: environment!) } """ ] @@ -238,12 +246,11 @@ struct JNIClassTests { * } */ public void doSomething(long x) { - long self$ = this.$memoryAddress(); - MyClass.$doSomething(x, self$); + MyClass.$doSomething(x, this.$memoryAddress()); } """, """ - private static native void $doSomething(long x, long selfPointer); + private static native void $doSomething(long x, long self); """ ] ) @@ -259,12 +266,9 @@ struct JNIClassTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_MyClass__00024doSomething__JJ") - func Java_com_example_swift_MyClass__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, selfPointer: jlong) { - guard let env$ = environment else { - fatalError("Missing JNIEnv in downcall to \\(#function)") - } - assert(selfPointer != 0, "selfPointer memory address was null") - let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + func Java_com_example_swift_MyClass__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, self: jlong) { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { fatalError("self memory address was null in call to \\(#function)!") } @@ -274,4 +278,108 @@ struct JNIClassTests { ] ) } + + @Test + func methodReturningClass_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func copy() -> MyClass + * } + */ + public MyClass copy(SwiftArena swiftArena$) { + return new MyClass(MyClass.$copy(this.$memoryAddress()), swiftArena$); + } + """, + """ + private static native long $copy(long self); + """ + ] + ) + } + + @Test + func methodReturningClass_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024copy__J") + func Java_com_example_swift_MyClass__00024copy__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } + let result$ = UnsafeMutablePointer.allocate(capacity: 1) + result$.initialize(to: self$.pointee.copy()) + let resultBits$ = Int64(Int(bitPattern: result$)) + return resultBits$.getJNIValue(in: environment!) + } + """, + ] + ) + } + + @Test + func classAsParameter_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func isEqual(to other: MyClass) -> Bool + * } + */ + public boolean isEqual(MyClass other) { + return MyClass.$isEqual(other.$memoryAddress(), this.$memoryAddress()); + } + """, + """ + private static native boolean $isEqual(long other, long self); + """ + ] + ) + } + + @Test + func classAsParameter_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024isEqual__JJ") + func Java_com_example_swift_MyClass__00024isEqual__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, other: jlong, self: jlong) -> jboolean { + assert(other != 0, "other memory address was null") + let otherBits$ = Int(Int64(fromJNI: other, in: environment!)) + guard let other$ = UnsafeMutablePointer(bitPattern: otherBits$) else { + fatalError("other memory address was null in call to \\(#function)!") + } + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) + guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + fatalError("self memory address was null in call to \\(#function)!") + } + return self$.pointee.isEqual(to: other$.pointee).getJNIValue(in: environment!) + } + """, + ] + ) + } } diff --git a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift index d6a030a8c..4696253cf 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift @@ -67,7 +67,12 @@ struct JNIModuleTests { * public func helloWorld() * } */ - public static native void helloWorld(); + public static void helloWorld() { + SwiftModule.$helloWorld(); + } + """, + """ + private static native void $helloWorld(); """, """ /** @@ -76,7 +81,12 @@ struct JNIModuleTests { * public func takeIntegers(i1: Int8, i2: Int16, i3: Int32, i4: Int64) -> UInt16 * } */ - public static native char takeIntegers(byte i1, short i2, int i3, long i4); + public static char takeIntegers(byte i1, short i2, int i3, long i4) { + return SwiftModule.$takeIntegers(i1, i2, i3, i4); + } + """, + """ + private static native char $takeIntegers(byte i1, short i2, int i3, long i4); """, """ /** @@ -85,7 +95,12 @@ struct JNIModuleTests { * public func otherPrimitives(b: Bool, f: Float, d: Double) * } */ - public static native void otherPrimitives(boolean b, float f, double d); + public static void otherPrimitives(boolean b, float f, double d) { + SwiftModule.$otherPrimitives(b, f, d); + } + """, + """ + private static native void $otherPrimitives(boolean b, float f, double d); """ ] ) @@ -100,21 +115,20 @@ struct JNIModuleTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_SwiftModule_helloWorld__") - func Java_com_example_swift_SwiftModule_helloWorld__(environment: UnsafeMutablePointer!, thisClass: jclass) { + @_cdecl("Java_com_example_swift_SwiftModule__00024helloWorld__") + func Java_com_example_swift_SwiftModule__00024helloWorld__(environment: UnsafeMutablePointer!, thisClass: jclass) { SwiftModule.helloWorld() } """, """ - @_cdecl("Java_com_example_swift_SwiftModule_takeIntegers__BSIJ") - func Java_com_example_swift_SwiftModule_takeIntegers__BSIJ(environment: UnsafeMutablePointer!, thisClass: jclass, i1: jbyte, i2: jshort, i3: jint, i4: jlong) -> jchar { - let result = SwiftModule.takeIntegers(i1: Int8(fromJNI: i1, in: environment!), i2: Int16(fromJNI: i2, in: environment!), i3: Int32(fromJNI: i3, in: environment!), i4: Int64(fromJNI: i4, in: environment!)) - return result.getJNIValue(in: environment) - } + @_cdecl("Java_com_example_swift_SwiftModule__00024takeIntegers__BSIJ") + func Java_com_example_swift_SwiftModule__00024takeIntegers__BSIJ(environment: UnsafeMutablePointer!, thisClass: jclass, i1: jbyte, i2: jshort, i3: jint, i4: jlong) -> jchar { + return SwiftModule.takeIntegers(i1: Int8(fromJNI: i1, in: environment!), i2: Int16(fromJNI: i2, in: environment!), i3: Int32(fromJNI: i3, in: environment!), i4: Int64(fromJNI: i4, in: environment!)).getJNIValue(in: environment!) + } """, """ - @_cdecl("Java_com_example_swift_SwiftModule_otherPrimitives__ZFD") - func Java_com_example_swift_SwiftModule_otherPrimitives__ZFD(environment: UnsafeMutablePointer!, thisClass: jclass, b: jboolean, f: jfloat, d: jdouble) { + @_cdecl("Java_com_example_swift_SwiftModule__00024otherPrimitives__ZFD") + func Java_com_example_swift_SwiftModule__00024otherPrimitives__ZFD(environment: UnsafeMutablePointer!, thisClass: jclass, b: jboolean, f: jfloat, d: jdouble) { SwiftModule.otherPrimitives(b: Bool(fromJNI: b, in: environment!), f: Float(fromJNI: f, in: environment!), d: Double(fromJNI: d, in: environment!)) } """ @@ -136,8 +150,13 @@ struct JNIModuleTests { * public func copy(_ string: String) -> String * } */ - public static native java.lang.String copy(java.lang.String string); + public static java.lang.String copy(java.lang.String string) { + return SwiftModule.$copy(string); + } """, + """ + private static native java.lang.String $copy(java.lang.String string); + """ ] ) } @@ -151,10 +170,9 @@ struct JNIModuleTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_SwiftModule_copy__Ljava_lang_String_2") - func Java_com_example_swift_SwiftModule_copy__Ljava_lang_String_2(environment: UnsafeMutablePointer!, thisClass: jclass, string: jstring?) -> jstring? { - let result = SwiftModule.copy(String(fromJNI: string, in: environment!)) - return result.getJNIValue(in: environment) + @_cdecl("Java_com_example_swift_SwiftModule__00024copy__Ljava_lang_String_2") + func Java_com_example_swift_SwiftModule__00024copy__Ljava_lang_String_2(environment: UnsafeMutablePointer!, thisClass: jclass, string: jstring) -> jstring { + return SwiftModule.copy(String(fromJNI: string, in: environment!)).getJNIValue(in: environment!) } """, ] @@ -175,7 +193,12 @@ struct JNIModuleTests { * public func methodA() throws * } */ - public static native void methodA() throws Exception; + public static void methodA() throws Exception { + SwiftModule.$methodA(); + } + """, + """ + private static native void $methodA(); """, """ /** @@ -184,8 +207,13 @@ struct JNIModuleTests { * public func methodB() throws -> Int64 * } */ - public static native long methodB() throws Exception; + public static long methodB() throws Exception { + return SwiftModule.$methodB(); + } """, + """ + private static native long $methodB(); + """ ] ) } @@ -199,9 +227,9 @@ struct JNIModuleTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_SwiftModule_methodA__") - func Java_com_example_swift_SwiftModule_methodA__(environment: UnsafeMutablePointer!, thisClass: jclass) { - do { + @_cdecl("Java_com_example_swift_SwiftModule__00024methodA__") + func Java_com_example_swift_SwiftModule__00024methodA__(environment: UnsafeMutablePointer!, thisClass: jclass) { + do { try SwiftModule.methodA() } catch { environment.throwAsException(error) @@ -209,11 +237,10 @@ struct JNIModuleTests { } """, """ - @_cdecl("Java_com_example_swift_SwiftModule_methodB__") - func Java_com_example_swift_SwiftModule_methodB__(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong { - do { - let result = try SwiftModule.methodB() - return result.getJNIValue(in: environment) + @_cdecl("Java_com_example_swift_SwiftModule__00024methodB__") + func Java_com_example_swift_SwiftModule__00024methodB__(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong { + do { + return try SwiftModule.methodB().getJNIValue(in: environment!) } catch { environment.throwAsException(error) return Int64.jniPlaceholderValue diff --git a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift index 47b1c4dc1..a4084654c 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift @@ -98,12 +98,11 @@ struct JNIStructTests { * } */ public static MyStruct init(long x, long y, SwiftArena swiftArena$) { - long self$ = MyStruct.allocatingInit(x, y); - return new MyStruct(self$, swiftArena$); + return new MyStruct(MyStruct.$init(x, y), swiftArena$); } """, """ - private static native long allocatingInit(long x, long y); + private static native long $init(long x, long y); """, ] ) @@ -118,11 +117,12 @@ struct JNIStructTests { detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_MyStruct_allocatingInit__JJ") - func Java_com_example_swift_MyStruct_allocatingInit__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jlong) -> jlong { - let self$ = UnsafeMutablePointer.allocate(capacity: 1) - self$.initialize(to: MyStruct(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) - return Int64(Int(bitPattern: self$)).getJNIValue(in: environment) + @_cdecl("Java_com_example_swift_MyStruct__00024init__JJ") + func Java_com_example_swift_MyStruct__00024init__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jlong) -> jlong { + let result$ = UnsafeMutablePointer.allocate(capacity: 1) + result$.initialize(to: MyStruct.init(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) + let resultBits$ = Int64(Int(bitPattern: result$)) + return resultBits$.getJNIValue(in: environment!) } """ ] @@ -170,12 +170,11 @@ struct JNIStructTests { * } */ public void doSomething(long x) { - long self$ = this.$memoryAddress(); - MyStruct.$doSomething(x, self$); + MyStruct.$doSomething(x, this.$memoryAddress()); } """, """ - private static native void $doSomething(long x, long selfPointer); + private static native void $doSomething(long x, long self); """ ] ) @@ -191,12 +190,9 @@ struct JNIStructTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_MyStruct__00024doSomething__JJ") - func Java_com_example_swift_MyStruct__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, selfPointer: jlong) { - guard let env$ = environment else { - fatalError("Missing JNIEnv in downcall to \\(#function)") - } - assert(selfPointer != 0, "selfPointer memory address was null") - let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + func Java_com_example_swift_MyStruct__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, self: jlong) { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { fatalError("self memory address was null in call to \\(#function)!") } diff --git a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift index 272d27b5a..5757e8daa 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift @@ -48,12 +48,11 @@ struct JNIVariablesTests { * } */ public long getConstant() { - long self$ = this.$memoryAddress(); - return MyClass.$getConstant(self$); + return MyClass.$getConstant(this.$memoryAddress()); } """, """ - private static native long $getConstant(long selfPointer); + private static native long $getConstant(long self); """ ]) } @@ -68,17 +67,13 @@ struct JNIVariablesTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_MyClass__00024getConstant__J") - func Java_com_example_swift_MyClass__00024getConstant__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { - guard let env$ = environment else { - fatalError("Missing JNIEnv in downcall to \\(#function)") - } - assert(selfPointer != 0, "selfPointer memory address was null") - let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + func Java_com_example_swift_MyClass__00024getConstant__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { fatalError("self memory address was null in call to \\(#function)!") } - let result = self$.pointee.constant - return result.getJNIValue(in: environment) + return self$.pointee.constant.getJNIValue(in: environment!) } """ ] @@ -101,8 +96,7 @@ struct JNIVariablesTests { * } */ public long getMutable() { - long self$ = this.$memoryAddress(); - return MyClass.$getMutable(self$); + return MyClass.$getMutable(this.$memoryAddress()); } """, """ @@ -113,15 +107,14 @@ struct JNIVariablesTests { * } */ public void setMutable(long newValue) { - long self$ = this.$memoryAddress(); - MyClass.$setMutable(newValue, self$); + MyClass.$setMutable(newValue, this.$memoryAddress()); } """, """ - private static native long $getMutable(long selfPointer); + private static native long $getMutable(long self); """, """ - private static native void $setMutable(long newValue, long selfPointer); + private static native void $setMutable(long newValue, long self); """ ] ) @@ -137,27 +130,20 @@ struct JNIVariablesTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_MyClass__00024getMutable__J") - func Java_com_example_swift_MyClass__00024getMutable__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { - guard let env$ = environment else { - fatalError("Missing JNIEnv in downcall to \\(#function)") - } - assert(selfPointer != 0, "selfPointer memory address was null") - let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + func Java_com_example_swift_MyClass__00024getMutable__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { fatalError("self memory address was null in call to \\(#function)!") } - let result = self$.pointee.mutable - return result.getJNIValue(in: environment) + return self$.pointee.mutable.getJNIValue(in: environment!) } """, """ @_cdecl("Java_com_example_swift_MyClass__00024setMutable__JJ") - func Java_com_example_swift_MyClass__00024setMutable__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, selfPointer: jlong) { - guard let env$ = environment else { - fatalError("Missing JNIEnv in downcall to \\(#function)") - } - assert(selfPointer != 0, "selfPointer memory address was null") - let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + func Java_com_example_swift_MyClass__00024setMutable__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, self: jlong) { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { fatalError("self memory address was null in call to \\(#function)!") } @@ -184,12 +170,11 @@ struct JNIVariablesTests { * } */ public long getComputed() { - long self$ = this.$memoryAddress(); - return MyClass.$getComputed(self$); + return MyClass.$getComputed(this.$memoryAddress()); } """, """ - private static native long $getComputed(long selfPointer); + private static native long $getComputed(long self); """, ] ) @@ -205,18 +190,13 @@ struct JNIVariablesTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_MyClass__00024getComputed__J") - func Java_com_example_swift_MyClass__00024getComputed__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { - guard let env$ = environment else { - fatalError("Missing JNIEnv in downcall to \\(#function)") - } - assert(selfPointer != 0, "selfPointer memory address was null") - let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + func Java_com_example_swift_MyClass__00024getComputed__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { fatalError("self memory address was null in call to \\(#function)!") } - - let result = self$.pointee.computed - return result.getJNIValue(in: environment) + return self$.pointee.computed.getJNIValue(in: environment!) } """, ] @@ -239,12 +219,11 @@ struct JNIVariablesTests { * } */ public long getComputedThrowing() throws Exception { - long self$ = this.$memoryAddress(); - return MyClass.$getComputedThrowing(self$); + return MyClass.$getComputedThrowing(this.$memoryAddress()); } """, """ - private static native long $getComputedThrowing(long selfPointer); + private static native long $getComputedThrowing(long self); """, ] ) @@ -260,19 +239,14 @@ struct JNIVariablesTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_MyClass__00024getComputedThrowing__J") - func Java_com_example_swift_MyClass__00024getComputedThrowing__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { - guard let env$ = environment else { - fatalError("Missing JNIEnv in downcall to \\(#function)") - } - assert(selfPointer != 0, "selfPointer memory address was null") - let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + func Java_com_example_swift_MyClass__00024getComputedThrowing__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { fatalError("self memory address was null in call to \\(#function)!") } - do { - let result = try self$.pointee.computedThrowing - return result.getJNIValue(in: environment) + return try self$.pointee.computedThrowing.getJNIValue(in: environment!) } catch { environment.throwAsException(error) return Int64.jniPlaceholderValue @@ -299,8 +273,7 @@ struct JNIVariablesTests { * } */ public long getGetterAndSetter() { - long self$ = this.$memoryAddress(); - return MyClass.$getGetterAndSetter(self$); + return MyClass.$getGetterAndSetter(this.$memoryAddress()); } """, """ @@ -311,15 +284,14 @@ struct JNIVariablesTests { * } */ public void setGetterAndSetter(long newValue) { - long self$ = this.$memoryAddress(); - MyClass.$setGetterAndSetter(newValue, self$); + MyClass.$setGetterAndSetter(newValue, this.$memoryAddress()); } """, """ - private static native long $getGetterAndSetter(long selfPointer); + private static native long $getGetterAndSetter(long self); """, """ - private static native void $setGetterAndSetter(long newValue, long selfPointer); + private static native void $setGetterAndSetter(long newValue, long self); """ ] ) @@ -335,32 +307,23 @@ struct JNIVariablesTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_MyClass__00024getGetterAndSetter__J") - func Java_com_example_swift_MyClass__00024getGetterAndSetter__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { - guard let env$ = environment else { - fatalError("Missing JNIEnv in downcall to \\(#function)") - } - assert(selfPointer != 0, "selfPointer memory address was null") - let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + func Java_com_example_swift_MyClass__00024getGetterAndSetter__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { fatalError("self memory address was null in call to \\(#function)!") } - - let result = self$.pointee.getterAndSetter - return result.getJNIValue(in: environment) + return self$.pointee.getterAndSetter.getJNIValue(in: environment!) } """, """ @_cdecl("Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ") - func Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, selfPointer: jlong) { - guard let env$ = environment else { - fatalError("Missing JNIEnv in downcall to \\(#function)") - } - assert(selfPointer != 0, "selfPointer memory address was null") - let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + func Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, self: jlong) { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { fatalError("self memory address was null in call to \\(#function)!") } - self$.pointee.getterAndSetter = Int64(fromJNI: newValue, in: environment!) } """ @@ -384,8 +347,7 @@ struct JNIVariablesTests { * } */ public boolean isSomeBoolean() { - long self$ = this.$memoryAddress(); - return MyClass.$isSomeBoolean(self$); + return MyClass.$isSomeBoolean(this.$memoryAddress()); } """, """ @@ -396,15 +358,14 @@ struct JNIVariablesTests { * } */ public void setSomeBoolean(boolean newValue) { - long self$ = this.$memoryAddress(); - MyClass.$setSomeBoolean(newValue, self$); + MyClass.$setSomeBoolean(newValue, this.$memoryAddress()); } """, """ - private static native boolean $isSomeBoolean(long selfPointer); + private static native boolean $isSomeBoolean(long self); """, """ - private static native void $setSomeBoolean(boolean newValue, long selfPointer); + private static native void $setSomeBoolean(boolean newValue, long self); """ ] ) @@ -420,27 +381,20 @@ struct JNIVariablesTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_MyClass__00024isSomeBoolean__J") - func Java_com_example_swift_MyClass__00024isSomeBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jboolean { - guard let env$ = environment else { - fatalError("Missing JNIEnv in downcall to \\(#function)") - } - assert(selfPointer != 0, "selfPointer memory address was null") - let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + func Java_com_example_swift_MyClass__00024isSomeBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jboolean { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { fatalError("self memory address was null in call to \\(#function)!") } - let result = self$.pointee.someBoolean - return result.getJNIValue(in: environment) + return self$.pointee.someBoolean.getJNIValue(in: environment!) } """, """ @_cdecl("Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ") - func Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, selfPointer: jlong) { - guard let env$ = environment else { - fatalError("Missing JNIEnv in downcall to \\(#function)") - } - assert(selfPointer != 0, "selfPointer memory address was null") - let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + func Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, self: jlong) { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { fatalError("self memory address was null in call to \\(#function)!") } @@ -467,8 +421,7 @@ struct JNIVariablesTests { * } */ public boolean isBoolean() { - long self$ = this.$memoryAddress(); - return MyClass.$isBoolean(self$); + return MyClass.$isBoolean(this.$memoryAddress()); } """, """ @@ -479,15 +432,14 @@ struct JNIVariablesTests { * } */ public void setBoolean(boolean newValue) { - long self$ = this.$memoryAddress(); - MyClass.$setBoolean(newValue, self$); + MyClass.$setBoolean(newValue, this.$memoryAddress()); } """, """ - private static native boolean $isBoolean(long selfPointer); + private static native boolean $isBoolean(long self); """, """ - private static native void $setBoolean(boolean newValue, long selfPointer); + private static native void $setBoolean(boolean newValue, long self); """ ] ) @@ -503,27 +455,20 @@ struct JNIVariablesTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_MyClass__00024isBoolean__J") - func Java_com_example_swift_MyClass__00024isBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jboolean { - guard let env$ = environment else { - fatalError("Missing JNIEnv in downcall to \\(#function)") - } - assert(selfPointer != 0, "selfPointer memory address was null") - let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + func Java_com_example_swift_MyClass__00024isBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jboolean { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { fatalError("self memory address was null in call to \\(#function)!") } - let result = self$.pointee.isBoolean - return result.getJNIValue(in: environment) + return self$.pointee.isBoolean.getJNIValue(in: environment!) } """, """ @_cdecl("Java_com_example_swift_MyClass__00024setBoolean__ZJ") - func Java_com_example_swift_MyClass__00024setBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, selfPointer: jlong) { - guard let env$ = environment else { - fatalError("Missing JNIEnv in downcall to \\(#function)") - } - assert(selfPointer != 0, "selfPointer memory address was null") - let selfBits$ = Int(Int64(fromJNI: selfPointer, in: env$)) + func Java_com_example_swift_MyClass__00024setBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, self: jlong) { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { fatalError("self memory address was null in call to \\(#function)!") } From f62a31752f9840f2fb3fa78b9720e179404000e7 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 22 Jul 2025 13:38:15 +0200 Subject: [PATCH 040/141] [JExtract/JNI] Add support for primitive non-escaping closures. (#327) --- .../Sources/MySwiftLibrary/Closures.swift | 31 ++++ .../java/com/example/swift/ClosuresTest.java | 44 +++++ .../Convenience/JavaType+Extensions.swift | 23 +-- ...t2JavaGenerator+JavaBindingsPrinting.swift | 71 +++++++- ...ISwift2JavaGenerator+JavaTranslation.swift | 114 ++++++++++-- ...wift2JavaGenerator+NativeTranslation.swift | 169 ++++++++++++++++-- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 5 +- Sources/JExtractSwiftLib/JNI/JNIType.swift | 16 ++ Sources/JavaKit/JavaEnvironment.swift | 2 +- Sources/JavaKit/JavaValue.swift | 2 +- .../JNI/JNIClosureTests.swift | 129 +++++++++++++ 11 files changed, 556 insertions(+), 50 deletions(-) create mode 100644 Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift create mode 100644 Samples/JExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java create mode 100644 Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift new file mode 100644 index 000000000..00d1f4b0f --- /dev/null +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +public func emptyClosure(closure: () -> ()) { + closure() +} + +public func closureWithInt(input: Int64, closure: (Int64) -> Int64) -> Int64 { + return closure(input) +} + +public func closureMultipleArguments( + input1: Int64, + input2: Int64, + closure: (Int64, Int64) -> Int64 +) -> Int64 { + return closure(input1, input2) +} + + diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java new file mode 100644 index 000000000..b8389d419 --- /dev/null +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java @@ -0,0 +1,44 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; + +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.junit.jupiter.api.Assertions.*; + +public class ClosuresTest { + @Test + void emptyClosure() { + AtomicBoolean closureCalled = new AtomicBoolean(false); + MySwiftLibrary.emptyClosure(() -> { + closureCalled.set(true); + }); + assertTrue(closureCalled.get()); + } + + @Test + void closureWithInt() { + long result = MySwiftLibrary.closureWithInt(10, (value) -> value * 2); + assertEquals(20, result); + } + + @Test + void closureMultipleArguments() { + long result = MySwiftLibrary.closureMultipleArguments(5, 10, (a, b) -> a + b); + assertEquals(15, result); + } +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift index 9da3ae5bc..f9a67419d 100644 --- a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift @@ -17,21 +17,22 @@ import JavaTypes extension JavaType { var jniTypeSignature: String { switch self { - case .boolean: "Z" - case .byte: "B" - case .char: "C" - case .short: "S" - case .int: "I" - case .long: "J" - case .float: "F" - case .double: "D" + case .boolean: return "Z" + case .byte: return "B" + case .char: return "C" + case .short: return "S" + case .int: return "I" + case .long: return "J" + case .float: return "F" + case .double: return "D" case .class(let package, let name): + let nameWithInnerClasses = name.replacingOccurrences(of: ".", with: "$") if let package { - "L\(package.replacingOccurrences(of: ".", with: "/"))/\(name);" + return "L\(package.replacingOccurrences(of: ".", with: "/"))/\(nameWithInnerClasses);" } else { - "L\(name);" + return "L\(nameWithInnerClasses);" } - case .array(let javaType): "[\(javaType.jniTypeSignature)" + case .array(let javaType): return "[\(javaType.jniTypeSignature)" case .void: fatalError("There is no type signature for 'void'") } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 9c3d14b8d..e0e5cb701 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -76,12 +76,12 @@ extension JNISwift2JavaGenerator { for decl in analysis.importedGlobalFuncs { self.logger.trace("Print global function: \(decl)") - printFunctionBinding(&printer, decl) + printFunctionDowncallMethods(&printer, decl) printer.println() } for decl in analysis.importedGlobalVariables { - printFunctionBinding(&printer, decl) + printFunctionDowncallMethods(&printer, decl) printer.println() } } @@ -117,17 +117,17 @@ extension JNISwift2JavaGenerator { printer.println() for initializer in decl.initializers { - printFunctionBinding(&printer, initializer) + printFunctionDowncallMethods(&printer, initializer) printer.println() } for method in decl.methods { - printFunctionBinding(&printer, method) + printFunctionDowncallMethods(&printer, method) printer.println() } for variable in decl.variables { - printFunctionBinding(&printer, variable) + printFunctionDowncallMethods(&printer, variable) printer.println() } @@ -175,12 +175,67 @@ extension JNISwift2JavaGenerator { } } - private func printFunctionBinding(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - guard let translatedDecl = translatedDecl(for: decl) else { + private func printFunctionDowncallMethods( + _ printer: inout CodePrinter, + _ decl: ImportedFunc + ) { + guard translatedDecl(for: decl) != nil else { // Failed to translate. Skip. return } + printer.printSeparator(decl.displayName) + + printJavaBindingWrapperHelperClass(&printer, decl) + + printJavaBindingWrapperMethod(&printer, decl) + } + + /// Print the helper type container for a user-facing Java API. + /// + /// * User-facing functional interfaces. + private func printJavaBindingWrapperHelperClass( + _ printer: inout CodePrinter, + _ decl: ImportedFunc + ) { + let translated = self.translatedDecl(for: decl)! + if translated.functionTypes.isEmpty { + return + } + + printer.printBraceBlock( + """ + public static class \(translated.name) + """ + ) { printer in + for functionType in translated.functionTypes { + printJavaBindingWrapperFunctionTypeHelper(&printer, functionType) + } + } + } + + /// Print "wrapper" functional interface representing a Swift closure type. + func printJavaBindingWrapperFunctionTypeHelper( + _ printer: inout CodePrinter, + _ functionType: TranslatedFunctionType + ) { + let apiParams = functionType.parameters.map(\.parameter.asParameter) + + printer.print( + """ + @FunctionalInterface + public interface \(functionType.name) { + \(functionType.result.javaType) apply(\(apiParams.joined(separator: ", "))); + } + """ + ) + } + + private func printJavaBindingWrapperMethod(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + guard let translatedDecl = translatedDecl(for: decl) else { + fatalError("Decl was not translated, \(decl)") + } + var modifiers = ["public"] if decl.isStatic || decl.isInitializer || !decl.hasParent { modifiers.append("static") @@ -215,7 +270,7 @@ extension JNISwift2JavaGenerator { if let selfParameter = nativeSignature.selfParameter { parameters.append(selfParameter) } - let renderedParameters = parameters.map { "\($0.javaParameter.type) \($0.javaParameter.name)"}.joined(separator: ", ") + let renderedParameters = parameters.map { "\($0.javaType) \($0.name)"}.joined(separator: ", ") printer.print("private static native \(resultType) \(translatedDecl.nativeFunctionName)(\(renderedParameters));") } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index e0253624c..e704739c4 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -24,7 +24,7 @@ extension JNISwift2JavaGenerator { let translated: TranslatedFunctionDecl? do { - let translation = JavaTranslation(swiftModuleName: swiftModuleName) + let translation = JavaTranslation(swiftModuleName: swiftModuleName, javaPackage: self.javaPackage) translated = try translation.translate(decl) } catch { self.logger.debug("Failed to translate: '\(decl.swiftDecl.qualifiedNameForDebug)'; \(error)") @@ -37,17 +37,10 @@ extension JNISwift2JavaGenerator { struct JavaTranslation { let swiftModuleName: String + let javaPackage: String func translate(_ decl: ImportedFunc) throws -> TranslatedFunctionDecl { - let nativeTranslation = NativeJavaTranslation() - - // Swift -> Java - let translatedFunctionSignature = try translate(functionSignature: decl.functionSignature) - // Java -> Java (native) - let nativeFunctionSignature = try nativeTranslation.translate( - functionSignature: decl.functionSignature, - translatedFunctionSignature: translatedFunctionSignature - ) + let nativeTranslation = NativeJavaTranslation(javaPackage: self.javaPackage) // Types with no parent will be outputted inside a "module" class. let parentName = decl.parentType?.asNominalType?.nominalTypeDecl.qualifiedName ?? swiftModuleName @@ -59,19 +52,81 @@ extension JNISwift2JavaGenerator { case .function, .initializer: decl.name } + // Swift -> Java + let translatedFunctionSignature = try translate( + functionSignature: decl.functionSignature, + methodName: javaName, + parentName: parentName + ) + // Java -> Java (native) + let nativeFunctionSignature = try nativeTranslation.translate( + functionSignature: decl.functionSignature, + translatedFunctionSignature: translatedFunctionSignature, + methodName: javaName, + parentName: parentName + ) + + // Closures. + var funcTypes: [TranslatedFunctionType] = [] + for (idx, param) in decl.functionSignature.parameters.enumerated() { + let parameterName = param.parameterName ?? "_\(idx)" + + switch param.type { + case .function(let funcTy): + let translatedClosure = try translateFunctionType( + name: parameterName, + swiftType: funcTy, + parentName: parentName + ) + funcTypes.append(translatedClosure) + default: + break + } + } + return TranslatedFunctionDecl( name: javaName, nativeFunctionName: "$\(javaName)", parentName: parentName, + functionTypes: funcTypes, translatedFunctionSignature: translatedFunctionSignature, nativeFunctionSignature: nativeFunctionSignature ) } - func translate(functionSignature: SwiftFunctionSignature) throws -> TranslatedFunctionSignature { + /// Translate Swift closure type to Java functional interface. + func translateFunctionType( + name: String, + swiftType: SwiftFunctionType, + parentName: String + ) throws -> TranslatedFunctionType { + var translatedParams: [TranslatedParameter] = [] + + for (i, param) in swiftType.parameters.enumerated() { + let paramName = param.parameterName ?? "_\(i)" + translatedParams.append( + try translateParameter(swiftType: param.type, parameterName: paramName, methodName: name, parentName: parentName) + ) + } + + let transltedResult = try translate(swiftResult: SwiftResult(convention: .direct, type: swiftType.resultType)) + + return TranslatedFunctionType( + name: name, + parameters: translatedParams, + result: transltedResult, + swiftType: swiftType + ) + } + + func translate( + functionSignature: SwiftFunctionSignature, + methodName: String, + parentName: String + ) throws -> TranslatedFunctionSignature { let parameters = try functionSignature.parameters.enumerated().map { idx, param in let parameterName = param.parameterName ?? "arg\(idx))" - return try translateParameter(swiftType: param.type, parameterName: parameterName) + return try translateParameter(swiftType: param.type, parameterName: parameterName, methodName: methodName, parentName: parentName) } // 'self' @@ -79,7 +134,9 @@ extension JNISwift2JavaGenerator { if case .instance(let swiftSelf) = functionSignature.selfParameter { selfParameter = try self.translateParameter( swiftType: swiftSelf.type, - parameterName: swiftSelf.parameterName ?? "self" + parameterName: swiftSelf.parameterName ?? "self", + methodName: methodName, + parentName: parentName ) } else { selfParameter = nil @@ -92,7 +149,12 @@ extension JNISwift2JavaGenerator { ) } - func translateParameter(swiftType: SwiftType, parameterName: String) throws -> TranslatedParameter { + func translateParameter( + swiftType: SwiftType, + parameterName: String, + methodName: String, + parentName: String + ) throws -> TranslatedParameter { switch swiftType { case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { @@ -121,7 +183,16 @@ extension JNISwift2JavaGenerator { conversion: .placeholder ) - case .metatype, .optional, .tuple, .function, .existential, .opaque: + case .function: + return TranslatedParameter( + parameter: JavaParameter( + name: parameterName, + type: .class(package: javaPackage, name: "\(parentName).\(methodName).\(parameterName)") + ), + conversion: .placeholder + ) + + case .metatype, .optional, .tuple, .existential, .opaque: throw JavaTranslationError.unsupportedSwiftType(swiftType) } } @@ -168,6 +239,9 @@ extension JNISwift2JavaGenerator { /// The name of the Java parent scope this function is declared in let parentName: String + /// Functional interfaces required for the Java method. + let functionTypes: [TranslatedFunctionType] + /// Function signature of the Java function the user will call let translatedFunctionSignature: TranslatedFunctionSignature @@ -220,6 +294,16 @@ extension JNISwift2JavaGenerator { let conversion: JavaNativeConversionStep } + /// Represent a Swift closure type in the user facing Java API. + /// + /// Closures are translated to named functional interfaces in Java. + struct TranslatedFunctionType { + var name: String + var parameters: [TranslatedParameter] + var result: TranslatedResult + var swiftType: SwiftFunctionType + } + /// Describes how to convert values between Java types and the native Java function enum JavaNativeConversionStep { /// The value being converted diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 461c73017..cae6010d1 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -17,14 +17,25 @@ import JavaTypes extension JNISwift2JavaGenerator { struct NativeJavaTranslation { + let javaPackage: String + /// Translates a Swift function into the native JNI method signature. func translate( functionSignature: SwiftFunctionSignature, - translatedFunctionSignature: TranslatedFunctionSignature + translatedFunctionSignature: TranslatedFunctionSignature, + methodName: String, + parentName: String ) throws -> NativeFunctionSignature { - let parameters = try zip(translatedFunctionSignature.parameters, functionSignature.parameters).map { translatedParameter, swiftParameter in + let parameters = try zip(translatedFunctionSignature.parameters, functionSignature.parameters).map { + translatedParameter, + swiftParameter in let parameterName = translatedParameter.parameter.name - return try translate(swiftParameter: swiftParameter, parameterName: parameterName) + return try translate( + swiftParameter: swiftParameter, + parameterName: parameterName, + methodName: methodName, + parentName: parentName + ) } // Lower the self parameter. @@ -32,7 +43,9 @@ extension JNISwift2JavaGenerator { case .instance(let selfParameter): try translate( swiftParameter: selfParameter, - parameterName: selfParameter.parameterName ?? "self" + parameterName: selfParameter.parameterName ?? "self", + methodName: methodName, + parentName: parentName ) case nil, .initializer(_), .staticMethod(_): nil @@ -47,7 +60,9 @@ extension JNISwift2JavaGenerator { func translate( swiftParameter: SwiftParameter, - parameterName: String + parameterName: String, + methodName: String, + parentName: String, ) throws -> NativeParameter { switch swiftParameter.type { case .nominal(let nominalType): @@ -57,28 +72,111 @@ extension JNISwift2JavaGenerator { } return NativeParameter( - javaParameter: JavaParameter(name: parameterName, type: javaType), + name: parameterName, + javaType: javaType, conversion: .initFromJNI(.placeholder, swiftType: swiftParameter.type) ) } case .tuple([]): return NativeParameter( - javaParameter: JavaParameter(name: parameterName, type: .void), + name: parameterName, + javaType: .void, conversion: .placeholder ) - case .metatype, .optional, .tuple, .function, .existential, .opaque: + case .function(let fn): + var parameters = [NativeParameter]() + for (i, parameter) in fn.parameters.enumerated() { + let parameterName = parameter.parameterName ?? "_\(i)" + let closureParameter = try translateClosureParameter( + parameter.type, + parameterName: parameterName + ) + parameters.append(closureParameter) + } + + let result = try translateClosureResult(fn.resultType) + + return NativeParameter( + name: parameterName, + javaType: .class(package: javaPackage, name: "\(parentName).\(methodName).\(parameterName)"), + conversion: .closureLowering( + parameters: parameters, + result: result + ) + ) + + case .metatype, .optional, .tuple, .existential, .opaque: throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) } // Classes are passed as the pointer. return NativeParameter( - javaParameter: JavaParameter(name: parameterName, type: .long), + name: parameterName, + javaType: .long, conversion: .pointee(.extractSwiftValue(.placeholder, swiftType: swiftParameter.type)) ) } + func translateClosureResult( + _ type: SwiftType + ) throws -> NativeResult { + switch type { + case .nominal(let nominal): + if let knownType = nominal.nominalTypeDecl.knownTypeKind { + guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType), javaType.implementsJavaValue else { + throw JavaTranslationError.unsupportedSwiftType(type) + } + + // Only support primitives for now. + return NativeResult( + javaType: javaType, + conversion: .initFromJNI(.placeholder, swiftType: type) + ) + } + + // Custom types are not supported yet. + throw JavaTranslationError.unsupportedSwiftType(type) + + case .tuple([]): + return NativeResult( + javaType: .void, + conversion: .placeholder + ) + + case .function, .metatype, .optional, .tuple, .existential, .opaque: + throw JavaTranslationError.unsupportedSwiftType(type) + } + } + + func translateClosureParameter( + _ type: SwiftType, + parameterName: String + ) throws -> NativeParameter { + switch type { + case .nominal(let nominal): + if let knownType = nominal.nominalTypeDecl.knownTypeKind { + guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType), javaType.implementsJavaValue else { + throw JavaTranslationError.unsupportedSwiftType(type) + } + + // Only support primitives for now. + return NativeParameter( + name: parameterName, + javaType: javaType, + conversion: .getJValue(.placeholder) + ) + } + + // Custom types are not supported yet. + throw JavaTranslationError.unsupportedSwiftType(type) + + case .function, .metatype, .optional, .tuple, .existential, .opaque: + throw JavaTranslationError.unsupportedSwiftType(type) + } + } + func translate( swiftResult: SwiftResult ) throws -> NativeResult { @@ -122,10 +220,11 @@ extension JNISwift2JavaGenerator { } struct NativeParameter { - let javaParameter: JavaParameter + let name: String + let javaType: JavaType var jniType: JNIType { - javaParameter.type.jniType + javaType.jniType } /// Represents how to convert the JNI parameter to a Swift parameter @@ -145,6 +244,9 @@ extension JNISwift2JavaGenerator { /// `value.getJNIValue(in:)` indirect case getJNIValue(NativeSwiftConversionStep) + /// `value.getJValue(in:)` + indirect case getJValue(NativeSwiftConversionStep) + /// `SwiftType(from: value, in: environment!)` indirect case initFromJNI(NativeSwiftConversionStep, swiftType: SwiftType) @@ -152,12 +254,13 @@ extension JNISwift2JavaGenerator { indirect case extractSwiftValue(NativeSwiftConversionStep, swiftType: SwiftType) /// Allocate memory for a Swift value and outputs the pointer - indirect case allocateSwiftValue(name: String, swiftType: SwiftType) + case allocateSwiftValue(name: String, swiftType: SwiftType) /// The thing to which the pointer typed, which is the `pointee` property /// of the `Unsafe(Mutable)Pointer` types in Swift. indirect case pointee(NativeSwiftConversionStep) + indirect case closureLowering(parameters: [NativeParameter], result: NativeResult) /// Returns the conversion string applied to the placeholder. func render(_ printer: inout CodePrinter, _ placeholder: String) -> String { @@ -171,6 +274,10 @@ extension JNISwift2JavaGenerator { let inner = inner.render(&printer, placeholder) return "\(inner).getJNIValue(in: environment!)" + case .getJValue(let inner): + let inner = inner.render(&printer, placeholder) + return "\(inner).getJValue(in: environment!)" + case .initFromJNI(let inner, let swiftType): let inner = inner.render(&printer, placeholder) return "\(swiftType)(fromJNI: \(inner), in: environment!)" @@ -203,6 +310,44 @@ extension JNISwift2JavaGenerator { case .pointee(let inner): let inner = inner.render(&printer, placeholder) return "\(inner).pointee" + + case .closureLowering(let parameters, let nativeResult): + var printer = CodePrinter() + + let methodSignature = MethodSignature( + resultType: nativeResult.javaType, + parameterTypes: parameters.map(\.javaType) + ) + + let closureParameters = !parameters.isEmpty ? "\(parameters.map(\.name).joined(separator: ", ")) in" : "" + printer.print("{ \(closureParameters)") + printer.indent() + + let arguments = parameters.map { + $0.conversion.render(&printer, $0.name) + } + + printer.print( + """ + let class$ = environment!.interface.GetObjectClass(environment, \(placeholder)) + let methodID$ = environment!.interface.GetMethodID(environment, class$, "apply", "\(methodSignature.mangledName)")! + let arguments$: [jvalue] = [\(arguments.joined(separator: ", "))] + """ + ) + + let upcall = "environment!.interface.\(nativeResult.javaType.jniType.callMethodAName)(environment, \(placeholder), methodID$, arguments$)" + let result = nativeResult.conversion.render(&printer, upcall) + + if nativeResult.javaType.isVoid { + printer.print(result) + } else { + printer.print("return \(result)") + } + + printer.outdent() + printer.print("}") + + return printer.finalize() } } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index d8b6b2750..20551465e 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -135,7 +135,7 @@ extension JNISwift2JavaGenerator { &printer, javaMethodName: translatedDecl.nativeFunctionName, parentName: translatedDecl.parentName, - parameters: parameters.map(\.javaParameter), + parameters: parameters.map { JavaParameter(name: $0.name, type: $0.javaType) }, resultType: nativeSignature.result.javaType.jniType ) { printer in self.printFunctionDowncall(&printer, decl) @@ -156,7 +156,7 @@ extension JNISwift2JavaGenerator { // Regular parameters. var arguments = [String]() for parameter in nativeSignature.parameters { - let lowered = parameter.conversion.render(&printer, parameter.javaParameter.name) + let lowered = parameter.conversion.render(&printer, parameter.name) arguments.append(lowered) } @@ -273,6 +273,7 @@ extension JNISwift2JavaGenerator { // Generated by swift-java import JavaKit + import JavaRuntime """ ) diff --git a/Sources/JExtractSwiftLib/JNI/JNIType.swift b/Sources/JExtractSwiftLib/JNI/JNIType.swift index d152942a2..feb8a5458 100644 --- a/Sources/JExtractSwiftLib/JNI/JNIType.swift +++ b/Sources/JExtractSwiftLib/JNI/JNIType.swift @@ -40,6 +40,22 @@ enum JNIType { case jfloatArray case jdoubleArray case jobjectArray + + var callMethodAName: String { + switch self { + case .jboolean: "CallBooleanMethodA" + case .jbyte: "CallByteMethodA" + case .jchar: "CallCharMethodA" + case .jshort: "CallShortMethodA" + case .jint: "CallIntMethodA" + case .jlong: "CallLongMethodA" + case .jfloat: "CallFloatMethodA" + case .jdouble: "CallDoubleMethodA" + case .void: "CallVoidMethodA" + case .jobject, .jstring, .jclass, .jthrowable: "CallObjectMethodA" + case .jbooleanArray, .jbyteArray, .jcharArray, .jshortArray, .jintArray, .jlongArray, .jfloatArray, .jdoubleArray, .jobjectArray: "CallObjectMethodA" + } + } } extension JavaType { diff --git a/Sources/JavaKit/JavaEnvironment.swift b/Sources/JavaKit/JavaEnvironment.swift index d74146abd..4895b32c7 100644 --- a/Sources/JavaKit/JavaEnvironment.swift +++ b/Sources/JavaKit/JavaEnvironment.swift @@ -15,5 +15,5 @@ import JavaRuntime extension UnsafeMutablePointer { - var interface: JNINativeInterface_ { self.pointee!.pointee } + public var interface: JNINativeInterface_ { self.pointee!.pointee } } diff --git a/Sources/JavaKit/JavaValue.swift b/Sources/JavaKit/JavaValue.swift index 1bc156a07..0f2081447 100644 --- a/Sources/JavaKit/JavaValue.swift +++ b/Sources/JavaKit/JavaValue.swift @@ -137,7 +137,7 @@ extension JavaValue where Self: ~Copyable { } /// Convert to a jvalue within the given JNI environment. - consuming func getJValue(in environment: JNIEnvironment) -> jvalue { + public consuming func getJValue(in environment: JNIEnvironment) -> jvalue { var result = jvalue() Self.assignJNIType(&result[keyPath: Self.jvalueKeyPath], to: getJNIValue(in: environment)) return result diff --git a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift new file mode 100644 index 000000000..47d7e35da --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift @@ -0,0 +1,129 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import Testing + +@Suite +struct JNIClosureTests { + let source = + """ + public func emptyClosure(closure: () -> ()) {} + public func closureWithArgumentsAndReturn(closure: (Int64, Bool) -> Int64) {} + """ + + @Test + func emptyClosure_javaBindings() throws { + try assertOutput(input: source, .jni, .java, expectedChunks: [ + """ + public static class emptyClosure { + @FunctionalInterface + public interface closure { + void apply(); + } + } + """, + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func emptyClosure(closure: () -> ()) + * } + */ + public static void emptyClosure(com.example.swift.SwiftModule.emptyClosure.closure closure) { + SwiftModule.$emptyClosure(closure); + } + """, + """ + private static native void $emptyClosure(com.example.swift.SwiftModule.emptyClosure.closure closure); + """ + ]) + } + + @Test + func emptyClosure_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024emptyClosure__Lcom_example_swift_SwiftModule_00024emptyClosure_00024closure_2") + func Java_com_example_swift_SwiftModule__00024emptyClosure__Lcom_example_swift_SwiftModule_00024emptyClosure_00024closure_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject) { + SwiftModule.emptyClosure(closure: { + let class$ = environment!.interface.GetObjectClass(environment, closure) + let methodID$ = environment!.interface.GetMethodID(environment, class$, "apply", "()V")! + let arguments$: [jvalue] = [] + environment!.interface.CallVoidMethodA(environment, closure, methodID$, arguments$) + } + ) + } + """ + ] + ) + } + + @Test + func closureWithArgumentsAndReturn_javaBindings() throws { + try assertOutput(input: source, .jni, .java, expectedChunks: [ + """ + public static class closureWithArgumentsAndReturn { + @FunctionalInterface + public interface closure { + long apply(long _0, boolean _1); + } + } + """, + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func closureWithArgumentsAndReturn(closure: (Int64, Bool) -> Int64) + * } + */ + public static void closureWithArgumentsAndReturn(com.example.swift.SwiftModule.closureWithArgumentsAndReturn.closure closure) { + SwiftModule.$closureWithArgumentsAndReturn(closure); + } + """, + """ + private static native void $closureWithArgumentsAndReturn(com.example.swift.SwiftModule.closureWithArgumentsAndReturn.closure closure); + """ + ]) + } + + @Test + func closureWithArgumentsAndReturn_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024closureWithArgumentsAndReturn__Lcom_example_swift_SwiftModule_00024closureWithArgumentsAndReturn_00024closure_2") + func Java_com_example_swift_SwiftModule__00024closureWithArgumentsAndReturn__Lcom_example_swift_SwiftModule_00024closureWithArgumentsAndReturn_00024closure_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject) { + SwiftModule.closureWithArgumentsAndReturn(closure: { _0, _1 in + let class$ = environment!.interface.GetObjectClass(environment, closure) + let methodID$ = environment!.interface.GetMethodID(environment, class$, "apply", "(JZ)J")! + let arguments$: [jvalue] = [_0.getJValue(in: environment!), _1.getJValue(in: environment!)] + return Int64(fromJNI: environment!.interface.CallLongMethodA(environment, closure, methodID$, arguments$), in: environment!) + } + ) + } + """ + ] + ) + } +} From 342b5ea87a04666adf8b7e1a4ec10741c3ae64ee Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 23 Jul 2025 20:26:54 +0900 Subject: [PATCH 041/141] docc documentation, first step (#319) --- .github/actions/prepare_env/action.yml | 6 +- .github/scripts/install_swiftly.sh | 6 +- .github/scripts/validate_docs.sh | 13 + .github/workflows/pull_request.yml | 25 ++ .gitignore | 1 + .spi.yml | 8 + Package.swift | 14 + .../JavaKit/Documentation.docc/JavaKit.md | 254 +---------------- .../Documentation.docc/SupportedFeatures.md | 94 +++++++ .../SwiftJavaCommandLineTool.md | 256 ++++++++++++++++++ .../Documentation.docc/SwiftPMPlugin.md | 126 +++++++++ .../Documentation.docc/index.md | 32 +++ Sources/SwiftJavaDocumentation/empty.swift | 15 + WIP.md | 9 - 14 files changed, 595 insertions(+), 264 deletions(-) create mode 100755 .github/scripts/validate_docs.sh create mode 100644 .spi.yml rename USER_GUIDE.md => Sources/JavaKit/Documentation.docc/JavaKit.md (62%) create mode 100644 Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md create mode 100644 Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md create mode 100644 Sources/SwiftJavaDocumentation/Documentation.docc/SwiftPMPlugin.md create mode 100644 Sources/SwiftJavaDocumentation/Documentation.docc/index.md create mode 100644 Sources/SwiftJavaDocumentation/empty.swift diff --git a/.github/actions/prepare_env/action.yml b/.github/actions/prepare_env/action.yml index 8fc986e06..53bb3fd71 100644 --- a/.github/actions/prepare_env/action.yml +++ b/.github/actions/prepare_env/action.yml @@ -26,9 +26,9 @@ runs: elif [[ -n "$JAVA_HOME_24_ARM64" ]]; then echo "JAVA_HOME_24=$JAVA_HOME_24_ARM64" >> $GITHUB_ENV fi - - name: Check Java environment - shell: bash - run: ./gradlew -q javaToolchains + # - name: Check Java environment + # shell: bash + # run: ./gradlew -q javaToolchains - name: Cache local SwiftPM repository if: matrix.os_version == 'jammy' uses: actions/cache@v4 diff --git a/.github/scripts/install_swiftly.sh b/.github/scripts/install_swiftly.sh index 78fa3f6b8..e0792894f 100755 --- a/.github/scripts/install_swiftly.sh +++ b/.github/scripts/install_swiftly.sh @@ -41,6 +41,7 @@ else curl -O https://download.swift.org/swiftly/darwin/swiftly.pkg && pkgutil --check-signature swiftly.pkg && pkgutil --verbose --expand swiftly.pkg "${SWIFTLY_HOME_DIR}" && tar -C "${SWIFTLY_HOME_DIR}" -xvf "${SWIFTLY_HOME_DIR}"/swiftly-*/Payload && "$SWIFTLY_HOME_DIR/bin/swiftly" init -y --skip-install + chmod +x "$SWIFTLY_HOME_DIR/env.sh" # shellcheck disable=SC1091 . "$SWIFTLY_HOME_DIR/env.sh" fi @@ -84,5 +85,8 @@ echo "Displaying swift version" swiftly run "${runSelector[@]}" swift --version if [[ "$(uname -s)" == "Linux" ]]; then - CC=clang swiftly run "${runSelector[@]}" "$(dirname "$0")/install-libarchive.sh" + if [[ -f "$(dirname "$0")/install-libarchive.sh" ]]; then + CC=clang swiftly run "${runSelector[@]}" "$(dirname "$0")/install-libarchive.sh" + fi fi + diff --git a/.github/scripts/validate_docs.sh b/.github/scripts/validate_docs.sh new file mode 100755 index 000000000..8ee777b00 --- /dev/null +++ b/.github/scripts/validate_docs.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -e +set -x + +cat <> Package.swift + +package.dependencies.append( + .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.0.0") +) +EOF + +swift package --disable-sandbox plugin generate-documentation --target "SwiftJavaDocumentation" --warnings-as-errors --analyze diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 9a0b59b89..b2338ed24 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -13,6 +13,31 @@ jobs: # FIXME: Something is off with the format task and it gets "stuck", need to investigate format_check_enabled: false license_header_check_project_name: Swift.org + # Since we need JAVA_HOME to be set up for building the project and depending on a different workflow won't + # give us that, we disable the checking and make a separate job that performs docs validation + docs_check_enabled: false + + # This replicates 'docs-check' from https://github.com/swiftlang/github-workflows/blob/main/.github/workflows/soundness.yml + # because we need to set up environment so we can build the SwiftJava project (including Java runtime/dependencies). + soundness-docs: + name: Documentation check + runs-on: ubuntu-latest + container: + image: 'swift:6.1-noble' + strategy: + fail-fast: true + matrix: + swift_version: ['6.1.2'] + os_version: ['jammy'] + jdk_vendor: ['corretto'] + steps: + - uses: actions/checkout@v4 + - name: Prepare CI Environment + uses: ./.github/actions/prepare_env + - name: Swift Build + run: swift build + - name: Run documentation check + run: ./.github/scripts/validate_docs.sh test-java: name: Test (Java) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) diff --git a/.gitignore b/.gitignore index f77390a73..28dfbae29 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ Package.resolved */**/*.o */**/*.swiftdeps */**/*.swiftdeps~ +*/**/.docc-build/ diff --git a/.spi.yml b/.spi.yml new file mode 100644 index 000000000..58345f251 --- /dev/null +++ b/.spi.yml @@ -0,0 +1,8 @@ +version: 1 +builder: + configs: + - documentation_targets: [ + SwiftJavaDocumentation, + JavaKit, + SwiftKitSwift + ] diff --git a/Package.swift b/Package.swift index 81d53b1d4..ef5294969 100644 --- a/Package.swift +++ b/Package.swift @@ -139,6 +139,12 @@ let package = Package( name: "swift-java", targets: ["SwiftJavaTool"] ), + + + .library( + name: "SwiftJavaDocumentation", + targets: ["SwiftJavaDocumentation"] + ), // ==== Plugin for building Java code .plugin( @@ -198,6 +204,14 @@ let package = Package( .package(url: "https://github.com/ordo-one/package-benchmark", .upToNextMajor(from: "1.4.0")), ], targets: [ + .target( + name: "SwiftJavaDocumentation", + dependencies: [ + "JavaKit", + "SwiftKitSwift", + ] + ), + .macro( name: "JavaKitMacros", dependencies: [ diff --git a/USER_GUIDE.md b/Sources/JavaKit/Documentation.docc/JavaKit.md similarity index 62% rename from USER_GUIDE.md rename to Sources/JavaKit/Documentation.docc/JavaKit.md index 351a3d5cb..fe0f50ba2 100644 --- a/USER_GUIDE.md +++ b/Sources/JavaKit/Documentation.docc/JavaKit.md @@ -2,11 +2,7 @@ Library and tools to make it easy to use Java libraries from Swift using the Java Native Interface (JNI). -## Getting started - -Before using this package, set the `JAVA_HOME` environment variable to point at your Java installation. Failing to do so will produce errors when processing the package manifest. Alternatively, you can put the path to your Java installation in the file `~/.java_home`. - -### Using Java libraries from Swift +## JavaKit: Using Java libraries from Swift Existing Java libraries can be wrapped for use in Swift with the `swift-java` tool. In a Swift program, the most direct way to access a Java API is to use the SwiftPM plugin to provide Swift wrappers for the Java classes. To do so, add a configuration file `swift-java.config` into the source directory for the Swift target. This is a JSON file that specifies Java classes and the Swift type name that should be generated to wrap them. For example, the following file maps `java.math.BigInteger` to a Swift type named `BigInteger`: @@ -282,7 +278,7 @@ Java native methods that throw any checked exception should be marked as `throws The Swift implementations of Java `native` constructors and static methods require an additional Swift parameter `environment: JNIEnvironment? = nil`, which will receive the JNI environment in which the function is being executed. In case of nil, the `JavaVirtualMachine.shared().environment()` value will be used. -## Using Java libraries from Swift +## JavaKit: Using Java libraries from Swift This section describes how Java libraries and mapped into Swift and their use from Swift. @@ -420,7 +416,7 @@ extension JavaClass { } ``` -### Interfaces +### Java Interfaces Java interfaces are similar to classes, and are projected into Swift in much the same way, but with the macro `JavaInterface`. The `JavaInterface` macro takes the Java interface name as well as any Java interfaces that this interface extends. As an example, here is the Swift projection of the [`java.util.Enumeration`](https://docs.oracle.com/javase/8/docs/api/java/util/Enumeration.html) generic interface: @@ -437,247 +433,3 @@ public struct Enumeration { public func nextElement() -> JavaObject! } ``` - -## Translating Java classes with `swift-java` - -The `swift-java` is a Swift program that uses Java's runtime reflection facilities to translate the requested Java classes into their Swift projections. The output is a number of Swift source files, each of which corresponds to a -single Java class. The `swift-java` can be executed like this: - -``` -swift-java -``` - -to produce help output like the following: - -``` -OVERVIEW: Generate sources and configuration for Swift and Java interoperability. - -USAGE: swift-java - -OPTIONS: - --depends-on - A Java2Swift configuration file for a given Swift module name on which this module depends, e.g., JavaKitJar=Sources/JavaKitJar/Java2Swift.config. There should be one of these options for each Swift module that this - module depends on (transitively) that contains wrapped Java sources. - --jar Specifies that the input is a Jar file whose public classes will be loaded. The output of Java2Swift will be a configuration file (Java2Swift.config) that can be used as input to a subsequent Java2Swift invocation to - generate wrappers for those public classes. - --fetch Fetch dependencies from given target (containing swift-java configuration) or dependency string - --swift-native-implementation - The names of Java classes whose declared native methods will be implemented in Swift. - --output-swift - The directory where generated Swift files should be written. Generally used with jextract mode. - --output-java - The directory where generated Java files should be written. Generally used with jextract mode. - --java-package - The Java package the generated Java code should be emitted into. - -c, --cache-directory - Directory where to write cached values (e.g. swift-java.classpath files) - -o, --output-directory - The directory in which to output the generated Swift files or the SwiftJava configuration file. - --input-swift - Directory containing Swift files which should be extracted into Java bindings. Also known as 'jextract' mode. Must be paired with --output-java and --output-swift. - -l, --log-level - Configure the level of logs that should be printed (values: trace, debug, info, notice, warning, error, critical; default: log level) - --cp, --classpath Class search path of directories and zip/jar files from which Java classes can be loaded. - -f, --filter-java-package - While scanning a classpath, inspect only types included in this package - -h, --help Show help information. - -SUBCOMMANDS: - configure Configure and emit a swift-java.config file based on an input dependency or jar file - resolve Resolve dependencies and write the resulting swift-java.classpath file - - See 'swift-java help ' for detailed help. -``` - -For example, the `JavaKitJar` library is generated with this command line: - -```swift -swift run swift-java --module-name JavaKitJar --depends-on JavaKit=Sources/JavaKit/swift-java.config -o Sources/JavaKitJar/generated Sources/JavaKitJar/swift-java.config -``` - -The `--swift-module JavaKitJar` parameter describes the name of the Swift module in which the code will be generated. - -The `--depends-on` option is followed by the swift-java configuration files for any library on which this Swift library depends. Each `--depends-on` option is of the form `=`, and tells swift-java which other Java classes have already been translated to Swift. For example, if your Java class uses `java.net.URL`, then you should include -`JavaKitNetwork`'s configuration file as a dependency here. - -The `-o` option specifies the output directory. Typically, this will be `Sources//generated` or similar to keep the generated Swift files separate from any hand-written ones. To see the output on the terminal rather than writing files to disk, pass `-` for this option. - -Finally, the command line should contain the `swift-java.config` file containing the list of classes that should be translated into Swift and their corresponding Swift type names. The tool will output a single `.swift` file for each class, along with warnings for any public API that cannot be translated into Swift. The most common warnings are due to missing Swift projections for Java classes. For example, here we have not translated (or provided the translation manifests for) the Java classes -`java.util.zip.ZipOutputStream` and `java.io.OutputStream`: - -``` -warning: Unable to translate 'java.util.jar.JarOutputStream' superclass: Java class 'java.util.zip.ZipOutputStream' has not been translated into Swift -warning: Unable to translate 'java.util.jar.JarOutputStream' constructor: Java class 'java.io.OutputStream' has not been translated into Swift -warning: Unable to translate 'java.util.jar.JarInputStream' method 'transferTo': Java class 'java.io.OutputStream' has not been translated into Swift -``` - -The result of such warnings is that certain information won't be statically available in Swift, e.g., the superclass won't be known (so we will assume it is `JavaObject`), or the specified constructors or methods won't be translated. If you don't need these APIs, the warnings can be safely ignored. The APIs can still be called dynamically via JNI. - -The `--jar` option changes the operation of `swift-java`. Instead of wrapping Java classes in Swift, it scans the given input Jar file to find all public classes and outputs a configuration file `swift-java.config` mapping all of the Java classes in the Jar file to Swift types. The `--jar` mode is expected to be used to help import a Java library into Swift wholesale, after which swift-java should invoked again given the generated configuration file. - -### Under construction: Create a Java class to wrap the Swift library - -**NOTE**: the instructions here work, but we are still smoothing out the interoperability story. - -All JavaKit-based applications start execution within the Java Virtual Machine. First, define your own Java class that loads your native Swift library and provides a `native` entry point to get into the Swift code. Here is a minimal Java class that has all of the program's logic written in Swift, including `main`: - - -```java -package org.swift.javakit; - -public class HelloSwiftMain { - static { - System.loadLibrary("HelloSwift"); - } - - public native static void main(String[] args); -} -``` - -Compile this into a `.class` file with `javac` before we build the Swift half, e.g.,: - -``` -javac Java/src/org/swift/javakit/JavaClassTranslator.java -``` - -### Create a Swift library - -The Java class created above loads a native library `HelloSwift` that needs to contain a definition of the `main` method in the class `org.swift.javakit.HelloSwiftMain`. `HelloSwift` should be defined as a SwiftPM dynamic library product, e.g., - -```swift - products: [ - .library( - name: "HelloSwift", - type: .dynamic, - targets: ["HelloSwift"] - ), - ] -``` - -with an associated target that depends on `JavaKit`: - -```swift - .target( - name: "HelloSwift", - dependencies: [ - .product(name: "ArgumentParser", package: "swift-argument-parser"), - .product(name: "JavaKit", package: "JavaKit") - ]) -``` - -### Implement the `native` Java method in Swift -Now, in the `HelloSwift` Swift library, define a `struct` that provides the `main` method for the Java class we already defined: - -```swift -import JavaKit - -@JavaImplementation("org.swift.javakit.HelloSwiftMain") -struct HelloSwiftMain { - @JavaStaticMethod - static func main(arguments: [String], environment: JNIEnvironment? = nil) { - print("Command line arguments are: \(arguments)") - } -} -``` - -Go ahead and build this library with `swift build`, and find the path to the directory containing the resulting shared library (e.g., `HelloSwift.dylib`, `HelloSwift.so`, or `HelloSwift.dll`, depending on platform). It is often in `.build/debug/` if you ran `swift build` on the command line. - -### Putting it all together! - -Finally, run this program on the command line like this: - -``` -java -cp Java/src -Djava.library.path=$(PATH_CONTAINING_HELLO_SWIFT)/ org.swift.javakit.HelloSwiftMain -v argument -``` - -This will prints the command-line arguments `-v` and `argument` as seen by Swift. - -### Bonus: Swift argument parser - -The easiest way to build a command-line program in Swift is with the [Swift argument parser library](https://github.com/apple/swift-argument-parser). We can extend our `HelloSwiftMain` type to conform to `ParsableCommand` and using the Swift argument parser to process the arguments provided by Java: - -```swift -import ArgumentParser -import JavaKit - -@JavaClass("org.swift.javakit.HelloSwiftMain") -struct HelloSwiftMain: ParsableCommand { - @Option(name: .shortAndLong, help: "Enable verbose output") - var verbose: Bool = false - - @JavaImplementation - static func main(arguments: [String], environment: JNIEnvironment? = nil) { - let command = Self.parseOrExit(arguments) - command.run(environment: environment) - } - - func run(environment: JNIEnvironment? = nil) { - print("Verbose = \(verbose)") - } -} -``` - -# `jextract-swift` - -The project is still very early days, however the general outline of using this approach is as follows: - -- **No code changes** need to be made to Swift libraries that are to be exposed to Java using jextract-swift. -- Swift sources are compiled to `.swiftinterface` files -- These `.swiftinterface` files are imported by jextract-swift which generates `*.java` files -- The generated Java files contain generated code for efficient native invocations. - -You can then use Swift libraries in Java just by calling the appropriate methods and initializers. - -## `jextract-swift`: Generating Java bridging files - -This repository also includes the `jextract-swift` tool which is similar to the JDK's [`jextract`](https://github.com/openjdk/jextract/). - -This approach is using Java's most recent (stable in JDK22) Foreign function and Memory APIs, collectively known as "Project Panama". You can read more about it here: https://openjdk.org/projects/panama/ It promises much higher performance than traditional approaches using JNI, and is primarily aimed for calling native code from a Java application. - -:warning: This feature requires JDK 22. The recommended way to install/manage JDKs is using [sdkman](https://sdkman.io): - -``` -curl -s "https://get.sdkman.io" | bash -sdk install java 22-open - -export JAVA_HOME=$(sdk home java 22-open) -``` - -`jextract-swift` can be pointed at `*.swiftinterface` files and will generate corresponding Java files that use the (new in Java 22) Foreign Function & Memory APIs to expose efficient ways to call "down" into Swift from Java. - -## JExtract: Swift <-> Java Type mapping - -TODO: these are not implemented yet. - -### Closures and Callbacks - -A Swift function may accept a closure which is used as a callback: - -```swift -func callMe(maybe: () -> ()) {} -``` - -Minimal support for c-compatible closures is implemented, more documentation soon. - - -## `jextract-swift` importer behavior - -Only `public` functions, properties and types are imported. - -Global Swift functions become static functions on on a class with the same name as the Swift module in Java, - -```swift -// Swift (Sources/SomeModule/Example.swift) - -public func globalFunction() -``` - -becomes: - -```java -// Java (SomeModule.java) - -public final class SomeModule ... { - public static void globalFunction() { ... } -} -``` diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md new file mode 100644 index 000000000..dc2c7b0ec --- /dev/null +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -0,0 +1,94 @@ +# Supported Features + +Summary of features supported by the swift-java interoperability libraries and tools. + +## Overview + +JavaKit supports both directions of interoperability, using Swift macros and source generation +(via the `swift-java wrap-java` command). + +### Java -> Swift + +It is possible to use JavaKit macros and the `wrap-java` command to simplify implementing +Java `native` functions. JavaKit simplifies the type conversions + +> tip: This direction of interoperability is covered in the WWDC2025 session 'Explore Swift and Java interoperability' +> around the [7-minute mark](https://youtu.be/QSHO-GUGidA?si=vUXxphTeO-CHVZ3L&t=448). + +| Feature | Macro support | +|--------------------------------------------------|-------------------------| +| Java `static native` method implemented by Swift | ✅ `@JavaImplementation` | +| **This list is very work in progress** | | + +### Swift -> Java + + +> tip: This direction of interoperability is covered in the WWDC2025 session 'Explore Swift and Java interoperability' +> around the [10-minute mark](https://youtu.be/QSHO-GUGidA?si=QyYP5-p2FL_BH7aD&t=616). + +| Java Feature | Macro support | +|----------------------------------------|---------------| +| Java `class` | ✅ | +| Java class inheritance | ✅ | +| Java `abstract class` | TODO | +| Java `enum` | ❌ | +| Java methods: `static`, member | ✅ `@JavaMethod` | +| **This list is very work in progress** | | + + +### JExtract: Java -> Swift + +SwiftJava's `swift-java jextract` tool automates generating Java bindings from Swift sources. + +> tip: This direction of interoperability is covered in the WWDC2025 session 'Explore Swift and Java interoperability' +> around the [14-minute mark](https://youtu.be/QSHO-GUGidA?si=b9YUwAWDWFGzhRXN&t=842). + + +| Swift Feature | FFM | JNI | +|--------------------------------------------------------------------------------------| -------- |-----| +| Initializers: `class`, `struct` | ✅ | ✅ | +| Optional Initializers / Throwing Initializers | ❌ | ❌ | +| Deinitializers: `class`, `struct` | ✅ | ✅ | +| `enum`, `actor` | ❌ | ❌ | +| Global Swift `func` | ✅ | ✅ | +| Class/struct member `func` | ✅ | ✅ | +| Throwing functions: `func x() throws` | ❌ | ✅ | +| Typed throws: `func x() throws(E)` | ❌ | ❌ | +| Stored properties: `var`, `let` (with `willSet`, `didSet`) | ✅ | ✅ | +| Computed properties: `var` (incl. `throws`) | ✅ / TODO | ✅ | +| Async functions `func async` and properties: `var { get async {} }` | ❌ | ❌ | +| Arrays: `[UInt8]`, `[T]` | ❌ | ❌ | +| Dictionaries: `[String: Int]`, `[K:V]` | ❌ | ❌ | +| Generic functions | ❌ | ❌ | +| `Foundation.Data`, `any Foundation.DataProtocol` | ✅ | ❌ | +| Tuples: `(Int, String)`, `(A, B, C)` | ❌ | ❌ | +| Protocols: `protocol`, existential parameters `any Collection` | ❌ | ❌ | +| Optional types: `Int?`, `AnyObject?` | ❌ | ❌ | +| Primitive types: `Bool`, `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `Float`, `Double` | ✅ | ✅ | +| Unsigned primitive types: `UInt`, `UInt8`, `UInt16`, `UInt32`, `UInt64` | ❌ | ❌ | +| String (with copying data) | ✅ | ✅ | +| Variadic parameters: `T...` | ❌ | ❌ | +| Parametrer packs / Variadic generics | ❌ | ❌ | +| Ownership modifiers: `inout`, `borrowing`, `consuming` | ❌ | ❌ | +| Default parameter values: `func p(name: String = "")` | ❌ | ❌ | +| Operators: `+`, `-`, user defined | ❌ | ❌ | +| Subscripts: `subscript()` | ❌ | ❌ | +| Equatable | ❌ | ❌ | +| Pointers: `UnsafeRawPointer`, UnsafeBufferPointer (?) | 🟡 | ❌ | +| Nested types: `struct Hello { struct World {} }` | ❌ | ❌ | +| Inheritance: `class Caplin: Capybara` | ❌ | ❌ | +| Non-escaping `Void` closures: `func callMe(maybe: () -> ())` | ✅ | ✅ | +| Non-escaping closures with primitive arguments/results: `func callMe(maybe: (Int) -> (Double))` | ✅ | ✅ | +| Non-escaping closures with object arguments/results: `func callMe(maybe: (JavaObj) -> (JavaObj))` | ❌ | ❌ | +| `@escaping` closures: `func callMe(_: @escaping () -> ())` | ❌ | ❌ | +| Swift type extensions: `extension String { func uppercased() }` | 🟡 | 🟡 | +| Swift macros (maybe) | ❌ | ❌ | +| Result builders | ❌ | ❌ | +| Automatic Reference Counting of class types / lifetime safety | ✅ | ✅ | +| Value semantic types (e.g. struct copying) | ❌ | ❌ | +| Opaque types: `func get() -> some Builder`, func take(worker: some Worker) | ❌ | ❌ | +| Swift concurrency: `func async`, `actor`, `distribued actor` | ❌ | ❌ | +| | | | +| | | | + +> tip: The list of features may be incomplete, please file an issue if something is unclear or should be clarified in this table. diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md new file mode 100644 index 000000000..f83458d43 --- /dev/null +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md @@ -0,0 +1,256 @@ +# swift-java command line tool + +The `swift-java` command line tool offers multiple ways to interact your Java interoperability enabled projects. + +## Overview + +The `swift-java` command line tool offers multiple modes which you can use to prepare your Swift and Java code to interact with eachother. + +The following sections will explain the modes in depth. When in doubt, you can always use the command line `--help` to get additional +guidance about the tool and available options: + +```bash +> swift-java --help + +USAGE: swift-java + +OPTIONS: + -h, --help Show help information. + +SUBCOMMANDS: + configure Configure and emit a swift-java.config file based on an input dependency or jar file + resolve Resolve dependencies and write the resulting swift-java.classpath file + wrap-java Wrap Java classes with corresponding Swift bindings. + jextract Wrap Swift functions and types with Java bindings, making them available to be called from Java + + See 'swift-java help ' for detailed help. +``` + +### Expose Java classes to Swift: swift-java wrap-java + +The `swift-java` is a Swift program that uses Java's runtime reflection facilities to translate the requested Java classes into their Swift projections. The output is a number of Swift source files, each of which corresponds to a +single Java class. The `swift-java` can be executed like this: + +``` +swift-java help wrap-java +``` + +to produce help output like the following: + +``` +USAGE: swift-java wrap-java [--output-directory ] [--input-swift ] [--log-level ] [--cp ...] [--filter-java-package ] --swift-module [--depends-on ...] [--swift-native-implementation ...] [--cache-directory ] [--swift-match-package-directory-structure ] + +ARGUMENTS: + Path to .jar file whose Java classes should be wrapped using Swift bindings + +OPTIONS: + -o, --output-directory + The directory in which to output generated SwiftJava configuration files. + --input-swift + Directory containing Swift files which should be extracted into Java bindings. Also known as 'jextract' mode. Must be paired with --output-java and --output-swift. + -l, --log-level + Configure the level of logs that should be printed (values: trace, debug, info, notice, warning, error, critical; default: log level) + --cp, --classpath Class search path of directories and zip/jar files from which Java classes can be loaded. + -f, --filter-java-package + While scanning a classpath, inspect only types included in this package + --swift-module + The name of the Swift module into which the resulting Swift types will be generated. + --depends-on + A swift-java configuration file for a given Swift module name on which this module depends, + e.g., JavaKitJar=Sources/JavaKitJar/Java2Swift.config. There should be one of these options + for each Swift module that this module depends on (transitively) that contains wrapped Java sources. + --swift-native-implementation + The names of Java classes whose declared native methods will be implemented in Swift. + --cache-directory + Cache directory for intermediate results and other outputs between runs + --swift-match-package-directory-structure + Match java package directory structure with generated Swift files (default: false) + -h, --help Show help information. + +``` + +For example, the `JavaKitJar` library is generated with this command line: + +```swift +swift-java wrap-java --swift-module JavaKitJar --depends-on JavaKit=Sources/JavaKit/swift-java.config -o Sources/JavaKitJar/generated Sources/JavaKitJar/swift-java.config +``` + +The `--swift-module JavaKitJar` parameter describes the name of the Swift module in which the code will be generated. + +The `--depends-on` option is followed by the swift-java configuration files for any library on which this Swift library depends. Each `--depends-on` option is of the form `=`, and tells swift-java which other Java classes have already been translated to Swift. For example, if your Java class uses `java.net.URL`, then you should include +`JavaKitNetwork`'s configuration file as a dependency here. + +The `-o` option specifies the output directory. Typically, this will be `Sources//generated` or similar to keep the generated Swift files separate from any hand-written ones. To see the output on the terminal rather than writing files to disk, pass `-` for this option. + +Finally, the command line should contain the `swift-java.config` file containing the list of classes that should be translated into Swift and their corresponding Swift type names. The tool will output a single `.swift` file for each class, along with warnings for any public API that cannot be translated into Swift. The most common warnings are due to missing Swift projections for Java classes. For example, here we have not translated (or provided the translation manifests for) the Java classes +`java.util.zip.ZipOutputStream` and `java.io.OutputStream`: + +``` +warning: Unable to translate 'java.util.jar.JarOutputStream' superclass: Java class 'java.util.zip.ZipOutputStream' has not been translated into Swift +warning: Unable to translate 'java.util.jar.JarOutputStream' constructor: Java class 'java.io.OutputStream' has not been translated into Swift +warning: Unable to translate 'java.util.jar.JarInputStream' method 'transferTo': Java class 'java.io.OutputStream' has not been translated into Swift +``` + +The result of such warnings is that certain information won't be statically available in Swift, e.g., the superclass won't be known (so we will assume it is `JavaObject`), or the specified constructors or methods won't be translated. If you don't need these APIs, the warnings can be safely ignored. The APIs can still be called dynamically via JNI. + +The `--jar` option changes the operation of `swift-java`. Instead of wrapping Java classes in Swift, it scans the given input Jar file to find all public classes and outputs a configuration file `swift-java.config` mapping all of the Java classes in the Jar file to Swift types. The `--jar` mode is expected to be used to help import a Java library into Swift wholesale, after which swift-java should invoked again given the generated configuration file. + +### Under construction: Create a Java class to wrap the Swift library + +**NOTE**: the instructions here work, but we are still smoothing out the interoperability story. + +All JavaKit-based applications start execution within the Java Virtual Machine. First, define your own Java class that loads your native Swift library and provides a `native` entry point to get into the Swift code. Here is a minimal Java class that has all of the program's logic written in Swift, including `main`: + + +```java +package org.swift.javakit; + +public class HelloSwiftMain { + static { + System.loadLibrary("HelloSwift"); + } + + public native static void main(String[] args); +} +``` + +Compile this into a `.class` file with `javac` before we build the Swift half, e.g.,: + +``` +javac Java/src/org/swift/javakit/JavaClassTranslator.java +``` + +### Create a Swift library + +The Java class created above loads a native library `HelloSwift` that needs to contain a definition of the `main` method in the class `org.swift.javakit.HelloSwiftMain`. `HelloSwift` should be defined as a SwiftPM dynamic library product, e.g., + +```swift + products: [ + .library( + name: "HelloSwift", + type: .dynamic, + targets: ["HelloSwift"] + ), + ] +``` + +with an associated target that depends on `JavaKit`: + +```swift + .target( + name: "HelloSwift", + dependencies: [ + .product(name: "ArgumentParser", package: "swift-argument-parser"), + .product(name: "JavaKit", package: "JavaKit") + ]) +``` + +### Implement the `native` Java method in Swift +Now, in the `HelloSwift` Swift library, define a `struct` that provides the `main` method for the Java class we already defined: + +```swift +import JavaKit + +@JavaImplementation("org.swift.javakit.HelloSwiftMain") +struct HelloSwiftMain { + @JavaStaticMethod + static func main(arguments: [String], environment: JNIEnvironment? = nil) { + print("Command line arguments are: \(arguments)") + } +} +``` + +Go ahead and build this library with `swift build`, and find the path to the directory containing the resulting shared library (e.g., `HelloSwift.dylib`, `HelloSwift.so`, or `HelloSwift.dll`, depending on platform). It is often in `.build/debug/` if you ran `swift build` on the command line. + +### Putting it all together! + +Finally, run this program on the command line like this: + +``` +java -cp Java/src -Djava.library.path=$(PATH_CONTAINING_HELLO_SWIFT)/ org.swift.javakit.HelloSwiftMain -v argument +``` + +This will prints the command-line arguments `-v` and `argument` as seen by Swift. + +### Bonus: Swift argument parser + +The easiest way to build a command-line program in Swift is with the [Swift argument parser library](https://github.com/apple/swift-argument-parser). We can extend our `HelloSwiftMain` type to conform to `ParsableCommand` and using the Swift argument parser to process the arguments provided by Java: + +```swift +import ArgumentParser +import JavaKit + +@JavaClass("org.swift.javakit.HelloSwiftMain") +struct HelloSwiftMain: ParsableCommand { + @Option(name: .shortAndLong, help: "Enable verbose output") + var verbose: Bool = false + + @JavaImplementation + static func main(arguments: [String], environment: JNIEnvironment? = nil) { + let command = Self.parseOrExit(arguments) + command.run(environment: environment) + } + + func run(environment: JNIEnvironment? = nil) { + print("Verbose = \(verbose)") + } +} +``` + +### Download Java dependencies in Swift builds: swift-java resolve + +> TIP: See the `Samples/DependencySampleApp` for a fully functional showcase of this mode. + +TODO: documentation on this feature + +### Expose Swift code to Java: swift-java jextract + +The project is still very early days, however the general outline of using this approach is as follows: + +- **No code changes** need to be made to Swift libraries that are to be exposed to Java using jextract-swift. +- Swift sources are compiled to `.swiftinterface` files +- These `.swiftinterface` files are imported by jextract-swift which generates `*.java` files +- The generated Java files contain generated code for efficient native invocations. + +You can then use Swift libraries in Java just by calling the appropriate methods and initializers. + +### Generating Java bindings for Swift libraries + +This repository also includes the `jextract-swift` tool which is similar to the JDK's [`jextract`](https://github.com/openjdk/jextract/). + +This approach offers two modes of operation: + +- the default `--mode ffm` which uses the [JEP-424 Foreign function and Memory APIs](https://openjdk.org/jeps/424) which are available since JDK **22**. It promises much higher performance than traditional approaches using JNI, and is primarily aimed for calling native code from a Java application. + +> Tip: In order to use the ffm mode, you need to install a recent enough JDK (at least JDK 22). The recommended, and simplest way, to install the a JDK distribution of your choice is [sdkman](https://sdkman.io): +> +> ``` +> curl -s "https://get.sdkman.io" | bash +> sdk install java 22-open +> +> export JAVA_HOME=$(sdk home java 22-open) +> ``` + +`jextract-swift` can be pointed at `*.swiftinterface` files and will generate corresponding Java files that use the (new in Java 22) Foreign Function & Memory APIs to expose efficient ways to call "down" into Swift from Java. + +### Default jextract behaviors + +Only `public` functions, properties and types are imported. + +Global Swift functions become static functions on on a class with the same name as the Swift module in Java, + +```swift +// Swift (Sources/SomeModule/Example.swift) + +public func globalFunction() +``` + +becomes: + +```java +// Java (SomeModule.java) + +public final class SomeModule ... { + public static void globalFunction() { ... } +} +``` diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftPMPlugin.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftPMPlugin.md new file mode 100644 index 000000000..d10ab387d --- /dev/null +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftPMPlugin.md @@ -0,0 +1,126 @@ +# SwiftJava SwiftPM Plugin + +The `SwiftJavaPlugin` automates `swift-java` command line tool invocations during the build process. + +## Overview + +### Installing the plugin + +To install the SwiftPM plugin in your target of choice include the `swift-java` package dependency: + +```swift +import Foundation + +let javaHome = findJavaHome() + +let javaIncludePath = "\(javaHome)/include" +#if os(Linux) + let javaPlatformIncludePath = "\(javaIncludePath)/linux" +#elseif os(macOS) + let javaPlatformIncludePath = "\(javaIncludePath)/darwin" +#elseif os(Windows) + let javaPlatformIncludePath = "\(javaIncludePath)/win32" +#endif + +let package = Package( + name: "MyProject", + + products: [ + .library( + name: "JavaKitExample", + type: .dynamic, + targets: ["JavaKitExample"] + ), + ], + + dependencies: [ + .package(url: "https://github.com/apple/swift-java", from: "..."), + ], + + targets: [ + .target( + name: "MyProject", + dependencies: [ + // ... + ], + swiftSettings: [ + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) + ], + plugins: [ + .plugin(name: "JavaCompilerPlugin", package: "swift-java"), + .plugin(name: "JExtractSwiftPlugin", package: "swift-java"), + .plugin(name: "SwiftJavaPlugin", package: "swift-java"), + ] + ), + ] +) +``` + +```swift + +// Note: the JAVA_HOME environment variable must be set to point to where +// Java is installed, e.g., +// Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home. +func findJavaHome() -> String { + if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] { + print("JAVA_HOME = \(home)") + return home + } + + // This is a workaround for envs (some IDEs) which have trouble with + // picking up env variables during the build process + let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home" + if let home = try? String(contentsOfFile: path, encoding: .utf8) { + if let lastChar = home.last, lastChar.isNewline { + return String(home.dropLast()) + } + + return home + } + + if let home = getJavaHomeFromLibexecJavaHome(), + !home.isEmpty { + return home + } + + fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.") +} + +/// On MacOS we can use the java_home tool as a fallback if we can't find JAVA_HOME environment variable. +func getJavaHomeFromLibexecJavaHome() -> String? { + let task = Process() + task.executableURL = URL(fileURLWithPath: "/usr/libexec/java_home") + + // Check if the executable exists before trying to run it + guard FileManager.default.fileExists(atPath: task.executableURL!.path) else { + print("/usr/libexec/java_home does not exist") + return nil + } + + let pipe = Pipe() + task.standardOutput = pipe + task.standardError = pipe // Redirect standard error to the same pipe for simplicity + + do { + try task.run() + task.waitUntilExit() + + let data = pipe.fileHandleForReading.readDataToEndOfFile() + let output = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) + + if task.terminationStatus == 0 { + return output + } else { + print("java_home terminated with status: \(task.terminationStatus)") + // Optionally, log the error output for debugging + if let errorOutput = String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) { + print("Error output: \(errorOutput)") + } + return nil + } + } catch { + print("Error running java_home: \(error)") + return nil + } +} +``` \ No newline at end of file diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/index.md b/Sources/SwiftJavaDocumentation/Documentation.docc/index.md new file mode 100644 index 000000000..7c695a710 --- /dev/null +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/index.md @@ -0,0 +1,32 @@ +# ``SwiftJavaDocumentation`` + +The SwiftJava project enables interoperability between Swift and Java. + +## Overview + +This project contains a number of support packages, java libraries, tools and plugins that provide a complete +Swift and Java interoperability story. + +Please refer to articles about the specific direction of interoperability you are interested in. + +### Getting started + +TODO: Some general intro + +If you prefer a video introduction, you may want to this +[Explore Swift and Java interoperability](https://www.youtube.com/watch?v=QSHO-GUGidA) +WWDC 2025 session, +which is a quick overview of all the features and approaches offered by SwiftJava. + +## Topics + +### Supported Features + +- + + +### Source generation + +- +- + diff --git a/Sources/SwiftJavaDocumentation/empty.swift b/Sources/SwiftJavaDocumentation/empty.swift new file mode 100644 index 000000000..9c28861c3 --- /dev/null +++ b/Sources/SwiftJavaDocumentation/empty.swift @@ -0,0 +1,15 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// Empty file, this target is a docs-only target. diff --git a/WIP.md b/WIP.md index e1f58612d..88671233b 100644 --- a/WIP.md +++ b/WIP.md @@ -6,9 +6,6 @@ Here is a long yet still very incomplete list of things we would like to do or improve: - Expressivity gaps: - - [x] Translate Java exceptions into Swift and vice versa - - [ ] Expose Swift types to Java - - [x] Figure out when to promote from the local to the global heap - [ ] Automatically turn get/set method pairs into Swift properties? - [ ] Implement a global registry that lets us find the Swift type corresponding to a canonical Java class name (e.g., `java.net.URL` -> `JavaKitNetwork.URL`) - [ ] Introduce overloads of `is` and `as` on the Swift projections so that conversion to any implemented interface or extended superclass returns non-optional. @@ -16,14 +13,8 @@ improve: - [ ] Recognize Java's enum classes and map them into Swift well - [ ] Translate Java constants into Swift constants - [ ] Support nested classes - - [x] Streamline the definition of "main" code in Swift - [ ] Figure out how to subclass a Java class from Swift - Tooling - - [ ] Extract documentation comments from Java and put them in the Swift projections - - [ ] [SwiftPM build plugin](https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/Plugins.md) to generate the Swift projections from Java as part of the build - - [x] Figure out how to launch the Java runtime from Swift code so we don't need to always start via `java` - - [x] Figure out how to unit-test this framework using Swift Testing - - [x] Add a "Jar mode" to `Java2Swift` that translates all classes in the given Jar file. - [ ] Generate Swift projections for more common Java types into JavaKit libraries to make it easier to get started - [ ] Teach `Java2Swift` when to create extensions of already-translated types that pick up any members that couldn't be translated because of missing types. See, for example, how `JavaKitReflection` adds extensions to `JavaClass` based on types like `Method` and `Parameter` - Performance: From c7adc8e063b7d2a7b24c435998b8ef2057ed4ff9 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Thu, 24 Jul 2025 02:07:29 +0900 Subject: [PATCH 042/141] Build: don't crash when building in SPI and JAVA_HOME is missing (#329) --- Package.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Package.swift b/Package.swift index ef5294969..84b348c5b 100644 --- a/Package.swift +++ b/Package.swift @@ -31,6 +31,11 @@ func findJavaHome() -> String { return home } + + if ProcessInfo.processInfo.environment["SPI_BUILD"] == "1" { + // just ignore that we're missing a JAVA_HOME when building in Swift Package Index + return "" + } fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.") } From 11dcbedc315845756b7dbf1ff6fa5369dc0d9f08 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 24 Jul 2025 15:42:04 +0200 Subject: [PATCH 043/141] [JExtract/JNI] Support JavaKit wrapped types as function parameters. (#330) --- .../JExtractSwiftPlugin.swift | 64 +++++++++++++++- .../Sources/MySwiftLibrary/MySwiftClass.swift | 6 ++ .../com/example/swift/MySwiftClassTest.java | 9 +++ .../Convenience/String+Extensions.swift | 39 ++++++++++ ...ISwift2JavaGenerator+JavaTranslation.swift | 42 +++++++++-- ...wift2JavaGenerator+NativeTranslation.swift | 53 +++++++++---- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 26 ------- .../JNI/JNISwift2JavaGenerator.swift | 10 ++- Sources/JExtractSwiftLib/Swift2Java.swift | 15 +++- .../Swift2JavaTranslator.swift | 17 ++++- .../SwiftTypes/SwiftType.swift | 7 ++ .../Configuration.swift | 20 +++++ .../Documentation.docc/SupportedFeatures.md | 2 + .../Commands/JExtractCommand.swift | 20 ++++- .../Commands/WrapJavaCommand.swift | 28 ++----- Sources/SwiftJavaTool/SwiftJava.swift | 4 +- .../Asserts/TextAssertions.swift | 5 +- .../JNI/JNIJavaKitTests.swift | 74 +++++++++++++++++++ 18 files changed, 362 insertions(+), 79 deletions(-) create mode 100644 Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift diff --git a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift index 83c5f0d9e..2778cebeb 100644 --- a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift +++ b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift @@ -15,6 +15,8 @@ import Foundation import PackagePlugin +fileprivate let SwiftJavaConfigFileName = "swift-java.config" + @main struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { @@ -51,12 +53,14 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { log("Skipping jextract step, no 'javaPackage' configuration in \(getSwiftJavaConfigPath(target: target) ?? "")") return [] } - + // We use the the usual maven-style structure of "src/[generated|main|test]/java/..." // that is common in JVM ecosystem let outputJavaDirectory = context.outputJavaDirectory let outputSwiftDirectory = context.outputSwiftDirectory + let dependentConfigFiles = searchForDependentConfigFiles(in: target) + var arguments: [String] = [ /*subcommand=*/"jextract", "--swift-module", sourceModule.name, @@ -71,6 +75,16 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { // as it depends on the contents of the input files. Therefore we have to implement this as a prebuild plugin. // We'll have to make up some caching inside the tool so we don't re-parse files which have not changed etc. ] + + let dependentConfigFilesArguments = dependentConfigFiles.flatMap { moduleAndConfigFile in + let (moduleName, configFile) = moduleAndConfigFile + return [ + "--depends-on", + "\(configFile.path(percentEncoded: false))" + ] + } + arguments += dependentConfigFilesArguments + if !javaPackage.isEmpty { arguments += ["--java-package", javaPackage] } @@ -117,5 +131,53 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { ) ] } + + /// Find the manifest files from other swift-java executions in any targets + /// this target depends on. + func searchForDependentConfigFiles(in target: any Target) -> [(String, URL)] { + var dependentConfigFiles = [(String, URL)]() + + func _searchForConfigFiles(in target: any Target) { + // log("Search for config files in target: \(target.name)") + let dependencyURL = URL(filePath: target.directory.string) + + // Look for a config file within this target. + let dependencyConfigURL = dependencyURL + .appending(path: SwiftJavaConfigFileName) + let dependencyConfigString = dependencyConfigURL + .path(percentEncoded: false) + + if FileManager.default.fileExists(atPath: dependencyConfigString) { + dependentConfigFiles.append((target.name, dependencyConfigURL)) + } + } + + // Process direct dependencies of this target. + for dependency in target.dependencies { + switch dependency { + case .target(let target): + // log("Dependency target: \(target.name)") + _searchForConfigFiles(in: target) + + case .product(let product): + // log("Dependency product: \(product.name)") + for target in product.targets { + // log("Dependency product: \(product.name), target: \(target.name)") + _searchForConfigFiles(in: target) + } + + @unknown default: + break + } + } + + // Process indirect target dependencies. + for dependency in target.recursiveTargetDependencies { + // log("Recursive dependency target: \(dependency.name)") + _searchForConfigFiles(in: dependency) + } + + return dependentConfigFiles + } } diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index b4447f4ff..9a78c3c27 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -12,6 +12,8 @@ // //===----------------------------------------------------------------------===// +import JavaKit + public class MySwiftClass { public let x: Int64 public let y: Int64 @@ -84,4 +86,8 @@ public class MySwiftClass { public func copy() -> MySwiftClass { return MySwiftClass(x: self.x, y: self.y) } + + public func addXWithJavaLong(_ other: JavaLong) -> Int64 { + return self.x + other.longValue() + } } diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java index 4425d6b21..f034b9040 100644 --- a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -139,4 +139,13 @@ void copy() { assertNotEquals(c1.$memoryAddress(), c2.$memoryAddress()); } } + + @Test + void addXWithJavaLong() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftClass c1 = MySwiftClass.init(20, 10, arena); + Long javaLong = 50L; + assertEquals(70, c1.addXWithJavaLong(javaLong)); + } + } } \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift index 1851e1549..0f5cfeac6 100644 --- a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift @@ -12,6 +12,8 @@ // //===----------------------------------------------------------------------===// +import JavaTypes + extension String { var firstCharacterUppercased: String { @@ -31,4 +33,41 @@ extension String { let thirdCharacterIndex = self.index(self.startIndex, offsetBy: 2) return self[thirdCharacterIndex].isUppercase } + + /// Returns a version of the string correctly escaped for a JNI + var escapedJNIIdentifier: String { + self.map { + if $0 == "_" { + return "_1" + } else if $0 == "/" { + return "_" + } else if $0 == ";" { + return "_2" + } else if $0 == "[" { + return "_3" + } else if $0.isASCII && ($0.isLetter || $0.isNumber) { + return String($0) + } else if let utf16 = $0.utf16.first { + // Escape any non-alphanumeric to their UTF16 hex encoding + let utf16Hex = String(format: "%04x", utf16) + return "_0\(utf16Hex)" + } else { + fatalError("Invalid JNI character: \($0)") + } + } + .joined() + } + + /// Looks up self as a JavaKit wrapped class name and converts it + /// into a `JavaType.class` if it exists in `lookupTable`. + func parseJavaClassFromJavaKitName(in lookupTable: [String: String]) -> JavaType? { + guard let canonicalJavaName = lookupTable[self] else { + return nil + } + let nameParts = canonicalJavaName.components(separatedBy: ".") + let javaPackageName = nameParts.dropLast().joined(separator: ".") + let javaClassName = nameParts.last! + + return .class(package: javaPackageName, name: javaClassName) + } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index e704739c4..57ea15b28 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -24,7 +24,11 @@ extension JNISwift2JavaGenerator { let translated: TranslatedFunctionDecl? do { - let translation = JavaTranslation(swiftModuleName: swiftModuleName, javaPackage: self.javaPackage) + let translation = JavaTranslation( + swiftModuleName: swiftModuleName, + javaPackage: self.javaPackage, + javaClassLookupTable: self.javaClassLookupTable + ) translated = try translation.translate(decl) } catch { self.logger.debug("Failed to translate: '\(decl.swiftDecl.qualifiedNameForDebug)'; \(error)") @@ -38,9 +42,13 @@ extension JNISwift2JavaGenerator { struct JavaTranslation { let swiftModuleName: String let javaPackage: String + let javaClassLookupTable: JavaClassLookupTable func translate(_ decl: ImportedFunc) throws -> TranslatedFunctionDecl { - let nativeTranslation = NativeJavaTranslation(javaPackage: self.javaPackage) + let nativeTranslation = NativeJavaTranslation( + javaPackage: self.javaPackage, + javaClassLookupTable: self.javaClassLookupTable + ) // Types with no parent will be outputted inside a "module" class. let parentName = decl.parentType?.asNominalType?.nominalTypeDecl.qualifiedName ?? swiftModuleName @@ -157,6 +165,8 @@ extension JNISwift2JavaGenerator { ) throws -> TranslatedParameter { switch swiftType { case .nominal(let nominalType): + let nominalTypeName = nominalType.nominalTypeDecl.name + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType) else { throw JavaTranslationError.unsupportedSwiftType(swiftType) @@ -168,11 +178,25 @@ extension JNISwift2JavaGenerator { ) } - // For now, we assume this is a JExtract class. + if nominalType.isJavaKitWrapper { + guard let javaType = nominalTypeName.parseJavaClassFromJavaKitName(in: self.javaClassLookupTable) else { + throw JavaTranslationError.wrappedJavaClassTranslationNotProvided(swiftType) + } + + return TranslatedParameter( + parameter: JavaParameter( + name: parameterName, + type: javaType + ), + conversion: .placeholder + ) + } + + // We assume this is a JExtract class. return TranslatedParameter( parameter: JavaParameter( name: parameterName, - type: .class(package: nil, name: nominalType.nominalTypeDecl.name) + type: .class(package: nil, name: nominalTypeName) ), conversion: .valueMemoryAddress(.placeholder) ) @@ -213,7 +237,11 @@ extension JNISwift2JavaGenerator { ) } - // For now, we assume this is a JExtract class. + if nominalType.isJavaKitWrapper { + throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + } + + // We assume this is a JExtract class. let javaType = JavaType.class(package: nil, name: nominalType.nominalTypeDecl.name) return TranslatedResult( javaType: javaType, @@ -350,5 +378,9 @@ extension JNISwift2JavaGenerator { enum JavaTranslationError: Error { case unsupportedSwiftType(SwiftType) + + /// The user has not supplied a mapping from `SwiftType` to + /// a java class. + case wrappedJavaClassTranslationNotProvided(SwiftType) } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index cae6010d1..5ccefadb9 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -18,6 +18,7 @@ extension JNISwift2JavaGenerator { struct NativeJavaTranslation { let javaPackage: String + let javaClassLookupTable: JavaClassLookupTable /// Translates a Swift function into the native JNI method signature. func translate( @@ -62,10 +63,12 @@ extension JNISwift2JavaGenerator { swiftParameter: SwiftParameter, parameterName: String, methodName: String, - parentName: String, + parentName: String ) throws -> NativeParameter { switch swiftParameter.type { case .nominal(let nominalType): + let nominalTypeName = nominalType.nominalTypeDecl.name + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType), javaType.implementsJavaValue else { throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) @@ -78,6 +81,25 @@ extension JNISwift2JavaGenerator { ) } + if nominalType.isJavaKitWrapper { + guard let javaType = nominalTypeName.parseJavaClassFromJavaKitName(in: self.javaClassLookupTable) else { + throw JavaTranslationError.wrappedJavaClassTranslationNotProvided(swiftParameter.type) + } + + return NativeParameter( + name: parameterName, + javaType: javaType, + conversion: .initializeJavaKitWrapper(wrapperName: nominalTypeName) + ) + } + + // JExtract classes are passed as the pointer. + return NativeParameter( + name: parameterName, + javaType: .long, + conversion: .pointee(.extractSwiftValue(.placeholder, swiftType: swiftParameter.type)) + ) + case .tuple([]): return NativeParameter( name: parameterName, @@ -110,13 +132,6 @@ extension JNISwift2JavaGenerator { case .metatype, .optional, .tuple, .existential, .opaque: throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) } - - // Classes are passed as the pointer. - return NativeParameter( - name: parameterName, - javaType: .long, - conversion: .pointee(.extractSwiftValue(.placeholder, swiftType: swiftParameter.type)) - ) } func translateClosureResult( @@ -193,6 +208,15 @@ extension JNISwift2JavaGenerator { ) } + if nominalType.isJavaKitWrapper { + throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + } + + return NativeResult( + javaType: .long, + conversion: .getJNIValue(.allocateSwiftValue(name: "result", swiftType: swiftResult.type)) + ) + case .tuple([]): return NativeResult( javaType: .void, @@ -203,13 +227,7 @@ extension JNISwift2JavaGenerator { throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) } - // TODO: Handle other classes, for example from JavaKit macros. - // for now we assume all passed in classes are JExtract generated - // so we pass the pointer. - return NativeResult( - javaType: .long, - conversion: .getJNIValue(.allocateSwiftValue(name: "result", swiftType: swiftResult.type)) - ) + } } @@ -262,6 +280,8 @@ extension JNISwift2JavaGenerator { indirect case closureLowering(parameters: [NativeParameter], result: NativeResult) + case initializeJavaKitWrapper(wrapperName: String) + /// Returns the conversion string applied to the placeholder. func render(_ printer: inout CodePrinter, _ placeholder: String) -> String { // NOTE: 'printer' is used if the conversion wants to cause side-effects. @@ -348,6 +368,9 @@ extension JNISwift2JavaGenerator { printer.print("}") return printer.finalize() + + case .initializeJavaKitWrapper(let wrapperName): + return "\(wrapperName)(javaThis: \(placeholder), environment: environment!)" } } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 20551465e..35d4dbef1 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -328,29 +328,3 @@ extension JNISwift2JavaGenerator { return newSelfParamName } } - -extension String { - /// Returns a version of the string correctly escaped for a JNI - var escapedJNIIdentifier: String { - self.map { - if $0 == "_" { - return "_1" - } else if $0 == "/" { - return "_" - } else if $0 == ";" { - return "_2" - } else if $0 == "[" { - return "_3" - } else if $0.isASCII && ($0.isLetter || $0.isNumber) { - return String($0) - } else if let utf16 = $0.utf16.first { - // Escape any non-alphanumeric to their UTF16 hex encoding - let utf16Hex = String(format: "%04x", utf16) - return "_0\(utf16Hex)" - } else { - fatalError("Invalid JNI character: \($0)") - } - } - .joined() - } -} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index 0cc523b4f..79f546aca 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -14,6 +14,10 @@ import JavaTypes +/// A table that where keys are Swift class names and the values are +/// the fully qualified canoical names. +package typealias JavaClassLookupTable = [String: String] + package class JNISwift2JavaGenerator: Swift2JavaGenerator { let analysis: AnalysisResult let swiftModuleName: String @@ -22,6 +26,8 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { let swiftOutputDirectory: String let javaOutputDirectory: String + let javaClassLookupTable: JavaClassLookupTable + var javaPackagePath: String { javaPackage.replacingOccurrences(of: ".", with: "/") } @@ -39,7 +45,8 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { translator: Swift2JavaTranslator, javaPackage: String, swiftOutputDirectory: String, - javaOutputDirectory: String + javaOutputDirectory: String, + javaClassLookupTable: JavaClassLookupTable ) { self.logger = Logger(label: "jni-generator", logLevel: translator.log.logLevel) self.analysis = translator.result @@ -47,6 +54,7 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { self.javaPackage = javaPackage self.swiftOutputDirectory = swiftOutputDirectory self.javaOutputDirectory = javaOutputDirectory + self.javaClassLookupTable = javaClassLookupTable // If we are forced to write empty files, construct the expected outputs if translator.config.writeEmptyFiles ?? false { diff --git a/Sources/JExtractSwiftLib/Swift2Java.swift b/Sources/JExtractSwiftLib/Swift2Java.swift index dad252997..6473cea32 100644 --- a/Sources/JExtractSwiftLib/Swift2Java.swift +++ b/Sources/JExtractSwiftLib/Swift2Java.swift @@ -20,9 +20,11 @@ import JavaKitConfigurationShared // TODO: this should become SwiftJavaConfigura public struct SwiftToJava { let config: Configuration + let dependentConfigs: [Configuration] - public init(config: Configuration) { + public init(config: Configuration, dependentConfigs: [Configuration]) { self.config = config + self.dependentConfigs = dependentConfigs } public func run() throws { @@ -83,6 +85,14 @@ public struct SwiftToJava { fatalError("Missing --output-java directory!") } + let wrappedJavaClassesLookupTable: JavaClassLookupTable = dependentConfigs.compactMap(\.classes).reduce(into: [:]) { + for (canonicalName, javaClass) in $1 { + $0[javaClass] = canonicalName + } + } + + translator.dependenciesClasses = Array(wrappedJavaClassesLookupTable.keys) + try translator.analyze() switch config.mode { @@ -101,7 +111,8 @@ public struct SwiftToJava { translator: translator, javaPackage: config.javaPackage ?? "", swiftOutputDirectory: outputSwiftDirectory, - javaOutputDirectory: outputJavaDirectory + javaOutputDirectory: outputJavaDirectory, + javaClassLookupTable: wrappedJavaClassesLookupTable ) try generator.generate() diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index 07023b097..d1141e5f6 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -39,6 +39,9 @@ public final class Swift2JavaTranslator { var inputs: [Input] = [] + /// A list of used Swift class names that live in dependencies, e.g. `JavaInteger` + package var dependenciesClasses: [String] = [] + // ==== Output state package var importedGlobalVariables: [ImportedFunc] = [] @@ -111,9 +114,11 @@ extension Swift2JavaTranslator { } package func prepareForTranslation() { + let dependenciesSource = self.buildDependencyClassesSourceFile() + self.symbolTable = SwiftSymbolTable.setup( moduleName: self.swiftModuleName, - inputs.map({ $0.syntax }), + inputs.map({ $0.syntax }) + [dependenciesSource], log: self.log ) } @@ -167,6 +172,16 @@ extension Swift2JavaTranslator { } return false } + + /// Returns a source file that contains all the available dependency classes. + private func buildDependencyClassesSourceFile() -> SourceFileSyntax { + let contents = self.dependenciesClasses.map { + "public class \($0) {}" + } + .joined(separator: "\n") + + return SourceFileSyntax(stringLiteral: contents) + } } // ==== ---------------------------------------------------------------------------------------------------------------- diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index 531ab45ca..d0f1f98d7 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -171,6 +171,13 @@ extension SwiftNominalType: CustomStringConvertible { } } +extension SwiftNominalType { + // TODO: Better way to detect Java wrapped classes. + var isJavaKitWrapper: Bool { + nominalTypeDecl.name.hasPrefix("Java") + } +} + extension SwiftType { init(_ type: TypeSyntax, symbolTable: SwiftSymbolTable) throws { switch type.as(TypeSyntaxEnum.self) { diff --git a/Sources/JavaKitConfigurationShared/Configuration.swift b/Sources/JavaKitConfigurationShared/Configuration.swift index 876b577be..5c57082eb 100644 --- a/Sources/JavaKitConfigurationShared/Configuration.swift +++ b/Sources/JavaKitConfigurationShared/Configuration.swift @@ -141,6 +141,26 @@ public func readConfiguration(configPath: URL, file: String = #fileID, line: UIn } } +/// Load all dependent configs configured with `--depends-on` and return a list of +/// `(SwiftModuleName, Configuration)` tuples. +public func loadDependentConfigs(dependsOn: [String]) throws -> [(String?, Configuration)] { + try dependsOn.map { dependentConfig in + let equalLoc = dependentConfig.firstIndex(of: "=") + + var swiftModuleName: String? = nil + if let equalLoc { + swiftModuleName = String(dependentConfig[.. [String] { let basePath: String = FileManager.default.currentDirectoryPath let pluginOutputsDir = URL(fileURLWithPath: basePath) diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index dc2c7b0ec..e7acb3eb9 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -65,6 +65,8 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Protocols: `protocol`, existential parameters `any Collection` | ❌ | ❌ | | Optional types: `Int?`, `AnyObject?` | ❌ | ❌ | | Primitive types: `Bool`, `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `Float`, `Double` | ✅ | ✅ | +| Parameters: JavaKit wrapped types `JavaLong`, `JavaInteger` | ❌ | ✅ | +| Return values: JavaKit wrapped types `JavaLong`, `JavaInteger` | ❌ | ❌ | | Unsigned primitive types: `UInt`, `UInt8`, `UInt16`, `UInt32`, `UInt64` | ❌ | ❌ | | String (with copying data) | ✅ | ✅ | | Variadic parameters: `T...` | ❌ | ❌ | diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index c7fe51a82..e4901bc2e 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -60,6 +60,15 @@ extension SwiftJava { @Flag(help: "Some build systems require an output to be present when it was 'expected', even if empty. This is used by the JExtractSwiftPlugin build plugin, but otherwise should not be necessary.") var writeEmptyFiles: Bool = false + + @Option( + help: """ + A swift-java configuration file for a given Swift module name on which this module depends, + e.g., Sources/JavaKitJar/Java2Swift.config. There should be one of these options + for each Swift module that this module depends on (transitively) that contains wrapped Java sources. + """ + ) + var dependsOn: [String] = [] } } @@ -82,15 +91,20 @@ extension SwiftJava.JExtractCommand { print("[debug][swift-java] Running 'swift-java jextract' in mode: " + "\(self.mode)".bold) - try jextractSwift(config: config) + // Load all of the dependent configurations and associate them with Swift modules. + let dependentConfigs = try loadDependentConfigs(dependsOn: self.dependsOn) + print("[debug][swift-java] Dependent configs: \(dependentConfigs.count)") + + try jextractSwift(config: config, dependentConfigs: dependentConfigs.map(\.1)) } } extension SwiftJava.JExtractCommand { func jextractSwift( - config: Configuration + config: Configuration, + dependentConfigs: [Configuration] ) throws { - try SwiftToJava(config: config).run() + try SwiftToJava(config: config, dependentConfigs: dependentConfigs).run() } } diff --git a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift index 05ac7651f..4c5bd97b2 100644 --- a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift +++ b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift @@ -77,7 +77,12 @@ extension SwiftJava.WrapJavaCommand { searchDirs: classpathSearchDirs, config: config) // Load all of the dependent configurations and associate them with Swift modules. - let dependentConfigs = try self.loadDependentConfigs() + let dependentConfigs = try loadDependentConfigs(dependsOn: self.dependsOn).map { moduleName, config in + guard let moduleName else { + throw JavaToSwiftError.badConfigOption + } + return (moduleName, config) + } print("[debug][swift-java] Dependent configs: \(dependentConfigs.count)") // Include classpath entries which libs we depend on require... @@ -102,27 +107,6 @@ extension SwiftJava.WrapJavaCommand { } } -extension SwiftJava.WrapJavaCommand { - - /// Load all dependent configs configured with `--depends-on` and return a list of - /// `(SwiftModuleName, Configuration)` tuples. - func loadDependentConfigs() throws -> [(String, Configuration)] { - try dependsOn.map { dependentConfig in - guard let equalLoc = dependentConfig.firstIndex(of: "=") else { - throw JavaToSwiftError.badConfigOption(dependentConfig) - } - - let afterEqual = dependentConfig.index(after: equalLoc) - let swiftModuleName = String(dependentConfig[..=" } } diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index 47397c630..4b0162f34 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -29,6 +29,7 @@ func assertOutput( _ renderKind: RenderKind, swiftModuleName: String = "SwiftModule", detectChunkByInitialLines _detectChunkByInitialLines: Int = 4, + javaClassLookupTable: [String: String] = [:], expectedChunks: [String], fileID: String = #fileID, filePath: String = #filePath, @@ -38,6 +39,7 @@ func assertOutput( var config = Configuration() config.swiftModule = swiftModuleName let translator = Swift2JavaTranslator(config: config) + translator.dependenciesClasses = Array(javaClassLookupTable.keys) try! translator.analyze(file: "/fake/Fake.swiftinterface", text: input) @@ -64,7 +66,8 @@ func assertOutput( translator: translator, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", - javaOutputDirectory: "/fake" + javaOutputDirectory: "/fake", + javaClassLookupTable: javaClassLookupTable ) switch renderKind { diff --git a/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift b/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift new file mode 100644 index 000000000..0283780af --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift @@ -0,0 +1,74 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import Testing + +@Suite +struct JNIJavaKitTests { + let source = + """ + public func function(javaLong: JavaLong, javaInteger: JavaInteger, int: Int64) {} + """ + + let classLookupTable = [ + "JavaLong": "java.lang.Long", + "JavaInteger": "java.lang.Integer" + ] + + @Test + func function_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + javaClassLookupTable: classLookupTable, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func function(javaLong: JavaLong, javaInteger: JavaInteger, int: Int64) + * } + */ + public static void function(java.lang.Long javaLong, java.lang.Integer javaInteger, long int) { + SwiftModule.$function(javaLong, javaInteger, int); + } + """, + """ + private static native void $function(java.lang.Long javaLong, java.lang.Integer javaInteger, long int); + """ + ] + ) + } + + @Test + func function_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + javaClassLookupTable: classLookupTable, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024function__Ljava_lang_Long_2Ljava_lang_Integer_2J") + func Java_com_example_swift_SwiftModule__00024function__Ljava_lang_Long_2Ljava_lang_Integer_2J(environment: UnsafeMutablePointer!, thisClass: jclass, javaLong: jobject, javaInteger: jobject, int: jlong) { + SwiftModule.function(javaLong: JavaLong(javaThis: javaLong, environment: environment!), javaInteger: JavaInteger(javaThis: javaInteger, environment: environment!), int: Int64(fromJNI: int, in: environment!)) + } + """ + ] + ) + } +} From cbbe172dc230c59b05d6e0faa4e18291caa8e8a2 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Mon, 28 Jul 2025 21:08:11 +0900 Subject: [PATCH 044/141] Update to Gradle 8.14.2 so we don't have to keep Java 21 around anymore (#332) --- .github/actions/prepare_env/action.yml | 1 - docker/install_jdk.sh | 13 ++----------- gradle.properties | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 43583 -> 43764 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 9 ++++----- gradlew.bat | 4 ++-- 7 files changed, 10 insertions(+), 21 deletions(-) diff --git a/.github/actions/prepare_env/action.yml b/.github/actions/prepare_env/action.yml index 53bb3fd71..0e4a1ca37 100644 --- a/.github/actions/prepare_env/action.yml +++ b/.github/actions/prepare_env/action.yml @@ -10,7 +10,6 @@ runs: distribution: ${{ matrix.jdk_vendor }} java-version: | 24 - 21 17 cache: 'gradle' - name: Set JAVA_HOME_{N} diff --git a/docker/install_jdk.sh b/docker/install_jdk.sh index 122c0d063..8746e3abc 100755 --- a/docker/install_jdk.sh +++ b/docker/install_jdk.sh @@ -36,10 +36,6 @@ download_and_install_jdk() { if [ "$JDK_VENDOR" = 'corretto' ]; then if [ "$(uname -m)" = 'aarch64' ]; then case "$jdk_version" in - "21") - jdk_url="https://corretto.aws/downloads/latest/amazon-corretto-21-aarch64-linux-jdk.tar.gz" - expected_md5="87e458029cf9976945dfa3a22af3f850" - ;; "24") jdk_url="https://corretto.aws/downloads/latest/amazon-corretto-24-aarch64-linux-jdk.tar.gz" expected_md5="3b543f4e971350b73d0ab6d8174cc030" @@ -51,10 +47,6 @@ download_and_install_jdk() { esac else case "$jdk_version" in - "21") - jdk_url="https://corretto.aws/downloads/latest/amazon-corretto-21-x64-linux-jdk.tar.gz" - expected_md5="a123e7f50807c27de521bef7378d3377" - ;; "24") jdk_url="https://corretto.aws/downloads/latest/amazon-corretto-24-x64-linux-jdk.tar.gz" expected_md5="130885ded3cbfc712fbe9f7dace45a52" @@ -102,13 +94,12 @@ download_and_install_jdk() { cd "$HOME" } -# Usage: Install both JDK versions -download_and_install_jdk "21" +# Usage: Install JDK 24 download_and_install_jdk "24" ls -la /usr/lib/jvm/ cd /usr/lib/jvm/ -ln -s jdk-21 default-jdk +ln -s jdk-24 default-jdk find . | grep java | grep bin echo "JAVA_HOME = /usr/lib/jvm/default-jdk" /usr/lib/jvm/default-jdk/bin/java -version \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 335b6f40a..679a1a1fa 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -org.gradle.java.installations.fromEnv=JAVA_HOME_24,JAVA_HOME_24_X64,JAVA_HOME_24_ARM64,JAVA_HOME_21,JAVA_HOME_21_X64,JAVA_HOME_21_ARM64 \ No newline at end of file +org.gradle.java.installations.fromEnv=JAVA_HOME_24,JAVA_HOME_24_X64,JAVA_HOME_24_ARM64 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index a4b76b9530d66f5e68d973ea569d8e19de379189..1b33c55baabb587c669f562ae36f953de2481846 100644 GIT binary patch delta 34943 zcmXuKV_+Rz)3%+)Y~1X)v28cDZQE*`9qyPrXx!Mg8{4+s*nWFo&-eXbzt+q-bFO1% zb$T* z+;w-h{ce+s>j$K)apmK~8t5)PdZP3^U%(^I<0#3(!6T+vfBowN0RfQ&0iMAo055!% z04}dC>M#Z2#PO7#|Fj;cQ$sH}E-n7nQM_V}mtmG_)(me#+~0gf?s@gam)iLoR#sr( zrR9fU_ofhp5j-5SLDQP{O+SuE)l8x9_(9@h%eY-t47J-KX-1(`hh#A6_Xs+4(pHhy zuZ1YS9axk`aYwXuq;YN>rYv|U`&U67f=tinhAD$+=o+MWXkx_;qIat_CS1o*=cIxs zIgeoK0TiIa7t`r%%feL8VieY63-Aakfi~qlE`d;ZOn8hFZFX|i^taCw6xbNLb2sOS z?PIeS%PgD)?bPB&LaQDF{PbxHrJQME<^cU5b!Hir(x32zy{YzNzE%sx;w=!C z_(A>eZXkQ1w@ASPXc|CWMNDP1kFQuMO>|1X;SHQS8w<@D;5C@L(3r^8qbbm$nTp%P z&I3Ey+ja9;ZiMbopUNc2txS9$Jf8UGS3*}Y3??(vZYLfm($WlpUGEUgQ52v@AD<~Y z#|B=mpCPt3QR%gX*c^SX>9dEqck79JX+gVPH87~q0-T;ota!lQWdt3C-wY1Ud}!j8 z*2x5$^dsTkXj}%PNKs1YzwK$-gu*lxq<&ko(qrQ_na(82lQ$ z7^0Pgg@Shn!UKTD4R}yGxefP2{8sZ~QZY)cj*SF6AlvE;^5oK=S}FEK(9qHuq|Cm! zx6ILQBsRu(=t1NRTecirX3Iv$-BkLxn^Zk|sV3^MJ1YKJxm>A+nk*r5h=>wW*J|pB zgDS%&VgnF~(sw)beMXXQ8{ncKX;A;_VLcq}Bw1EJj~-AdA=1IGrNHEh+BtIcoV+Te z_sCtBdKv(0wjY{3#hg9nf!*dpV5s7ZvNYEciEp2Rd5P#UudfqXysHiXo`pt27R?Rk zOAWL-dsa+raNw9^2NLZ#Wc^xI=E5Gwz~_<&*jqz0-AVd;EAvnm^&4Ca9bGzM_%(n{>je5hGNjCpZJ%5#Z3&4}f3I1P!6?)d65 z-~d}g{g!&`LkFK9$)f9KB?`oO{a0VXFm1`W{w5bAIC5CsyOV=q-Q7Z8YSmyo;$T?K za96q@djtok=r#TdUkd#%`|QlBywo>ifG69&;k%Ahfic6drRP;K{V8ea_t2qbY48uYWlB3Hf6hnqsCO?kYFhV+{i> zo&AE+)$%ag^)ijm!~gU78tD%tB63b_tbv9gfWzS&$r@i4q|PM+!hS+o+DpKfnnSe{ zewFbI3Jc0?=Vz}3>KmVj$qTWkoUS8@k63XRP2m^e50x-5PU<4X!I#q(zj@EyT9K_E z9P%@Sy6Mq`xD<-E!-<3@MLp2Dq8`x}F?@}V6E#A9v6xm%@x1U3>OoFY{fX5qpxngY z+=2HbnEErBv~!yl%f`Eq2%&K%JTwgN1y@FZ#=ai+TFMFlG?UV{M1#%uCi#Knkb_h| z&ivG$>~NQ4Ou2-gy=8JdRe8`nJDsqYYs?)(LJkJ}NHOj|3gZxVQJWWp>+`H?8$$J5 z*_)+tlyII%x#dId3w(oXo`YEm^-|tFNNj-0rbEuUc2-=pZDk7fxWUlw;|@M9s1 zmK9*C)1Q?F5@NPUJOYOAe`GHnYB%G37_sg3dxAttqLs6Bro)4z ziy8j%C7KKDNL8r#Oj6!IHx|N(?%Zvo31y4;*L1%_KJh$v$6XhFkw*E|fEu9`or?JD_ z13X4g92;TZm0jA0!2R5qPD$W^U z`5XK|Y^27y_Q%D>wWGtF=K00-N0;=svka>o`(;~dOS(eT0gwsP{=Rq+-e2Ajq?D<)zww5V36u6^Ta8YT4cDaw} zfuGnhr_5?)D*1+*q<3tVhg(AsKhR1Di=nsJzt_si+)uac_7zx_pl#t(dh816IM zvToHR%D)$!Zj4Q^$s8A%HLRYa>q9dpbh=*kcF7nkM0RhMIOGq^7Tgn|Fvs)A% zznI7nlbWoA2=rHHbUZ4PJMXf{T$@>W1Tt4lb|Or4L;O!oFj8Op8KEE`^x^*VSJ`9~ z;Pe~{V3x*-2c|jBrvSV8s+*Y3VqFKa@Napr#JAd}4l7;sgn|Q#M!(<|IX1<)z!AC3 zv<5YpN58Fs4NYi|ndYcb=jVO6Ztpwd={@3Yp6orUYe6EG#s{qhX+L^7zMK+@cX1hh?gbp56>jX*_Z|2u9 zb*glt!xK>j!LyLnFtxs&1SLkyiL%xbMqgxywI-U*XV%%qwa5oiufFerY!wn*GgMq` zZ6mFf8MukDPHVaCQk#oyg^dhl*9p@Jc+4Q9+0iv?{}=}+&=>n+q{o z#rEZ<&Ku65y+1eRHwcl3G7bR`e{&~^fGg|0))$uW?B@;_sWSls!ctnjH6ykmM8WJx};hvdXZ>YKLS($5`yBK38HULv}&PKRo9k zdFzj>`CDIUbq8GxeIJ?8=61G-XO?7dYZ;xqtlG?qr`wzbh7YyaD=>eup7bVH`q*N5 z)0&n)!*wW$G<3A&l$vJ^Z-%1^NF$n3iPgqr6Yn_SsAsFQw?9fj z&AvH|_-6zethC3^$mLF7mF$mTKT<_$kbV6jMK0f0UonRN_cY?yM6v&IosO?RN=h z{IqdUJvZd#@5qsr_1xVnaRr`ba-7MyU4<_XjIbr$PmPBYO6rLrxC`|5MN zD8ae4rTxau=7125zw|TQsJpqm`~hLs@w_iUd%eMY6IR9{(?;$f^?`&l?U%JfX%JyV z$IdA`V)5CkvPA0yljj4!Ja&Hjx`zIkg_ceQ;4)vhoyBeW$3D<_LDR~M-DPzQQ?&!L*PUNb^moIz|QXB=S z9^9NnEpF+>_Oh6+Xr55ZLJ7`V=H}@D<70NiNGH{~^QE-U)*Sg@O}M|%{Rcpn z{0nD@D%@8!dE*mndd2g!-q9;)jb=IUED<(Pxh`9B>V3z#f>82~&CVZASC?|;C-VKy zJU35T|3jd(p8F|#n@T~Wh2l1yURI=LC>Uj_!8i7-DE_IaSKIMAx`WMEq8kN%8sAx% zOQs~R1v12(=_ghVxzylsYZum-%8QmjM3-s2V!jY|w#ccP)}OSW?MWhNu@o-t0eTg{ zyy`}x+}GObZC(k>-upb2C6#S*NOfWbKEyReP%gay8MT!pJpsx4jwCu%>7%sY}1L6Vybj_P+;yP`YS92 z^o_G!Gr_NP!ixe7d&82H&achfi83L;le3Fs?u%E*xbeOKkJr7mp=)RXjZF;h*hR<= zP_cs1hjc}0JlHal=enmG&G8wsn%Sm$5Wcgs=Zc}}A%3i6_<4k_`-$k2E5f6QV{a$V zg3VZO36o^w5q`q2ASwJw#?n7pBJyGt3R<`Sd8d|52=h&`|CPq&1Cz&42rRCHNjDZL z$}Y*L+#N;!K2Ov){~fmQM8hVYzj3H@{yS>?q3QhhDHWfNAJ#q@qko|rhlaGG4Qrvh zmHpmg&7YvgRuI|i78-{)|wFx(R^_ z{ag(}Kbbbx=UW42sAu}kg3yB#96dJlOB{+or<(51ylVwpXII7Hrlztq!pefQ?6pQhqSb76y=sQx zOC-swAJaqnL_ok{74u_IHojFk;RSSFfjdLrfqq{syUxA$Ld6D2#TMX(Phf~dvSuuX zmN2xzjwZxWHmbvK2M#OhE#{`urOzs=>%ku}nxymK-dB~smas?Z(YM^>x#K)M@?<&L zeagMnj!XK4=Mid$NvJ+JfSjvc`4rX9mTo^+iFs0q7ntZ{gfU3oSAbK_yzW3WA^`6x zWgPSLXlEVvh!G^fOzZ-O{C_v;V6=;DE+ZqRT4mbCq}xeQ0o z98Cho%25r#!cT_ozTd~FK^@AB3OnrAAEDI4==}#I_v}iw0nhA{y99mFRG*1kxFkZP z+are- z8D|3WoYE>s0<=h)^)0>^up+nPeu}Sv-A($6t3AUedFczOLn;NW5_xM0tMvvrOSZ}) zA2YG1m4GxLAHZ5k>%}pHYtf-caXMGcYmH8ZPLX9VCew0;@Pi-8zkH^#}Cu$%FmKJb=!)Twj!PgBmY0+>VUsyyT}Jy>vMt zo<^5lmPo5Jt-=)z2-F{2{jB{CpW2JDj%~JnP*rq^=(okNQpH=}#{kqMUw{&=e-5;G z!FwJVQTDS7YGL&|=vJ+xhg{dMika2m2A#l@$PazLQ<6$GLC+>4B37`4aW3&MgENJ% z#*tOQsg{>zmcuSgU?peLA}!Rlu&K3LTc@drSBaI?91dK75;_`(V`NHjkMj``jwjJx zcm_!liUxn=^!~0|#{g2#AuX9%;GTBq&k+Jz!~Cc+r?S}y=Q1okG0PRIi3C3wgP8F| zO2jcmnVbGXp*Mu&e#a9Q5a}w7$sITx@)8b}sh(v9#V(H$3GLHF@k!Wh+)kNueq;+r zFtj+^b1TQe?R#Y8{m!7~e6%83hbPKoizd2LIg3yS5=X2HE^l4_|(2q#LB zeNv&njrS$?=zzG?0Min#kY+3A)H1uMfogMYSm|vT%3i<_d9X&~N*ZCL4iB@YaJuo; zq}-;EGx~T43kq-UHmTn!@sc z3bwcs$rp?~73h*uZl_ysD*WK3_PS1G3N^t3U=KoRm_Gz@C?M>+x9HRMk(cA4m&L`! z=Lb~4*9zt*SHJgsAMAcTy*!1W^B>4T_doWvNw7UwmyA=Wq&kE{*GVHp9Yk5goUO;k zVb_3ARrFPG;&>Jv@P&`z%}t!*M|2127pm{S)gs~f_ID^lOH@nIW9DgU$=FjqNW0pv z&GYdoxe@)RAWWx^j|$N}sj*p)_bFpk`Y=NilvsI(>!Z&KBo&I+wb*kM5Vvkkr#;q< z3CobbF+GJ#MxL?rMldP0@XiC~yQCR57=wW_<$j!SY*$5J+^v{Pn!1{&@R-lHCiK8@ z&O=XQ=V?hjM;h&qCitHmHKJ_$=`v%;jixnQrve^x9{ykWs(;!Q9mlr#{VYVE93oaW z&z+vBD}!tBghkriZy7gX7xJp8c}ajR4;JDu^0#RdQo2itM^~uc==~eBgwx5-m7vLj zP)vE#k%~*N$bT#^>(C1sohq+DwAC{U*z(D)qjgghKKSy#$dPih`R09rfbfI-FLE!` zn!tg71Wr(D7ZV*4R@GqG&7)2K*Zc6_CMJoGu#Yc>9D#{eyZ>u-mrWG@4Hk(je3lnH zu9qvXdq+!`5R1mlzWjV^jvaHl>-^Z+g^s5dy49yem$0$>341=EGuOY=W5PCFBTbNN^19iIQ57C3KcV}z~z#Rvngs#j;g2gswC(TLWlViYW}tB5T#g4 z%vDUYTo1@+&zE&`P%fXc^@prE5z;E@;; zKtpEFYftJq-c0sD6lKYoEQ;O1X4uFZZ;3gdgfAKqIc=Dj6>unXAdM}DD*@a5LHk~o zyJjW@aK;XG%qr<)7Rqh7NdUpnTR6jc;6{FKcK_v_#h{IO{mez>^^70DAWB5whqq!J zevvLUotE;I?IWWf!ieJ-Hx`TqY5)ND>K0NCb7IW40Jk*J* z^#m%kIA~Go2=R|y5zM|*ehJxyuX;lOQZkArKVbQV(XmidUH|8U^q`wP(7%F}=uG}U z2~&~CLebE`c%SCdeU(l&hryL~+Y)6I^d@|||6F15IAGo`G+CdVf zc+!EycZnQH)OBE zyTd8k{(_v9d2}osA$*>Q>Q&OB(7ShxA$}p8ChVnYlXl5My$HlVx@ATprrj0}6)ycK zcQy#bwOms1CnS+xd26}k?J;WI{HR_U+1T^I!$B^S=pJkT705QaMF88VJp!s%`?y9z8f$&Xw(A}3u_(n5G{!)yH&zN)S?c1$SZlo>XieJ zyEFa>_p9B*cY){ct8=dq>uQTf# zd4vB4)(ebwQHlSAu}(6GCe28H32pz^}l%Zqs;Yl|B=l2d9HrCcUf%wxLYs4CBqJ#{gz*u6V$>?9IT@uSf~2Rgk6CNw;C21ZbNkm>ZTc@2zeOSXVE^>i5!2>t%!1cI z{FZA`*o4=dTDG3&{v$3xVr%g;3d(!SFJU}w6x_Re(ohlni)I54Wg{t zWLK{A(}qEIH@pamgtr3serA{THlp_IR(gt0CFguk={|Ochh10)7UV4DcnO7fvL<=x z^WCMg_TI?U8(loaUnAe+Nc9I1JIO#_C`=kJG(&wy%Cr9vRFcY9^8{A3A>GuSW~Zk( zMA#t~0Dw?;3^Ue|lhSp4p%YvYmw-&3ey3}+{6Uhz?l1D|6nYNok6?4N_C!OSR=QtS z2X&QtWlkZshPo#-dXBOlSqh3D;#*_`hyohR>vl$W+QC>HPOs0zwHKN`?zIKqCTw&w&NUGNS|abulHe{D+{q z`WvLw?C4K97cd}6V6f2NtfIAO;=c>qi^+y4#oMjK?5Hy9$Tg1#S~Cxoo-Zdpnt2kG^n}`9)Df-Spvx&Oi+6xXT=N*0l|d`p!ZU ziQo9$y}PYIF~Zqh^?6QZ8YS*JtD^gynifSLMlVYRhBi*f-mJFS<>l%5sp5$V$p*X9?V-0r4bKYvo3n@XkCm4vO-_v? zOsLkR?)>ogb>Ys*m^2>*6%Db0!J?Qvpyd+ODlbslPci9r#W>d~%vcU7J_V;#Um1+` zG0>Q$TrOLUF0%a3g=PaCdQVoUUWXgk>($39-P;tusnMlJ=Dz}#S|E== zl6b3bbYaYguw3Bpv|O(YR2aBk?(jo+QqN*^6f0x+to-@2uj!nu6X{qLK>*PxM!i0C zZwrQ}prOw6Ghz?ApvM`!L3Dzc@6mp<2hO0y{_`lqtt!FcUmBG+PBwl?>0Mwu)Ey{L zU;A{ywkT}jCZpPKH4`_o0$#4*^L7=29%)~!L4*czG!bAva#7ZCDR|6@lBE&cyy5eE zlKHwzv7R9gKZTF<8}3*8uVtI)!HE%AZRD-iW!AJI7oY43@9Z$0^MO@Egj1c?o(BwF ziz1|k#WOgAG?^r1 z>+p=DK?cA-RLIvcdmwq$q?R;ina0SPj@;Mus}W_V2xHnYhOq~=sxzA`yTUOsJ`8`VOSTE=IZ!x`cZYqHbgPijF>J>N7( zqbNsHK50vkB1NI52gyb^PflpU0DRw{&v7Y}Hy2>pV@W2f1EOd2j;H?|WiV%2?Dk7u zS(NrEUDl81<}yY9J#OCwM)N?x&PB-%1{oD*`_ZLiBJ=16uR{n+Lk~!t(&9U#>ZfVd8Iqn&idGd>uo?L@sjm>c|Lk z12d3Y>N9U`342@xaHl&Q@oE5V-f$s`04q983f0#m_WF=X_A89W8C#{uCdTNUZ+))$ zakPyNU)?MDayCKxWh0(-v~1rd8FxocW=Dc6B1%N4^SgQj$?ZMoAMQ-35)IMgf&)M?c@}4QG7=DTq{nHc7yp=CZ z1dh~VkK%OTr23U1mJ*a-DxX0Psvh_13t^YcPl9t?_^$pPEhhwGp}s~f=GFR;4@;@f z@B;R1U6Df?yl#Y=BgYTlP&<|8K27||rx_?{s|L);GM3^{Nn8HZp zFqxiG6s3Nb;PW3O=u;(-o(*q!^2i)jHY%N@;O5Hder~_@$zh4xG#-7?#S^-&M~yc} zh5Y=ltLBnTzt;Y%YNqi2d1M1LOz?MJbZ|Nc6>x19&l_S*2Rgk$DhaP7Y-C)4_uPzf zQm)OY)$AFfE1(0SxkbbN4}CHnlU`RqYFGIE7S9ipx_Q0vkE5JRq4Uc%zV7$?y(x$y zV^)5zwjH~+4?xN z9s@x~w`C_cS}khfI14K4Xgn^iuBxkd^u}3cY=VZI@-8iWHolPtt?JD5lZ1V=@g6yR zj0>bd7Z(dw+@)v#r!xpZaAxgT?4Ton(h`0}fkfF!ZDSu{f*r#{ZRp^oOrO3iB|Fa- z;|+PpW5JKZxJ-kjHf`-7ohmnO=a)Xl9lhI8&$)g6R#6PBIN$QSC8kT=4zj?w&=`!qjkCvvz;ypOfR7P)w^ z-7LFhXd6GLrFa_vGLwR5MRvcV*(r!NhQ@}T-ikBGy!fHaiePD$iA{|Q1$kct2`qHz z6nAyERuqvM6i2^?g@w7W2LLr~3s?pBDk6ce8@CxV;b%4%-rXK-GOk+($sSNK;_FBku zm89B}tpzL-x{dPS-IAjwyL*t7N%7~2E)9OsWJJWHc|}BNa5Xwdx(j7i7AmZhs?#zi z5{y$uQdx?O8x3>+5MR05HwUa-YZa*|UVLOb`T)KHk|~Gmwx8MfBUtM|afuM$0wb7m zR+_lU9=W~Y$uNlxt&(@&1;6t!r69A|W%;k3-%SzLlBzc0 z`b?Jmo`8{LI=d|I3JDAa|iK*D6=I_3q?%xFSLg1 zI^!pA=K}l1joBBj8aa8XHp^;Lf`9xNa&Cv+twW&$_HAwZfHrVcNUrRccn_ z1+L!z$k@LK28nc1VB|Fbwm$wO;B~yEdww1EUn|s&{-Tu;@$d94BLL(OQYx|aCa|&2WPT{qJzbNU!ep>j){o5=6le6 z>~Amqs+mCuOR2)aB!#sK5fuui7LsO!Qzl)lz?Lm!QoQFWbNIkfdkrn|)YbSu8WwxZ zO{}a~wE2Cu)`a3X+KI#LHm(Mi+}bOB6@N~H2}Y)e*}w8_z^Sx`c?CWvu*2{K#yqGo zx!Cu*+8&tdw!eiKqZIQlJg5Cb^hZ^Zh~Mb0l(4m4hc1mP&>oTdt7eS-bEz8mU~oObme{^%56|ou~EPOSFBa7VpUZC z0gVc<@IUeo~q)&?o zU@=bz-qfWm)&0Qn@W_fc9{wx={&-#8>0xHJ-+Ijl#P&1qB-%*KUU*DCPkKCLzF*#t z0U_vrk1(&Vwy6Vm8@#Th3J5J%5ZWd)G0mifB3onY8dA&%g6Hir5gqMH|hnEBL0VVvl~aJjdljF$-X@a zMg=J-bI?2LGw-8mHVF7Jbsk1K4LgWi7U>~QovGT2*t^U&XF#iDs_E$~G+t;U;tZn_@73Y6x>vU%x` z6?l`$@U4JYYe#|GcI^f+rsy|MdB|`PQunKSKkja4IGtj9G6buN&ZSnYi|ieaf{k5q z@ABM@!S(A6Y}Sv~YJcB;9JeqsM|-fPIZZfOgc*FSzIpEdT=YYT(R(z{(~X&x%6ZM1 zY0(|PepBl4dK*@9n6@`rUMd)K^^0!^?U-1rrB*b?LEZe<5taFp!NoC^lc>}YUy?5FjT9tFmC+%%DYNa+L zWr)zMB%y_6L{S%;dk6bJPO!wmT=wPPK1b$%+ffWcO8;2T+7C28T?{!96{%d`0G~j3 z)6g<%$dC{vAKJ22nY)fnxlD>P_Xb&@>wrG+ZpfQ%RX=R2kd@bH3N*M8=BO zi|Z$Z5e`0NcU5&aN_DST8O@4v3vroq3t<_5hBX;d)*AJgWPb~p=qx4}^Ms6pgyY`) zu z^|u7XSP^~b1)*61r(}zd!JOny@$KviSp>L|jSR!u*1IgKwId5jmAi2`qe%u+XCTwU z;a62_a~Z}TqDJ?6lje5hblv1f1(6U@kWpc)z|&nRBV*UIieQR{Rru*|$L2SzxtL&| z7abeg@xniYhexYoN6zxY{nI^*xKW0Gz8D~}tE>O4iCkpWn8wt4?S`(Ftv?<8vIvbw z(FFd5`p4~#m<(3uv2+pv7uVC$R(iZuhnxFEY{o}BxPg2nYK zzOjuMR`}t3{8z#zfLXy||4JCt|1nv5VFjS#|JEhRLI>(-;Rh~J7gK{as*K1{IJ%7F zoZnXx&Y54ABfp9q!HDWAJlvFFdSC9}J*llUYXFDN8meEa<0}s z8M~X?%iKLB$*-a}G_$rTh;U{M0vc<}N#PVAE1vQdL#9a-`uH3*cbJZ~u9ag-fny$i z8aCs;3E85mgVK&vWM6}FH9o^WI#G!=%YOB#gT`1^VttnSVf4$YKja@-;zARB-`7v< z*imICw^KX73Gq-go6e?w^os0U0HSxH>60JLWhFbDeGT&Z$d3;9NWy;WvICuoZaKMi z=UvTpLDrtssbhiK&A3EuWf6!)>$sUlRcn5?Pk^OCtvApB=6suN42uKN-Xs7u7EjXh zG|>-1Rp>w1KB%sI*b5dGwFbuHNN=|})sR(dekHBL=>I~l@Nao%H=w0q==`3$zP>!I zmgoBoi7ylm<9Fw6s3&T%wJ%>VQmx(H)!iq?ABhdSzitwHlFNGcBW4sc&9DmTThb^qz`diS`xzQT# zhZff!yj2#rS>yfS5?}{inV5BfcZw zF5uh!Z8b#76;GcBDp7^zWtzQ%J;D}es(iWWWQNA{SvyhO`X8oyNL?j8Afn=x(zHct z7)3c%RKTPAyKS0gwVpGLqR2_%EowBpk>rW}MFfsR9>#2aOL!HKZtg$bAOe+#;;w?3*If zQk=HPWSlX7cF?h1PVE1D>LL{K&Ze4d!#Y2qN+^N-`~RG(O^Gjg~EsZbW^ipD9*+uf$K4Cq=H zxnYj(#+^eUa_1nRDkJJH|9$VB>+n4c)jji1MPz$dV4Ojf;)iYjgw#m+4puPdwgLSj zubNnwfz=z1DqFmy@X!!7D}kTo6yBjVFYT`CisjAgjS^cO%|(B2vzWb5PcrnxTK4xu zm?ZZkCy>+)-K8*)fo5JCWa@}^R!iI}a6OA*S&ibX6V zKk0=}K_M7m$#QEMW=_j=4tDXgH{_l5u?oFF?CXKmk73#~&>ha8CH{7jDKT2WoJ&sW zD1wk_C4Q6m{-YEWeAg*gP5`2Yl>4S@DAbob$M?&Gk2@2%+H*H2wu_)XL3fn{D8ljl zh41$!&_(kR($}4zJj3?zH-A0f2$4;9tH|N9XT48P;?coFH~9`z4S_35{xiUZC4&-3 zo3Yt|ee&RI&qBF zW$mPrwbqtHO$6De21%1=8zUX5=uMV*>#k-H>d5vP zz8OPyI|HLGKn`U2i>k8-dUX}5DJ(|Oy>)cK%QOwU>>~+Wn?bp?yFpx?yE;9q{;DTa$CFGK2S&xDNk$24GuzOgK{np ztsuRfjYmLjvhn$}jK3F_+!AtM`LVw=u&FUIGIU6>0@nqZq~REsb}_1w!VB5-wbS#J zYPBNKKJcnu^LTORcjX|sa8KU?rH5RRhfJ&l7@AtLVi|n8R7-?$+OVx!2BrQCD8{a)Kc#rtcWIC2(YYu=0edjgP9sFpp0=(eKUE2*>jc+n@q? zKTY!?h-S?Ms1kNuRAjowlnTQZF=#1S3XPx<()Wc1>r=QN?#W;6OL z2|Y0fxO0y=?Qi#F4?$+-Qpt&J>-JT?;d6ITN&7R`s4l(v17J7rOD3#Mu@anT`A z88>nZmkgV5o2{_IQ^TOFu9g}ImZrc~3yltx&sdaLvM=bAFpUK=XGx*;5U2#%A{^-G zEpT(GF(}NVJNzn$I*!S`&mA<1j#FEw4`lJ|^Ii?VA+!l%tC)`Q6kS&`LD*!rp)SSZ z!fOJa=BWFG0rWJE<~c2SnT{ykD23&sE?h7iTM20!s3!XMY*WJK_oA3FzU zScKW==wTvjelr=iu2>(0OLprW-Pv$m4wZ7v>;gB4M5m0(gOK>_@aIy}t&Y`H8crZ% zbo1L-*2^hdvzq`~_{<=PT=3jZ#UgMI*bQbOCzf~T53X2F9_QJ+KHwwQCpU%g4AGP z7i4m>KYOFyVXw`L5P#h};Q56X@OHZ-P-1qabm)G~GS>9sP0ToSI#43Q5iDCjG6r<1 zyJZa^U&>SXTW+bvJNB5oHW0xNpCGimZgaFJSb^??Uz1|jbXP-h<65N`CgZYX8jM3^ zSJ2tNSxr8>9)`mMi8nHw1aDz_?+ZRuMO@tou|Q9z11zdD#ka!jZfeXi(bGK&_vVQ^ z?b#6fYLRy70Mb9>3LcE``^rMcoxj~!hvBT%&cQK#L#nhF)C)iw(B$hY1fwak15v#J z-<0Kg=Zh1uk_^yGnO~&Hl|4?14*DFz9!$a(EAbT!5(<}0xUlYlC%`_JfofaWqfWNEfhlbLb2Ds@#m_oKXUJ0 zdSUbdO-BOnM!b2U2o3t3AQ&HGTzjL}LBTpwM2|gf3<(USB~4unKD6^_G>?@N%R2V zE+a}P6(vB@x|W>|ol!d5vws)e>m=0+2Y~#n1%kb=NXlT+^$#v9N z0Lt8wQ#?o)_j$PRavtm~z!aRPQ85^H^}u0bjlfDm(!3xG(oMQY?(DW6m1QdXq-PG; z7jW?rNj(vW&SZZ>B^q=2mU!8NLql4|nTI;pSkw9gbip(A^U<9DVj%Sjd-T0)ldwku z!O)$tFvVGRJnSI!t*v+U;QlSXfMu%J>v5B@Rq<`V$DQ>YTCkc=so?hUx&dda4;A1r z>~5vZ0E0M|B&lv|71*mTuRX`GB3G>9RzF7}+2HIgGrV-?p|bN%&4si|xxb+z1S}F2 zOBQ37uO?>1n_T3UF8nYp?uWnU&+53X|N94hR8WunjZ{}VH({S=x7sRbdLq7vyftJ? z2@;dF{)x|0nI%sYQ|%pe)%r zxP>}6S+ylPH{St~1KGov%?}z^A&&&(B(s+ngv{wKZ_L(*D^+nzoie`$NZ_*#zQ@&T zeLY@LZ5;akVZ}L=Qc=fIphsO^5%YJ0FQWW3*3|ahxk16yr=ZgTqunNMFFko^CZVSh zlk<_(ZLf{~ks&04%zz`tNla=O_`5r6W>d-%mdkEryHLIgIZyrq88$=4=Im4xR_}|) zZ!?V3+6QZ7$+wYJ=>nqKQ2L_gKw%=9`ds2Mdo6`avM-uO$tdP}7Jandkx0}XQhkn# zzq9uFBxvJ^#%sW$s)6J+j5 zXmAN{4mTo60nJnc2C6XtOBsVbJYc5&a0nZ|e?0yj+kThaCezk^Cm!F<|A=cu`uO@u zMai;5H6<@WD$n?-1{?Pzr2mF?F||EI+58#(N9dB2U*+$o$gl7(T>0jTu!?94mCA7^eb%}7cOyZN?nfVx+L$x~x>^tyJj$vmKZOXBKkU?mdopygE`0+rPi zx3F#q)PBC|6M{n@2|m%_24@G{?ql$@S=PPaEh1sG9v zxo35;K!!nAr&^P|c$6z+&vUa@eX|Uw&nednN1SCQSFNx={#kvzFb``4ixf3m zIY=2lKDmS2WGQx#gfP0BOAD4i?UoNdWtRz&Q=#>Y75@;X*z^@rxbLVa`YnIz{oaTE zNGmThd0`N_?*0!a>=f<^TOdF{&|-km!E9iB4IUs0KsvY|y6}%EN>L%XAjjOs+WGAJ z=wAmEmK)JGoI&Uq$`1%&(sh$n^lmT{o9pDd>t(CQ;o9Sr;gFtdZ>-qZg7jbc*P~uh_&U$wOO;{P3h!F3|a}dH-WoGGsXGBvB2c7p<>_CnJAYP}_#gD0t)$ z$Is_In%83bCJkJDij^-Lbnh)JKexs8f3E|dDy=BUEES;}7{*+oxV&iNODhNv#y<$} z=-mY})V@*#j#N6^A*B940E$3$zfmk;3ReX3DO;=d*_(!|f4FL$#0mL1ToWidl)O|S z_mi9mELAQ#S-D7+a2+=an87R;9t|U~1&sgF{`AZ#ZsOL+=sb67R?kPP;SQrDJP#F^ zsr<9}0#5FYl#3;3$mekh_XV=g`LVN$408Oz1ZU^F@kv7gMcyAWTE+yQfcY<&di4?0 z09J)>xHkZoQg!{E*RBSy?JCKOX7n%2$6 z-dzz8T10-8&ZG00yi<2%x`4@L8oj$ZXP|WgZ7E%-(h>@kqIJqt!{ou4J@Anf#HcEw zPSv)TmeUHAmeK2Am3|mkp+~W?)6eVg;c7e2H48x zBw;iPnvFX(a}Y+nn8^W#;6K4qA&N3hg$HYE=n|Dy)1^$6Gxud`0!yZ0d*p;(03ud^ zy^hvb&{_%?^-|c8>2fAn_!5YCX`?Ov6`*x_BAqZdP7`m!E4|c0ttvHBo2}NJT1HQs ze_rYk1e$5HO|)A}>0a7uufbmK{SDV?ndJ&?hXXVWWefy|nb5Neb%C#pK9tl%P-U{v z%DOV=mf@tF5qHo|q4_JBR-PLXOPn6TUrQ#9e83Sw*iIv zU^kn1C|EKWK_mS%Ah;Pks|+@@OxM8{T4o@Zf(mvI z55b=nM5d)6kW5m_Lx%`#@%0J~At8s1=`iJf)}P0CE6_pa-@`H5WIHbP7t4>QJLNX9vAkd8^)UWbAP6$@LZXWxAVbOYkgCYh!Pi4lzTy1%B>Pf9ZYnAH}3- z*{;*nGg_ZWZvV-oB*dF(WQ0^x71UW+hk8Cp_g2sc=tD&+CHpenk8FnaqFX;|TH%e* z9ifj@(1+=xs1s>xxwM`XyvIu)rw0VwCz$GAQ(yL@$J9)4{viA{r49G#c+Z$S3LaiI z8H1fq(Zeb|M4x7oLLr4te=>z$^SG9N2w2ERGL4D=I9HuNqS6>W3ax}f`>ts|P^Zvm z@RHI@6xXbm9v9ry(J7RMY_2a`aPR71XW4B1S$a}He-4?~NS8>v_Z&;WYl>KnqBJ7-hpw*<(4p-DB;Erm4B)LPDS{#kCnL(dCt zzl#E4aVwa$czprcYdPwIDCcme_C!|1U))PSuuI$zk*W(Ap#uWp$Ho58;-{sE*^$YJ zfcvRRKNF?1B4(sbe>9@m?fS5nel8lSJLrFy&YLbuYc7$Di~9RZ6dwe@uT*+bv?gxR zf2UDHLuJLEg$yM9E&WcA_+R7?)37(a^as(%yhwk9vCtzREf&@5r9ab0gl1l{v<@{6 zC3O?M!(VOl{tcWYFh zcWyW`&qG3pOe@HR0(&Pf@bG-DEH=)i05VspTrF}nH!FPJEICoc3S)q%V+;_aFop)l zP;Po#SxD2ff0q4{T+T}wqs1MJ(W0uHR%OPB;l?2?$s`KN)CwvpIWi|N=M^e1V@wxw zhcbE=o-@%8PA~qV;Cea8wH_!IqWp_Sb&NfdNz}9rhH)r2Br^t) zMeQA%TY4kA4{q7j(jMtJ*xS>w>)_TMT^(L-L2JjGxOJj&ZV-)ggVi{5yFFtT>@y74 zJf{=@f2D8cEh09yg6#A&72XCLgRGuD?B$3Jh}mU9;ruBh4ewxD7AzgZW*I&BN(>mh ziz!$}F_R7^NNhzIC6VZOw|xa*NB`8Izi`@_wbT62%UAIpm3#SWG=pW%ix>j~;()!P z=|~#* zs~lrgJ~te{KY{96l8>ex)n>uuGMb%`c#snwpktC*Tn4EfgILng;xZ@8J7YPjGNU7z ziy8fhkvX(Gk4lucz zopwj%<+s`80do~2D`Ae3vs%C2n@KP&f1Tw*W`gvc{0^aDj8k(=qot>B`xmPR?nWM%F_Tp@8f$^zMC-x zxq5eR4y{vI3_c*+I&2E>TUd_fzE&@Pkna^rKrwaahT_Qipb*^GDr(jJ{9!?Jf23IL z(A^If6~w*; z?}1Z(f$4(T18(_hnK5l-&KgXmo>nd-3e?K(mCc5>6~3tQ)BGjdE37LV)Q^&pwQ#S) z&+u1NlKHDJYC|%1Na3%+nyEu^jPYK6&d&RoKPnRF@-yfpj11b3Z`tb@e>%>eq_``W zHjyW%v=QIIjMQf2l5wjwh-GwmTwut$YYW7S)B^oRCLq)v5C#Y+jB#TgxNhmo8p)ig z+m?O7x>V%vtNgs^JCwARHbhpo8tiRe{t^FJ)aIYKNc@@Cy2(NO%_oXe2h_a_mDEVt zmb7j{8H0tCIim0{RsMyjf5xg%)u5J6>nIZ!1*crg#_ZLsWwQbZRQGHCjX?b^(~`4- z%8a=}HZ#K!NGa0IY^23L=>CEKsPgamPfQ#BAATw`rjrHMokCmE$m&;$>$>FdWOl&m z)`l3}takOU{5O^V!Y`N18@mT#Hk8i4BUNORx;`YLf13b*mCvaBe-8<>i!%lf^-2;U z9Xu^Lie6DxK3T%#A{V~ncqJJ#j^vgU*fE*tQzR9Izl^818it9apbd#{E7lZ_VRf}E zc~xnS$S$5Fa)vkpeqLJ|acM0jlw*p5vTxcoxin9j54VyQ6lcuBR|hLNBB)YOqvR9U z!GXe8h=^BOD85uIf0M*0GA*2n7=9$tiDqrej<}AS5rg&?cv&o6pi1XUOT5%!|GH4f zvaj?*$t>7b&`TGoQk8_MWDe?v2r}Dt(=V&+RUEinS|JRG@uWH{KKj7Hj+!Oxo*$h3 zJSiyE3UmxBOJT8wLQ9;~a_QJ0+H$+Y7xq%5dSM}87BbO_f7fWu3%N;ZkQ#*^Fy;8l z+=R>08U>@C^*y3XHwO(!x~UB1eKROeJu9R4i#yRqn*t8KOlnf8LRwpLV^InvOY4y& z6Y0aoAta#nWk$@|ua--OGHHW!xhjPv3`wq-h()h-g$Rf$X%kb&Wa>o&%jl;Juf;h@YL`0DJV={S3<~|Q zxVKlNt>PnLnaimuw=2>%bOF+Krp5q#4}8Z1N3?_qAS?S%)arm{Ww3y0Sj8X=>X^3N zqTq|)7_lk>iEJQee_T8ouuaPZ z`ZGo<5HsR>A7m?9YOlD%ISXt11#1V2EoPx>=owC%+R@3XD;+F;=(T8c8;0RJ zTsm&wf4E6n@v_B&nSvZcHW#06QG>Wc4M@NZjXq_R6tyGE%uPgmQ2BjdC;x_^K7e<&Sro+Qon7}Z6ij>=e%vr_NLQ=+o& zBpJok>#>>@t9yzoIjkHJE78hf09L;KB)w^jj*Zi;(XexzZjXje(A)F$&QZE+l#Y+n z`=Vi2$nPAb_di1SF@@cJ_apQ%rsI6t?-IX1$@BzBhvht-IL`O`<;uJelNOBA7;pvZ zfB49mXR!WQo}M^PexS)v&gcE|!8|>kr>}-xBWE7K{@1Mi2C+ZCIZxkg5`fhJ{k9ES z?Q&jg{rY^Kz9*250O|V{Qa~U%CqezPdlGEt!}O!OX%T>bVgb8HsA8Oc79FMkJ{1BQ zAj1lz_A7b%#c`?Pf$=T5(=0B&}8~QNxNwRw*HCGxKs7 zAbuqb0wZTm!A@E!voDKNVzcs90B98$d1mpu$?pVH>>OjYdz|h7=c8OvnalIse-rG> z^TJ7MQ)h{-eY_~oi=$1-J+wg3^YM~AU$kfB%yWKA6u<1KR)jRN^V))`t?f_yozaju za%E*q=!xg(Q{=;$gM(CgBtI%caf_(Rsq{@aD+#S}=pC z86ka~*GGN4VU#aFW&hkLem=}?e|vn~F~*%Z>oir1(1J)V;P~B;pF%#~KE~a%?9Q`R zT%aOCGZYoCbw1uX$~|Kog$!cB?q~!dDf0Qo*L&^G+IB- z%c7$kALW4)e5h-jQveUupWrMkF~&y@j`9uT{Dx>3B5#~;1W8xjD8D&0f6BK2KH7bP zZxi%s6BzdKTl4((Xp?-8aO}B$ceSl^VLKn+QQT7@lRQFm{BB3JY*{801(`8^XP)m0 zD?Wbj7{5On_W1Gh19`qL&mS4*kHL?eO-i0WS*?JlPt9MR=TBSiCFAu3oJ*WezdvZZ zSy&eKQ%>+G2tl=09#H+Rf3Rl+Zi1CZ#ESIpy09nYSNtA9DI^G;;Ll9Z5|JT@L8pS6 z=LDaMhSef9kKYv$QmRE_E9?E9x+#R7EG1O<>7Jl@f=`e0)6s|@lKP$XQ0bTR{H&FQ zqg^6St}cX+CEqrS#MdXVu^sKs^EdCN)gfU|nuEu;t&|cN=jWpWf4BaikH05EkAG0a z`{60><}kwSr&av3l#hRYOk3;XuMV}FV=&DU*-9CmLvT+ z+WizQMWlnqEBL#Bo<24v@d&Bg{c`sRFGPy!hJDXGw0(p%#G{63F=LblwcdY3eAs2Vm zpQhd8QdM++1Q6AEX;GK+F4-R9ZGBt;ETo9?DCrv0D+1IDFD2JwEAD ztgpk0jFnYAjJJ(@@>0vEgx;*>?T$KtwXGVHwg{EYV4k~Ae-(8Mq(-WYZ0p$a#PooH1&29;1t$_t9$S2(58GNS8RjOP4xdqRX7GP!mS( zwXWr~Th0}t^{$I4?CPWqt{rr_D@Dz&!?e*gOjo$xOPgE|Qj5EaTHR}@&3zZOyYHqB z_w%$_-a=dCx6@YnYt$*fK-=U$L01^rp)ZLX{|8V@2MEVi07E4e007D}b)$q0%WLwQzAecs$;-Nd zASxmv2qLK4kS~#nq5^hlp^Wh%1BQZAKtXf}4pBfw6cmwp&P}qWT{hR>FFo(vkMniU z{hxF9eEi_U02Ygt0^2UTZ1s{$s=JNge?~JFs`gh0d#dZJgLbsfiWrV%$9z#cWYT!t zjF?8kq{&_*;S2Vf!HtPzG*RvEF(L`GzPc~$iyD1Ci)C~-H!lhd7@Lg7h!G1np548{3_1!t0yE`k(y=0q zK|2;q#^YwpX>6fwMt8(ipwh-oMr2;Z4jPg3t-iFjiEVP5Wj8W^l0Y%930Vneg%uYl z%W`q6JIRq+8;=~^6f>R1wX0ice^UuBBdtAFI2o4_6~UJ^kg?F#!|# zYr2j}n9N@@1>7~fuMD#_D5w%BpwLtNrqnEG8-Ir6ou2E2f_VZH!ltvzf8c{mpVs8; z#;m70j=`}S=A%Yn>Zr&LhjZ?R7!(;@XXOpGy-LRkte_4{1m@;F!7*B7==^LD=cSdP zjHE!>@hvj2=j%8b%Xsz_e=^rfuoNB3(?h2TOd@BOcPH#f(lJ*VPOpv?Y41)Ks62d1 zDEI_jNFx|D6O@q)DJR1``t~a28pcUU-Hb zr2w4G3E7TSV_>3VOTsau3RY9(%sAca@`GltA}bxT)ik1H!5XYBe?kY&r90kZSdnDh zJd5IBgehf8^CirA2(Y&E2`TajRIr|su8#*Igb3yNQi%@vQ|Qug0WPFt3=sf32k5POw*CcHVT&e?km<5rfT#*GFEMn@M&;M?CEXnO;5$&MkH%LTOA|6AF?7MP{_m z+0sTkD8^Y27Oe4f``K{+ti76n(*d037~VYDfUe=5dU+nO0CJFdc)it$BU zO%5G8uizR=3aYQ|=4MC7SFo%Y*Wx+?$Cw=WD(3RQ4HU_UDH>}?$Qz?#n3%XpD7%RuqWbW)B70MGJctpNfASD{o7H++vZu$4o1xXFA?ww{ zbWYj1)>vOM11H((N3yjpV{pzA1&`%9C|O8;qTz8oAyBw>%}U=A6;BG(jxNlRaoAGy zw1!8qhjHlOwzNr^`JZaog`d$CAt|9Y>il#($06H=pOe~P#7@x2FSr@lgz zs*2f8e^n2IOcmXU-YNne%Gnnv>GNc2HZc_ZisGIydd#(P!m?R4 zivLigs3CR?D@I^FJ=eFEUL)RNUX(Or!8C~c7a#Nf0~EDxE0#HPRnWs=+UPC{6t^VV zf1XabIi-5(-Jyy?!mSgUnpB~XV_Ytcm>sjoUU_Xrk!*W}#(=%bsJCjxKxz05sY_ z@G}Yk3Dc=EH=Dtv!#Ajku0+&I@M|%_fIyc`EM&DL*fHD9e%b4a#j?E+)M{6be`;Ty zj5$`+JbiP}?32xoXwpP8m%f=<^e{tJxy7oghoq4Pa<`(&N{~HO^qjLoRa7tJT!Sk7 zSsgN9G|@;e$Q&I@$3Q{O#Il^uu=VVmiBk!-Mt8Jk<70+$)=(E;&_XY3YUUYE+mq35 zGroo+M7UH)O&>)Tg_BG8Jq8ffe>0TcVv^EJOj3He0dUd!GEAWt_X^@_X}^c)tlGf( z_1=OVsHoe4Y4tl$>Dz%B-ohQ2HH10$f&WTSjk)Q4h1*FdNq1jYJA(Ovw%S2VOJTtX z>H@W0L#UVR!W51#ZKi)IoH&G~gQ!g5)U9Z$OQB^e8fZ@i{VD?~tQIWX*I2w);@?C{sP+OFC4_IfZtP}LT~3FqJG8Qta_S@ zd{Vkvu5N`^@ADRYnG%9GerFINTpiWH}CfKwRa=su8@xYMtWNUdJgtNAiV;Y+Vvf0(n9&Vd3lf?a|2 zyyMZp2p%U3hp@Z!sUbWwglALO>sM2F-mChR0km_#io86qt3HtRNa-qlkvtm4D=F+N z{ry3=vh!+J>Fd(tHxEt;zf#bwmKV7$3^W(rBK+m*wvRirDL}s&QrJB?i6Atd4)_cB zfJ^^8jKAEEf28nXf9Xdl4z_0iFG!aQePzN$eu?%GQ4sL##QTAOx3DYVE)$-Pf-<3Y z6gGQOqPX1C)iER{rbH=aO-fALiUh}@oulAayfieU^rNVS(J z)mTl^2~@tAe^!b)l2(foB|TZJmNY8*#H->Iagn%6(yPU_l3p*iOM0^ymh>U9SJJ)W zd9fc5FN&8WzhAt?)OC&PM)w4HMnSamqf#jJo|Dn53@=S?$ zm$)mKmy~z{%+m=xH=vS$SKv$n;7+))4h8h&FQj*-2UijZ-vAYN5vYCyO)N(-fvhgV zm>{B<=vszJt~HqKx&S4vAWB_fl({a&6!&VByDvb6JBX?7UQBaugx76LJ#Go~?*9Q$ zO9u!}1dt)a<&)icU4Pq312GVW|5&xPuGV_G@op77bzQ0`Ma3II6cj;0@G{*_x6$l@ zWLq!9K8SDOg$Q2w06vsBTNM!*$jtot=1)l8KVIJeY+_#EvERRF+`CN~+)~_fcio`v z*4!Y8Ql(|4lGuxq7O`$fleEN}9cjIwL&2@>M%LYJOKqvn8>I&WVJ`e@>#4mHnuhzUW>Zd%6?zt$4SI~lcxhl zC4TO|$3j~w-G4Q7M%K!ZiRsf{m&+`_EmNcWDpuKnz~ahZga7dAl|W%-^~!;R$uf$l zI4EIk3?ryIC}TXYW(0;0`IS)TrpP}tglbN4Rm~aBg2TZCuXEfjpuhoC)~>H#Ftz@S z>Dn`9pMU{c7+4fO0Z>Z^2t=Mc0&4*P0OtV!08mQ<1d~V*7L&|-M}HA1L$(|qvP}`9 z6jDcE$(EPEf?NsMWp)>mXxB>G$Z3wYX%eT2l*V%1)^uAZjamt$qeSWzyLHo~Y15=< z+Qx3$rdOKYhok&&0FWRF%4wrdA7*Ff&CHwk{`bE(eC0czzD`8jMNZJgbLWP4J>EL1 zrBCT*rZv%;&bG!{(|=Ze!pLc^VVUu~mC-S7>p5L>bWDzGPCPxXr%ySBywjS7eiGK;*?i?^3SIg!6H8!T(g4QQ%tWV0x-GTxc>x`MRw2YvQwFLXi(-2*! zpH1fqj&WM*)ss%^jQh*xx>$V^%w2Z&j!JV31wR!8-t%AmCUa;)Y-AU<8!|LS2%021Y5tmW3yZsi6 zH<#N!hAI1YOn3Won&Sv+4!2kBB?os0>2|tcxyat=z9bOEGV>NELSSm<+>3@EO`so2dTfRpG`DsAVrtljgQiju@ zLi;Ew$mLtxrwweRuSZebVg~sWWptaT7 z4VV)J7hC9B-cNaEhxy8v@MbAw(nN(FFn>3184{8gUtj=V_*gGP(WQby4xL6c6(%y8 z3!VL#8W`a1&e9}n@)*R^Im^+5^aGq99C`xc8L2Ne1WWY>>Fx9mmi@ts)>Sv|Ef~2B zXN7kvbe@6II43cH)FLy+yI?xkdQd-GTC)hTvjO{VdXGXsOz-7Xj=I4e57Lj&0e_C+ zAH@(u#l-zKg!>k+E-Qjf-cLWyx_m%Td}$9YvGPN_@+qVd*Q)5cI$TrLpP-Mh>_<6k zysd!BC`cEXVf*Q0Y(UgdE^PYo5;;FDXeF@IGwN8mf~#|e4$?Ec!zTJEQCEM2VQr*k z8Kzplz+)oH5+-jyAK;GP8!A zSKV>V#gDFTsa`xXt|1Uc3i&PSgl%D=JEwjW^F5vD0l6G!z|~>y03#T)?a;@!*(vAwmBFr?|-8vt&)jK z!?QG5DNz%WTH4H>vbUDpIEl_O19mVOmP_8bVz-kCsYEtX_1Ovb zj+KS444hDHKJfNHwq&hQ29#QGU>;3P1P+D_kVfmXiA~y=y{YGCGep{s6iwTA*ge*SZSH9K;{Gc1^NWT z@{>XOdHMwf#oVVr5e4%x1I%+r&CEE*Qu8V$tmu5mm?%|OR}{L++~wCzm$RIp(7a-4 zuUW|Jw)8G^n5G$)e{tS^RU&@6hKR!RWWQzWdvkgoyCMKT%caX_=zlus#?;Tc<%xwM zJewbXg?^RAe+_wMk=A>m=A@r~0~#Z6hmh`q^b!Z`=jde+%aR2&hxQ>`<7bXmDk+!% ze+$*7qh)2_^In4P`ktr>O8z!|UZGd$clcz~c=h>Hr~z=--z_oAmq3RVC-fGwS&sJu z1-B|M{Jx;us@*hy_J0o)`U?9cH0RlBfikrIP@yl=AE9!T32=5+P-i$<+jN!7%+FG| z&!5nrvTOegUa57UpZ*+hJA>p2ga0MxsK21E^Uo8!3b{#gdjViLw zDj?{%qL2b=fc}>G8S&udSPszN3la#if5csvd~EsYTU;zzV}C*VHpkOH)4w1W41*h( zbOQ8mmEBsPEo@ObLg z93$OR0O5mpOQ~kA@~zx=sm%~6;&yQdTLO>ECg3w&$V;K3Rxm$Mx#E3$#)AP`Y5ET>GF+K7Ons=3AJy$clM99)e@XPVK;DaXeI#{!nwqZB>eS#gwM4Gc z+UQjZ#jeu&%Mv~fw1GC37KsP2q#o_EXrxGY9xc+Ai=@m@d~k~Hixz2HYVc*MpSt<2 z$TixLN>0<8uJ7@5d0V_2pQVkF7Vq{{!dIm33#3Ft_}G2)yjM)!d^I{4d6C{M=mM$U zf6tOXHRy?rH1$Si=)u8jv@ewuk!jjLMIV6_5a7L3EjF@9Y$D=$k&f1(*4c#dO{r8e z(v+H}hoI~Q3P)vOmA?n#aMPBi8^%0|sj#w@`5rIzh zQ!tSbr|=trz3XA)gH(s7qlZqzSnr3Gf1k$a6s-R${PJy>^CsjPC{3BNQR^|!p8G=V zW%6Eb%Fa-3=o*=+gf}`(Z);pdp9v&gz7C z*}oPKd5d(eNI!)2=dpg8p7eD2T72>A&r(Oc#kZr8Zl0T=_oWh8{A0N9vXFPxf7T*> z@F=#&(1(wn_rW1wit#=dQbR@h$qP^^nkv#IIQ!Y8pN*0_p744iBi`tUFE&yiA8GoT zkhf%^=TflG&)tw(+<*mIXdUgu%{CxCbK8#JowN2@0SO=M^#R!H6?`{v`CUe5FJ?Sw zyCTwGaWuckZrbd*cS97n*}$HSe?&KIhht~x@pz>vsk20GwyCM?#|=m*99Q+xzrHv4AaMp^qVvE1qqxlUZ9nHsoy&~b@Pi; zbSxIXMqg&hucX*B)AZGlZ<_wNNMB2M8@&ts^)Xsm@z<+UH@_KAm7Vk&fBsM1e8*q} zC%twfR;0hW%s)2}p$g))S6XPbY}b-1+g56mZJ4@bdpGTo?Oxg^+aw*3?Jyme?QuE* z>k?^{mF+lLvMtd2WXr!S_d)uoY)gJo;16IEvvuH(Z&YlEF~4MtgVERw{mtdnP$YGQ zLX5QNiKcH()87Fhz);gaf8Zxp{{AQY07^yr*Rp8*MAN@Z(f^s9xq-6?{;3ChGh2NJ z5h72l13;O%#FbbiB|~{IS`?nriNJPIz>*(s7WJjAq^m9+Eguv+(JTTuX-2FlipGi# z>xbCfU@qZdcZ!5pBz#h2ErNo*n((t*0g$h4ur7sb6@-iGc#L$?z0#Uu)Xh){P%^cBVZ7wOS8%9=n+@X6!d z0j(RK8a`Hw2l5S1eVl@8los!kPhF(7@ijcCcL%PBB!<=~MKK)m$2=`T0Eu_#R=NXI zH=h{{`4iqLa>{Mue;U1>Y8Hp4#o-&#kU!*$UlB)|#anUx3hcmxfhe0Q0&^ZadKv7! zbC8#@-C);d@h~h3LJ*D3;sie9@`|I)B2%(-WLk{fsNVS{3NYNyg}nR)ue=tyK_MEW zlVVgDvV8=;&C^-g=a&0t>2a|ceQr0P|8{y#_POQ$^YjVXUgwtkpQOvO&n@>kdb!Un z_g|vV%RaZ<|2lm`_POQ$>nH%Z&n^1GBO19cTkgk1x9oGv{j_*W>RF15CZPW_^!Tj4^T{T!k9N#2;RO7iBy{i;&QUo$Tz+ znfE#GOwP=ozrTJ1Sc55We021t`blp}YoGj;%5y1uf!uNG{2U zc(N@c!)lX%wI3y3q;Kp>H=-52V;i3A7>>%(TwkwPYfo4kR?qm|#C16kwWU$vA^EoB z6NQd%bM%nHh`l&oU46V-HClA2e;$PpNH>BcwCIK7lE8cr+NK@KmP_V`PLn)Sf8 zDbz3|Fu5lWrRhrFHeWUO$ci zK|;QNMYU4B-{xxq=2gh0MJ_>CzIO%I2C`dQ0}U%zLwzhCD9eXj_~Pck%ya+e`Xnf; z1j}62O+JMJ**YJ(mx~=JE+{p9z;saHl6M^@O>uaJ(zL_pbbfg95AEkMI{P zQrP_-wu~WeK)#DjC~RTz1jWl>>J%&u_A8uVH0UJwtHj+O|MgSsVS$&sSO#aG3~yMr6^X${<>0 zQle|Lj@}|34Nrzqkl>m>`@k4<9*UKfc&#)tI4W!!rdA{x!$&L15^Z=Vs_fD^%wvtV z4GjkS3$YfV7A6gE;|0p94J`((b7fR@!QilW^Ak`-SZ_W1@A@+aUavpvf)AYzv|)!q z4VaP^lJwjZ|A#8&wqkPDwLy5?V^3lqxn2iXkLKsKp3v z)lw?h02Q#9dcl*)Nir~*8P80hEVZkB@JF-{`qDZ}%ic=6I zm%FuV~79YG9K?LnO!Z^jy-SC}sEQ=yjZJve> zhLEVZ{w5(ZoQbyviJ%i_b(}#LLsvu9$Wy~P3VYSGP5*j5?A-{?qgO|N4=ynDG-o(t zyH$VDmx5O`yrrVG6j*nCTSp%*G6XD#7Z}brjGFxGwwDl7VfqSEf=l#B~g+q=IW=b5Z!M<&ucX9YRuprWo1}sWhaiRi-Z__Z`V_?vU@yo}2(i zFdD}DxXjRbRIlL*gGOwBofG%{2tGu67-Ps#wKfT;#rvpD6d}xUOenjnl!5P12Z*7q zw!2cYy^fD{X!wL7>>Y4wID{LA*tcu0;U>}9^SSiBWz#PcPvS>06_ak^GaXZyW_ZJ^ z=DocXy5lp)=I}XgE9)%v+M=maz{HH12<9-a6nE%cQa3OVKU(g8u^m{zqPmtPawHNk zWR7wCpHO$PtcdUx!|AF`o4_oZJa38m07T<0{69Jm_wcovhi@1zG{6_Cwr^I%)O|y^ zYO*wZw@?12&fKV)RzYoo?-}~1q;zC-qb%&GVmhg#?!i<=i!>0|LdgHijnpTlpo4>E zJ*c*hO|z2vk8U1+%7RKMp{yWG^+$Y3922QYvQ(DNhU(N_cuU6$Dzv>0=5xNOeup?c zNo$t6oTaTgSFPlQTvG0VOE^gcRX<`ALi8~FK&RITk_PxKQN!sc(4M3F**1D|x$G9+ z+(ut+b|{%kY$001J2kwwjltaQEs*i>3w*#Zn|y(f7#?GPoIb8Gtu3 z6l++mVQpv&_A5%Vi@5j`T=XJZe@D@ehm?9h2I}XB_@(}4kR&~YHrm3(cAUT?`X&;S z^aR@e0Z>Z|2MApz`fv6F008!r5R-0yTcB1zlqZ!0#k7KfkdSS=y&hcen!76`8u=i8 z2484mW8w=xfFH^@+q=`!9=6HN?9Tr;yF0V{>-UeJ0FZ%A0-r7~^SKXVk(SPwS{9eZ zQbn8-OIociE7X)VHCfZj4Ci&GFlsOiR;iIJRaxoGXw(dGxk43#&53m>S)=uTq|9>^ zv)ObhvxHhb=kS$=qTqy4rO7l7nJURDW4f$LID5`?1J}a&-2B3PE?H*h;zu740{(*5 z&`a#OtS|ymO_x%VPRj~QUFfu4XL{-O9v0OB=uyFEst^tz2VT!z4g<2#lRmMJ`j5ZM7xZ*AM>%2rvSpe(=Ig+{%mm`qu9D$$nuwfAVtg)wU1D1@Oa-0qBDX0)tL}srdd3AKVr| zu!4652w2`d0fsD36d(v8?%fw448z=eKw!vV=GK+cg<@B0$2aAJ0j^IF7?!T;tpbe1 z;%>zpHr&Lcv2JbrpgXly(as#!?0ARvZ(9Tyw9dPLBI6nnUO(iIoc8&R_JI|#ma!w& zAcT?E9qq-QVS__Pcf=Ea+u?_rKX*`?w+8~YR^5P4}7sOkF z9^v<)Wd+*~+BRU@A=_f}TNYc7Hi#bHH2iMhXaTblw9&-j;qmcz7z^KOLL_{r36tEL z;@)&98f?OhrwP%oz<(i#LEKIdh93L_^e1MUFzdwUAZf=#X!!zWeTi=n`C^CXA?1cg z9Q>gxKI!0TcYM;pGp_iegD<(`iw>T3#itznkvl%+;5k=(+QA>Y9v3?#|5p?&G^NcjljeZ~g^f18y^%J9)Cd^>|=NijQzL5oim< zlYvkmuB9`wBAK$LhSPsqg44Xt6)qW^7KbGx93STK5hI&60&Pi2F?cADNrlr=CM*jZ zLoF@q;~O@SuHKr*C$ow|6UMLxJIZx~e9?Ss^Ty`ZaDtBpPPoAs zJW(yH$N4T<;S2#yPeoF?lu&qNOqVhlu1EGea_2aYXH89ap^|@L(Gh7>iYStriu4X0 z;c?T2YBH74HPSR?ZZItAvUReitVH^z=C?2`C}=rO7dV=-77=68sE%uDQcf{6cFi77 zhpm&o07Yne+0~cxtd5_*)sP&)@HC}ize=e%9 z#0xj(imzo}crbrYe63*c7RTYjDhiU1%Z6##t_Qui5BGbp8h+wH(WFEnJTC%R=pic) zGR)Vxl-NNqUE8ZG40R2ST?P81rl{~1FV5^e_8Pg(x$FW_6(mpMLKFJ(*W5>({#DW*Q zoCKbj>CJyx?{us_MShE|Mu(*hn_8mTv>ROv%chy0TJ@sGvER$E`JN~loQ0D;f|Gu7 zWz6bozzKCPos?s8CQ8kPJJs7yy@Vnhlrv7zVopqhG;I`3KjYvJ7U3Q84o~47P9z6E zG=+Dj6AqqAR72W5+#J*NkpVf)wXA6$(M~T?7#4pzGDBrUrkr3p#=R| z)ud>4j>mb%X;#lOggUgWlJKjV=@*U0pX+Y^LM!$sbuI0$Ut`oayK%Cl!#hQF;YI3S zNlkxGOJ@1oTeu+m*V=%8d-n8%+f;C_H)8o;-_FbP`qm5+m$!#sUS3~az?6UCnEncp zrIoW1GYikZ3^9(J+*73a_E2=I+@yTZzO&nHEt<<$te&=8HKwBfgjml-JG}$lI=92@ z4z$bd>F@tEaq6laA2^*uV=f+<_SYxIZ2lu1)15Avq4jrv%t_4M85a1jrdBbg?&OBO z?w|X;yr%s=o>F|n{!ss|&@a-Ga?>Xp`Tt1WnzOgFxn}QvF`pdqH+A0O6M<{R?*8aI zm|Fe9w=3;hq}hV*9V%VFm_Nouyj`+eMRi@5yyP88PxBQT&vbZ!!)Ky@-W>G*(aL2R zRrh*#Vd#O=-{*82{_t)2Q0>X_c9z?Dty^;DE4*(gK1oaCZ038&qGr3{1N+o{&GW)S zR_RrFeoeXT93w9WTJ=k2WmwRsyZJjz~raN31L?*7OZAKosxIC_$obw$Vto-F(G};KG84}n`sf{TwU%2wY3la+hh1Mo zOk8XAThu>BWiTy&7qj>ZQ^xVsJ)L}CZf)Xc&#mN8-WF1DX4>(>Q`45ejQ0=-ZM4zk z5L6XanSS@s%!u+}4U5KdXED2N1@ELz7MFYE%Vl0?GTZp&z)8j5fxVV0(M{Jk-YLI# zD7^e3@2_*4y-s~w)iFmb?A6PWbS|JU~kQ>A{z z<#_KpR{ZVn&J%Zz?8+_T3iQ3CX&uXK`8Ms6*u@`B+O_xJ&pYz;K_cUp%GV7lwA_XQ7h?=EiYO%jA1g4LkyE%H;C7 zPBKh~SnewUyI}=DY{&pStppCf@lAGIC^PvppTgt~O9f-}d3G+pn zHcEm8XU#X20bkb$bjx(06{tEH6~T)57MRE&F1=%5uthQcpfXUA=H!#g@?du$?pR}B zus~7Bs}5H9dx4fr4CvY|pq0)*@1y!kP7|oePX>Iq6EG0Z0Tmgcm@-Wp?51-IwPcVl z;ju?iv_==K$b6Bx4B|cu^pKur092#|ys(EK0ARQEYY^^{l%|QCuAjeEkp14?q>9h4@!6nkbbJ&fg5yu+?X8=+3#!VJj5-STn zB^PM!VxULuP~>AB87AvHdVm8Jad0aGgFcF?DbAA>SBOrobXEl`gda@_j7wDOI$XgD zA?Lm7ffXYk=VyXqs+K2Iu@*=nEBNf4$p*_rnW}xj5^+A_U=u*+w%i1|eiP93x+o@C zhJh7Ihbe;@`y&KjUXYgX_u)8xbzqD+z9U^n!xP?doXqyT+|nlWGZ zf)zbpp(6wDM6oe2=%E;$(+^UFIrO3?4Q`17gDC*02i4ujCr@1I$qFe_?ym&yj++j) RhRK)Bhkwq`;Yh)md4RrtR%sNbw?F7+wVN@9oT5^KvyxHCChVwDz29-_(~6`YI}kOI zb^sOR2x~T#ZdIJ>Rf@`fWMMck8Z~Fk7!ymA-q=^Hp5eZ$X)}%69EWv#a)HMQBo+#f z36F86&q=PH!h1hfL>Ol{cXt`zy7GFq%Eq79O{IA-u!cH*(wj1wN}D2M4WT6o(qxrW zEB}r}@-+r4&wIr;xO0(AI@=cYWb?m21~K;0A^-T{gEQnxfCN&@N(#Zq#RXZY87O0m z;t0Wp7M~;I&<5qU1T+?pjfUye_TixR_f>$?rT1}+*6u;9Gn0cXM{`4grB6(W zyBDpHwv$&%UIzt(jZMh^e3jZ{I@kE301olpI{yj0+;ZWogmFjno1+v zMW;sMFf7sR(_fhVjl~QhEC!kN?S1GnQ8&fuPw9z{5eDbyAAsT&CyjpUf=RK)X*YhW zwf>HLeXJxlm0mFjo>lB@ni;CUkg)*JRligsG*5>@wN*UJvbS&X^}x zn@^UJmJ90QY)d4OLkji-vg;l*>VWz+eRS?0G0Bg!HhZc?2Wz}S3kMg^_@+65nA?uo zkBwh=aDQVGH8XVK>zh0u{gJbev&iTnS1h3p(pF$?`aC^rhJj2lK`5&HHV#_?kJb zGMSi_SJ(*5xg|k>>Dvgt0#5hN#b8)>x5&pj4Wy_c7=p-XQ=>p*vRykohWoq+vj1uk znu?X~2=n2?uaB_*+Lr;+&434q#3lhbD9@_k1Te#nwy}MM^TTHt=B7p23Hvw*C##@< z$6AnfJ+Ri~X^`J(;3$v;d?J5C5U~zQwBA9#k|t1Y#>7ZrY#I@2J`|kfQ=Sxhc*rH| z{varkusu6HJ$Ca6x^v$ZA6sX;#AVi73(ebp61*3)LCF6yToc0LMMm{D%k+S_eJ<3CTZgjVEpgE=i5mX z0o|kFlPT7$0gM?NfN_Wk=T=zCXFhtz_fJrXuKFQ#uaUzUCWj%}$pz$g05t#ar{-1o z#ZYh6o&A&s>>NA5>#m&gf?X>M)bj>Q7YY}AR8nPC<0CJ`QolY!M*@PhNF4%4$5nFf z4{VxA-;8{~$A&>%Yo@~y4|O}IqYemSgP7Sy?d}}+e`ng%{?_hDUhCm`I`hP=rda|n zVWx~(i&}Q|fj^k+l$Y30zv6ME&AX7HTjy~frLaX)QgCMmQq3_qKEcRyY7nk_fa}Z$ ztrwMjNeJ|A@3=y7o^6LMBj@LkTyHm7pK(Vxq%M=uXr;M7{wWsrG~I1ki5OQ6#92Ih%Quj|8Z|qUzyy6 zUf%s*-I*73e%AX}cTI5r+ZsgVR1jr6I*hnu%*rSWqzs(T0KD7A4U}76 z)lH{eBF=pRy0q*o<*iM4@ojv65`y{#TKm=!5+7PwC>z)to^he4BI9`z60IYcFC8XC zZ<65C;OV<=0*{u4*i@nn?J4m6_p_jauY-;RSof^%yxer|uPQvyzOCP1x_-}6H;)~6 zkQH$^6A(lu&B^q)5vwSypjGu5P`Y#UdzM%Uhuh>vlisoS7c?a}|1hah-vo_i`e5;! z93hb``au;ow+t;(wB3-=ww(pgb`ZrEODvFvfEiQvXaSX6+A0ooWdEx3u-oBf9V((3iwRO z7r|AqsNjl$(oTUVvOf^E%G%WX=xJnm>@^c!%RBGy7j<>%w26$G5`?s89=$6leu-z; zm&YocPl2@2EDw6AVuSU&r>cR{&34@7`cLYzqnX)TU_5wibwZ+NC5dMyxz3f!>0(Y zJDdZUg*VS5udu>$bd~P>Zq^r)bO{ndzlaMiO5{7vEWb3Jf#FOpb7ZDmmnP?5x?`TX z@_zlHn)+{T;BtNeJ1Kdp2+u!?dDx4`{9omcB_-%HYs2n5W-t74WV76()dbBN+P)HN zEpCJy82#5rQM+vTjIbX*7<~F)AB_%L*_LL*fW-7b@ATWT1AoUpajnr9aJ19 zmY}jSdf+bZ;V~9%$rJ-wJ3!DTQ3``rU@M~E-kH$kdWfBiS8QL&(56OM&g*O73qNi( zRjq8{%`~n?-iv!fKL>JDO7S4!aujA}t+u6;A0sxCv_hy~Y2Pbe53I*A1qHMYgSCj0z6O zJ!z}o>nI#-@4ZvRP|M!GqkTNYb7Y)$DPWBF3NCjNU-395FoDOuM6T+OSEwNQn3C`D z-I}Tw$^1)2!XX+o@sZp^B4*!UJ=|lZi63u~M4Q%rQE`2}*SW$b)?||O1ay`#&Xjc! z0RB3AaS%X&szV$SLIsGT@24^$5Z8p%ECKsnE92`h{xp^i(i3o%;W{mjAQmWf(6O8A zf7uXY$J^4o{w}0hV)1am8s1awoz0g%hOx4-7 zx8o@8k%dNJ(lA#*fC+}@0ENA#RLfdZB|fY9dXBb;(hk%{m~8J)QQ7CO5zQ4|)Jo4g z67cMld~VvYe6F!2OjfYz?+gy}S~<7gU@;?FfiET@6~z&q*ec+5vd;KI!tU4``&reW zL3}KkDT;2%n{ph5*uxMj0bNmy2YRohzP+3!P=Z6JA*Crjvb+#p4RTQ=sJAbk@>dP^ zV+h!#Ct4IB`es)P;U!P5lzZCHBH#Q(kD*pgWrlx&qj1p`4KY(+c*Kf7$j5nW^lOB#@PafVap`&1;j9^+4;EDO%G9G4gK zBzrL7D#M1;*$YefD2I-+LH{qgzvY8#|K=-X`LN578mTYqDhU}$>9W&VOs z*wW$@o?Vfqr4R0v4Yo_zlb?HKOFS zU@WY7^A8Y{P)qU9gAz52zB8JHL`Ef!)aK7P)8dct2GxC*y2eQV4gSRoLzW*ovb>hR zb0w+7w?v6Q5x1@S@t%$TP0Wiu2czDS*s8^HFl3HOkm{zwCL7#4wWP6AyUGp_WB8t8 zon>`pPm(j}2I7<SUzI=fltEbSR`iSoE1*F3pH4`ax^yEo<-pi;Os;iXcNrWfCGP^Jmp935cN;!T8bve@Qljm z>3ySDAULgN1!F~X7`sAjokd_;kBL99gBC2yjO+ zEqO##8mjsq`|9xpkae&q&F=J#A}#1%b%i3jK-lptc_O$uVki1KJ?Y=ulf*D$sa)HC z=vNki?1aP~%#31<#s+6US0>wX5}nI zhec(KhqxFhhq%8hS?5p|OZ02EJsNPTf!r5KKQB>C#3||j4cr3JZ%iiKUXDCHr!!{g z=xPxc@U28V8&DpX-UCYz*k~2e)q?lRg<{o%1r;+U)q^{v&abJ9&nc6a32ft(Yk}`j ztiQP@yEKf@Nu3F;yo9O})Roh9P08j7@%ftn7U1y;`mard4+5 zB62wpg$Py_YvQ!PE2HpuC}3el-F3g{*&a z3q{eLy6Xz|F+aMrn8R8IW2NZu{tgsyc(>*TdV79@?V$jG(O+Iz2rnDBc|1cK8gR$Y zthvVTI;(eYhOdjapHe=9KI`|2i;{VIfvnR6`qof=4a=(BTZkev78+6GJW**Z!|yvS zes)T%U573C~Hm`&XJzE=2t7tFIZM`!^r^&z;W?dOj-N+a10^>wV(l~2naa?s; zTxU{z;Go|Ve!vUjUrZ$B#mWH)NSdxi;dWa-@w)-$wBOpo`DEG<;C#W||W}&@z>C`*j9V|`ai)z*2PG`TZt6T{a zj!#m3`Vz5R9wJkNMsJ1`fSCS2mHnizWDT!G0Ukp$%*_^X1=k=%mmO$^_0_d|kc8ek4_DZwomL(>GGtfEB)Wy&cfZ@9-T|hAq&fx;XR$$_yl6iogcR{u zm9g)axS6=_IL4=wQXf|EkzO68$Ms4*JXAt8gFxLCibt^C#C|I|v|U{%A;+NaBX-Yn z`HAmP*x5Ux@@Wkpxest$F~K8v0wlb9$3gHoPU(RMt+!BfjH?`8>KMK|!{28+fAk%6 zWdfyaD;Dr~`aJHn0}HIf^Y9*keGvm6!t?o%;je)wm`Dm$fN?YtdPI7S=Y23+15L{J zr;n3MYg`<50nW^`BM$&M(+PQ7@p7Lvn(kE`cmoNS7UkQmfvXQBs_unhdfM){k`Ho! zHL0#a6}Uzs=(bu;jnBAu>}%LzU3+{sDa6~)q_|pW1~*Is5J(~!lWvX(NpK_$=3Rbn zej|)%uR0imC;D5qF7p}kdg(-e{8#o!D_}?Fa<&{!5#8^b(dQl40ES%O_S(k8Z$?Hs z;~ee=^2*5S#A*gzEJgBkXyn*|;BBH97OOmvaZ>&U&RfU0P(?jgLPyFzybR2)7wG`d zkkwi) zJ^sn7D-;I;%VS+>JLjS6a2bmmL^z^IZTokqBEWpG=9{ zZ@<^lIYqt3hPZgAFLVv6uGt}XhW&^JN!ZUQ|IO5fq;G|b|H@nr{(q!`hDI8ss7%C$ zL2}q02v(8fb2+LAD>BvnEL8L(UXN0um^QCuG@s}4!hCn@Pqn>MNXS;$oza~}dDz>J zx3WkVLJ22a;m4TGOz)iZO;Era%n#Tl)2s7~3%B<{6mR!X`g^oa>z#8i)szD%MBe?uxDud2It3SKV>?7XSimsnk#5p|TaeZ7of*wH>E{djABdP7#qXq- z7iLK+F>>2{EYrg>)K^JAP;>L@gIShuGpaElqp)%cGY2UGfX1E;7jaP6|2dI@cYG%4 zr`K1dRDGg3CuY~h+s&b2*C>xNR_n>ftWSwQDO(V&fXn=Iz`58^tosmz)h73w%~rVOFitWa9sSsrnbp|iY8z20EdnnHIxEX6||k-KWaxqmyo?2Yd?Cu$q4)Qn8~hf0=Lw#TAuOs(*CwL085Qn9qZxg=)ntN*hVHrYCF3cuI2CJk7zS2a%yTNifAL{2M>vhQxo?2 zfu8%hd1$q{Sf0+SPq8pOTIzC&9%Ju9Rc1U9&yjGazlHEDaxY|nnS7rATYCW_NA&U? zN!7-zF#DXu0}k4pjN05yu#>x8o#Jx7|Fk=%OR((ti%UVKWQNH>+JhH#ziW1hD=rk* zD#1j?WuGxd-8VqG@n_Lqj^i=VBOg@GLePo0oHX9P*e7qBzIs1lzyp;}L3tP1 zl5;OiHG&-flQ;rYznH%~hz>fuJ!n*H#O)3NM3`3Z9H|VFfS-_xHRCuLjoIS9wT!F0 zJ-kV3w>7EguDzoBPxW>Rra0#+Y?;Woi7qJ1kpxTad?O?^=1cG@GeNtRZRi8_l-1CS z`(#oF<;VYR(l(gHIYH$y2=rj5m3QL{HQgbW9O!TU*jGj!bFazIL?MYnJEvELf}=I5 zTA6EhkHVTa0U#laMQ6!wT;4Tm4_gN$lp?l~w37UJeMInp}P>2%3b^Pv_E1wcwh zI$`G-I~h!*k^k!)POFjjRQMq+MiE@Woq$h3Dt8A%*8xj1q#x?x%D+o3`s*)JOj2oD7-R4Z*QKknE3S9x z8yA8NsVl&>T`a;qPP9b7l{gF&2x9t5iVUdV-yOC12zJnqe5#5wx0so2I)@8xb$uPG zNmv=X)TjpHG(H!$6Xp>)*S}r538R99Y{Pofv}pAFlUK;xi{E43^->z1srWR=J$8N! z4jRu;EAiLG9R$5#{gR){5?o^W^!t140^f=vCVSs@vK7#`-fv`P*WV|>nX610pK08< z>r#{r)fR?2pNG}8o)?uvX#UJI)YM5CG@0E8s1lEV`rom|kBmf={%h!o|26a=lNJbX z6gkBS7e{-p$-Vubn$(l_IbwS02j;+6h2Q5F7P?Du2N!r;Ql$M>S7Frf*r3M`!bvWU zbTgl2p}E<*fv?`N8=B71Dk03J=K@EEQ^|GY*NoHaB~(}_ zx`Su{onY@5(Owc#f`!=H`+_#I<0#PTT9kxp4Ig;Y4*Zi>!ehJ3AiGpwSGd<{Q7Ddh z8jZ(NQ*Nsz5Mu_F_~rtIK$YnxRsOcP-XzNZ)r|)zZYfkLFE8jK)LV-oH{?#)EM%gW zV^O7T z0Kmc1`!7m_~ zJl!{Cb80G#fuJa1K3>!bT@5&ww_VSVYIh_R#~;If$43z`T4-@R=a1Px7r@*tdBOTw zj-VzI{klG5NP!tNEo#~KLk(n`6CMgiinc1-i79z$SlM+eaorY!WDll+m6%i+5_6Mc zf#5j#MYBbY)Z#rd21gtgo3y@c(zQVYaIYKI%y2oVzbPWm;IE#Cw$8O$fV}v}S%QDA zkwxW{fa#Goh1O|+=CF3h3DWNw+L^ly?BNQ7DY~Eca}5nt^>p#3cc9s3iDub0nh`Wy z?oH|dW8-HG@d5E@U>NWPjnhTjr7C${Iwj#;F2G@++N=Y2tjV;z57RNgE|kXQC)1h- zx8ODU>kk};J8KiSUx5jSsA_XPou1OH8=R~q9{`r>VnHkU6A=!zNOH8IGJoO!+bQys zDS2-H(7+Jfe+&zf#;OSV=83I|^M;0`Kv*#4%%O7x>@BgGMU*@ajUvY>cYw^`*jm@+ z{LZ2lr{OTMoQXn2XUsK-l72oysi9vgV4Sux^1GsW6zTV;?p#J06EvSVyUq5$f4kq< z{Chq5Z?I%ZW}6&uL+f&0uCW#^LyL!Ac2*QRII5TDGfZ43YpXyS^9%6HBqqog$Sal3 zJjI$J+@}ja9Xp)Bnbk+pi=*ZAHN}8q@g$$g<6_4?ej&Rw)I%w(%jgGlS5dTHN`9(^<}Hg zD$PbZX+X>;$v4NjGJxMDvVBiIam$cP-;h0YqQ{YgxYn-g&!}lHgaG3^B=>Z!D*7tp zu19e;r`u*+@4h41Da&NZv$qy-i6#DdI)EVvmKO*PvIKz-9E5R*k#|`$zJza8QJ)Q{ zf~Vl+I=8oaq)K!lL7Et5ycH;m&LKIvC|z4FH5bo|>#Kg5z+Jy*8Ifai}5A#%@)TgPRaC4f>Qk&} z4WciN&V(T~u^xBgH=iP(#nd;_@L&`7FUF>Qm-;hOljv(!74f&if;fz2Mg=b%^8$^C zna!2I&iCz&9I5ckX-5mVoAwz~)_&b#&k$e+pp=U2q-OjkS@yZ8ly1$2Vh?}yF0={P zPd3O@g{0L=eT-Dm9?imeUP(!As&DJ_D=5lwQ=3)XWXg)12CoB=-g-HX9RSXgL;yo0 z?$7z8Sy9w?DvA^u`Fnl7r_J&_jJ7claq*2l9E~#iJIWAPXuAHfmF3-4YjFYhOXkNJ zVz8BS_4KCUe68n{cPOTTuD<#H&?*|ayPR2-eJ2U0j$#P!>fhd(LXM>b_0^Gm27$;s ze#JTrkdpb*ws{iJ1jprw#ta&Lz6OjSJhJgmwIaVo!K}znCdX>y!=@@V_=VLZlF&@t z!{_emFt$Xar#gSZi_S5Sn#7tBp`eSwPf73&Dsh52J3bXLqWA`QLoVjU35Q3S4%|Zl zR2x4wGu^K--%q2y=+yDfT*Ktnh#24Sm86n`1p@vJRT|!$B3zs6OWxGN9<}T-XX>1; zxAt4#T(-D3XwskNhJZ6Gvd?3raBu$`W+c(+$2E{_E_;yghgs~U1&XO6$%47BLJF4O zXKZLVTr6kc$Ee0WUBU0cw+uAe!djN=dvD*scic%t)0Jp*1& zhjKqEK+U~w93c<~m_Oh;HX{|zgz=>@(45=Ynh{k#3xlfg!k z>hsq90wPe(!NljYbnuL6s`Z!wQSL8|(A*@M8K>`nPJ<9Hb^ zB6o?#^9zP>3hp0>JAite*3N?Rm>nJ1Lpq4)eqSe8KM_f(0DB?k8DNN6(3 zU#>-{0}3~vYJ7iIwC?Zbh@aJ8kfIvY%RveZltThMN73#Ew}jOwVw+|vU5u-wMoo9C zO(tv#&5`DOhlzunPV?M~qlM|K74x4cBC_AC?2GNw_-Uv&QtPOj(7L4NtVh$`J%xci zioGVvj5s|GY886)(}g`4WS3_%%PrF(O|s-n&-SdfbssL`!Gi7Hrz_r$IO@*$1fYbQ zgdp6?(IUaNPaH7}0%U|9X8HFonsJRrVwfmf*o1;k0+PwV^i%f7U{LAayu`!x*FmhN za(#a^@Idw9)jN)K!=sFC(G)ZNaYY169*IJ_ouY9>W8tC>S&MEp$+7 zy)NFumpuE>=7T@`j}8pa)MGpJaZoG(Ex3AzzH>gUU^eyWp*N2Fx+9*4k~BU;lQ1PG zj4)_JlelzJ==t*7=n2(}B4^^bqqcKFcJ7yVzbH_CWK?{eXdpKm);4|o{aM=M&`E$=_~PVi2>>L zKTN_x&qA)@ak=v=0Hl5H6~?LOfO@1+fu5(sB|VWID)w?%{m+n#7bLaszEJ#;$HMdt z9qP0gk)hIYvE1!jseA^FGTyK=i4eTPjTL$R;6FywMBZBPlh2ar9!8wlj1sinLF-1g zR5}hLq>pb1|AC-WcF!38e*kFv|9n<$etuB=xE%B=PUs}iVFl>m;BiWUqRIxYh7}L&2w@{SS-t(zUp`wLWAyO=PEE=Ekvn@YS*K@($=i zBkTMaH<&cAk${idNy0KZ8xh}u;eAl*tstdM8DYnM5N;bDa`AB+(8>DqX+mj17R2xBp45UES|H*#GHb_%Nc{xWs7l{0pqmiBIPe@r=X%Y-h<-Ceo;4I>isrw1Hd zZd*VjT`H9gxbf{b3krEKNAaV$k>SzK(gzv}>;byq##WEhzTN^@B4+VJvW>y|U}}AQ z4^Bdz9%QKBWCy+h$I?L@ffl{fLLL41Tx|M+NjjRf(`KjHG4^y=x3l z!!-{*v7_^6MiJOC@C$WV=hz9J^Y^lK9#tzs6}-

Gn4F+B~IivciU9^t0j-Mgao3 zSDF_?f~c=V=QJRSDTG0SibzjML$_?2eqZ;J*7Sv$*0SQ|ck$fX&LMyXFj}UH(!X;; zB_rKmM-taavzEk&gLSiCiBQajx$z%gBZY2MWvC{Hu6xguR`}SPCYt=dRq%rvBj{Fm zC((mn$ribN^qcyB1%X3(k|%E_DUER~AaFfd`ka)HnDr+6$D@YQOxx6KM*(1%3K(cN)g#u>Nj zSe+9sTUSkMGjfMgDtJR@vD1d)`pbSW-0<1e-=u}RsMD+k{l0hwcY_*KZ6iTiEY zvhB)Rb+_>O`_G{!9hoB`cHmH^`y16;w=svR7eT_-3lxcF;^GA1TX?&*pZ^>PO=rAR zf>Bg{MSwttyH_=OVpF`QmjK>AoqcfNU(>W7vLGI)=JN~Wip|HV<;xk6!nw-e%NfZ| zzTG*4uw&~&^A}>E>0cIw_Jv-|Eb%GzDo(dt3%-#DqGwPwTVxB|6EnQ;jGl@ua``AFlDZP;dPLtPI}=%iz-tv8 z0Wsw+|0e=GQ7YrS|6^cT|7SaRiKzV3V^_ao_ zLY3Jnp<0O6yE&KIx6-5V@Xf^n02@G2n5}2Z;SiD4L{RAFnq$Q#yt1)MDoHmEC6mX1 zS^rhw8mZJk9tiETa5*ryrCn&Ev?`7mQWz*vQE!SAF{D@b7IGpKrj^_PC2Cpj!8E{W zvFzy&O4Z-Exr$Z*YH4e|imE`&n<$L-_Bju=Axiik+hBtA4XNDik(G_;6^mQ3bT)Y% z6x=a+LKFZbjyb;`MRk~Dbxyc&L; z8*}!9&j0wewMM#O`c#7HJ|+Gh5%3~W10b6sdmCg3G_v+@H>n*c5H`f+7%{TeSrzt89GYJqm>j-!*dReeu&KHubhzjSy_c~BJcbaFtZWAB}~KP3%*u{zHi zVSUi2H8EsuSb3l7_T1hP!$xTtb{3|ZZNAJ{&Ko;#>^^43b7`eE;`87q81Jp;dZfC< z$BD`h-*j=%uTpG8Me6dF zrH%)Bw-a0}S41ILo*k2zn6P@?USXtC>pX*tzce7A^JD7^^p7K5kh-HO&2haDTL%2^ zSWQb2B6}e*;x?eKq?CdG7F=wHVY)Lb(kQu1R#1Fx|3?>_%cjNM-xJlAg9kr`!>&;E zTYmHhqHh&qbfO`~w3V;BM(q(_Q-5^!esaBI&QbZ^%N-ZDYft#FTS;%{ zKzlSwZIS%zDi#%DMK>`_vmE^krJL5@PmpT2m26Q`O)VRAL>){MN45|7GTk=q^zLpF zjS(Os=`#On$XI#$A5ewac9Ma}mDxSu^5{#jHC+24a2GbfBJ&Zn8W= zm=l7VE0g^z$3ikyU#ysh8b-PH(&-yZL$JV-of-ZM@~N^#DbQ3Ltlq*5@>WzSNxrRK zYl2VS8r;TT`wLfD_O0dhX9vR#S8rMOuUCRkWZE#OjRi$l*#C7}mgGzZBD%Z=p3z|CaVM$$pyW5-pJJDCToY zO3R5)P(Gnd>6wh9Z$Sr@cMXmClU(h-@5kmiBTNTU-|5vq&Fs!ah|o47kW?SO8uWv> zW$=Ud@@|*9p@Rb=!wl;%>k)kH7fPtcD=gd}^IxN^=Cg>zq^jij!f=1PlT|9jh3K9g zF~Z)B;kb^a0hLmJvON8Ho)foq-oC)&E)b|a^|b}6n!8&AIaousO^VnYzYfuijuEo5 z7IcUMbYD=vec4eZX7;p31NB+T9BOMJp9ZI9$dH1kJsJpEtf@}tL4)_*PxgdOge9_EaR!?wWtBx%*f$IGoR>f3Qf2aT0%+fq=1xVEqRl;UaA2Ncs4B1M1#foI2bj4 znX}t7;-FCLK&;>ZGP}{GxK67$Kz&pO%%J>DBMP_zZsLOmdpDUDp&f8=L>(Kcj+S^jA5dco4-7XN z)h;m#54CEy9)Ch-E7gHP@a@TXl=_%&|iUlIrQzn=LqONBu9FCn`3f8aqvRu=RrJ_RH1^Uf=t z%Ir*({+wEeC??C+u!hCi<5m`RsRO6ti7YaEtY0|U)-QfNsdN{=83K_}m$0Z=ElWyt znvo5=%f<;|hNnL-r#v5ab&S2*yK>~a7m(My$cfd*tff?=?7-j3^|&9H7G*W`)m8M7 zzd0+b)c@`bQN1-^dC$_04tK0{mU5tx_zo;&TWou8F(H_J?O+Y)VLXzmU^> zvL!5+1H?opj`?lAktaOu%N#k4;X;UX5LuO`4UCVO$t+kZBYu`1&6IV@J>0}x1ecuH zlD9U=_lk1TIRMm6DeY2;BJJEE%b0z;UdvH_a3%o)Z^wM&<$zhQpv90@0c+t?W`9kolKUklpX5M&Qw06u=>GPCr5Imvh*% zfI`tI-eneDRQo?m*zD1i;!B>*z4Xioa_-S=cbv-k_#Wg=)b$0@{SK>Mr!_T?H`S-?j;3$4)ITn$`g;J$^TppD)^pRz#^l?XgZ2CW z3g5G^iF*GZYQ}{B|H-fqh=_>)E~=3y3Zg=i75G5E)*a>R9bn~cNW{h5&P(vQ6!WHv zw1-89smtY~JnCQS(=9zM)6>UAi%G-r^LA9_HF0Vp3%JF2P%+E&^afy61yxnAyU;Z{ z$~H5X6?sMoUuOT_tU7i5i%5HI{^@#Hx@zhtP55>r_<3LwusK*SC#%i+gn&iRg z_8UN=rLVp*gT(K~{0X0f_=?~bBbfB`=XrTFn3U!)9n*@Uj$-mr^9PNi<22UJKAK&D z|1@Ck3(Ub;>68;)gIn_Zu{uoVRMhAkIqgBS(v2b2{gf?0xd(1sJfY`56mVy>~^w!wmX_kjW8#?_Nk{}zB9ULo>4fO(vnWfC+pG4>%*KZ?JuCdXu%aZ}q7pC%E50@U9+KQZL5 z!*I`SOtNf$Y$CsRsNaf~yyw^>#X_mCiF&*gr=cBb zoPu7PwX(+Wvl~i(XH|)jj@Cu+rzpJMn4kVvCJ~ReCf08viF$q9;CYnv-96k{G?pf_ zQglN`JiS#vok)~^Z2>41#7LPFgd_xrqNO%DQI|!Qs|nWt`co#BwY$&Wm^6#~)`_1k zpwiR~&z#mtSDuYm(=NoLv$%Y}bTjog$RJ8$j1(s})=}su0b?o8i28-|xu58ipFBml z2`4qZ$BbY5>(i2%wmh!+C}$97?X3LgTQ_{(SaFZvq9YCn@BNz z&h#;4h?5#`&_0()uJ;_rR(Q^eY*=&vu)#EeMeaN1puPv5+iQFg1EC(`_99_5v<1r4D ztc(+-eVWf_np;q$M*H49#{R)eIWCI%R&6F34;h9eNG(XNO5ao2MI8;j}y% zZeA>zX{#$;muhtY{_|;bkk~!U~Ih z2QUO}hk~o?sn;#|Mt$0}4=+BRa703n6>fBm(cesk8Cmugg_wi|BWj}V-VuU9jNH+o zgNYGSKPm>qR&nI(2Gu*})AOBfXf0J~CC50C!3KXu6-qZAG!VMZbmnqL6HWG>o$^sjoSLbQxra@WyKV$+_Qe}t7d)c`bpJG++ zw|9D3>XUH^Wplo~MN%WK18n3HeXoe*jKwVRK!=RMtIr1v z;Py~7;eZl&=^UyumN&CecrGBEat}4?mtZ>@`wPjVK@Z)FZ;05^9kztq;qmbxQIJ4kXTk)) zaVfD^K2x7SB6E!Zz@0p|Fkge*0(0?ogmTX8d=?n{2x)}K2$`bjDmcLg3#wU)i)by? zW^G8rRQKBwjke5zHScinRlE|wo0XyhBc9R52IsKWf4-@=l!yO&+l=K`-7Ib9U~hPy z!cH>H)e6$;m&w^0d`axGqDwBgu`B+L4a`xr#5g%b=0?c41`|lx0O9fiIVaFAsO$Ol zayhm4C9X%hzUf&ctylV$%ntuA$(yo*X`gaVX0$|x{#!YK^cvLmNWPZaTd3&xP7ny% zkn}2AdJkpAgmsh}Q$tY3(2RtO;%R*~8r#ZbSbMR4LaL9Sb6O&Ce(GlO${jtl&`n|D z9;zUQPXCHqTm&t^lk9RlZiiquSY_og^?kgVruz%myd95Fr!V z-$OIXSt?(pxN-M{NjA)j1KKIp(&c2RVjd_}7+CbQfw zTRjg}A0~}Ht_?-@wD0bI-;LQwT?mKywmDZ7*j4>4pR6@UVU3mb?-cbQt~aIG&RBjl zs-4UNtOH3+dAF%U=={qB@qijh4J6K?Et zPLlfPlv<+i>ty5rh;Q>iGFoaq4LyBIZl3L{KGUmqPL~ZCosOl;7w2SxcE}pvK;5|6 zly3JjUsvk|d7L3bFs&;q@_|p?vdU_UzhrS$Fw-_NoEdoIT#-0hKC37!>-i6FaO(es zY97)m4YO<|eqGMrYejC&-IFmc{=P7>qFWX;)}q!&e9-F59o>V+`X>J}%Te0$|A>0W z;7*>m4>udzwr$(C?TzhZqi<~6wv&x*+qP}v?C<}aI_Jeq*K|$4>AGurZe5=U>-0IX z>&2?v81(_Tn1tITYDSF@^Enhl9>e1$iAnX!+&YJVi>1uYEWsZ?o*Vyg+K~%XCxQP(WrdtEpc3sgbpTM_ zI7i6|pDr z{=xGh4O=PrB}pkX@o@A(%GfdU!c<$p#T*mLo^*7@bd4rIJ5eS&&A9VB$EhabJ1^TG z+dke8lOG5I(xMYZ`Xw8+olY0y6M)M0rcr%9tZHa=G0zICN@DQ>0rVASCK4=3OeMSv zD!v+POT0`UZEnP~1ro1?HPLqJ)xx0#Pg^yBJz@S6gmFN~cGvl(#fz4oTs7_Pi^+i_ zZP7<#ukx>i%V;uJJ~WwUW7pgq=>yuT+A5w(J5$1no67e(;mIO5>@`(U0{}+kg)B_8 zs=bfBbmZ{U`xjMpkAcEcEeF7^#ka}2zDU-sBt6yQqw&2p<+6Hb(Hi56S!+bU9AJJv*{ep2vD zG;PVwX@NC)+=6@I6J=nW6_99&4R00FKpUPepXoBVN*|V*C{e7X+Q({6O_^@SlI(9Y z8kRO3WDG5u=vmTjZ4DW89H&vNa;i%H@`{%(|J%tVs;1gDadzF0Jy%}C68|k?Zr!B9 z*lBN4{#6p#SQS-q#Ck&x#xhAOu4mK=Jxf+5E$h8l3-F4mQY^qaS5;Z* z-ddglOueLtXJhJ!%yJGk^-iZ_+qLJ zpTZn+6kq81D@^m(v$VFFI1Q!dtczYBt1xSn9~Q=@h%tsf*hCm%fwfx2u(u=-4|qf=I8WR*%`lsQ ziP!-b?(d_`TdA=^<$@(2c77&FowB0vhswM)fS>lYvjK7B_$<0SiQNzL6T?D721Y*( z9nG=@aWvmJMd%j$Jxp3-L4x99-X-9aGkW}yiPAo*9{^6b1>tDg4zIPFiTqVK$xq1rv1*kaE|~T5-jH#8{g31#^7M_uSsmQvNjyk; zbo|yP0w|uD1)wGrSavi=<;=H>IejRQlac$HMkU2rbq1{8UntI;oJ}*o(bXy{JC*l&^W{Y^}<%Nj1Tk z$(9f2a`BoyZZqxWF=hhmc3ldg+8&Ep%fVCSjopduonggw7@?XulP^JPo+_le`o@z)ofi9U%I z=~YZ3?Jok#3NeQ)U&qUqvoyuEMA?b&Ki=s%;_MTDX+8^>z@TOxb3qw~biG4!)XuQp z=>cVLGcp<{Piu-TqWLFz^P0>R1go1M41xFSn~y%8LZ{~t{iz!z$|ne5qkw!VwuI<6 z*6Bsnap!L>JA;B$u$J09!L&_iGdX<&v1jeDcEWM4&2q97^g9gK1%+zl7nY)PUU9<~ z!B??-0oFH5TEpfNW#V1m;(6-=mlUxm699O$g=ZrFZpn(6h%3n#!U7eFnC1BJzLFB) z-)SER^cpQ~AF(`0^?pNYWsz6(suJg4)Ke+|iTo4!8P8ND$ML1a%4|QMYe@SDDH#d& z)P6SOk~%xdQ?i^t{N0)(baSgQ(Fp*daGXR>=Vt-*#@)>A1Sfz0!iqKtjlY4}1i0v0 zyz)Z|vB+_QIX99Q+NFppI1+3`=qUen8NVELr!SOS8Vq1;{<}WKOhe7HMurM4mg~j5 z%|wM0)r4^=uC{9_OTf*An{G}>6hw}C=H|&8MY~l@u zmW-R8h;dJxjKNqEdGf85(5BrR>lY2A= z-_%9;IglQfHBuO%U)bt|g%1h-OMbL9H{TdFgM^rdBTt~gJ%{*c<;b$D13(ac>}*nJ zo@&y3%13-hUh^Oa$9U1ImdNfGO4bPX$I!c!6e;sRC>z{knTf~G5{#4J7y(vbrq-qWk%J5#0Iv((P!QKa6f#3?;#q$+(teR!nw%kOp&_W`3L^Xw}Dw&e2#l zc{fk56;UyHDpT@XdB?u!*)EdIMT8X1&e>VO;M_QH&MXI5|3xTbET#NTfyi14#+0+t zDS(NC?jbc{yIDjm-=9g^4*f1c;0!ytb~iQ;DSTKoa4ow@d-x3HI`EYcAe(li zjajb0cM*@u*kiU{)jd9yTNeRZLL+Y1&q`L>gx^Jj_B%sh2+%Z1d6xNVmTw5Fw!kd@ z+uT`4r(0=PXUZCNn9$VPo=aj+p${a|eqjB{Mf+k&$GEGV(lWHl#1xy1%5E)1KD$bK z0Z1Tsk4LpTn+b-iy}25uN>wvTfN+B~4r!aC19d7}&hDFchbqZ0;e7I0BK}RNujj9n zY8As>D%ez?Fkng~c1L3e^}<%h%!NhB5ZFmv4qmi`am*+A28lE6Pu4ekBJ8DW?YR4c zPeG`sZYLihHq~K3`oYvnQL$26Ojwnj1AOypgX_ca^06&6f`T8bedVhWj1y>F>d-sg zr9@SeL^T`CHIwyKW*F#~AZd==$aA_zOLRP>>S_&HK0s{HcEDpNQm9u|IZ{W%#*w4} zmN;)dX5OA?I{M$KLje0TCiQd&|g9E!YKD5 z)_8>@<$&L)EoO;WhhvUYgEDDJ8PPVpR_u`RN${}`PnjHc-4^~CwIh;mLF+#KK>Wc> zE|Wkj(OZ@zIa8-8rUq=a=x-F%J+$ozWaVUV@yS!{UWJ)}=^jM1_f&XffEjCb6H?Es zrqQ!sdrLtEHq=DIu@B|%&N$@{wC|>I`>>2EXn@+22x7PaM4p3V5XhXp8gSH8{)yq+VsXB@4DmPLA`4Qc`r2Z>3E&lVsUbpRejKO8Xc|ayAI6YT)d!q zrfQj!sa@T&5KPMxDUd4bZwub#5<;yenI>0~Zx=@R*M{S6d|Z3TAEsEW-w#undSQP7 z0ryg{By3CNOC^`$t=P&xCf<~vRz1}|>Oh+v>rBMi?&+;xKSGs;7Ie~^T>J4C9Ke&G zL&{aTYZk-|Pa*unK});DaF?Y=y73~NA0(lMPUz1G>G;8n^cmm2S>twrpU6ynN~J1! zHD!AXWk^D?nq)%#A^&d%DwIkh3Ku$<4{$Bnqe{R^e!E zD6qaK4g^V5kCJH~Ot$Im{2T}8sS28Gk(>QFg9I7A-=nDns|{X8NjAD%l(zhXxPR+i zsaKZiVQjKRN#@N{`Cm?#slb!NghtaUv~`T@mvslIbq5TcS-15muB2Hb$Zs``b(Pmm z>-keg*068f|SD zm-1~aS@!4?{PuWQ(%MlB?$oG~Y0UBQX_Nz{MC3%JvnoK+x5+GR`cIfTOE7r3_Xi|f z(1x{Bqg$A^m57WLbkEAc&hWkBABmV|cqNS(`o`}NaSI8Lm6{l$b%3paaK-^r1yrc* zQM|lY+je@P=AS7fX6VXPV>UYV77X|5G z5Zow(9=j+q0*H%#H}fpu-HF%`(GEbvHmWK({pqfv^b!p^KiWxjYXL)gZO^yLvY!1#{eH$?|l`7XcETF-V>)m#$Y-KUauf z^b+<*r?&Mks6o?n2JrEvgk?j+9|~S~2U~dq^}6M%or)_T?%jaFi!#+q3>YaIG?m3X z;{>&cQSHf29MCWgsDR$xyTZCe^~uYQ{iM+(@1tKCpyDxFoeVGQeW)9uT349)IDK!3 zsmbQfykCr7P5@r7$@N8b6KjN-vAfM%rz7|bveQ2v`Y|)B{2rfRwNw!r&1%%b*lWIy z+l$A~f%;yYgfY6h_(-1nXB!C4(VAsEqS^YKh9a{{_uW8t$M^?gPsm-J}^#E z_uO7hC+?sb1Iw^TeS$QC`8qwrX85eSYLIFX93I>dS^)6QIMdwX$;6F>2_T&M6o;jL zp&W3|Bd8rLlV}iSVY9G7Lo?V2_E`JVM(`rw^}DX9)wk0Q5GJ%esB@}u@C>dZ-byh| zBFz*MoXGGiF}DG?h!UZ#FN`;~1bd*pAWflMa5AtD-+Ut8Ymf#=b`potx5YLf&A%ZwGv$|Si7 z(0)Re$(F;{=Dhtq1%wCl0ijfk+T4jd3}^2Z$Q?L=1_lkM&nIax-Yo%VqZk6#Et%n& z0S9_V?yja0r@wi$m!-JJM2G=aQ@nYectR_Ln*dN6gmAR8L^dIf-bxR>0A)c$?#Ug@ zVlrY8#6Wp4wiP3OZ1@T=EBaaz(jrxuLG%?*J+=c#K7CorpL5*eKWVYiw<>#a7zv(N zO^RpkPM=xn!2?&s^7NCTu~a+aiGwc^_4Rnyqj!-l3-f+;6mkOx5@ynO(YF&u{yH5a z0{{W^{1E}V-LFeZcLzkH=SpZ_y1l&>1S=X`+@!Ai#KmNT?5ox%_;tp9`=F^;&%fxn zpX4I|M!d6`y%-8hequbo4%INVKruc+o|NwhsZB0<&TBCe}v2@CyI^$jlCsTrwmBFnzIMofx8PeKa1Av-Nj zlLtw2SI?rq_1(xc%<3sF%)ZrYIf>Xe7@jPt9BWoU%bg~g+6=1f;eW00nOrbo#*(mjYHCr_?8!#my~|i(0+2j{Uo+J%%rvg+%X5* z4!HCVyg~`t!LBG+X&89L&@QkGXe};GQ^moDsqI%U>#?IVQc53nUukdN%ij?m+%#Fv z*$`n_GFdWHC(!1z-ZhRjEV&n1wt#7VUXkgkW9Q5V;)k`XOO{*>9)xi@4}6zxlm4Ck zPC4Eq^0qB+yLg@{^VCgieuns3B!x#NzSr6q_VlhP>I4gzH4BI}DTx^r5(>Dyhc;-w znWU^i-9$N49%O1eIWyBV{K>wROpYjgCc5b?os*f=l~V;o)CB3G-E7LA7Rg3;!)~m@8(whM7Es zwF%4mEd^gMI<<|N60&DB)!+6-+8@EFbvGs4UP0$q5NEO<7?$NeaVcvz#eXkrXV;$H zPjNrI8gWTpphtwY&md>1N7T|$T^i@CM$EWZ;`6{q__Yr(^B!<>OPXT5%ICC%;4jl=T77^3T z0A$3`@j>`8*wH>vT`en;tj&YA60zbZw2F#^jE;rfTJ}-rcajHddN|Q>g}o$TX~osy`RPP=q0j_f1g@QgXPlY@q1Jh?-r4bB@~25Cj@AmJph{QR^Ya<4r(z*{F~ z=-nsVQY2K`sKEl*CR=AMEDIZD88T(wtjZ_((xf$>SIA*D#|jjfGw84wta;Nk03w~g zI(#i!OQDMse#AO065D@_gm?pQx@{rBjMat|bA$6MfVPq;S5zT5IKK&|LFZXuA zqj(kJK8jP}^ZYm?74hlPtf)m?w!rUP42d;f3Xx1K3raV-*P;*>hmzjAkyfcbEfZVM zJuLMoUQ0*&6p_BS@>f9!k`6HtNO_~}(0Jkg|_f8#- z!m%Jn^dX^G#qp$LnY0H)6WbFMeDL2eCjALoKs@6Ai81!~l3d5bNgZQ?f zTgufN#)|A&im|)K13cIGc?~(RCQ+E^pAR%xa6I`LxD$=mcOf z@v4=zb!i^TVJ(CsX?zlhk2fs((qe>+8Y#o60peO430M?7HT|g( zcVfD7@Ob>SyV%mu6}7g*=p&J}hJTo9hFn2o9Jy}QCXfAbC}WgpkeMXs7QNle)Z`PI zaU4~Uz`idIpQPmpq$?{N(5Wj_y%UX!5{=9|{BFV$P&Z}ciIVj<`zLyWb*T2wf|8o* zOk|-Qs_aJayia$?0k_jr6b#)1ONJ!Z;{~4NDyZJ6id*&SjT|kFCPH^!Q8MlaAE-*_ zNR!vqG}YZ6i}M3h>ENPmCHxC(#1( z7}2c0*RmVw1@+)M+n8t~gQT#+Yg3>|OA<9`Ynl5)ftY4g0EGA!t?E*;j*jRcB>mr~ z4f=etCrR1X;V_euWY<6p_AK%IoHB+bS8vl&LZ-5Q*QvzmfHq zZ>>MgWVvSa-wRV7cJ8O%vi&R+@2I&X=r`1P1;x8lhOpY4Z58^@Wm+--yBQ{&>GOL- zIJm(euOw?WYjBR|f~ue4(%k0i{lp`gI1~mF;g{;-0_gdf@ z*Q?M9wQ1ZdZwvrK|IY39={n^R^(zI|p=Px@ff|e_NEBug4N0vK!L9-J_DIiI7e5Pr z^Sce&Prjs*$mOY7Rf3V+?poBWP^ki{PIa+)OK%4)E`rV zxx7V^Qy14sZ;Dc2jD|ccyt5(5Zp~;Rg7N_IwB&EZ1jv&GoxT!1H7k>pY>Aa{$&oHg z`ykhr&GpvCL?|Xb;O}(ErzQAl=DZgICR);;Y=xkO<~chKzvaND<3}Wy~d>W0L>Q| z2-}wM73&w!hC@XZojB#$EnGzb4HAp3FWovUq|4f%x4KLKUg6YfVpokO|+JO^JSzIZEji>8`uBI~^1wYq9L`S;8*pu)y zTN!cO5)p_vO7vsEgglr#ee5WTiRh}7f0zLYNA)eB;_ z63%8_pGF-Dnkx@eu`dPn7Z1~vMk@*nIMW6HtpQX86HiyI1H>8W+4Y50C=@;!{F)Za-A9+#^G9aiAu<-#DuLR>+Vm6|21n$W?isfhl9KnurA)AcxJ* zIl$Iy_sl)Ewu1nV)Wiqc6M8RZ-OvG~x&%#S9h{L)QE&q|7$gk|*5h2|^bAvwHm@~P zRY4`*Kw4vB$#(Yqt2+Rd{vNGl*GA$FksiM6%fjfp!BEgA!3EEIq!j+(-cS%{(44@I z+KuDSMAy-fyJ3j}-3vV|_^?zVAkrrzw!3@QF<9e~z*m55Kjm<#D3z(4wCoyq=E3Z+5+o%*c82=9Dn;-mR<5ukCVG}$pfS0a zGXdRdAa-u4>?Cv7*|^+XrkWQGzzvT;h$l5u$vMI>9ouxPD^S{5-qvWAprQ>*&?#SpxdJ-SE&Kk2hn zy8lWI>IKrj;hSj%<-bXl8V%B!q_?jcj{k-hy&J%P3vb%^Qfyv08YOw$Qv~F2IOcFi z%I^ScI`VdU!El-&Werf%8X2asF7Tsk7{xt!qlOL$mCejuXC38O9pJ8y|M>$P50HUy zhcG}uKWP7NB@OTY;fq3kG@GPwLy>1x#YEu`vmQ=(0K)g*ckkeaAkM(C2nZ)rJS}8_IMTxIBXH|>190=4 zD%!`?a-E!T;jSVXMP%ETk{4ij&~`Q)&DZieRx)rLfXGfwvm9#PvZgMyX7+TpsoXa= z4Qq583C|0#1W{@tX6kUwtN40v^oyycsiqPP<(V!5f5bA~B0ZGZ{CU#4q>RznC|I_) z7I8BytRK$$wnfi79s*Phn%|0s_u9`zwWi2#=GE5F_sk({H`bq&(QCDy^X97O7~dVV zjm7hN0FhFY>Zr6d?l;%A(Z~&Ew$4)I4_&92>1%LB&Iz>(85AY z;VB`o-(qZZj2^wUL9TY=pDZ9{|L{Rg0eiHZxKR(>6I;B}xV?kpOG_~18o5kM9>bF; zvl22sk@FP)d1Mu!iPBd8n%hqPUH?B{lf+vBfKDaUjH};FB`hI|=TD}i4-Df(W|+FB zCt09JV@dNOy}=s3AS(U4&Ca^LI#IkDbY6-0Iby5ba=y`Wp2hYzhwTE5+|7W}HwTbp z9OzNwQYpe;mIt%rDX*W89h~mxYK3jmf-7Q*)B9kUP?Evo3sn(X81NyML>*eVx+RUlBPA+sDViBwk z7*Dl;#i5JP1+7=3^WriySJy*Ub#&|n!0jaOtW}%-grYW2t+eT{wz)iu1P?+?*78D4 z?m5`fN!6Uv7J4JU)^8tW`D-N9QO%RdtYTA8+bXhEgPf34?k{g{4Tq?|%C$Kz+U{9j z8RcUt*R}dKX*G74+BGaNebZUV{DCm;@U(5XnJYWyX(1gNvxR#br(Qa6)^hmsfX#aR zk+}yFE?Rp5@=+8!0rVoYMrk4eHt6+-pV!|CZFOXL81z;&nOQ!ct!B%hYyCe z$8CC^HadwLAC?`$JgYtvu%$b7`9Y=%pqA!R6Z96z- zLhL(4qE89OG&)oMjo05P>;5?Mp60` zPWdJ5-2@SE9T{-ytDRE{6sX)|Y1X;+C@K>yY^}14Y!088xh~SPfbJG?M1tBi?E>u?zdU>G{5+S>|$%tGJB zQ*X_vOy)g;@fbPm0a(Zh7zTzw2Ct$FB6Gz7!tmK*tZ2h588F#jY1p`jSJMli*7u-; z3tSU(fscAw1h}5i`&i`+?4UAF;AeV|b}3)i5zA^E*L0X|u;#%xYNx~?#g6jEh~;8t zQ8$5Sx)(-Y-j-9ugVW%b2(t*(k6(`>S>s9^t-podjkrgd0G}k7#${=(J0T7``%9)` zbz@# z89pMA4}>(ymEcPbh@I>#D9Az~sbv{(OXEh+fnx{b z6H8ULM@UCCdJbtvxLPl+w?prh49<(wWQ*(&g-1S%fFdrWy;&bp2wdG!zXt0n@O|(h^&64U7Am>%tK&1tn{(CN?9?pRJVbV0abQse6W* zjaunJ1r9_dkDSXE8y~{blX@E9+XdZr?+Cj9fSv4Dr%sM0X8+%}yVNrc%}Pks zfLfd-a~NL@9Ae&`->H9ihbrSTQK7`l0(9ei<9)-C-ZjdIKdOKOVrZbL^1x5+({hmz z^ka^IzOo7Z5kDX{UB^aJa=ZJ664{}im=U8r5}V}6e33gr#%&kPksN&;R!|y`-hx0+!ub!fTfgoWJ@3*jQ48CTp{?Y z$+bKR>!aBjD7x?Y0>>e`M#1*rfv0;edmByS@dJq0U>!j z12B#0J8%)E#AT3Tv<7hwsa2De$TgZ!6ya*gBbt8{dMpCoYg`{48qN!f$4KFI>9kSj zXqP7qQXV6DfRu{Jr(Mj>;=zUW>U{0sd8$z^(2$UE1b=z(K3T=YUsL(r3UwB%vS_@i zUw15;g`ql@wnozVkC>v|rqdrPO1t2>x^$SM@_>ucDEgntIq=60A2|p%szF-JmH5_! z>2S4sVX}c!H;5b!MnOy^fZYTP60VDhA{ikCTh{$>P4GK|N)1u_VGJ22k_IyXwj7Sj zcn5~M5{rQqE`|I<$3Bj`K#{b$K^z(UVwE$D46wB&kBgN&?rjSskPyQ3X&G^Acx^iv zW6lXF-}{o%ux^olbi{%ZmZM_C=6u(%CKQ={xs{jYqD zM26k$`Qj{UlW5Jt`l&1QP|d=7B{Dx;qd$8JdU$AE5&l(!MUkXC0mFRCM3JnDw?zVe z7`mm7)u~!VZs$|ahb9Y>#(9sjOV zcH~0w!lwVVM3oxLQd(|~MDZCpxbXh7qmbj2l;)N4J+?HVc6Jx7LG<@F&tGUvek#38UUOBInuVP22k}b4Ep?bEu^--cB#Ag|hqHNP79!T*v5&|g?2bQG86x5lB{ff(Rjr7|;rT&I0Ef(#dGARy zq-)N|z^0X-fAevH$bL+ip~x^dH#=T?vKN@HF~)7*3?~kd(`GwzGp*%S?H7db>`8F> zgx!tP`bl5-7lQ@AQ4i^?mNUb^ki+(Qvxg{R!^Ut%ya1_K$Ci-wGtO^W+(5We9^Z|i*}v@%bg{vBl7i??boO`xvQUh$k~C|d$i?y7U=W| z!<=;Y;tf9FpB=nOaU(_U#7Npj4id5?8H4? zsL^r@1_p9?VMR4cVe#mEOOH=f?>dB_m{#vzpM&E&KVbxd<&r?NMbz+F*duzV(?Y8LUgUpO4?&3)QPk z5&HoWONJr}EUHfHzJW4vCdqg&<>PN7f)paE#1!i^P<-8JfbLD7%T`A%By{h7P)CAW zJ1E&XBE96%#4a;dwNYQjcdiR0Nxh?uH~|2q&7C9LQ+QSv8X^PP0>Usz*HSS9C0>to ze1pO&s7BCS{x!VW_Pg@E-%TErJGYbnQ2hXL%RBzBNmFecgMmO#_uULhV~c2I)KHP{ zv{Eui!aMjaX?Mf>WoHp0KtGR^e4E^69*4@*{%8^>HwxUFNcSt7W0h7X$VzQ5JTGQg zLpd?yN%(bgiP_o-cst z@QA_VD0&n&*dj?j63J-vndy~X;lwmo=Q_8PV#w^VZOiYw;}mS|B;|u)e#GS8JRqxP zoWEuBMb#F=PknRG3P* z4GJA~MMpEbM%i4(YahXGEOSo2nB;oM z*5&1O`U}@hdRDps0PqD~2c@$6cz7sxmZ+b)O!Nllqto*I#I^<9nQ}0`3gtZjgFSc` zr<;IuXQCn=vP25FV3h8Z+}TdG6Sel7VCP+9#!U`9SHR~u*QtV&Ir;S6Z^sSGm|s;y z-f{CTn7y-&!B@eo#~6{h(77Nh6dHLyQG)b$p_3Gj)aRs!q6N>lUC*~^HSvWstrW}u z*CU=O3^xF*0&%aIQS)f~p!Vfgr70q9_)Pqs1=T}zL2n7bM8o8g#*F|Q%n>{#zGI3aoM5ptgqb|5#Q0-fuPveFm}*t#6J>nQI?04W zddadPl-27!^`1tRpwAVEqlr1diwI*)RCifevrPbt5Gp@fxs&zT5 zsb*ne&_BG~c(7H^P%7ADWn2!iMjp*h2XH3HT6VU72#$t`4=n-ZMCj(Lx2fTA@Q*v3DH1nr6oj-PQmZ9zCOcnn|~y1H8R1_aO#cRLv8n zA^SQ>qnD0V>X0{ZGw#)({*;uB(U$-bb3>y#gPQ0j{V0TAh2!q01pnET-gA>Z&%Zu& z{QmIumszVzi2m>gDlumvArvK|eWjErehNwr_*YQB+{U0n2iH{TJ z;qL1>Q|tNR;tK>w-Y~Xr!pxa~?@n`+EF(yvE$iV|s+c}C9kp5-ApELWNNyD z|D+=Q7PY%KH^%y&U#ewXB(vfZd=y2g6mLmY^!M=zO*K@jEGVFm+gRBYv6`7`j!j#_ z9w|2DzzCJJ^>~J#5j;E8*py74CK@&dIy0mkEqwTPE}}scXFHs_!v+39v(Q!~u%}FWO}FpFHX>#>99{bVQXu z&Mv05icalrL5O4IcpQ-%8V0q0)*4^oV6E1=wCFNkQG8D|Vcl#K3ekLmEmuno2}tcn+QcBWaoDND z?$>_WkP~3jJBVSpFIV5PxKA;nAt-PpDTxDvS|U0B~sCx$DrPuUWy1s-9;QX4FU@5U37&vhcuXyFpWC$dZ2bo2M?j zANK_Zrju>J;S;e;$Q-lXs>AJ;X+V(MnIVQV<}7RvF2tip0dAnk>SJRl?)-~WoU!77 zQ=Tzv)wwG*H6)RHIJxxBSAnc$34YukwX=MWwb+&MO&{6*3?R8{8xnSKM?Fx^SIqyB zbIrq9*-wfEPB-!(hD)U;417Yhr*_v$3yfCOLjgK9ct=m3wC4po@*K`;f?423NQ%Ha z=HQfTdxjl&#yC@aA?gUOwDc`m_JtKN%GtmX{+jhTzM{j)Zz!HLVWS zT3ud61ZuseM>#VB zB1v^H3>~f3ZuQ1y1W{>t-Z=ZAh`cL8Ph>}_y|h?Wg&}{_PP-`L`oK-Ig}U9hdlkA` zD(w7nYK?aP_vu?cAgjvw$DWY~|Nr`6dn+Ike-c>$`F=-2aTLj*LyZCcadEaCUHG~; z86DPAtoK5nu-&tR!-E*UKmtjQ&F-bed^U;yv{`=a-Q3MyR&EFcei`C7LwUEikDKv_ z{n2hUv{KSVf+2Ghr?p6~s8Uo}UNjM-Va{4f?=S0P)GQHiP&5mMDO6_~Oh#6NWhYTD zHVIY-Br?zR-A}*_d1E(u4)4jZiSX;qv}@p<)$5PHa8uof$- zN#h;PX!Sh`GyKY@#3`XavDTF!tlLp7pOnP|n7ydSTSeRN`9lT0{FsiXdyibTb1c%L zVA^GmC!c-pE7zzK?fNiiRLgGuZTzKsr@X+hJ&sngBnxa3+bfw(?G&G3Q%W|MUt{C{~s zF!W;nx?2MjfY!+%*n5u;$!Pee07wYZ@g^V02=j281Q-OI#l0q(9<@WCr<;o4(a|TM zH_t`S9?g&v-JRw*Z;u>5#?|UTBD=ggqWPrGOk$%Eut6-?OV>%E(R=5l*y|X#64&>rZ z#W3LPCfr7TgzQ0(qgidWUQd+uWMCx7o zEB>|%Jj&TVz$-D|qVAVU4!CF!@J}!yxFe4cX8SF|Y-XBWZzD>se-R!+{t?Wh6=}E7 zVI*Eoa1su_6K2`e8XfsS4OJM|U+&-7VS zIRJ0}JFs%}kcBm|$KkOHXW8Yj-C+KS#mq``V56%9am)P^?MzJPWU+*SyoQeWkRCz< zQ&Lq-Q>VTUJh=@7B#nHSC6HUHAey1!j}y>tP-yPh!o;992`-QHd7AI5t9 zPzm;}i0kMO6~Kl4TT`Y-BTU9Ku;r}*Q1TDl8m%S{+PFzk4&HGip;0#LkTx>X5q%>5 zvea2A%tl(PyC6CoWZ>)xHQQMu6n`UxQHJwS^%+zbld7C*CafaNLfh=(7&7eb)>jvC znLDJo2#ICn^BvWW7|$|a>!k)dOwPL;_Ao<@lzuJMoVs>;vkRhel4yyS2) zNMgz=@z?&pdF|R2kYSCb~_c?Vn#f0va))?V7TyrsA4t^o14=CVLW+YJt zornR!@R}SEh5X@8Mecwsv4(I7&TsC{FBAkUqM~hI4`ElK`EdgmwXTtz>9XPZVjTba zBi?BtsK{w&VnIK?b}XqbS5ujgFthngi(n$Qf0!GV*Ck3#A5=c-XwE4I2shGOBSw|T zij+DsI~26%8A9#jM#!kkG4k(|p=DlNOtp$^w;d!`3Z6v)Np-zYDWC&3J{ zwaUiwtA2L~pTeKQ%+q-puz^>p5WizwIVWT}a7;I6vmOl}V!9x!Q0+N)w0dK<>Zy?Q zIMqMK-zUY;#%$)=v;*}7l%0g)L@qrQ%(KKJ+7(26naCnPXDl!4!)l8vCvdPEi@Jw* z|6Y0vPmvHvkk-$$00p5yRzY+{Zx>_nKI_Xh)l_9kFz3dgjETw(U=}g;=}5EaiyMu4 z_K5!H6(p54QnUJxGgc8!K#+;aOOofhNq5c;z10R2IrtP1H4@T9A)rjBp`BPHrYhlL z+@cieQ3~0svr%Pi6*}fPW-L9x=CjjPl73d0y^9szowR56%tm}k>B)RtEMvOL*=5n6 z-O4NJdBneKC@(Ak6105naj(;SX_5pO7!J@7^!qDe`+jzeJ|J9eMX~dq_a4ty_&9?( zEDkVKBj$N0>Ka>58Y|PQq{Q2j-1e%45yo0bM~*k}vj%t;)h4!(={qG%V1_LSFm}aK zY-tE~MG&?}B;H1))pTEj@~LYqj3<1_=`$4^b24-b8Y}Do-qUr>x|NiG?ruc-9+TCz z;?EP^qy0SZdX`9sh!jt2^KgHyRrl?I`X8rO z8NK~qffuwrcv^i<^-sN;(~rF>En&Wk(?xUpXJ1i$BT!_#xy7-)Kt@ezB>Cmr;5qh^mji@urT}VzT*Om+_r%F`x$OqeakZ|EVfr%`L5IZXlLN1Lx$X$ z+~*?=bbBH!DkWE20Z&N_tCU_B5$>9N<-1b_)B4t9h0o5Fdg(TV#T=ZS;k;e9y5Pt( zcf%BKR`r}pq4b=}Y5!VT0!2?uu5S_u400^GsdDb9m9+E0!adTPK5T5=_*&)oy9xJV zF2%9jIC6B{IhfKk_L`{##PdAGvbj`=i^IWZR_QpWl7Pcg=0JJdXRWYv_wxuM9&rzRW2JGR-w|x_nY#<=SNhGv@xPUGak-)N>My zOneaxybJRv4`{BQkx7I>1a{^b!-nmXAIx>-%-v{b>i|3i&3>}pJSUmS2~`n_z^+yS z5F0W84=jO$-F%Y+=gUmi<5!s6KVLxR@N}V>dBECiGq5qIhN93#0IX18zN$3hPIm?d zV-!XFlLO}a%OLKmW?-;Ek-sboG(;JA1H1~@Hsm`!ZBY~!NrDxAkW>XLMBK-SZsJh| zutEn#h>3_B?HCwPO>9vHDV(GNHjo8$f7;~2gO;L~=q~SL-0fWZ~#j)X&6Bqf(AYY$jk0PJ03wGnXMds4rYbk)o%O?X5s6!3k zfXNPvon#Tm&!fx7m@-U0Xlej*iY)lxbYN7j0b(5#t3F$TR4GoDU7{+BI87QonpRme zOct=Q1)0SHI@Eabh9zRm!uB9RsmW9A4Z;2eABzjLU@_3Yb|{tzO}1YeB?~&EwGSvS z2b9-Gk@s+Bn7q;166{pOsgw*1jwq^ZTtTWtCL1hsmqk9p&jdx)T@RQl&dDjBieNJl zr|tj``9o2y>jP8GF7ag{X4W>)a%KhoKvyva1`M9A)97C%`B`O-U1bAu471WI(n_BRXdc33Qc~vQcM(m z%*7)yFC}Mk;$lTsaNBmW!75Q^;mHs)A-y`Vxw6QmkOqpmsncMpwYY?M85qRpg322J DDw4oP diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9355b4155..ff23a68d7 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f5feea6d6..23d15a936 100755 --- a/gradlew +++ b/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9b42019c7..5eed7ee84 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell From 4495da6b8704035486b377420d8b962d022bc1d3 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Tue, 29 Jul 2025 12:09:45 +0900 Subject: [PATCH 045/141] more readme updates, installation instruction for all (#331) --- README.md | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 73 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f9f4ca3f2..940b478c9 100644 --- a/README.md +++ b/README.md @@ -62,22 +62,65 @@ Required language/runtime versions: This project contains multiple builds, living side by side together. -Depending on which part you are developing, you may want to run just the swift tests: +You will need to have: +- Swift (6.1.x+) +- Java (24+ for FFM, even though we support lower JDK targets) +- Gradle (installed by "Gradle wrapper" automatically when you run gradle through `./gradlew`) + +### Preparing your environment + +Install **Swift**, the easiest way to do this is to use **Swiftly**: [swift.org/install/](https://www.swift.org/install/). +This should automatically install a recent Swift, but you can always make sure by running: + +```bash +swiftly install 6.1.2 --use +``` + +Install a recent enough Java distribution. We validate this project using Corretto so you can choose to use that as well, +however any recent enough Java distribution should work correctly. You can use sdkman to install Java: + +```bash +# Install sdkman from: https://sdkman.io +curl -s "https://get.sdkman.io" | bash +sdk install java 17.0.15-amzn +sdk install java 24.0.1-amzn + +sdk use java 24.0.1-amzn +``` + +The use of JDK 24 is required to build the project, even though the libraries being published may target lower Java versions. + +❗️ Please make sure to `export JAVA_HOME` such that swift-java can find the necessary java libraries! +When using sdkman the easiest way to export JAVA_HOME is to export the "current" used JDK's home, like this: + +```bash +export JAVA_HOME="$(sdk home java current) +``` + +### Testing your changes + +Many tests, including source generation tests, are written in Swift and you can execute them all by running the +swift package manager test command: ```bash > swift test ``` -or the Java tests through the Gradle build. The Gradle build may also trigger some Swift compilation because of -interlinked dependencies of the two parts of Swift-Java. To run the Java build and tests use the Gradle wrapper script: +When adding tests in `Tests/...` targets, you can run these tests (or filter a specific test using `swift test --filter type-or-method-name`). + +Some tests are implemented in Java and therefore need to be executed using Gradle. +Please always use the gradle wrapper (`./gradlew`) to make sure to use the appropriate Gradle version ```bash > ./gradlew test ``` -Currently it is suggested to use Swift 6.0 and a Java 24+. +> Tip: A lot of the **runtime tests** for code relying on `jextract` are **located in sample apps**, +> so if you need to runtime test any code relying on source generation steps of jextract, consider adding the tests +> to an appropriate Sample. These tests are also executed in CI (which you can check in the `ci-validate.sh` script +> contained in every sample repository). -### Sample Apps +### Sample apps & tests Sample apps are located in the `Samples/` directory, and they showcase full "roundtrip" usage of the library and/or tools. @@ -101,12 +144,18 @@ To run a simple example app showcasing the jextract (Java calling Swift) approac This will also generate the necessary sources (by invoking jextract, extracting the `Sources/ExampleSwiftLibrary`) and generating Java sources in `src/generated/java`. +#### Other sample apps + +Please refer to the [Samples](Samples) directory for more sample apps which showcase the various usage modes of swift-java. + ## Benchmarks You can run Swift [ordo-one/package-benchmark](https://github.com/ordo-one/package-benchmark) and OpenJDK [JMH](https://github.com/openjdk/jmh) benchmarks in this project. Swift benchmarks are located under `Benchmarks/` and JMH benchmarks are currently part of the SwiftKit sample project: `Samples/SwiftKitSampleApp/src/jmh` because they depend on generated sources from the sample. +### Swift benchmarks + To run **Swift benchmarks** you can: ```bash @@ -114,6 +163,8 @@ cd Benchmarks swift package benchmark ``` +### Java benchmarks + In order to run JMH benchmarks you can: ```bash @@ -125,4 +176,20 @@ Please read documentation of both performance testing tools and understand that ## User Guide -More details about the project and how it can be used are available in [USER_GUIDE.md](USER_GUIDE.md) +More details about the project can be found in [docc](https://www.swift.org/documentation/docc/) documentation. + +To view the rendered docc documentation you can use the docc preview command: + +```bash +xcrun docc preview Sources/SwiftJavaDocumentation/Documentation.docc + +# OR JavaKit to view JavaKit documentation: +# xcrun docc preview Sources/JavaKit/Documentation.docc + +# ======================================== +# Starting Local Preview Server +# Address: http://localhost:8080/documentation/documentation +# ======================================== +# Monitoring /Users/ktoso/code/swift-java/Sources/SwiftJavaDocumentation/Documentation.docc for changes... + +``` From 7f24d6276f59c34544a4c8f6771a8bc560aa5db9 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Wed, 30 Jul 2025 19:51:11 -0700 Subject: [PATCH 046/141] [JExtract] Translate generic parameters (#336) * Integrate `SwiftLexicalLookup` for unqualified type lookups * Introduce `SwiftTypeLookupContext` as a facade for `SwiftLexicalLookup` and a cached conversion from Syntax to `SwiftTypeDeclaration` instance. This wraps and replaces `SwiftSymbolTable`. * Introduce `SwiftGenericParameterDeclration` as a subclass of `SwiftTypeDeclaration` to represent generic parameters. * Store generic parameters and requirements in `SwiftFunctionSignature`. * `SwiftType.representativeConcreteTypeIn()` for getting `Data` from `T: DataProtocol` type. --- Package.swift | 1 + .../FFM/CDeclLowering/CRepresentation.swift | 4 +- ...Swift2JavaGenerator+FunctionLowering.swift | 75 ++++----- ...MSwift2JavaGenerator+JavaTranslation.swift | 81 ++++++---- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 2 +- .../FFM/FFMSwift2JavaGenerator.swift | 4 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 4 +- ...wift2JavaGenerator+NativeTranslation.swift | 8 +- .../Swift2JavaTranslator.swift | 16 +- .../JExtractSwiftLib/Swift2JavaVisitor.swift | 6 +- .../SwiftTypes/SwiftFunctionSignature.swift | 133 +++++++++++---- .../SwiftTypes/SwiftFunctionType.swift | 6 +- .../SwiftNominalTypeDeclaration.swift | 46 ++++-- .../SwiftTypes/SwiftParameter.swift | 4 +- .../SwiftParsedModuleSymbolTableBuilder.swift | 3 +- ...wiftType+RepresentativeConcreteeType.swift | 65 ++++++++ .../SwiftTypes/SwiftType.swift | 85 ++++++---- .../SwiftTypes/SwiftTypeLookupContext.swift | 151 ++++++++++++++++++ .../Asserts/TextAssertions.swift | 1 + .../JExtractSwiftTests/DataImportTests.swift | 29 ++-- .../FunctionLoweringTests.swift | 21 +++ 21 files changed, 567 insertions(+), 178 deletions(-) create mode 100644 Sources/JExtractSwiftLib/SwiftTypes/SwiftType+RepresentativeConcreteeType.swift create mode 100644 Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift diff --git a/Package.swift b/Package.swift index 84b348c5b..51608c319 100644 --- a/Package.swift +++ b/Package.swift @@ -422,6 +422,7 @@ let package = Package( name: "JExtractSwiftLib", dependencies: [ .product(name: "SwiftBasicFormat", package: "swift-syntax"), + .product(name: "SwiftLexicalLookup", package: "swift-syntax"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), .product(name: "ArgumentParser", package: "swift-argument-parser"), diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift index c2bb53abd..d2a71defd 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift @@ -68,7 +68,7 @@ extension CType { case .optional(let wrapped) where wrapped.isPointer: try self.init(cdeclType: wrapped) - case .metatype, .optional, .tuple, .opaque, .existential: + case .genericParameter, .metatype, .optional, .tuple, .opaque, .existential: throw CDeclToCLoweringError.invalidCDeclType(cdeclType) } } @@ -98,7 +98,7 @@ extension CFunction { enum CDeclToCLoweringError: Error { case invalidCDeclType(SwiftType) - case invalidNominalType(SwiftNominalTypeDeclaration) + case invalidNominalType(SwiftTypeDeclaration) case invalidFunctionConvention(SwiftFunctionType) } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index bb9941642..085a6331b 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -26,10 +26,10 @@ extension FFMSwift2JavaGenerator { ) throws -> LoweredFunctionSignature { let signature = try SwiftFunctionSignature( decl, - enclosingType: try enclosingType.map { try SwiftType($0, symbolTable: symbolTable) }, - symbolTable: symbolTable + enclosingType: try enclosingType.map { try SwiftType($0, lookupContext: lookupContext) }, + lookupContext: lookupContext ) - return try CdeclLowering(symbolTable: symbolTable).lowerFunctionSignature(signature) + return try CdeclLowering(symbolTable: lookupContext.symbolTable).lowerFunctionSignature(signature) } /// Lower the given initializer to a C-compatible entrypoint, @@ -42,11 +42,11 @@ extension FFMSwift2JavaGenerator { ) throws -> LoweredFunctionSignature { let signature = try SwiftFunctionSignature( decl, - enclosingType: try enclosingType.map { try SwiftType($0, symbolTable: symbolTable) }, - symbolTable: symbolTable + enclosingType: try enclosingType.map { try SwiftType($0, lookupContext: lookupContext) }, + lookupContext: lookupContext ) - return try CdeclLowering(symbolTable: symbolTable).lowerFunctionSignature(signature) + return try CdeclLowering(symbolTable: lookupContext.symbolTable).lowerFunctionSignature(signature) } /// Lower the given variable decl to a C-compatible entrypoint, @@ -66,10 +66,10 @@ extension FFMSwift2JavaGenerator { let signature = try SwiftFunctionSignature( decl, isSet: isSet, - enclosingType: try enclosingType.map { try SwiftType($0, symbolTable: symbolTable) }, - symbolTable: symbolTable + enclosingType: try enclosingType.map { try SwiftType($0, lookupContext: lookupContext) }, + lookupContext: lookupContext ) - return try CdeclLowering(symbolTable: symbolTable).lowerFunctionSignature(signature) + return try CdeclLowering(symbolTable: lookupContext.symbolTable).lowerFunctionSignature(signature) } } @@ -98,7 +98,9 @@ struct CdeclLowering { try lowerParameter( selfParameter.type, convention: selfParameter.convention, - parameterName: selfParameter.parameterName ?? "self" + parameterName: selfParameter.parameterName ?? "self", + genericParameters: signature.genericParameters, + genericRequirements: signature.genericRequirements ) case nil, .initializer(_), .staticMethod(_): nil @@ -106,10 +108,12 @@ struct CdeclLowering { // Lower all of the parameters. let loweredParameters = try signature.parameters.enumerated().map { (index, param) in - try lowerParameter( + return try lowerParameter( param.type, convention: param.convention, - parameterName: param.parameterName ?? "_\(index)" + parameterName: param.parameterName ?? "_\(index)", + genericParameters: signature.genericParameters, + genericRequirements: signature.genericRequirements ) } @@ -142,7 +146,9 @@ struct CdeclLowering { func lowerParameter( _ type: SwiftType, convention: SwiftParameterConvention, - parameterName: String + parameterName: String, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) throws -> LoweredParameter { // If there is a 1:1 mapping between this Swift type and a C type, we just // return it. @@ -257,7 +263,7 @@ struct CdeclLowering { guard let genericArgs = nominal.genericArguments, genericArgs.count == 1 else { throw LoweringError.unhandledType(type) } - return try lowerOptionalParameter(genericArgs[0], convention: convention, parameterName: parameterName) + return try lowerOptionalParameter(genericArgs[0], convention: convention, parameterName: parameterName, genericParameters: genericParameters, genericRequirements: genericRequirements) case .string: // 'String' is passed in by C string. i.e. 'UnsafePointer' ('const uint8_t *') @@ -300,7 +306,7 @@ struct CdeclLowering { case .tuple(let tuple): if tuple.count == 1 { - return try lowerParameter(tuple[0], convention: convention, parameterName: parameterName) + return try lowerParameter(tuple[0], convention: convention, parameterName: parameterName, genericParameters: genericParameters, genericRequirements: genericRequirements) } if convention == .inout { throw LoweringError.inoutNotSupported(type) @@ -310,7 +316,7 @@ struct CdeclLowering { for (idx, element) in tuple.enumerated() { // FIXME: Use tuple element label. let cdeclName = "\(parameterName)_\(idx)" - let lowered = try lowerParameter(element, convention: convention, parameterName: cdeclName) + let lowered = try lowerParameter(element, convention: convention, parameterName: cdeclName, genericParameters: genericParameters, genericRequirements: genericRequirements) parameters.append(contentsOf: lowered.cdeclParameters) conversions.append(lowered.conversion) @@ -330,20 +336,14 @@ struct CdeclLowering { conversion: conversion ) - case .opaque(let proto), .existential(let proto): - // If the protocol has a known representative implementation, e.g. `String` for `StringProtocol` - // Translate it as the concrete type. - // NOTE: This is a temporary workaround until we add support for generics. - if - let knownProtocol = proto.asNominalTypeDeclaration?.knownTypeKind, - let concreteTy = knownTypes.representativeType(of: knownProtocol) - { - return try lowerParameter(concreteTy, convention: convention, parameterName: parameterName) + case .opaque, .existential, .genericParameter: + if let concreteTy = type.representativeConcreteTypeIn(knownTypes: knownTypes, genericParameters: genericParameters, genericRequirements: genericRequirements) { + return try lowerParameter(concreteTy, convention: convention, parameterName: parameterName, genericParameters: genericParameters, genericRequirements: genericRequirements) } throw LoweringError.unhandledType(type) case .optional(let wrapped): - return try lowerOptionalParameter(wrapped, convention: convention, parameterName: parameterName) + return try lowerOptionalParameter(wrapped, convention: convention, parameterName: parameterName, genericParameters: genericParameters, genericRequirements: genericRequirements) } } @@ -354,7 +354,9 @@ struct CdeclLowering { func lowerOptionalParameter( _ wrappedType: SwiftType, convention: SwiftParameterConvention, - parameterName: String + parameterName: String, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) throws -> LoweredParameter { // If there is a 1:1 mapping between this Swift type and a C type, lower it to 'UnsafePointer?' if let _ = try? CType(cdeclType: wrappedType) { @@ -398,18 +400,15 @@ struct CdeclLowering { conversion: .pointee(.typedPointer(.optionalChain(.placeholder), swiftType: wrappedType)) ) - case .existential(let proto), .opaque(let proto): - if - let knownProtocol = proto.asNominalTypeDeclaration?.knownTypeKind, - let concreteTy = knownTypes.representativeType(of: knownProtocol) - { - return try lowerOptionalParameter(concreteTy, convention: convention, parameterName: parameterName) + case .existential, .opaque, .genericParameter: + if let concreteTy = wrappedType.representativeConcreteTypeIn(knownTypes: knownTypes, genericParameters: genericParameters, genericRequirements: genericRequirements) { + return try lowerOptionalParameter(concreteTy, convention: convention, parameterName: parameterName, genericParameters: genericParameters, genericRequirements: genericRequirements) } throw LoweringError.unhandledType(.optional(wrappedType)) case .tuple(let tuple): if tuple.count == 1 { - return try lowerOptionalParameter(tuple[0], convention: convention, parameterName: parameterName) + return try lowerOptionalParameter(tuple[0], convention: convention, parameterName: parameterName, genericParameters: genericParameters, genericRequirements: genericRequirements) } throw LoweringError.unhandledType(.optional(wrappedType)) @@ -514,7 +513,7 @@ struct CdeclLowering { // Custom types are not supported yet. throw LoweringError.unhandledType(type) - case .function, .metatype, .optional, .tuple, .existential, .opaque: + case .genericParameter, .function, .metatype, .optional, .tuple, .existential, .opaque: // TODO: Implement throw LoweringError.unhandledType(type) } @@ -668,7 +667,7 @@ struct CdeclLowering { conversion: .tupleExplode(conversions, name: outParameterName) ) - case .function, .optional, .existential, .opaque: + case .genericParameter, .function, .optional, .existential, .opaque: throw LoweringError.unhandledType(type) } } @@ -754,7 +753,9 @@ public struct LoweredFunctionSignature: Equatable { selfParameter: nil, parameters: allLoweredParameters, result: SwiftResult(convention: .direct, type: result.cdeclResultType), - effectSpecifiers: [] + effectSpecifiers: [], + genericParameters: [], + genericRequirements: [] ) } } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 9f6581888..b29ca5d9e 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -24,7 +24,7 @@ extension FFMSwift2JavaGenerator { let translated: TranslatedFunctionDecl? do { - let translation = JavaTranslation(symbolTable: self.symbolTable) + let translation = JavaTranslation(knownTypes: SwiftKnownTypes(symbolTable: lookupContext.symbolTable)) translated = try translation.translate(decl) } catch { self.log.info("Failed to translate: '\(decl.swiftDecl.qualifiedNameForDebug)'; \(error)") @@ -115,8 +115,8 @@ extension FFMSwift2JavaGenerator { struct JavaTranslation { var knownTypes: SwiftKnownTypes - init(symbolTable: SwiftSymbolTable) { - self.knownTypes = SwiftKnownTypes(symbolTable: symbolTable) + init(knownTypes: SwiftKnownTypes) { + self.knownTypes = knownTypes } func translate( @@ -256,7 +256,9 @@ extension FFMSwift2JavaGenerator { convention: swiftSelf.convention, parameterName: swiftSelf.parameterName ?? "self", loweredParam: loweredFunctionSignature.selfParameter!, - methodName: methodName + methodName: methodName, + genericParameters: swiftSignature.genericParameters, + genericRequirements: swiftSignature.genericRequirements ) } else { selfParameter = nil @@ -272,7 +274,9 @@ extension FFMSwift2JavaGenerator { convention: swiftParam.convention, parameterName: parameterName, loweredParam: loweredParam, - methodName: methodName + methodName: methodName, + genericParameters: swiftSignature.genericParameters, + genericRequirements: swiftSignature.genericRequirements ) } @@ -295,7 +299,9 @@ extension FFMSwift2JavaGenerator { convention: SwiftParameterConvention, parameterName: String, loweredParam: LoweredParameter, - methodName: String + methodName: String, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) throws -> TranslatedParameter { // If there is a 1:1 mapping between this Swift type and a C type, that can @@ -352,7 +358,15 @@ extension FFMSwift2JavaGenerator { guard let genericArgs = swiftNominalType.genericArguments, genericArgs.count == 1 else { throw JavaTranslationError.unhandledType(swiftType) } - return try translateOptionalParameter(wrappedType: genericArgs[0], convention: convention, parameterName: parameterName, loweredParam: loweredParam, methodName: methodName) + return try translateOptionalParameter( + wrappedType: genericArgs[0], + convention: convention, + parameterName: parameterName, + loweredParam: loweredParam, + methodName: methodName, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) case .string: return TranslatedParameter( @@ -399,20 +413,16 @@ extension FFMSwift2JavaGenerator { conversion: .call(.placeholder, function: "\(methodName).$toUpcallStub", withArena: true) ) - case .existential(let proto), .opaque(let proto): - // If the protocol has a known representative implementation, e.g. `String` for `StringProtocol` - // Translate it as the concrete type. - // NOTE: This is a temporary workaround until we add support for generics. - if - let knownProtocol = proto.asNominalTypeDeclaration?.knownTypeKind, - let concreteTy = knownTypes.representativeType(of: knownProtocol) - { + case .existential, .opaque, .genericParameter: + if let concreteTy = swiftType.representativeConcreteTypeIn(knownTypes: knownTypes, genericParameters: genericParameters, genericRequirements: genericRequirements) { return try translateParameter( type: concreteTy, convention: convention, parameterName: parameterName, loweredParam: loweredParam, - methodName: methodName + methodName: methodName, + genericParameters: genericParameters, + genericRequirements: genericRequirements ) } @@ -420,7 +430,15 @@ extension FFMSwift2JavaGenerator { throw JavaTranslationError.unhandledType(swiftType) case .optional(let wrapped): - return try translateOptionalParameter(wrappedType: wrapped, convention: convention, parameterName: parameterName, loweredParam: loweredParam, methodName: methodName) + return try translateOptionalParameter( + wrappedType: wrapped, + convention: convention, + parameterName: parameterName, + loweredParam: loweredParam, + methodName: methodName, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) } } @@ -430,12 +448,14 @@ extension FFMSwift2JavaGenerator { convention: SwiftParameterConvention, parameterName: String, loweredParam: LoweredParameter, - methodName: String + methodName: String, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) throws -> TranslatedParameter { // If there is a 1:1 mapping between this Swift type and a C type, that can // be expressed as a Java primitive type. if let cType = try? CType(cdeclType: swiftType) { - var (translatedClass, lowerFunc) = switch cType.javaType { + let (translatedClass, lowerFunc) = switch cType.javaType { case .int: ("OptionalInt", "toOptionalSegmentInt") case .long: ("OptionalLong", "toOptionalSegmentLong") case .double: ("OptionalDouble", "toOptionalSegmentDouble") @@ -473,23 +493,30 @@ extension FFMSwift2JavaGenerator { ], conversion: .call(.placeholder, function: "SwiftRuntime.toOptionalSegmentInstance", withArena: false) ) - case .existential(let proto), .opaque(let proto): - if - let knownProtocol = proto.asNominalTypeDeclaration?.knownTypeKind, - let concreteTy = knownTypes.representativeType(of: knownProtocol) - { + case .existential, .opaque, .genericParameter: + if let concreteTy = swiftType.representativeConcreteTypeIn(knownTypes: knownTypes, genericParameters: genericParameters, genericRequirements: genericRequirements) { return try translateOptionalParameter( wrappedType: concreteTy, convention: convention, parameterName: parameterName, loweredParam: loweredParam, - methodName: methodName + methodName: methodName, + genericParameters: genericParameters, + genericRequirements: genericRequirements ) } throw JavaTranslationError.unhandledType(.optional(swiftType)) case .tuple(let tuple): if tuple.count == 1 { - return try translateOptionalParameter(wrappedType: tuple[0], convention: convention, parameterName: parameterName, loweredParam: loweredParam, methodName: methodName) + return try translateOptionalParameter( + wrappedType: tuple[0], + convention: convention, + parameterName: parameterName, + loweredParam: loweredParam, + methodName: methodName, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) } throw JavaTranslationError.unhandledType(.optional(swiftType)) default: @@ -580,7 +607,7 @@ extension FFMSwift2JavaGenerator { // TODO: Implement. throw JavaTranslationError.unhandledType(swiftType) - case .optional, .function, .existential, .opaque: + case .genericParameter, .optional, .function, .existential, .opaque: throw JavaTranslationError.unhandledType(swiftType) } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift index 10dab4b9b..1e32d1e59 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -125,7 +125,7 @@ extension FFMSwift2JavaGenerator { } func printSwiftThunkImports(_ printer: inout CodePrinter) { - for module in self.symbolTable.importedModules.keys.sorted() { + for module in self.lookupContext.symbolTable.importedModules.keys.sorted() { guard module != "Swift" else { continue } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index a24dabb1a..d30d3e749 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -24,7 +24,7 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { let javaPackage: String let swiftOutputDirectory: String let javaOutputDirectory: String - let symbolTable: SwiftSymbolTable + let lookupContext: SwiftTypeLookupContext var javaPackagePath: String { javaPackage.replacingOccurrences(of: ".", with: "/") @@ -51,7 +51,7 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { self.javaPackage = javaPackage self.swiftOutputDirectory = swiftOutputDirectory self.javaOutputDirectory = javaOutputDirectory - self.symbolTable = translator.symbolTable + self.lookupContext = translator.lookupContext // If we are forced to write empty files, construct the expected outputs if translator.config.writeEmptyFiles ?? false { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 57ea15b28..8d18e6aef 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -216,7 +216,7 @@ extension JNISwift2JavaGenerator { conversion: .placeholder ) - case .metatype, .optional, .tuple, .existential, .opaque: + case .metatype, .optional, .tuple, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(swiftType) } } @@ -251,7 +251,7 @@ extension JNISwift2JavaGenerator { case .tuple([]): return TranslatedResult(javaType: .void, conversion: .placeholder) - case .metatype, .optional, .tuple, .function, .existential, .opaque: + case .metatype, .optional, .tuple, .function, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 5ccefadb9..48a6e8d45 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -129,7 +129,7 @@ extension JNISwift2JavaGenerator { ) ) - case .metatype, .optional, .tuple, .existential, .opaque: + case .metatype, .optional, .tuple, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) } } @@ -160,7 +160,7 @@ extension JNISwift2JavaGenerator { conversion: .placeholder ) - case .function, .metatype, .optional, .tuple, .existential, .opaque: + case .function, .metatype, .optional, .tuple, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(type) } } @@ -187,7 +187,7 @@ extension JNISwift2JavaGenerator { // Custom types are not supported yet. throw JavaTranslationError.unsupportedSwiftType(type) - case .function, .metatype, .optional, .tuple, .existential, .opaque: + case .function, .metatype, .optional, .tuple, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(type) } } @@ -223,7 +223,7 @@ extension JNISwift2JavaGenerator { conversion: .placeholder ) - case .metatype, .optional, .tuple, .function, .existential, .opaque: + case .metatype, .optional, .tuple, .function, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) } diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index d1141e5f6..3e2359f27 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -23,7 +23,7 @@ import SwiftSyntax public final class Swift2JavaTranslator { static let SWIFT_INTERFACE_SUFFIX = ".swiftinterface" - package var log = Logger(label: "translator", logLevel: .info) + package var log: Logger let config: Configuration @@ -52,7 +52,11 @@ public final class Swift2JavaTranslator { /// type representation. package var importedTypes: [String: ImportedNominalType] = [:] - package var symbolTable: SwiftSymbolTable! = nil + var lookupContext: SwiftTypeLookupContext! = nil + + var symbolTable: SwiftSymbolTable! { + return lookupContext?.symbolTable + } public init( config: Configuration @@ -60,6 +64,7 @@ public final class Swift2JavaTranslator { guard let swiftModule = config.swiftModule else { fatalError("Missing 'swiftModule' name.") // FIXME: can we make it required in config? but we shared config for many cases } + self.log = Logger(label: "translator", logLevel: config.logLevel ?? .info) self.config = config self.swiftModuleName = swiftModule } @@ -116,11 +121,12 @@ extension Swift2JavaTranslator { package func prepareForTranslation() { let dependenciesSource = self.buildDependencyClassesSourceFile() - self.symbolTable = SwiftSymbolTable.setup( + let symbolTable = SwiftSymbolTable.setup( moduleName: self.swiftModuleName, inputs.map({ $0.syntax }) + [dependenciesSource], log: self.log ) + self.lookupContext = SwiftTypeLookupContext(symbolTable: symbolTable) } /// Check if any of the imported decls uses a nominal declaration that satisfies @@ -140,6 +146,8 @@ extension Swift2JavaTranslator { return check(ty) case .existential(let ty), .opaque(let ty): return check(ty) + case .genericParameter: + return false } } @@ -206,7 +214,7 @@ extension Swift2JavaTranslator { func importedNominalType( _ typeNode: TypeSyntax ) -> ImportedNominalType? { - guard let swiftType = try? SwiftType(typeNode, symbolTable: self.symbolTable) else { + guard let swiftType = try? SwiftType(typeNode, lookupContext: lookupContext) else { return nil } guard let swiftNominalDecl = swiftType.asNominalTypeDeclaration else { diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index 7ce982a24..90181757f 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift @@ -104,7 +104,7 @@ final class Swift2JavaVisitor { signature = try SwiftFunctionSignature( node, enclosingType: typeContext?.swiftType, - symbolTable: self.translator.symbolTable + lookupContext: translator.lookupContext ) } catch { self.log.debug("Failed to import: '\(node.qualifiedNameForDebug)'; \(error)") @@ -145,7 +145,7 @@ final class Swift2JavaVisitor { node, isSet: kind == .setter, enclosingType: typeContext?.swiftType, - symbolTable: self.translator.symbolTable + lookupContext: translator.lookupContext ) let imported = ImportedFunc( @@ -193,7 +193,7 @@ final class Swift2JavaVisitor { signature = try SwiftFunctionSignature( node, enclosingType: typeContext.swiftType, - symbolTable: self.translator.symbolTable + lookupContext: translator.lookupContext ) } catch { self.log.debug("Failed to import: \(node.qualifiedNameForDebug); \(error)") diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift index 5cca79bf3..85b961101 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift @@ -15,6 +15,11 @@ import SwiftSyntax import SwiftSyntaxBuilder +enum SwiftGenericRequirement: Equatable { + case inherits(SwiftType, SwiftType) + case equals(SwiftType, SwiftType) +} + /// Provides a complete signature for a Swift function, which includes its /// parameters and return type. public struct SwiftFunctionSignature: Equatable { @@ -22,17 +27,23 @@ public struct SwiftFunctionSignature: Equatable { var parameters: [SwiftParameter] var result: SwiftResult var effectSpecifiers: [SwiftEffectSpecifier] + var genericParameters: [SwiftGenericParameterDeclaration] + var genericRequirements: [SwiftGenericRequirement] init( selfParameter: SwiftSelfParameter? = nil, parameters: [SwiftParameter], result: SwiftResult, - effectSpecifiers: [SwiftEffectSpecifier] + effectSpecifiers: [SwiftEffectSpecifier], + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) { self.selfParameter = selfParameter self.parameters = parameters self.result = result self.effectSpecifiers = effectSpecifiers + self.genericParameters = genericParameters + self.genericRequirements = genericRequirements } } @@ -54,13 +65,8 @@ extension SwiftFunctionSignature { init( _ node: InitializerDeclSyntax, enclosingType: SwiftType?, - symbolTable: SwiftSymbolTable + lookupContext: SwiftTypeLookupContext ) throws { - // Prohibit generics for now. - if let generics = node.genericParameterClause { - throw SwiftFunctionTranslationError.generic(generics) - } - guard let enclosingType else { throw SwiftFunctionTranslationError.missingEnclosingType(node) } @@ -70,33 +76,36 @@ extension SwiftFunctionSignature { throw SwiftFunctionTranslationError.failableInitializer(node) } - // Prohibit generics for now. - if let generics = node.genericParameterClause { - throw SwiftFunctionTranslationError.generic(generics) - } - + let (genericParams, genericRequirements) = try Self.translateGenericParameters( + parameterClause: node.genericParameterClause, + whereClause: node.genericWhereClause, + lookupContext: lookupContext + ) let (parameters, effectSpecifiers) = try Self.translateFunctionSignature( node.signature, - symbolTable: symbolTable + lookupContext: lookupContext ) self.init( selfParameter: .initializer(enclosingType), parameters: parameters, result: SwiftResult(convention: .direct, type: enclosingType), - effectSpecifiers: effectSpecifiers + effectSpecifiers: effectSpecifiers, + genericParameters: genericParams, + genericRequirements: genericRequirements ) } init( _ node: FunctionDeclSyntax, enclosingType: SwiftType?, - symbolTable: SwiftSymbolTable + lookupContext: SwiftTypeLookupContext ) throws { - // Prohibit generics for now. - if let generics = node.genericParameterClause { - throw SwiftFunctionTranslationError.generic(generics) - } + let (genericParams, genericRequirements) = try Self.translateGenericParameters( + parameterClause: node.genericParameterClause, + whereClause: node.genericWhereClause, + lookupContext: lookupContext + ) // If this is a member of a type, so we will have a self parameter. Figure out the // type and convention for the self parameter. @@ -132,7 +141,7 @@ extension SwiftFunctionSignature { // Translate the parameters. let (parameters, effectSpecifiers) = try Self.translateFunctionSignature( node.signature, - symbolTable: symbolTable + lookupContext: lookupContext ) // Translate the result type. @@ -140,20 +149,81 @@ extension SwiftFunctionSignature { if let resultType = node.signature.returnClause?.type { result = try SwiftResult( convention: .direct, - type: SwiftType(resultType, symbolTable: symbolTable) + type: SwiftType(resultType, lookupContext: lookupContext) ) } else { result = .void } - self.init(selfParameter: selfParameter, parameters: parameters, result: result, effectSpecifiers: effectSpecifiers) + self.init( + selfParameter: selfParameter, + parameters: parameters, + result: result, + effectSpecifiers: effectSpecifiers, + genericParameters: genericParams, + genericRequirements: genericRequirements + ) + } + + static func translateGenericParameters( + parameterClause: GenericParameterClauseSyntax?, + whereClause: GenericWhereClauseSyntax?, + lookupContext: SwiftTypeLookupContext + ) throws -> (parameters: [SwiftGenericParameterDeclaration], requirements: [SwiftGenericRequirement]) { + var params: [SwiftGenericParameterDeclaration] = [] + var requirements: [SwiftGenericRequirement] = [] + + // Parameter clause + if let parameterClause { + for parameterNode in parameterClause.parameters { + guard parameterNode.specifier == nil else { + throw SwiftFunctionTranslationError.genericParameterSpecifier(parameterNode) + } + let param = try lookupContext.typeDeclaration(for: parameterNode) as! SwiftGenericParameterDeclaration + params.append(param) + if let inheritedNode = parameterNode.inheritedType { + let inherited = try SwiftType(inheritedNode, lookupContext: lookupContext) + requirements.append(.inherits(.genericParameter(param), inherited)) + } + } + } + + // Where clause + if let whereClause { + for requirementNode in whereClause.requirements { + let requirement: SwiftGenericRequirement + switch requirementNode.requirement { + case .conformanceRequirement(let conformance): + requirement = .inherits( + try SwiftType(conformance.leftType, lookupContext: lookupContext), + try SwiftType(conformance.rightType, lookupContext: lookupContext) + ) + case .sameTypeRequirement(let sameType): + guard let leftType = sameType.leftType.as(TypeSyntax.self) else { + throw SwiftFunctionTranslationError.expressionInGenericRequirement(requirementNode) + } + guard let rightType = sameType.rightType.as(TypeSyntax.self) else { + throw SwiftFunctionTranslationError.expressionInGenericRequirement(requirementNode) + } + requirement = .equals( + try SwiftType(leftType, lookupContext: lookupContext), + try SwiftType(rightType, lookupContext: lookupContext) + ) + case .layoutRequirement: + throw SwiftFunctionTranslationError.layoutRequirement(requirementNode) + } + requirements.append(requirement) + } + } + + return (params, requirements) } /// Translate the function signature, returning the list of translated /// parameters and effect specifiers. static func translateFunctionSignature( _ signature: FunctionSignatureSyntax, - symbolTable: SwiftSymbolTable + lookupContext: SwiftTypeLookupContext ) throws -> ([SwiftParameter], [SwiftEffectSpecifier]) { var effectSpecifiers = [SwiftEffectSpecifier]() if signature.effectSpecifiers?.throwsClause != nil { @@ -164,13 +234,18 @@ extension SwiftFunctionSignature { } let parameters = try signature.parameterClause.parameters.map { param in - try SwiftParameter(param, symbolTable: symbolTable) + try SwiftParameter(param, lookupContext: lookupContext) } return (parameters, effectSpecifiers) } - init(_ varNode: VariableDeclSyntax, isSet: Bool, enclosingType: SwiftType?, symbolTable: SwiftSymbolTable) throws { + init( + _ varNode: VariableDeclSyntax, + isSet: Bool, + enclosingType: SwiftType?, + lookupContext: SwiftTypeLookupContext + ) throws { // If this is a member of a type, so we will have a self parameter. Figure out the // type and convention for the self parameter. @@ -205,7 +280,7 @@ extension SwiftFunctionSignature { guard let varTypeNode = binding.typeAnnotation?.type else { throw SwiftFunctionTranslationError.missingTypeAnnotation(varNode) } - let valueType = try SwiftType(varTypeNode, symbolTable: symbolTable) + let valueType = try SwiftType(varTypeNode, lookupContext: lookupContext) var effectSpecifiers: [SwiftEffectSpecifier]? = nil switch binding.accessorBlock?.accessors { @@ -230,6 +305,8 @@ extension SwiftFunctionSignature { self.parameters = [] self.result = .init(convention: .direct, type: valueType) } + self.genericParameters = [] + self.genericRequirements = [] } private static func effectSpecifiers(from decl: AccessorDeclSyntax) throws -> [SwiftEffectSpecifier] { @@ -287,11 +364,13 @@ extension VariableDeclSyntax { enum SwiftFunctionTranslationError: Error { case `throws`(ThrowsClauseSyntax) case async(TokenSyntax) - case generic(GenericParameterClauseSyntax) case classMethod(TokenSyntax) case missingEnclosingType(InitializerDeclSyntax) case failableInitializer(InitializerDeclSyntax) case multipleBindings(VariableDeclSyntax) case missingTypeAnnotation(VariableDeclSyntax) case unsupportedAccessor(AccessorDeclSyntax) + case genericParameterSpecifier(GenericParameterSyntax) + case expressionInGenericRequirement(GenericRequirementSyntax) + case layoutRequirement(GenericRequirementSyntax) } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionType.swift index 565a24c3e..da6fd2a2a 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionType.swift @@ -40,18 +40,18 @@ extension SwiftFunctionType { init( _ node: FunctionTypeSyntax, convention: Convention, - symbolTable: SwiftSymbolTable + lookupContext: SwiftTypeLookupContext ) throws { self.convention = convention self.parameters = try node.parameters.map { param in let isInout = param.inoutKeyword != nil return SwiftParameter( convention: isInout ? .inout : .byValue, - type: try SwiftType(param.type, symbolTable: symbolTable) + type: try SwiftType(param.type, lookupContext: lookupContext) ) } - self.resultType = try SwiftType(node.returnClause.type, symbolTable: symbolTable) + self.resultType = try SwiftType(node.returnClause.type, lookupContext: lookupContext) // check for effect specifiers if let throwsClause = node.effectSpecifiers?.throwsClause { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift index 8be645c8b..b377fd858 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -18,9 +18,24 @@ import SwiftSyntax @_spi(Testing) public typealias NominalTypeDeclSyntaxNode = any DeclGroupSyntax & NamedDeclSyntax & WithAttributesSyntax & WithModifiersSyntax +package class SwiftTypeDeclaration { + /// The module in which this nominal type is defined. If this is a nested type, the + /// module might be different from that of the parent type, if this nominal type + /// is defined in an extension within another module. + let moduleName: String + + /// The name of this nominal type, e.g., 'MyCollection'. + let name: String + + init(moduleName: String, name: String) { + self.moduleName = moduleName + self.name = name + } +} + /// Describes a nominal type declaration, which can be of any kind (class, struct, etc.) /// and has a name, parent type (if nested), and owning module. -package class SwiftNominalTypeDeclaration { +package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { enum Kind { case actor case `class` @@ -40,14 +55,6 @@ package class SwiftNominalTypeDeclaration { /// MyCollection.Iterator. let parent: SwiftNominalTypeDeclaration? - /// The module in which this nominal type is defined. If this is a nested type, the - /// module might be different from that of the parent type, if this nominal type - /// is defined in an extension within another module. - let moduleName: String - - /// The name of this nominal type, e.g., 'MyCollection'. - let name: String - // TODO: Generic parameters. /// Identify this nominal declaration as one of the known standard library @@ -63,9 +70,7 @@ package class SwiftNominalTypeDeclaration { parent: SwiftNominalTypeDeclaration?, node: NominalTypeDeclSyntaxNode ) { - self.moduleName = moduleName self.parent = parent - self.name = node.name.text self.syntax = node // Determine the kind from the syntax node. @@ -77,6 +82,7 @@ package class SwiftNominalTypeDeclaration { case .structDecl: self.kind = .struct default: fatalError("Not a nominal type declaration") } + super.init(moduleName: moduleName, name: node.name.text) } /// Determine the known standard library type for this nominal type @@ -107,13 +113,25 @@ package class SwiftNominalTypeDeclaration { } } -extension SwiftNominalTypeDeclaration: Equatable { - package static func ==(lhs: SwiftNominalTypeDeclaration, rhs: SwiftNominalTypeDeclaration) -> Bool { +package class SwiftGenericParameterDeclaration: SwiftTypeDeclaration { + let syntax: GenericParameterSyntax + + init( + moduleName: String, + node: GenericParameterSyntax + ) { + self.syntax = node + super.init(moduleName: moduleName, name: node.name.text) + } +} + +extension SwiftTypeDeclaration: Equatable { + package static func ==(lhs: SwiftTypeDeclaration, rhs: SwiftTypeDeclaration) -> Bool { lhs === rhs } } -extension SwiftNominalTypeDeclaration: Hashable { +extension SwiftTypeDeclaration: Hashable { package func hash(into hasher: inout Hasher) { hasher.combine(ObjectIdentifier(self)) } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParameter.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParameter.swift index 24b3d8b49..75d165e9b 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParameter.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParameter.swift @@ -58,7 +58,7 @@ enum SwiftParameterConvention: Equatable { } extension SwiftParameter { - init(_ node: FunctionParameterSyntax, symbolTable: SwiftSymbolTable) throws { + init(_ node: FunctionParameterSyntax, lookupContext: SwiftTypeLookupContext) throws { // Determine the convention. The default is by-value, but there are // specifiers on the type for other conventions (like `inout`). var type = node.type @@ -90,7 +90,7 @@ extension SwiftParameter { self.convention = convention // Determine the type. - self.type = try SwiftType(type, symbolTable: symbolTable) + self.type = try SwiftType(type, lookupContext: lookupContext) // FIXME: swift-syntax itself should have these utilities based on identifiers. if let secondName = node.secondName { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift index 4026e624a..9b8ef2366 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift @@ -122,7 +122,8 @@ extension SwiftParsedModuleSymbolTableBuilder { parsedModule: symbolTable, importedModules: importedModules ) - guard let extendedType = try? SwiftType(node.extendedType, symbolTable: table) else { + let lookupContext = SwiftTypeLookupContext(symbolTable: table) + guard let extendedType = try? SwiftType(node.extendedType, lookupContext: lookupContext) else { return false } guard let extendedNominal = extendedType.asNominalTypeDeclaration else { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType+RepresentativeConcreteeType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType+RepresentativeConcreteeType.swift new file mode 100644 index 000000000..ce668485d --- /dev/null +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType+RepresentativeConcreteeType.swift @@ -0,0 +1,65 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +extension SwiftType { + /// Returns a concrete type if this is a generic parameter in the list and it + /// conforms to a protocol with representative concrete type. + func representativeConcreteTypeIn( + knownTypes: SwiftKnownTypes, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] + ) -> SwiftType? { + return representativeConcreteType( + self, + knownTypes: knownTypes, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) + } +} + +private func representativeConcreteType( + _ type: SwiftType, + knownTypes: SwiftKnownTypes, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] +) -> SwiftType? { + var maybeProto: SwiftType? = nil + switch type { + case .existential(let proto), .opaque(let proto): + maybeProto = proto + case .genericParameter(let genericParam): + // If the type is a generic parameter declared in this function and + // conforms to a protocol with representative concrete type, use it. + if genericParameters.contains(genericParam) { + for requirement in genericRequirements { + if case .inherits(let left, let right) = requirement, left == type { + guard maybeProto == nil else { + // multiple requirements on the generic parameter. + return nil + } + maybeProto = right + break + } + } + } + default: + return nil + } + + if let knownProtocol = maybeProto?.asNominalTypeDeclaration?.knownTypeKind { + return knownTypes.representativeType(of: knownProtocol) + } + return nil +} diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index d0f1f98d7..da738d39b 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -18,6 +18,8 @@ import SwiftSyntax enum SwiftType: Equatable { case nominal(SwiftNominalType) + case genericParameter(SwiftGenericParameterDeclaration) + indirect case function(SwiftFunctionType) /// `.Type` @@ -43,7 +45,7 @@ enum SwiftType: Equatable { switch self { case .nominal(let nominal): nominal case .tuple(let elements): elements.count == 1 ? elements[0].asNominalType : nil - case .function, .metatype, .optional, .existential, .opaque: nil + case .genericParameter, .function, .metatype, .optional, .existential, .opaque: nil } } @@ -86,7 +88,7 @@ enum SwiftType: Equatable { return nominal.nominalTypeDecl.isReferenceType case .metatype, .function: return true - case .optional, .tuple, .existential, .opaque: + case .genericParameter, .optional, .tuple, .existential, .opaque: return false } } @@ -98,13 +100,14 @@ extension SwiftType: CustomStringConvertible { private var postfixRequiresParentheses: Bool { switch self { case .function, .existential, .opaque: true - case .metatype, .nominal, .optional, .tuple: false + case .genericParameter, .metatype, .nominal, .optional, .tuple: false } } var description: String { switch self { case .nominal(let nominal): return nominal.description + case .genericParameter(let genericParam): return genericParam.name case .function(let functionType): return functionType.description case .metatype(let instanceType): var instanceTypeStr = instanceType.description @@ -179,7 +182,7 @@ extension SwiftNominalType { } extension SwiftType { - init(_ type: TypeSyntax, symbolTable: SwiftSymbolTable) throws { + init(_ type: TypeSyntax, lookupContext: SwiftTypeLookupContext) throws { switch type.as(TypeSyntaxEnum.self) { case .arrayType, .classRestrictionType, .compositionType, .dictionaryType, .missingType, .namedOpaqueReturnType, @@ -192,7 +195,7 @@ extension SwiftType { // FIXME: This string matching is a horrible hack. switch attributedType.attributes.trimmedDescription { case "@convention(c)", "@convention(swift)": - let innerType = try SwiftType(attributedType.baseType, symbolTable: symbolTable) + let innerType = try SwiftType(attributedType.baseType, lookupContext: lookupContext) switch innerType { case .function(var functionType): let isConventionC = attributedType.attributes.trimmedDescription == "@convention(c)" @@ -208,7 +211,7 @@ extension SwiftType { case .functionType(let functionType): self = .function( - try SwiftFunctionType(functionType, convention: .swift, symbolTable: symbolTable) + try SwiftFunctionType(functionType, convention: .swift, lookupContext: lookupContext) ) case .identifierType(let identifierType): @@ -217,7 +220,7 @@ extension SwiftType { try genericArgumentClause.arguments.map { argument in switch argument.argument { case .type(let argumentTy): - try SwiftType(argumentTy, symbolTable: symbolTable) + try SwiftType(argumentTy, lookupContext: lookupContext) default: throw TypeTranslationError.unimplementedType(type) } @@ -228,13 +231,13 @@ extension SwiftType { self = try SwiftType( originalType: type, parent: nil, - name: identifierType.name.text, + name: identifierType.name, genericArguments: genericArgs, - symbolTable: symbolTable + lookupContext: lookupContext ) case .implicitlyUnwrappedOptionalType(let optionalType): - self = .optional(try SwiftType(optionalType.wrappedType, symbolTable: symbolTable)) + self = .optional(try SwiftType(optionalType.wrappedType, lookupContext: lookupContext)) case .memberType(let memberType): // If the parent type isn't a known module, translate it. @@ -244,7 +247,7 @@ extension SwiftType { if memberType.baseType.trimmedDescription == "Swift" { parentType = nil } else { - parentType = try SwiftType(memberType.baseType, symbolTable: symbolTable) + parentType = try SwiftType(memberType.baseType, lookupContext: lookupContext) } // Translate the generic arguments. @@ -252,7 +255,7 @@ extension SwiftType { try genericArgumentClause.arguments.map { argument in switch argument.argument { case .type(let argumentTy): - try SwiftType(argumentTy, symbolTable: symbolTable) + try SwiftType(argumentTy, lookupContext: lookupContext) default: throw TypeTranslationError.unimplementedType(type) } @@ -262,27 +265,27 @@ extension SwiftType { self = try SwiftType( originalType: type, parent: parentType, - name: memberType.name.text, + name: memberType.name, genericArguments: genericArgs, - symbolTable: symbolTable + lookupContext: lookupContext ) case .metatypeType(let metatypeType): - self = .metatype(try SwiftType(metatypeType.baseType, symbolTable: symbolTable)) + self = .metatype(try SwiftType(metatypeType.baseType, lookupContext: lookupContext)) case .optionalType(let optionalType): - self = .optional(try SwiftType(optionalType.wrappedType, symbolTable: symbolTable)) + self = .optional(try SwiftType(optionalType.wrappedType, lookupContext: lookupContext)) case .tupleType(let tupleType): self = try .tuple(tupleType.elements.map { element in - try SwiftType(element.type, symbolTable: symbolTable) + try SwiftType(element.type, lookupContext: lookupContext) }) case .someOrAnyType(let someOrAntType): if someOrAntType.someOrAnySpecifier.tokenKind == .keyword(.some) { - self = .opaque(try SwiftType(someOrAntType.constraint, symbolTable: symbolTable)) + self = .opaque(try SwiftType(someOrAntType.constraint, lookupContext: lookupContext)) } else { - self = .opaque(try SwiftType(someOrAntType.constraint, symbolTable: symbolTable)) + self = .opaque(try SwiftType(someOrAntType.constraint, lookupContext: lookupContext)) } } } @@ -290,25 +293,37 @@ extension SwiftType { init( originalType: TypeSyntax, parent: SwiftType?, - name: String, + name: TokenSyntax, genericArguments: [SwiftType]?, - symbolTable: SwiftSymbolTable + lookupContext: SwiftTypeLookupContext ) throws { // Look up the imported types by name to resolve it to a nominal type. - guard let nominalTypeDecl = symbolTable.lookupType( - name, - parent: parent?.asNominalTypeDeclaration - ) else { - throw TypeTranslationError.unknown(originalType) + let typeDecl: SwiftTypeDeclaration? + if let parent { + guard let parentDecl = parent.asNominalTypeDeclaration else { + throw TypeTranslationError.unknown(originalType) + } + typeDecl = lookupContext.symbolTable.lookupNestedType(name.text, parent: parentDecl) + } else { + typeDecl = try lookupContext.unqualifiedLookup(name: Identifier(name)!, from: name) + } + guard let typeDecl else { + throw TypeTranslationError.unknown(originalType) } - self = .nominal( - SwiftNominalType( - parent: parent?.asNominalType, - nominalTypeDecl: nominalTypeDecl, - genericArguments: genericArguments + if let nominalDecl = typeDecl as? SwiftNominalTypeDeclaration { + self = .nominal( + SwiftNominalType( + parent: parent?.asNominalType, + nominalTypeDecl: nominalDecl, + genericArguments: genericArguments + ) ) - ) + } else if let genericParamDecl = typeDecl as? SwiftGenericParameterDeclaration { + self = .genericParameter(genericParamDecl) + } else { + fatalError("unknown SwiftTypeDeclaration: \(type(of: typeDecl))") + } } init?( @@ -345,11 +360,11 @@ extension SwiftType { enum TypeTranslationError: Error { /// We haven't yet implemented support for this type. - case unimplementedType(TypeSyntax) + case unimplementedType(TypeSyntax, file: StaticString = #file, line: Int = #line) /// Missing generic arguments. - case missingGenericArguments(TypeSyntax) + case missingGenericArguments(TypeSyntax, file: StaticString = #file, line: Int = #line) /// Unknown nominal type. - case unknown(TypeSyntax) + case unknown(TypeSyntax, file: StaticString = #file, line: Int = #line) } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift new file mode 100644 index 000000000..9ede2b1b9 --- /dev/null +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift @@ -0,0 +1,151 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftSyntax +@_spi(Experimental) import SwiftLexicalLookup + +/// Unqualified type lookup manager. +/// All unqualified lookup should be done via this instance. This caches the +/// association of `Syntax.ID` to `SwiftTypeDeclaration`, and guarantees that +/// there's only one `SwiftTypeDeclaration` per declaration `Syntax`. +class SwiftTypeLookupContext { + var symbolTable: SwiftSymbolTable + + private var typeDecls: [Syntax.ID: SwiftTypeDeclaration] = [:] + + init(symbolTable: SwiftSymbolTable) { + self.symbolTable = symbolTable + } + + /// Perform unqualified type lookup. + /// + /// - Parameters: + /// - name: name to lookup + /// - node: `Syntax` node the lookup happened + func unqualifiedLookup(name: Identifier, from node: some SyntaxProtocol) throws -> SwiftTypeDeclaration? { + + for result in node.lookup(name) { + switch result { + case .fromScope(_, let names): + if !names.isEmpty { + return typeDeclaration(for: names) + } + + case .fromFileScope(_, let names): + if !names.isEmpty { + return typeDeclaration(for: names) + } + + case .lookInMembers(let scopeNode): + if let nominalDecl = try typeDeclaration(for: scopeNode) { + if let found = symbolTable.lookupNestedType(name.name, parent: nominalDecl as! SwiftNominalTypeDeclaration) { + return found + } + } + + case .lookInGenericParametersOfExtendedType(let extensionNode): + // TODO: Implement + _ = extensionNode + break + + case .mightIntroduceDollarIdentifiers: + // Dollar identifier can't be a type, ignore. + break + } + } + + // Fallback to global symbol table lookup. + return symbolTable.lookupTopLevelNominalType(name.name) + } + + /// Find the first type declaration in the `LookupName` results. + private func typeDeclaration(for names: [LookupName]) -> SwiftTypeDeclaration? { + for name in names { + switch name { + case .identifier(let identifiableSyntax, _): + return try? typeDeclaration(for: identifiableSyntax) + case .declaration(let namedDeclSyntax): + return try? typeDeclaration(for: namedDeclSyntax) + case .implicit(let implicitDecl): + // TODO: Implement + _ = implicitDecl + break + case .dollarIdentifier: + break + } + } + return nil + } + + /// Returns the type declaration object associated with the `Syntax` node. + /// If there's no declaration created, create an instance on demand, and cache it. + func typeDeclaration(for node: some SyntaxProtocol) throws -> SwiftTypeDeclaration? { + if let found = typeDecls[node.id] { + return found + } + + let typeDecl: SwiftTypeDeclaration + switch Syntax(node).as(SyntaxEnum.self) { + case .genericParameter(let node): + typeDecl = SwiftGenericParameterDeclaration(moduleName: symbolTable.moduleName, node: node) + case .classDecl(let node): + typeDecl = try nominalTypeDeclaration(for: node) + case .actorDecl(let node): + typeDecl = try nominalTypeDeclaration(for: node) + case .structDecl(let node): + typeDecl = try nominalTypeDeclaration(for: node) + case .enumDecl(let node): + typeDecl = try nominalTypeDeclaration(for: node) + case .protocolDecl(let node): + typeDecl = try nominalTypeDeclaration(for: node) + case .typeAliasDecl: + fatalError("typealias not implemented") + case .associatedTypeDecl: + fatalError("associatedtype not implemented") + default: + throw TypeLookupError.notType(Syntax(node)) + } + + typeDecls[node.id] = typeDecl + return typeDecl + } + + /// Create a nominal type declaration instance for the specified syntax node. + private func nominalTypeDeclaration(for node: NominalTypeDeclSyntaxNode) throws -> SwiftNominalTypeDeclaration { + SwiftNominalTypeDeclaration( + moduleName: self.symbolTable.moduleName, + parent: try parentTypeDecl(for: node), + node: node + ) + } + + /// Find a parent nominal type declaration of the specified syntax node. + private func parentTypeDecl(for node: some DeclSyntaxProtocol) throws -> SwiftNominalTypeDeclaration? { + var node: DeclSyntax = DeclSyntax(node) + while let parentDecl = node.ancestorDecl { + switch parentDecl.as(DeclSyntaxEnum.self) { + case .structDecl, .classDecl, .actorDecl, .enumDecl, .protocolDecl: + return (try typeDeclaration(for: parentDecl) as! SwiftNominalTypeDeclaration) + default: + node = parentDecl + continue + } + } + return nil + } +} + +enum TypeLookupError: Error { + case notType(Syntax) +} diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index 4b0162f34..ffefe9078 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -37,6 +37,7 @@ func assertOutput( column: Int = #column ) throws { var config = Configuration() + config.logLevel = .trace config.swiftModule = swiftModuleName let translator = Swift2JavaTranslator(config: config) translator.dependenciesClasses = Array(javaClassLookupTable.keys) diff --git a/Tests/JExtractSwiftTests/DataImportTests.swift b/Tests/JExtractSwiftTests/DataImportTests.swift index af71c157e..7416779dc 100644 --- a/Tests/JExtractSwiftTests/DataImportTests.swift +++ b/Tests/JExtractSwiftTests/DataImportTests.swift @@ -28,7 +28,7 @@ final class DataImportTests { """ import Foundation - public func receiveDataProtocol(dat: some DataProtocol) + public func receiveDataProtocol(dat: some DataProtocol, dat2: T?) """ @@ -342,9 +342,9 @@ final class DataImportTests { import Foundation """, """ - @_cdecl("swiftjava_SwiftModule_receiveDataProtocol_dat") - public func swiftjava_SwiftModule_receiveDataProtocol_dat(_ dat: UnsafeRawPointer) { - receiveDataProtocol(dat: dat.assumingMemoryBound(to: Data.self).pointee) + @_cdecl("swiftjava_SwiftModule_receiveDataProtocol_dat_dat2") + public func swiftjava_SwiftModule_receiveDataProtocol_dat_dat2(_ dat: UnsafeRawPointer, _ dat2: UnsafeRawPointer?) { + receiveDataProtocol(dat: dat.assumingMemoryBound(to: Data.self).pointee, dat2: dat2?.assumingMemoryBound(to: Data.self).pointee) } """, @@ -365,22 +365,23 @@ final class DataImportTests { """ /** * {@snippet lang=c : - * void swiftjava_SwiftModule_receiveDataProtocol_dat(const void *dat) + * void swiftjava_SwiftModule_receiveDataProtocol_dat_dat2(const void *dat, const void *dat2) * } */ - private static class swiftjava_SwiftModule_receiveDataProtocol_dat { + private static class swiftjava_SwiftModule_receiveDataProtocol_dat_dat2 { private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( - /* dat: */SwiftValueLayout.SWIFT_POINTER + /* dat: */SwiftValueLayout.SWIFT_POINTER, + /* dat2: */SwiftValueLayout.SWIFT_POINTER ); private static final MemorySegment ADDR = - SwiftModule.findOrThrow("swiftjava_SwiftModule_receiveDataProtocol_dat"); + SwiftModule.findOrThrow("swiftjava_SwiftModule_receiveDataProtocol_dat_dat2"); private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); - public static void call(java.lang.foreign.MemorySegment dat) { + public static void call(java.lang.foreign.MemorySegment dat, java.lang.foreign.MemorySegment dat2) { try { if (CallTraces.TRACE_DOWNCALLS) { - CallTraces.traceDowncall(dat); + CallTraces.traceDowncall(dat, dat2); } - HANDLE.invokeExact(dat); + HANDLE.invokeExact(dat, dat2); } catch (Throwable ex$) { throw new AssertionError("should not reach here", ex$); } @@ -392,11 +393,11 @@ final class DataImportTests { /** * Downcall to Swift: * {@snippet lang=swift : - * public func receiveDataProtocol(dat: some DataProtocol) + * public func receiveDataProtocol(dat: some DataProtocol, dat2: T?) * } */ - public static void receiveDataProtocol(Data dat) { - swiftjava_SwiftModule_receiveDataProtocol_dat.call(dat.$memorySegment()); + public static void receiveDataProtocol(Data dat, Optional dat2) { + swiftjava_SwiftModule_receiveDataProtocol_dat_dat2.call(dat.$memorySegment(), SwiftRuntime.toOptionalSegmentInstance(dat2)); } """, diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift index 8419ec61c..ba1aad064 100644 --- a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift +++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift @@ -404,6 +404,27 @@ final class FunctionLoweringTests { """) } + @Test("Lowering generic parameters") + func genericParam() throws { + try assertLoweredFunction( + """ + func fn(x: T, y: U?) where T: DataProtocol + """, + sourceFile: """ + import Foundation + """, + expectedCDecl: """ + @_cdecl("c_fn") + public func c_fn(_ x: UnsafeRawPointer, _ y: UnsafeRawPointer?) { + fn(x: x.assumingMemoryBound(to: Data.self).pointee, y: y?.assumingMemoryBound(to: Data.self).pointee) + } + """, + expectedCFunction: """ + void c_fn(const void *x, const void *y) + """ + ) + } + @Test("Lowering read accessor") func lowerGlobalReadAccessor() throws { try assertLoweredVariableAccessor( From ac30ee40131c3062ff1131d74bcc9a1d493bcf8c Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Thu, 31 Jul 2025 17:25:52 +0900 Subject: [PATCH 047/141] Initial support for unsigned numbers in jextract (#333) --- .unacceptablelanguageignore | 3 +- .../MySwiftLibrary/MySwiftLibrary.swift | 5 + .../com/example/swift/MySwiftLibraryTest.java | 7 + .../Sources/MySwiftLibrary/MySwiftClass.swift | 12 + .../example/swift/UnsignedNumbersTest.java | 36 +++ .../Common/TypeAnnotations.swift | 26 ++ .../Configuration+Extensions.swift | 25 ++ ...t2JavaGenerator+JavaBindingsPrinting.swift | 40 ++- ...MSwift2JavaGenerator+JavaTranslation.swift | 112 +++++++- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 4 +- .../FFM/FFMSwift2JavaGenerator.swift | 14 +- .../FFM/ForeignValueLayouts.swift | 8 + Sources/JExtractSwiftLib/ImportedDecls.swift | 9 + .../JNI/JNIJavaTypeTranslator.swift | 57 ++++ ...t2JavaGenerator+JavaBindingsPrinting.swift | 20 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 136 ++++++--- ...wift2JavaGenerator+NativeTranslation.swift | 16 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 2 +- .../JNI/JNISwift2JavaGenerator.swift | 7 +- Sources/JExtractSwiftLib/JNI/JNIType.swift | 2 +- Sources/JExtractSwiftLib/JavaParameter.swift | 17 +- .../JavaType+JDK.swift} | 0 .../JavaTypes/JavaType+SwiftKit.swift | 88 ++++++ Sources/JExtractSwiftLib/Swift2Java.swift | 2 + .../SwiftTypes/SwiftType.swift | 11 + .../BridgedValues/JavaValue+Integers.swift | 178 ++++++++++++ .../Configuration.swift | 5 + .../GenerationMode.swift | 44 +++ Sources/JavaTypes/JavaAnnotation.swift | 47 +++ Sources/JavaTypes/JavaType+JavaSource.swift | 15 +- Sources/JavaTypes/JavaType+SwiftNames.swift | 44 +-- Sources/JavaTypes/JavaType.swift | 34 ++- .../Documentation.docc/SupportedFeatures.md | 70 ++++- .../SwiftJavaLib/JavaClassTranslator.swift | 6 +- .../Commands/JExtractCommand.swift | 32 +++ .../Commands/ResolveCommand.swift | 2 +- Sources/_Subprocess/Configuration.swift | 2 +- SwiftKitCore/build.gradle | 16 +- .../swiftkit/core/NotImplementedError.java | 24 ++ .../swift/swiftkit/core/Preconditions.java | 153 ++++++++++ .../swiftkit/core/annotations/NonNull.java | 28 ++ .../swiftkit/core/annotations/Nullable.java | 28 ++ .../swiftkit/core/annotations/Unsigned.java | 42 +++ .../swift/swiftkit/ffm/SwiftValueLayout.java | 10 +- .../Asserts/LoweringAssertions.swift | 2 + .../Asserts/TextAssertions.swift | 6 +- .../FuncCallbackImportTests.swift | 4 +- .../FunctionDescriptorImportTests.swift | 2 + .../JNI/JNIClassTests.swift | 172 +++++------ .../JNI/JNIModuleTests.swift | 5 + .../JNI/JNIStructTests.swift | 128 +++++---- .../JNI/JNIUnsignedNumberTests.swift | 154 ++++++++++ .../JNI/JNIVariablesTests.swift | 1 + .../MethodImportTests.swift | 9 + .../UnsignedNumberTests.swift | 268 ++++++++++++++++++ 55 files changed, 1922 insertions(+), 268 deletions(-) create mode 100644 Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedNumbersTest.java create mode 100644 Sources/JExtractSwiftLib/Common/TypeAnnotations.swift create mode 100644 Sources/JExtractSwiftLib/Configuration+Extensions.swift create mode 100644 Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift rename Sources/JExtractSwiftLib/{JavaConstants/JavaTypes.swift => JavaTypes/JavaType+JDK.swift} (100%) create mode 100644 Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift create mode 100644 Sources/JavaTypes/JavaAnnotation.swift create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/NotImplementedError.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/Preconditions.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/NonNull.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Nullable.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Unsigned.java create mode 100644 Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift create mode 100644 Tests/JExtractSwiftTests/UnsignedNumberTests.swift diff --git a/.unacceptablelanguageignore b/.unacceptablelanguageignore index 92a1d726e..c3f97d3d4 100644 --- a/.unacceptablelanguageignore +++ b/.unacceptablelanguageignore @@ -3,4 +3,5 @@ Sources/_Subprocess/Platforms/Subprocess+Darwin.swift Sources/_Subprocess/Platforms/Subprocess+Linux.swift Sources/_Subprocess/Platforms/Subprocess+Unix.swift Sources/_Subprocess/Teardown.swift -Sources/_Subprocess/Subprocess.swift \ No newline at end of file +Sources/_Subprocess/Subprocess.swift +NOTICE.txt \ No newline at end of file diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index 1dd845470..09903638e 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -45,6 +45,11 @@ public func globalTakeIntInt(i: Int64, j: Int64) { p("i:\(i), j:\(j)") } +public func echoUnsignedInt(i: UInt32, j: UInt64) -> UInt64 { + p("i:\(i), j:\(j)") + return UInt64(i) + j +} + // ==== Internal helpers func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: String = #function) { diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java index 5c9c2358d..6da2fd4b0 100644 --- a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -61,4 +61,11 @@ void globalVariable() { MySwiftLibrary.setGlobalVariable(100); assertEquals(100, MySwiftLibrary.getGlobalVariable()); } + + @Test + void globalUnsignedIntEcho() { + int i = 12; + long l = 1200; + assertEquals(1212, MySwiftLibrary.echoUnsignedInt(12, 1200)); + } } \ No newline at end of file diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index 306610456..e1139c2b3 100644 --- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -60,4 +60,16 @@ public class MySwiftClass { public func makeRandomIntMethod() -> Int { return Int.random(in: 1..<256) } + + public func takeUnsignedChar(arg: UInt16) { + p("\(UInt32.self) = \(arg)") + } + + public func takeUnsignedInt(arg: UInt32) { + p("\(UInt32.self) = \(arg)") + } + + public func takeUnsignedLong(arg: UInt64) { + p("\(UInt64.self) = \(arg)") + } } diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedNumbersTest.java b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedNumbersTest.java new file mode 100644 index 000000000..beb0f817f --- /dev/null +++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedNumbersTest.java @@ -0,0 +1,36 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.ffm.AllocatingSwiftArena; + +public class UnsignedNumbersTest { + @Test + void take_uint32() { + try (var arena = AllocatingSwiftArena.ofConfined()) { + var c = MySwiftClass.init(1, 2, arena); + c.takeUnsignedInt(128); + } + } + + @Test + void take_uint64() { + try (var arena = AllocatingSwiftArena.ofConfined()) { + var c = MySwiftClass.init(1, 2, arena); + c.takeUnsignedLong(Long.MAX_VALUE); + } + } +} diff --git a/Sources/JExtractSwiftLib/Common/TypeAnnotations.swift b/Sources/JExtractSwiftLib/Common/TypeAnnotations.swift new file mode 100644 index 000000000..70a86e820 --- /dev/null +++ b/Sources/JExtractSwiftLib/Common/TypeAnnotations.swift @@ -0,0 +1,26 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JavaTypes +import JavaKitConfigurationShared + +/// Determine if the given type needs any extra annotations that should be included +/// in Java sources when the corresponding Java type is rendered. +func getTypeAnnotations(swiftType: SwiftType, config: Configuration) -> [JavaAnnotation] { + if swiftType.isUnsignedInteger, config.effectiveUnsignedNumbersMode == .annotate { + return [JavaAnnotation.unsigned] + } + + return [] +} diff --git a/Sources/JExtractSwiftLib/Configuration+Extensions.swift b/Sources/JExtractSwiftLib/Configuration+Extensions.swift new file mode 100644 index 000000000..d85cf4472 --- /dev/null +++ b/Sources/JExtractSwiftLib/Configuration+Extensions.swift @@ -0,0 +1,25 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JavaKitConfigurationShared // TODO: this should become SwiftJavaConfigurationShared +import JavaTypes // TODO: this should become SwiftJavaConfigurationShared + +extension Configuration { + public var effectiveUnsignedNumericsMode: UnsignedNumericsMode { + switch effectiveUnsignedNumbersMode { + case .annotate: .ignoreSign + case .wrapGuava: .wrapUnsignedGuava + } + } +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index 28b3aba12..b87492999 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -105,9 +105,16 @@ extension FFMSwift2JavaGenerator { var params: [String] = [] var args: [String] = [] for param in cFunc.parameters { - // ! unwrapping because cdecl lowering guarantees the parameter named. - params.append("\(param.type.javaType) \(param.name!)") - args.append(param.name!) + let name = param.name! // !-safe, because cdecl lowering guarantees the parameter named. + + let annotationsStr = + if param.type.javaType.parameterAnnotations.isEmpty { + "" + } else { + param.type.javaType.parameterAnnotations.map({$0.render()}).joined(separator: " ") + " " + } + params.append("\(annotationsStr)\(param.type.javaType) \(name)") + args.append(name) } let paramsStr = params.joined(separator: ", ") let argsStr = args.joined(separator: ", ") @@ -316,23 +323,21 @@ extension FFMSwift2JavaGenerator { let translatedSignature = translated.translatedSignature let returnTy = translatedSignature.result.javaResultType + var annotationsStr = translatedSignature.annotations.map({ $0.render() }).joined(separator: "\n") + if !annotationsStr.isEmpty { annotationsStr += "\n" } + var paramDecls = translatedSignature.parameters .flatMap(\.javaParameters) - .map { "\($0.type) \($0.name)" } + .map { $0.renderParameter() } if translatedSignature.requiresSwiftArena { paramDecls.append("AllocatingSwiftArena swiftArena$") } // TODO: we could copy the Swift method's documentation over here, that'd be great UX + printDeclDocumentation(&printer, decl) printer.printBraceBlock( """ - /** - * Downcall to Swift: - * {@snippet lang=swift : - * \(decl.signatureString) - * } - */ - \(modifiers) \(returnTy) \(methodName)(\(paramDecls.joined(separator: ", "))) + \(annotationsStr)\(modifiers) \(returnTy) \(methodName)(\(paramDecls.joined(separator: ", "))) """ ) { printer in if case .instance(_) = decl.functionSignature.selfParameter { @@ -344,6 +349,19 @@ extension FFMSwift2JavaGenerator { } } + private func printDeclDocumentation(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + printer.print( + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * \(decl.signatureString) + * } + */ + """ + ) + } + /// Print the actual downcall to the Swift API. /// /// This assumes that all the parameters are passed-in with appropriate names. diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index b29ca5d9e..f8b2e7e84 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import JavaTypes +import JavaKitConfigurationShared extension FFMSwift2JavaGenerator { func translatedDecl( @@ -24,7 +25,9 @@ extension FFMSwift2JavaGenerator { let translated: TranslatedFunctionDecl? do { - let translation = JavaTranslation(knownTypes: SwiftKnownTypes(symbolTable: lookupContext.symbolTable)) + let translation = JavaTranslation( + config: self.config, + knownTypes: SwiftKnownTypes(symbolTable: lookupContext.symbolTable)) translated = try translation.translate(decl) } catch { self.log.info("Failed to translate: '\(decl.swiftDecl.qualifiedNameForDebug)'; \(error)") @@ -52,6 +55,9 @@ extension FFMSwift2JavaGenerator { /// Java type that represents the Swift result type. var javaResultType: JavaType + /// Java annotations that should be propagated from the result type onto the method + var annotations: [JavaAnnotation] = [] + /// Required indirect return receivers for receiving the result. /// /// 'JavaParameter.name' is the suffix for the receiver variable names. For example @@ -85,8 +91,13 @@ extension FFMSwift2JavaGenerator { /// Function signature. let translatedSignature: TranslatedFunctionSignature - /// Cdecl lowerd signature. + /// Cdecl lowered signature. let loweredSignature: LoweredFunctionSignature + + /// Annotations to include on the Java function declaration + var annotations: [JavaAnnotation] { + self.translatedSignature.annotations + } } /// Function signature for a Java API. @@ -94,6 +105,12 @@ extension FFMSwift2JavaGenerator { var selfParameter: TranslatedParameter? var parameters: [TranslatedParameter] var result: TranslatedResult + + // if the result type implied any annotations, + // propagate them onto the function the result is returned from + var annotations: [JavaAnnotation] { + self.result.annotations + } } /// Represent a Swift closure type in the user facing Java API. @@ -113,15 +130,15 @@ extension FFMSwift2JavaGenerator { } struct JavaTranslation { + let config: Configuration var knownTypes: SwiftKnownTypes - init(knownTypes: SwiftKnownTypes) { + init(config: Configuration, knownTypes: SwiftKnownTypes) { + self.config = config self.knownTypes = knownTypes } - func translate( - _ decl: ImportedFunc - ) throws -> TranslatedFunctionDecl { + func translate(_ decl: ImportedFunc) throws -> TranslatedFunctionDecl { let lowering = CdeclLowering(knownTypes: knownTypes) let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature) @@ -303,6 +320,18 @@ extension FFMSwift2JavaGenerator { genericParameters: [SwiftGenericParameterDeclaration], genericRequirements: [SwiftGenericRequirement] ) throws -> TranslatedParameter { + // If the result type should cause any annotations on the method, include them here. + let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config) + + // If we need to handle unsigned integers do so here + if config.effectiveUnsignedNumbersMode.needsConversion { + if let unsignedWrapperType = JavaType.unsignedWrapper(for: swiftType) { + return TranslatedParameter( + javaParameters: [ + JavaParameter(name: parameterName, type: unsignedWrapperType, annotations: parameterAnnotations) + ], conversion: .call(.placeholder, function: "UnsignedNumbers.toPrimitive", withArena: false)) + } + } // If there is a 1:1 mapping between this Swift type and a C type, that can // be expressed as a Java primitive type. @@ -311,8 +340,9 @@ extension FFMSwift2JavaGenerator { return TranslatedParameter( javaParameters: [ JavaParameter( - name: parameterName, type: javaType - ) + name: parameterName, + type: javaType, + annotations: parameterAnnotations) ], conversion: .placeholder ) @@ -324,7 +354,10 @@ extension FFMSwift2JavaGenerator { return TranslatedParameter( javaParameters: [ JavaParameter( - name: parameterName, type: JavaType.class(package: "org.swift.swiftkit.ffm", name: "SwiftAnyType")) + name: parameterName, + type: JavaType.class(package: "org.swift.swiftkit.ffm", name: "SwiftAnyType"), + annotations: parameterAnnotations + ) ], conversion: .swiftValueSelfSegment(.placeholder) ) @@ -524,6 +557,37 @@ extension FFMSwift2JavaGenerator { } } + func unsignedResultConversion(_ from: SwiftType, to javaType: JavaType, + mode: JExtractUnsignedIntegerMode) -> JavaConversionStep { + switch mode { + case .annotate: + return .placeholder // no conversions + + case .wrapGuava: + guard let typeName = javaType.fullyQualifiedClassName else { + fatalError("Missing target class name for result conversion step from \(from) to \(javaType)") + } + + switch from { + case .nominal(let nominal): + switch nominal.nominalTypeDecl.knownTypeKind { + case .uint8: + return .call(.placeholder, function: "\(typeName).fromIntBits", withArena: false) + case .uint16: + return .placeholder // no conversion, UInt16 can be returned as-is and will be seen as char by Java + case .uint32: + return .call(.placeholder, function: "\(typeName).fromIntBits", withArena: false) + case .uint64: + return .call(.placeholder, function: "\(typeName).fromLongBits", withArena: false) + default: + fatalError("unsignedResultConversion: Unsupported conversion from \(from) to \(javaType)") + } + default: + fatalError("unsignedResultConversion: Unsupported conversion from \(from) to \(javaType)") + } + } + } + /// Translate a Swift API result to the user-facing Java API result. func translate( swiftResult: SwiftResult, @@ -531,12 +595,29 @@ extension FFMSwift2JavaGenerator { ) throws -> TranslatedResult { let swiftType = swiftResult.type + // If we need to handle unsigned integers do so here + if config.effectiveUnsignedNumbersMode.needsConversion { + if let unsignedWrapperType = JavaType.unsignedWrapper(for: swiftType) /* and we're in safe wrapper mode */ { + return TranslatedResult( + javaResultType: unsignedWrapperType, + outParameters: [], + conversion: unsignedResultConversion( + swiftType, to: unsignedWrapperType, + mode: self.config.effectiveUnsignedNumbersMode) + ) + } + } + + // If the result type should cause any annotations on the method, include them here. + let resultAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config) + // If there is a 1:1 mapping between this Swift type and a C type, that can // be expressed as a Java primitive type. if let cType = try? CType(cdeclType: swiftType) { let javaType = cType.javaType return TranslatedResult( javaResultType: javaType, + annotations: resultAnnotations, outParameters: [], conversion: .placeholder ) @@ -548,6 +629,7 @@ extension FFMSwift2JavaGenerator { let javaType = JavaType.class(package: "org.swift.swiftkit.ffm", name: "SwiftAnyType") return TranslatedResult( javaResultType: javaType, + annotations: resultAnnotations, outParameters: [], conversion: .construct(.placeholder, javaType) ) @@ -558,6 +640,7 @@ extension FFMSwift2JavaGenerator { case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: return TranslatedResult( javaResultType: .javaForeignMemorySegment, + annotations: resultAnnotations, outParameters: [ JavaParameter(name: "pointer", type: .javaForeignMemorySegment), JavaParameter(name: "count", type: .long), @@ -597,6 +680,7 @@ extension FFMSwift2JavaGenerator { let javaType: JavaType = .class(package: nil, name: swiftNominalType.nominalTypeDecl.name) return TranslatedResult( javaResultType: javaType, + annotations: resultAnnotations, outParameters: [ JavaParameter(name: "", type: javaType) ], @@ -702,7 +786,7 @@ extension CType { case .integral(.signed(bits: 32)): return .int case .integral(.signed(bits: 64)): return .long case .integral(.unsigned(bits: 8)): return .byte - case .integral(.unsigned(bits: 16)): return .short + case .integral(.unsigned(bits: 16)): return .char // char is Java's only unsigned primitive, we can use it! case .integral(.unsigned(bits: 32)): return .int case .integral(.unsigned(bits: 64)): return .long @@ -739,10 +823,10 @@ extension CType { case .integral(.signed(bits: 32)): return .SwiftInt32 case .integral(.signed(bits: 64)): return .SwiftInt64 - case .integral(.unsigned(bits: 8)): return .SwiftInt8 - case .integral(.unsigned(bits: 16)): return .SwiftInt16 - case .integral(.unsigned(bits: 32)): return .SwiftInt32 - case .integral(.unsigned(bits: 64)): return .SwiftInt64 + case .integral(.unsigned(bits: 8)): return .SwiftUInt8 + case .integral(.unsigned(bits: 16)): return .SwiftUInt16 + case .integral(.unsigned(bits: 32)): return .SwiftUInt32 + case .integral(.unsigned(bits: 64)): return .SwiftUInt64 case .floating(.double): return .SwiftDouble case .floating(.float): return .SwiftFloat diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift index 1e32d1e59..f35973eb9 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -38,7 +38,7 @@ extension FFMSwift2JavaGenerator { let moduleFilenameBase = "\(self.swiftModuleName)Module+SwiftJava" let moduleFilename = "\(moduleFilenameBase).swift" do { - log.info("Printing contents: \(moduleFilename)") + log.debug("Printing contents: \(moduleFilename)") try printGlobalSwiftThunkSources(&printer) @@ -58,7 +58,7 @@ extension FFMSwift2JavaGenerator { for (_, ty) in self.analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) { let fileNameBase = "\(ty.swiftNominal.qualifiedName)+SwiftJava" let filename = "\(fileNameBase).swift" - log.info("Printing contents: \(filename)") + log.debug("Printing contents: \(filename)") do { try printSwiftThunkSources(&printer, ty: ty) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index d30d3e749..299626213 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -15,10 +15,12 @@ import JavaTypes import SwiftSyntax import SwiftSyntaxBuilder +import JavaKitConfigurationShared import struct Foundation.URL package class FFMSwift2JavaGenerator: Swift2JavaGenerator { let log: Logger + let config: Configuration let analysis: AnalysisResult let swiftModuleName: String let javaPackage: String @@ -40,12 +42,14 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { var expectedOutputSwiftFiles: Set package init( + config: Configuration, translator: Swift2JavaTranslator, javaPackage: String, swiftOutputDirectory: String, javaOutputDirectory: String ) { self.log = Logger(label: "ffm-generator", logLevel: translator.log.logLevel) + self.config = config self.analysis = translator.result self.swiftModuleName = translator.swiftModuleName self.javaPackage = javaPackage @@ -96,7 +100,9 @@ extension FFMSwift2JavaGenerator { "org.swift.swiftkit.core.*", "org.swift.swiftkit.core.util.*", "org.swift.swiftkit.ffm.*", - "org.swift.swiftkit.ffm.SwiftRuntime", + + // NonNull, Unsigned and friends + "org.swift.swiftkit.core.annotations.*", // Necessary for native calls and type mapping "java.lang.foreign.*", @@ -120,7 +126,7 @@ extension FFMSwift2JavaGenerator { package func writeExportedJavaSources(printer: inout CodePrinter) throws { for (_, ty) in analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) { let filename = "\(ty.swiftNominal.name).java" - log.info("Printing contents: \(filename)") + log.debug("Printing contents: \(filename)") printImportedNominal(&printer, ty) if let outputFile = try printer.writeContents( @@ -134,7 +140,7 @@ extension FFMSwift2JavaGenerator { do { let filename = "\(self.swiftModuleName).java" - log.info("Printing contents: \(filename)") + log.debug("Printing contents: \(filename)") printModule(&printer) if let outputFile = try printer.writeContents( @@ -178,7 +184,7 @@ extension FFMSwift2JavaGenerator { func printImportedNominal(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { printHeader(&printer) printPackage(&printer) - printImports(&printer) + printImports(&printer) // TODO: we could have some imports be driven from types used in the generated decl printNominal(&printer, decl) { printer in // We use a static field to abuse the initialization order such that by the time we get type metadata, diff --git a/Sources/JExtractSwiftLib/FFM/ForeignValueLayouts.swift b/Sources/JExtractSwiftLib/FFM/ForeignValueLayouts.swift index 3784a75a0..329efaad3 100644 --- a/Sources/JExtractSwiftLib/FFM/ForeignValueLayouts.swift +++ b/Sources/JExtractSwiftLib/FFM/ForeignValueLayouts.swift @@ -67,11 +67,19 @@ extension ForeignValueLayout { public static let SwiftBool = Self(javaConstant: "SWIFT_BOOL") public static let SwiftInt = Self(javaConstant: "SWIFT_INT") + public static let SwiftUInt = Self(javaConstant: "SWIFT_UINT") + public static let SwiftInt64 = Self(javaConstant: "SWIFT_INT64") + public static let SwiftUInt64 = Self(javaConstant: "SWIFT_UINT64") + public static let SwiftInt32 = Self(javaConstant: "SWIFT_INT32") + public static let SwiftUInt32 = Self(javaConstant: "SWIFT_UINT32") + public static let SwiftInt16 = Self(javaConstant: "SWIFT_INT16") public static let SwiftUInt16 = Self(javaConstant: "SWIFT_UINT16") + public static let SwiftInt8 = Self(javaConstant: "SWIFT_INT8") + public static let SwiftUInt8 = Self(javaConstant: "SWIFT_UINT8") public static let SwiftFloat = Self(javaConstant: "SWIFT_FLOAT") public static let SwiftDouble = Self(javaConstant: "SWIFT_DOUBLE") diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index dd7e9c10e..4c4d9c0f4 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -78,6 +78,15 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible { } } + /// If this function type uses types that require any additional `import` statements, + /// these would be exported here. + var additionalJavaImports: Set { + var imports: Set = [] +// imports += self.functionSignature.parameters.flatMap { $0.additionalJavaImports } +// imports += self.functionSignature.result.additionalJavaImports + return imports + } + var isStatic: Bool { if case .staticMethod = functionSignature.selfParameter { return true diff --git a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift new file mode 100644 index 000000000..fe10ef72d --- /dev/null +++ b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift @@ -0,0 +1,57 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024-2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JavaTypes +import JavaKitConfigurationShared + +enum JNIJavaTypeTranslator { + + static func translate(knownType: SwiftKnownTypeDeclKind, config: Configuration) -> JavaType? { + let unsigned = config.effectiveUnsignedNumbersMode + guard unsigned == .annotate else { + // We do not support wrap mode in JNI mode currently; + // In the future this is where it would be interesting to implement Kotlin UInt support. + return nil + } + + switch knownType { + case .bool: return .boolean + + case .int8: return .byte + case .uint8: return .byte + + case .int16: return .short + case .uint16: return .char + + case .int32: return .int + case .uint32: return .int + + case .int64: return .long + case .uint64: return .long + + case .float: return .float + case .double: return .double + case .void: return .void + + case .string: return .javaLangString + case .int, .uint, // FIXME: why not supported int/uint? + .unsafeRawPointer, .unsafeMutableRawPointer, + .unsafePointer, .unsafeMutablePointer, + .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, + .unsafeBufferPointer, .unsafeMutableBufferPointer, + .optional, .data, .dataProtocol: + return nil + } + } +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index e0e5cb701..63b9dcd1e 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -20,6 +20,9 @@ extension JNISwift2JavaGenerator { static let defaultJavaImports: Array = [ "org.swift.swiftkit.core.*", "org.swift.swiftkit.core.util.*", + + // NonNull, Unsigned and friends + "org.swift.swiftkit.core.annotations.*", ] } @@ -32,9 +35,11 @@ extension JNISwift2JavaGenerator { } package func writeExportedJavaSources(_ printer: inout CodePrinter) throws { - for (_, ty) in analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) { + let importedTypes = analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) + + for (_, ty) in importedTypes { let filename = "\(ty.swiftNominal.name).java" - logger.info("Printing contents: \(filename)") + logger.debug("Printing contents: \(filename)") printImportedNominal(&printer, ty) if let outputFile = try printer.writeContents( @@ -62,6 +67,7 @@ extension JNISwift2JavaGenerator { private func printModule(_ printer: inout CodePrinter) { printHeader(&printer) printPackage(&printer) + printImports(&printer) printModuleClass(&printer) { printer in printer.print( @@ -81,6 +87,7 @@ extension JNISwift2JavaGenerator { } for decl in analysis.importedGlobalVariables { + self.logger.trace("Print global variable: \(decl)") printFunctionDowncallMethods(&printer, decl) printer.println() } @@ -219,7 +226,7 @@ extension JNISwift2JavaGenerator { _ printer: inout CodePrinter, _ functionType: TranslatedFunctionType ) { - let apiParams = functionType.parameters.map(\.parameter.asParameter) + let apiParams = functionType.parameters.map({ $0.parameter.renderParameter() }) printer.print( """ @@ -243,18 +250,21 @@ extension JNISwift2JavaGenerator { let translatedSignature = translatedDecl.translatedFunctionSignature let resultType = translatedSignature.resultType.javaType - var parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.parameter.asParameter) + var parameters = translatedDecl.translatedFunctionSignature.parameters.map({ $0.parameter.renderParameter() }) if translatedSignature.requiresSwiftArena { parameters.append("SwiftArena swiftArena$") } let throwsClause = decl.isThrowing ? " throws Exception" : "" + var annotationsStr = translatedSignature.annotations.map({ $0.render() }).joined(separator: "\n") + if !annotationsStr.isEmpty { annotationsStr += "\n" } + let modifiersStr = modifiers.joined(separator: " ") let parametersStr = parameters.joined(separator: ", ") printDeclDocumentation(&printer, decl) printer.printBraceBlock( - "\(modifiersStr) \(resultType) \(translatedDecl.name)(\(parametersStr))\(throwsClause)" + "\(annotationsStr)\(modifiersStr) \(resultType) \(translatedDecl.name)(\(parametersStr))\(throwsClause)" ) { printer in printDowncall(&printer, decl) } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 8d18e6aef..5908cfb12 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import JavaTypes +import JavaKitConfigurationShared extension JNISwift2JavaGenerator { func translatedDecl( @@ -25,6 +26,7 @@ extension JNISwift2JavaGenerator { let translated: TranslatedFunctionDecl? do { let translation = JavaTranslation( + config: config, swiftModuleName: swiftModuleName, javaPackage: self.javaPackage, javaClassLookupTable: self.javaClassLookupTable @@ -40,12 +42,14 @@ extension JNISwift2JavaGenerator { } struct JavaTranslation { + let config: Configuration let swiftModuleName: String let javaPackage: String let javaClassLookupTable: JavaClassLookupTable func translate(_ decl: ImportedFunc) throws -> TranslatedFunctionDecl { let nativeTranslation = NativeJavaTranslation( + config: self.config, javaPackage: self.javaPackage, javaClassLookupTable: self.javaClassLookupTable ) @@ -117,12 +121,12 @@ extension JNISwift2JavaGenerator { ) } - let transltedResult = try translate(swiftResult: SwiftResult(convention: .direct, type: swiftType.resultType)) + let translatedResult = try translate(swiftResult: SwiftResult(convention: .direct, type: swiftType.resultType)) return TranslatedFunctionType( name: name, parameters: translatedParams, - result: transltedResult, + result: translatedResult, swiftType: swiftType ) } @@ -150,10 +154,12 @@ extension JNISwift2JavaGenerator { selfParameter = nil } - return try TranslatedFunctionSignature( + let resultType = try translate(swiftResult: functionSignature.result) + + return TranslatedFunctionSignature( selfParameter: selfParameter, parameters: parameters, - resultType: translate(swiftResult: functionSignature.result) + resultType: resultType ) } @@ -163,17 +169,33 @@ extension JNISwift2JavaGenerator { methodName: String, parentName: String ) throws -> TranslatedParameter { + + // If the result type should cause any annotations on the method, include them here. + let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config) + + // If we need to handle unsigned integers do so here + if config.effectiveUnsignedNumbersMode.needsConversion { + if let unsignedWrapperType = JavaType.unsignedWrapper(for: swiftType) { + return TranslatedParameter( + parameter: JavaParameter(name: parameterName, type: unsignedWrapperType, annotations: parameterAnnotations), + conversion: unsignedResultConversion( + swiftType, to: unsignedWrapperType, + mode: self.config.effectiveUnsignedNumbersMode) + ) + } + } + switch swiftType { case .nominal(let nominalType): let nominalTypeName = nominalType.nominalTypeDecl.name if let knownType = nominalType.nominalTypeDecl.knownTypeKind { - guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType) else { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { throw JavaTranslationError.unsupportedSwiftType(swiftType) } return TranslatedParameter( - parameter: JavaParameter(name: parameterName, type: javaType), + parameter: JavaParameter(name: parameterName, type: javaType, annotations: parameterAnnotations), conversion: .placeholder ) } @@ -184,10 +206,7 @@ extension JNISwift2JavaGenerator { } return TranslatedParameter( - parameter: JavaParameter( - name: parameterName, - type: javaType - ), + parameter: JavaParameter(name: parameterName, type: javaType, annotations: parameterAnnotations), conversion: .placeholder ) } @@ -196,14 +215,15 @@ extension JNISwift2JavaGenerator { return TranslatedParameter( parameter: JavaParameter( name: parameterName, - type: .class(package: nil, name: nominalTypeName) + type: .class(package: nil, name: nominalTypeName), + annotations: parameterAnnotations ), conversion: .valueMemoryAddress(.placeholder) ) case .tuple([]): return TranslatedParameter( - parameter: JavaParameter(name: parameterName, type: .void), + parameter: JavaParameter(name: parameterName, type: .void, annotations: parameterAnnotations), conversion: .placeholder ) @@ -211,7 +231,8 @@ extension JNISwift2JavaGenerator { return TranslatedParameter( parameter: JavaParameter( name: parameterName, - type: .class(package: javaPackage, name: "\(parentName).\(methodName).\(parameterName)") + type: .class(package: javaPackage, name: "\(parentName).\(methodName).\(parameterName)"), + annotations: parameterAnnotations ), conversion: .placeholder ) @@ -221,30 +242,46 @@ extension JNISwift2JavaGenerator { } } - func translate( - swiftResult: SwiftResult - ) throws -> TranslatedResult { - switch swiftResult.type { + func unsignedResultConversion(_ from: SwiftType, to javaType: JavaType, + mode: JExtractUnsignedIntegerMode) -> JavaNativeConversionStep { + switch mode { + case .annotate: + return .placeholder // no conversions + + case .wrapGuava: + fatalError("JExtract in JNI mode does not support the \(JExtractUnsignedIntegerMode.wrapGuava) unsigned numerics mode") + } + } + + func translate(swiftResult: SwiftResult) throws -> TranslatedResult { + let swiftType = swiftResult.type + + // If the result type should cause any annotations on the method, include them here. + let resultAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config) + + switch swiftType { case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { - guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType) else { - throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) } return TranslatedResult( javaType: javaType, + annotations: resultAnnotations, conversion: .placeholder ) } if nominalType.isJavaKitWrapper { - throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + throw JavaTranslationError.unsupportedSwiftType(swiftType) } // We assume this is a JExtract class. let javaType = JavaType.class(package: nil, name: nominalType.nominalTypeDecl.name) return TranslatedResult( javaType: javaType, + annotations: resultAnnotations, conversion: .constructSwiftValue(.placeholder, javaType) ) @@ -252,7 +289,7 @@ extension JNISwift2JavaGenerator { return TranslatedResult(javaType: .void, conversion: .placeholder) case .metatype, .optional, .tuple, .function, .existential, .opaque, .genericParameter: - throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + throw JavaTranslationError.unsupportedSwiftType(swiftType) } } } @@ -275,33 +312,23 @@ extension JNISwift2JavaGenerator { /// Function signature of the native function that will be implemented by Swift let nativeFunctionSignature: NativeFunctionSignature - } - static func translate(knownType: SwiftKnownTypeDeclKind) -> JavaType? { - switch knownType { - case .bool: .boolean - case .int8: .byte - case .uint16: .char - case .int16: .short - case .int32: .int - case .int64: .long - case .float: .float - case .double: .double - case .void: .void - case .string: .javaLangString - case .int, .uint, .uint8, .uint32, .uint64, - .unsafeRawPointer, .unsafeMutableRawPointer, - .unsafePointer, .unsafeMutablePointer, - .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, - .unsafeBufferPointer, .unsafeMutableBufferPointer, .optional, .data, .dataProtocol: - nil + /// Annotations to include on the Java function declaration + var annotations: [JavaAnnotation] { + self.translatedFunctionSignature.annotations } } struct TranslatedFunctionSignature { - let selfParameter: TranslatedParameter? - let parameters: [TranslatedParameter] - let resultType: TranslatedResult + var selfParameter: TranslatedParameter? + var parameters: [TranslatedParameter] + var resultType: TranslatedResult + + // if the result type implied any annotations, + // propagate them onto the function the result is returned from + var annotations: [JavaAnnotation] { + self.resultType.annotations + } var requiresSwiftArena: Bool { return self.resultType.conversion.requiresSwiftArena @@ -318,6 +345,9 @@ extension JNISwift2JavaGenerator { struct TranslatedResult { let javaType: JavaType + /// Java annotations that should be propagated from the result type onto the method + var annotations: [JavaAnnotation] = [] + /// Represents how to convert the Java native result into a user-facing result. let conversion: JavaNativeConversionStep } @@ -343,6 +373,8 @@ extension JNISwift2JavaGenerator { /// Call `new \(Type)(\(placeholder), swiftArena$)` indirect case constructSwiftValue(JavaNativeConversionStep, JavaType) + indirect case call(JavaNativeConversionStep, function: String) + /// Returns the conversion string applied to the placeholder. func render(_ printer: inout CodePrinter, _ placeholder: String) -> String { // NOTE: 'printer' is used if the conversion wants to cause side-effects. @@ -350,14 +382,18 @@ extension JNISwift2JavaGenerator { switch self { case .placeholder: return placeholder - + case .valueMemoryAddress: return "\(placeholder).$memoryAddress()" - + case .constructSwiftValue(let inner, let javaType): let inner = inner.render(&printer, placeholder) return "new \(javaType.className!)(\(inner), swiftArena$)" - + + case .call(let inner, let function): + let inner = inner.render(&printer, placeholder) + return "\(function)(\(inner))" + } } @@ -372,12 +408,18 @@ extension JNISwift2JavaGenerator { case .valueMemoryAddress(let inner): return inner.requiresSwiftArena + + case .call(let inner, _): + return inner.requiresSwiftArena } } } enum JavaTranslationError: Error { - case unsupportedSwiftType(SwiftType) + case unsupportedSwiftType(SwiftType, fileID: String, line: Int) + static func unsupportedSwiftType(_ type: SwiftType, _fileID: String = #fileID, _line: Int = #line) -> JavaTranslationError { + .unsupportedSwiftType(type, fileID: _fileID, line: _line) + } /// The user has not supplied a mapping from `SwiftType` to /// a java class. diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 48a6e8d45..9f1113fc6 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -13,10 +13,12 @@ //===----------------------------------------------------------------------===// import JavaTypes +import JavaKitConfigurationShared extension JNISwift2JavaGenerator { struct NativeJavaTranslation { + let config: Configuration let javaPackage: String let javaClassLookupTable: JavaClassLookupTable @@ -70,7 +72,8 @@ extension JNISwift2JavaGenerator { let nominalTypeName = nominalType.nominalTypeDecl.name if let knownType = nominalType.nominalTypeDecl.knownTypeKind { - guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType), javaType.implementsJavaValue else { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), + javaType.implementsJavaValue else { throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) } @@ -140,7 +143,8 @@ extension JNISwift2JavaGenerator { switch type { case .nominal(let nominal): if let knownType = nominal.nominalTypeDecl.knownTypeKind { - guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType), javaType.implementsJavaValue else { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), + javaType.implementsJavaValue else { throw JavaTranslationError.unsupportedSwiftType(type) } @@ -172,7 +176,8 @@ extension JNISwift2JavaGenerator { switch type { case .nominal(let nominal): if let knownType = nominal.nominalTypeDecl.knownTypeKind { - guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType), javaType.implementsJavaValue else { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), + javaType.implementsJavaValue else { throw JavaTranslationError.unsupportedSwiftType(type) } @@ -198,7 +203,10 @@ extension JNISwift2JavaGenerator { switch swiftResult.type { case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { - guard let javaType = JNISwift2JavaGenerator.translate(knownType: knownType), javaType.implementsJavaValue else { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { + throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + } + guard javaType.implementsJavaValue else { throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 35d4dbef1..8b910ffb3 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -59,7 +59,7 @@ extension JNISwift2JavaGenerator { for (_, ty) in self.analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) { let fileNameBase = "\(ty.swiftNominal.qualifiedName)+SwiftJava" let filename = "\(fileNameBase).swift" - logger.info("Printing contents: \(filename)") + logger.debug("Printing contents: \(filename)") do { try printNominalTypeThunks(&printer, ty) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index 79f546aca..c5f3a5b67 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -13,16 +13,19 @@ //===----------------------------------------------------------------------===// import JavaTypes +import JavaKitConfigurationShared /// A table that where keys are Swift class names and the values are /// the fully qualified canoical names. package typealias JavaClassLookupTable = [String: String] package class JNISwift2JavaGenerator: Swift2JavaGenerator { + + let logger: Logger + let config: Configuration let analysis: AnalysisResult let swiftModuleName: String let javaPackage: String - let logger: Logger let swiftOutputDirectory: String let javaOutputDirectory: String @@ -42,12 +45,14 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { var expectedOutputSwiftFiles: Set package init( + config: Configuration, translator: Swift2JavaTranslator, javaPackage: String, swiftOutputDirectory: String, javaOutputDirectory: String, javaClassLookupTable: JavaClassLookupTable ) { + self.config = config self.logger = Logger(label: "jni-generator", logLevel: translator.log.logLevel) self.analysis = translator.result self.swiftModuleName = translator.swiftModuleName diff --git a/Sources/JExtractSwiftLib/JNI/JNIType.swift b/Sources/JExtractSwiftLib/JNI/JNIType.swift index feb8a5458..cdedb0a17 100644 --- a/Sources/JExtractSwiftLib/JNI/JNIType.swift +++ b/Sources/JExtractSwiftLib/JNI/JNIType.swift @@ -88,7 +88,7 @@ extension JavaType { /// Returns whether this type returns `JavaValue` from JavaKit var implementsJavaValue: Bool { - switch self { + return switch self { case .boolean, .byte, .char, .short, .int, .long, .float, .double, .void, .javaLangString: true default: diff --git a/Sources/JExtractSwiftLib/JavaParameter.swift b/Sources/JExtractSwiftLib/JavaParameter.swift index 728964199..a12b13b28 100644 --- a/Sources/JExtractSwiftLib/JavaParameter.swift +++ b/Sources/JExtractSwiftLib/JavaParameter.swift @@ -18,8 +18,21 @@ import JavaTypes struct JavaParameter { let name: String let type: JavaType + /// Parameter annotations are used in parameter declarations like this: `@Annotation int example` + let annotations: [JavaAnnotation] - var asParameter: String { - "\(type) \(name)" + init(name: String, type: JavaType, annotations: [JavaAnnotation] = []) { + self.name = name + self.type = type + self.annotations = annotations + } + + func renderParameter() -> String { + if annotations.isEmpty { + return "\(type) \(name)" + } + + let annotationsStr = annotations.map({$0.render()}).joined(separator: "") + return "\(annotationsStr) \(type) \(name)" } } diff --git a/Sources/JExtractSwiftLib/JavaConstants/JavaTypes.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift similarity index 100% rename from Sources/JExtractSwiftLib/JavaConstants/JavaTypes.swift rename to Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift diff --git a/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift new file mode 100644 index 000000000..2ab9c0a22 --- /dev/null +++ b/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift @@ -0,0 +1,88 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JavaTypes + +extension JavaType { + + /// Try to map a Swift type name (e.g., from the module Swift) over to a + /// primitive Java type, or fail otherwise. + public init?(swiftTypeName: String, WHT_unsigned unsigned: UnsignedNumericsMode) { + switch swiftTypeName { + case "Bool": self = .boolean + + case "Int8": self = .byte + case "UInt8": + self = switch unsigned { + case .ignoreSign: .byte + case .wrapUnsignedGuava: JavaType.guava.primitives.UnsignedInteger + } + + case "Int16": self = .short + case "UInt16": self = .char + + case "Int32": self = .int + case "UInt32": + self = switch unsigned { + case .ignoreSign: .int + case .wrapUnsignedGuava: JavaType.guava.primitives.UnsignedInteger + } + + case "Int64": self = .long + case "UInt64": + self = switch unsigned { + case .ignoreSign: .long + case .wrapUnsignedGuava: JavaType.guava.primitives.UnsignedLong + } + + case "Float": self = .float + case "Double": self = .double + case "Void": self = .void + default: return nil + } + } +} + +extension JavaType { + + static func unsignedWrapper(for swiftType: SwiftType) -> JavaType? { + switch swiftType { + case .nominal(let nominal): + switch nominal.nominalTypeDecl.knownTypeKind { + case .uint8: return guava.primitives.UnsignedInteger + case .uint16: return .char // no wrapper necessary, we can express it as 'char' natively in Java + case .uint32: return guava.primitives.UnsignedInteger + case .uint64: return guava.primitives.UnsignedLong + default: return nil + } + default: return nil + } + } + + /// Known types from the Google Guava library + enum guava { + enum primitives { + static let package = "com.google.common.primitives" + + static var UnsignedInteger: JavaType { + .class(package: primitives.package, name: "UnsignedInteger") + } + + static var UnsignedLong: JavaType { + .class(package: primitives.package, name: "UnsignedLong") + } + } + } + +} diff --git a/Sources/JExtractSwiftLib/Swift2Java.swift b/Sources/JExtractSwiftLib/Swift2Java.swift index 6473cea32..5a73e3288 100644 --- a/Sources/JExtractSwiftLib/Swift2Java.swift +++ b/Sources/JExtractSwiftLib/Swift2Java.swift @@ -98,6 +98,7 @@ public struct SwiftToJava { switch config.mode { case .some(.ffm), .none: let generator = FFMSwift2JavaGenerator( + config: self.config, translator: translator, javaPackage: config.javaPackage ?? "", swiftOutputDirectory: outputSwiftDirectory, @@ -108,6 +109,7 @@ public struct SwiftToJava { case .jni: let generator = JNISwift2JavaGenerator( + config: self.config, translator: translator, javaPackage: config.javaPackage ?? "", swiftOutputDirectory: outputSwiftDirectory, diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index da738d39b..3cc14406a 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -92,6 +92,17 @@ enum SwiftType: Equatable { return false } } + + var isUnsignedInteger: Bool { + switch self { + case .nominal(let nominal): + switch nominal.nominalTypeDecl.knownTypeKind { + case .uint8, .uint16, .uint32, .uint64: true + default: false + } + default: false + } + } } extension SwiftType: CustomStringConvertible { diff --git a/Sources/JavaKit/BridgedValues/JavaValue+Integers.swift b/Sources/JavaKit/BridgedValues/JavaValue+Integers.swift index 1af1b5494..005753eb6 100644 --- a/Sources/JavaKit/BridgedValues/JavaValue+Integers.swift +++ b/Sources/JavaKit/BridgedValues/JavaValue+Integers.swift @@ -14,6 +14,66 @@ import JavaTypes +extension UInt8: JavaValue { + public typealias JNIType = jbyte + + public static var jvalueKeyPath: WritableKeyPath { \.b } + + public static var javaType: JavaType { .byte } + + /// Retrieve the JNI value. + public func getJNIValue(in environment: JNIEnvironment) -> JNIType { JNIType(self) } + + /// Initialize from a JNI value. + public init(fromJNI value: JNIType, in environment: JNIEnvironment) { + self = Self(value) + } + + public static func jniMethodCall( + in environment: JNIEnvironment + ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) { + environment.interface.CallByteMethodA + } + + public static func jniFieldGet(in environment: JNIEnvironment) -> JNIFieldGet { + environment.interface.GetByteField + } + + public static func jniFieldSet(in environment: JNIEnvironment) -> JNIFieldSet { + environment.interface.SetByteField + } + + public static func jniStaticMethodCall( + in environment: JNIEnvironment + ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) { + environment.interface.CallStaticByteMethodA + } + + public static func jniStaticFieldGet(in environment: JNIEnvironment) -> JNIStaticFieldGet { + environment.interface.GetStaticByteField + } + + public static func jniStaticFieldSet(in environment: JNIEnvironment) -> JNIStaticFieldSet { + environment.interface.SetStaticByteField + } + + public static func jniNewArray(in environment: JNIEnvironment) -> JNINewArray { + environment.interface.NewByteArray + } + + public static func jniGetArrayRegion(in environment: JNIEnvironment) -> JNIGetArrayRegion { + environment.interface.GetByteArrayRegion + } + + public static func jniSetArrayRegion(in environment: JNIEnvironment) -> JNISetArrayRegion { + environment.interface.SetByteArrayRegion + } + + public static var jniPlaceholderValue: jbyte { + 0 + } +} + extension Int8: JavaValue { public typealias JNIType = jbyte @@ -170,6 +230,66 @@ extension Int16: JavaValue { } } +extension UInt32: JavaValue { + public typealias JNIType = jint + + public static var jvalueKeyPath: WritableKeyPath { \.i } + + public static var javaType: JavaType { .int } + + /// Retrieve the JNI value. + public func getJNIValue(in environment: JNIEnvironment) -> JNIType { JNIType(self) } + + /// Initialize from a JNI value. + public init(fromJNI value: JNIType, in environment: JNIEnvironment) { + self = Self(value) + } + + public static func jniMethodCall( + in environment: JNIEnvironment + ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) { + environment.interface.CallIntMethodA + } + + public static func jniFieldGet(in environment: JNIEnvironment) -> JNIFieldGet { + environment.interface.GetIntField + } + + public static func jniFieldSet(in environment: JNIEnvironment) -> JNIFieldSet { + environment.interface.SetIntField + } + + public static func jniStaticMethodCall( + in environment: JNIEnvironment + ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) { + environment.interface.CallStaticIntMethodA + } + + public static func jniStaticFieldGet(in environment: JNIEnvironment) -> JNIStaticFieldGet { + environment.interface.GetStaticIntField + } + + public static func jniStaticFieldSet(in environment: JNIEnvironment) -> JNIStaticFieldSet { + environment.interface.SetStaticIntField + } + + public static func jniNewArray(in environment: JNIEnvironment) -> JNINewArray { + environment.interface.NewIntArray + } + + public static func jniGetArrayRegion(in environment: JNIEnvironment) -> JNIGetArrayRegion { + environment.interface.GetIntArrayRegion + } + + public static func jniSetArrayRegion(in environment: JNIEnvironment) -> JNISetArrayRegion { + environment.interface.SetIntArrayRegion + } + + public static var jniPlaceholderValue: jint { + 0 + } +} + extension Int32: JavaValue { public typealias JNIType = jint @@ -228,6 +348,64 @@ extension Int32: JavaValue { } } +extension UInt64: JavaValue { + public typealias JNIType = jlong + + public static var jvalueKeyPath: WritableKeyPath { \.j } + + public func getJNIValue(in environment: JNIEnvironment) -> JNIType { JNIType(self) } + + public init(fromJNI value: JNIType, in environment: JNIEnvironment) { + self = UInt64(value) + } + + public static var javaType: JavaType { .long } + + public static func jniMethodCall( + in environment: JNIEnvironment + ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) { + environment.interface.CallLongMethodA + } + + public static func jniFieldGet(in environment: JNIEnvironment) -> JNIFieldGet { + environment.interface.GetLongField + } + + public static func jniFieldSet(in environment: JNIEnvironment) -> JNIFieldSet { + environment.interface.SetLongField + } + + public static func jniStaticMethodCall( + in environment: JNIEnvironment + ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) { + environment.interface.CallStaticLongMethodA + } + + public static func jniStaticFieldGet(in environment: JNIEnvironment) -> JNIStaticFieldGet { + environment.interface.GetStaticLongField + } + + public static func jniStaticFieldSet(in environment: JNIEnvironment) -> JNIStaticFieldSet { + environment.interface.SetStaticLongField + } + + public static func jniNewArray(in environment: JNIEnvironment) -> JNINewArray { + environment.interface.NewLongArray + } + + public static func jniGetArrayRegion(in environment: JNIEnvironment) -> JNIGetArrayRegion { + environment.interface.GetLongArrayRegion + } + + public static func jniSetArrayRegion(in environment: JNIEnvironment) -> JNISetArrayRegion { + environment.interface.SetLongArrayRegion + } + + public static var jniPlaceholderValue: jlong { + 0 + } +} + extension Int64: JavaValue { public typealias JNIType = jlong diff --git a/Sources/JavaKitConfigurationShared/Configuration.swift b/Sources/JavaKitConfigurationShared/Configuration.swift index 5c57082eb..c1ae7dd2b 100644 --- a/Sources/JavaKitConfigurationShared/Configuration.swift +++ b/Sources/JavaKitConfigurationShared/Configuration.swift @@ -42,6 +42,11 @@ public struct Configuration: Codable { public var writeEmptyFiles: Bool? // FIXME: default it to false, but that plays not nice with Codable + public var unsignedNumbersMode: JExtractUnsignedIntegerMode? + public var effectiveUnsignedNumbersMode: JExtractUnsignedIntegerMode { + unsignedNumbersMode ?? .default + } + // ==== java 2 swift --------------------------------------------------------- /// The Java class path that should be passed along to the swift-java tool. diff --git a/Sources/JavaKitConfigurationShared/GenerationMode.swift b/Sources/JavaKitConfigurationShared/GenerationMode.swift index b4a964769..190323a05 100644 --- a/Sources/JavaKitConfigurationShared/GenerationMode.swift +++ b/Sources/JavaKitConfigurationShared/GenerationMode.swift @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +/// Determines which source generation mode JExtract should be using: JNI or Foreign Function and Memory. public enum JExtractGenerationMode: String, Codable { /// Foreign Value and Memory API case ffm @@ -19,3 +20,46 @@ public enum JExtractGenerationMode: String, Codable { /// Java Native Interface case jni } + +/// Configures how Swift unsigned integers should be extracted by jextract. +public enum JExtractUnsignedIntegerMode: String, Codable { + /// Treat unsigned Swift integers as their signed equivalents in Java signatures, + /// however annotate them using the `@Unsigned` annotation which serves as a hint + /// to users of APIs with unsigned integers that a given parameter or return type + /// is actually unsigned, and must be treated carefully. + /// + /// Specifically negative values of a `@Unchecked long` must be interpreted carefully as + /// a value larger than the Long.MAX_VALUE can represent in Java. + case annotate + + /// Wrap any unsigned Swift integer values in an explicit `Unsigned...` wrapper types. + /// + /// This mode trades off performance, due to needing to allocate the type-safe wrapper objects around + /// primitive values, however allows to retain static type information about the unsignedness of + /// unsigned number types in the Java side of generated bindings. + case wrapGuava + +// /// If possible, use a wider Java signed integer type to represent an Unsigned Swift integer type. +// /// For example, represent a Swift `UInt32` (width equivalent to Java `int`) as a Java signed `long`, +// /// because UInt32's max value is possible to be stored in a signed Java long (64bit). +// /// +// /// Since it is not possible to widen a value beyond 64bits (Java `long`), the Long type would be wrapped +// case widenOrWrap +// +// /// Similar to `widenOrWrap`, however instead of wrapping `UInt64` as an `UnsignedLong` in Java, +// /// only annotate it as `@Unsigned long`. +// case widenOrAnnotate +} + +extension JExtractUnsignedIntegerMode { + public var needsConversion: Bool { + switch self { + case .annotate: false + case .wrapGuava: true + } + } + + public static var `default`: JExtractUnsignedIntegerMode { + .annotate + } +} diff --git a/Sources/JavaTypes/JavaAnnotation.swift b/Sources/JavaTypes/JavaAnnotation.swift new file mode 100644 index 000000000..a643c2987 --- /dev/null +++ b/Sources/JavaTypes/JavaAnnotation.swift @@ -0,0 +1,47 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +/// Describes a Java annotation (e.g. `@Deprecated` or `@Unsigned`) +public struct JavaAnnotation: Equatable, Hashable { + public let type: JavaType + public let arguments: [String] + + public init(className name: some StringProtocol, arguments: [String] = []) { + type = JavaType(className: name) + self.arguments = arguments + } + + public func render() -> String { + guard let className = type.className else { + fatalError("Java annotation must have a className") + } + + var res = "@\(className)" + guard !arguments.isEmpty else { + return res + } + + res += "(" + res += arguments.joined(separator: ",") + res += ")" + return res + } + +} + +extension JavaAnnotation { + public static var unsigned: JavaAnnotation { + JavaAnnotation(className: "Unsigned") + } +} \ No newline at end of file diff --git a/Sources/JavaTypes/JavaType+JavaSource.swift b/Sources/JavaTypes/JavaType+JavaSource.swift index ccb4e96b5..f9e2e0cdc 100644 --- a/Sources/JavaTypes/JavaType+JavaSource.swift +++ b/Sources/JavaTypes/JavaType+JavaSource.swift @@ -60,7 +60,7 @@ extension JavaType: CustomStringConvertible { } } - /// Returns the a class name if this java type was a class, + /// Returns the class name if this java type was a class, /// and nil otherwise. public var className: String? { switch self { @@ -70,4 +70,17 @@ extension JavaType: CustomStringConvertible { return nil } } + + /// Returns the fully qualified class name if this java type was a class, + /// and nil otherwise. + public var fullyQualifiedClassName: String? { + switch self { + case .class(.some(let package), let name): + return "\(package).\(name)" + case .class(nil, let name): + return name + default: + return nil + } + } } diff --git a/Sources/JavaTypes/JavaType+SwiftNames.swift b/Sources/JavaTypes/JavaType+SwiftNames.swift index 492ff4595..20de73fc8 100644 --- a/Sources/JavaTypes/JavaType+SwiftNames.swift +++ b/Sources/JavaTypes/JavaType+SwiftNames.swift @@ -85,20 +85,32 @@ extension JavaType { } } - /// Try to map a Swift type name (e.g., from the module Swift) over to a - /// primitive Java type, or fail otherwise. - public init?(swiftTypeName: String) { - switch swiftTypeName { - case "Bool": self = .boolean - case "Int8": self = .byte - case "UInt16": self = .char - case "Int16": self = .short - case "Int32": self = .int - case "Int64": self = .long - case "Float": self = .float - case "Double": self = .double - case "Void": self = .void - default: return nil - } - } +} + +/// Determines how type conversion should deal with Swift's unsigned numeric types. +/// +/// When `ignoreSign` is used, unsigned Swift types are imported directly as their corresponding bit-width types, +/// which may yield surprising values when an unsigned Swift value is interpreted as a signed Java type: +/// - `UInt8` is imported as `byte` +/// - `UInt16` is imported as `char` (this is always correct, since `char` is unsigned in Java) +/// - `UInt32` is imported as `int` +/// - `UInt64` is imported as `long` +/// +/// When `wrapUnsignedGuava` is used, unsigned Swift types are imported as safe "wrapper" types from the popular Guava +/// library on the Java side. SwiftJava does not include these types, so you would have to make sure your project depends +/// on Guava for such generated code to be able to compile. +/// +/// These make the Unsigned nature of the types explicit in Java, however they come at a cost of allocating the wrapper +/// object, and indirection when accessing the underlying numeric value. These are often useful as a signal to watch out +/// when dealing with a specific API, however in high performance use-cases, one may want to choose using the primitive +/// values directly, and interact with them using {@code UnsignedIntegers} SwiftKit helper classes on the Java side. +/// +/// The type mappings in this mode are as follows: +/// - `UInt8` is imported as `com.google.common.primitives.UnsignedInteger` +/// - `UInt16` is imported as `char` (this is always correct, since `char` is unsigned in Java) +/// - `UInt32` is imported as `com.google.common.primitives.UnsignedInteger` +/// - `UInt64` is imported as `com.google.common.primitives.UnsignedLong` +public enum UnsignedNumericsMode { + case ignoreSign + case wrapUnsignedGuava } diff --git a/Sources/JavaTypes/JavaType.swift b/Sources/JavaTypes/JavaType.swift index 6c5f5357c..2a2d901fe 100644 --- a/Sources/JavaTypes/JavaType.swift +++ b/Sources/JavaTypes/JavaType.swift @@ -13,13 +13,15 @@ //===----------------------------------------------------------------------===// /// Describes the Java type system. +/// +/// Some types may need to be annotated when in parameter position, public enum JavaType: Equatable, Hashable { case boolean - case byte - case char - case short - case int - case long + case byte(parameterAnnotations: [JavaAnnotation]) + case char(parameterAnnotations: [JavaAnnotation]) + case short(parameterAnnotations: [JavaAnnotation]) + case int(parameterAnnotations: [JavaAnnotation]) + case long(parameterAnnotations: [JavaAnnotation]) case float case double case void @@ -31,6 +33,12 @@ public enum JavaType: Equatable, Hashable { /// A Java array. indirect case array(JavaType) + public static var byte: JavaType { .byte(parameterAnnotations: []) } + public static var char: JavaType { .char(parameterAnnotations: []) } + public static var short: JavaType { .short(parameterAnnotations: []) } + public static var int: JavaType { .int(parameterAnnotations: []) } + public static var long: JavaType { .long(parameterAnnotations: []) } + /// Given a class name such as "java.lang.Object", split it into /// its package and class name to form a class instance. public init(className name: some StringProtocol) { @@ -45,6 +53,21 @@ public enum JavaType: Equatable, Hashable { } } +extension JavaType { + /// List of Java annotations this type should have include in parameter position, + /// e.g. `void example(@Unsigned long num)` + public var parameterAnnotations: [JavaAnnotation] { + switch self { + case .byte(let parameterAnnotations): return parameterAnnotations + case .char(let parameterAnnotations): return parameterAnnotations + case .short(let parameterAnnotations): return parameterAnnotations + case .int(let parameterAnnotations): return parameterAnnotations + case .long(let parameterAnnotations): return parameterAnnotations + default: return [] + } + } +} + extension JavaType { /// Whether this is a primitive Java type. public var isPrimitive: Bool { @@ -57,3 +80,4 @@ extension JavaType { } } } + diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index e7acb3eb9..b3d02276f 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -45,7 +45,7 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Swift Feature | FFM | JNI | -|--------------------------------------------------------------------------------------| -------- |-----| +|--------------------------------------------------------------------------------------|----------|-----| | Initializers: `class`, `struct` | ✅ | ✅ | | Optional Initializers / Throwing Initializers | ❌ | ❌ | | Deinitializers: `class`, `struct` | ✅ | ✅ | @@ -67,7 +67,7 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Primitive types: `Bool`, `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `Float`, `Double` | ✅ | ✅ | | Parameters: JavaKit wrapped types `JavaLong`, `JavaInteger` | ❌ | ✅ | | Return values: JavaKit wrapped types `JavaLong`, `JavaInteger` | ❌ | ❌ | -| Unsigned primitive types: `UInt`, `UInt8`, `UInt16`, `UInt32`, `UInt64` | ❌ | ❌ | +| Unsigned primitive types: `UInt`, `UInt8`, `UInt16`, `UInt32`, `UInt64` | ✅ * | ✅ * | | String (with copying data) | ✅ | ✅ | | Variadic parameters: `T...` | ❌ | ❌ | | Parametrer packs / Variadic generics | ❌ | ❌ | @@ -76,14 +76,14 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Operators: `+`, `-`, user defined | ❌ | ❌ | | Subscripts: `subscript()` | ❌ | ❌ | | Equatable | ❌ | ❌ | -| Pointers: `UnsafeRawPointer`, UnsafeBufferPointer (?) | 🟡 | ❌ | +| Pointers: `UnsafeRawPointer`, UnsafeBufferPointer (?) | 🟡 | ❌ | | Nested types: `struct Hello { struct World {} }` | ❌ | ❌ | | Inheritance: `class Caplin: Capybara` | ❌ | ❌ | | Non-escaping `Void` closures: `func callMe(maybe: () -> ())` | ✅ | ✅ | | Non-escaping closures with primitive arguments/results: `func callMe(maybe: (Int) -> (Double))` | ✅ | ✅ | | Non-escaping closures with object arguments/results: `func callMe(maybe: (JavaObj) -> (JavaObj))` | ❌ | ❌ | | `@escaping` closures: `func callMe(_: @escaping () -> ())` | ❌ | ❌ | -| Swift type extensions: `extension String { func uppercased() }` | 🟡 | 🟡 | +| Swift type extensions: `extension String { func uppercased() }` | 🟡 | 🟡 | | Swift macros (maybe) | ❌ | ❌ | | Result builders | ❌ | ❌ | | Automatic Reference Counting of class types / lifetime safety | ✅ | ✅ | @@ -94,3 +94,65 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | | | | > tip: The list of features may be incomplete, please file an issue if something is unclear or should be clarified in this table. + +## Detailed feature support discussion + +### Unsigned integers + +### Java <-> Swift Type mapping + +Java does not support unsigned numbers (other than the 16-bit wide `char`), and therefore mapping Swift's (and C) +unsigned integer types is somewhat problematic. + +SwiftJava's jextract mode, similar to OpenJDK jextract, does extract unsigned types from native code to Java +as their bit-width equivalents. This is potentially dangerous because values larger than the `MAX_VALUE` of a given +*signed* type in Java, e.g. `200` stored in an `UInt8` in Swift, would be interpreted as a `byte` of value `-56`, +because Java's `byte` type is _signed_. + +#### Unsigned numbers mode: annotate (default) + +Because in many situations the data represented by such numbers is merely passed along, and not interpreted by Java, +this may be safe to pass along. However, interpreting unsigned values incorrectly like this can lead to subtle mistakes +on the Java side. + +| Swift type | Java type | +|------------|-----------| +| `Int8` | `byte` | +| `UInt8` | `byte` ⚠️ | +| `Int16` | `short` | +| `UInt16` | `char` | +| `Int32` | `int` | +| `UInt32` | `int` ⚠️ | +| `Int64` | `long` | +| `UInt64` | `long` ⚠️ | +| `Float` | `float` | +| `Double` | `double` | + +#### Unsigned numbers mode: wrap-guava + +You can configure `jextract` (in FFM mode) to instead import unsigned values as their unsigned type-safe representations +as offered by the Guava library: `UnsignedLong` or `UnsignedInt`. To enable this mode pass the `--unsigned-numbers wrap-guava` +command line option, or set the corresponding configuration value in `swift-java.config` (TODO). + +This approach is type-safe, however it incurs a performance penalty for allocating a wrapper class for every +unsigned integer parameter passed to and from native Swift functions. + +SwiftJava _does not_ vendor or provide the Guava library as a dependency, and when using this mode +you are expected to add a Guava dependency to your Java project. + +> You can read more about the unsigned integers support + +| Swift type | Java type | +|------------|--------------------------------------------------------| +| `Int8` | `byte` | +| `UInt8` | `com.google.common.primitives.UnsignedInteger` (class) | +| `Int16` | `short` | +| `UInt16` | `char` | +| `Int32` | `int` | +| `UInt32` | `com.google.common.primitives.UnsignedInteger` (class)️ | +| `Int64` | `long` | +| `UInt64` | `com.google.common.primitives.UnsignedLong` (class) | +| `Float` | `float` | +| `Double` | `double` | + +> Note: The `wrap-guava` mode is currently only available in FFM mode of jextract. diff --git a/Sources/SwiftJavaLib/JavaClassTranslator.swift b/Sources/SwiftJavaLib/JavaClassTranslator.swift index bfd1657f5..ea6ca4810 100644 --- a/Sources/SwiftJavaLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaLib/JavaClassTranslator.swift @@ -517,7 +517,7 @@ extension JavaClassTranslator { package func renderConstructor( _ javaConstructor: Constructor ) throws -> DeclSyntax { - let parameters = try translateParameters(javaConstructor.getParameters()) + ["environment: JNIEnvironment? = nil"] + let parameters = try translateJavaParameters(javaConstructor.getParameters()) + ["environment: JNIEnvironment? = nil"] let parametersStr = parameters.map { $0.description }.joined(separator: ", ") let throwsStr = javaConstructor.throwsCheckedException ? "throws" : "" let accessModifier = javaConstructor.isPublic ? "public " : "" @@ -537,7 +537,7 @@ extension JavaClassTranslator { whereClause: String = "" ) throws -> DeclSyntax { // Map the parameters. - let parameters = try translateParameters(javaMethod.getParameters()) + let parameters = try translateJavaParameters(javaMethod.getParameters()) let parametersStr = parameters.map { $0.description }.joined(separator: ", ") @@ -700,7 +700,7 @@ extension JavaClassTranslator { } // Translate a Java parameter list into Swift parameters. - private func translateParameters(_ parameters: [Parameter?]) throws -> [FunctionParameterSyntax] { + private func translateJavaParameters(_ parameters: [Parameter?]) throws -> [FunctionParameterSyntax] { return try parameters.compactMap { javaParameter in guard let javaParameter else { return nil } diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index e4901bc2e..03ca0cd71 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -61,6 +61,9 @@ extension SwiftJava { @Flag(help: "Some build systems require an output to be present when it was 'expected', even if empty. This is used by the JExtractSwiftPlugin build plugin, but otherwise should not be necessary.") var writeEmptyFiles: Bool = false + @Option(help: "The mode of generation to use for the output files. Used with jextract mode. By default, unsigned Swift types are imported as their bit-width compatible signed Java counterparts, and annotated using the '@Unsigned' annotation. You may choose the 'wrap-guava' mode in order to import types as class wrapper types (`UnsignedInteger` et al) defined by the Google Guava library's `com.google.common.primitives' package. that ensure complete type-safety with regards to unsigned values, however they incur an allocation and performance overhead.") + var unsignedNumbers: JExtractUnsignedIntegerMode = .default + @Option( help: """ A swift-java configuration file for a given Swift module name on which this module depends, @@ -81,6 +84,12 @@ extension SwiftJava.JExtractCommand { config.outputJavaDirectory = outputJava config.outputSwiftDirectory = outputSwift config.writeEmptyFiles = writeEmptyFiles + config.unsignedNumbersMode = unsignedNumbers + + guard checkModeCompatibility() else { + // check would have logged the reason for early exit. + return + } if let inputSwift = commonOptions.inputSwift { config.inputSwiftDirectory = inputSwift @@ -97,6 +106,28 @@ extension SwiftJava.JExtractCommand { try jextractSwift(config: config, dependentConfigs: dependentConfigs.map(\.1)) } + + /// Check if the configured modes are compatible, and fail if not + func checkModeCompatibility() -> Bool { + if self.mode == .jni { + switch self.unsignedNumbers { + case .annotate: + print("Error: JNI mode does not support '\(JExtractUnsignedIntegerMode.wrapGuava)' Unsigned integer mode! \(Self.helpMessage)") + return false + case .wrapGuava: + () // OK + } + } + + return true + } +} + +struct IncompatibleModeError: Error { + let message: String + init(_ message: String) { + self.message = message + } } extension SwiftJava.JExtractCommand { @@ -110,3 +141,4 @@ extension SwiftJava.JExtractCommand { } extension JExtractGenerationMode: ExpressibleByArgument {} +extension JExtractUnsignedIntegerMode: ExpressibleByArgument {} diff --git a/Sources/SwiftJavaTool/Commands/ResolveCommand.swift b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift index 4f022d717..3eb01fe75 100644 --- a/Sources/SwiftJavaTool/Commands/ResolveCommand.swift +++ b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift @@ -148,7 +148,7 @@ extension SwiftJava.ResolveCommand { } else { let suggestDisablingSandbox = "It may be that the Sandbox has prevented dependency fetching, please re-run with '--disable-sandbox'." fatalError("Gradle output had no SWIFT_JAVA_CLASSPATH! \(suggestDisablingSandbox). \n" + - "Output was:<<<\(outString ?? "")>>>; Err was:<<<\(errString ?? "")>>>") + "Output was:<<<\(outString)>>>; Err was:<<<\(errString ?? "")>>>") } return String(classpathOutput.dropFirst(SwiftJavaClasspathPrefix.count)) diff --git a/Sources/_Subprocess/Configuration.swift b/Sources/_Subprocess/Configuration.swift index 4dad1a479..ba6f15bab 100644 --- a/Sources/_Subprocess/Configuration.swift +++ b/Sources/_Subprocess/Configuration.swift @@ -40,7 +40,7 @@ public struct Configuration: Sendable { public var environment: Environment /// The working directory to use when running the executable. public var workingDirectory: FilePath - /// The platform specifc options to use when + /// The platform specific options to use when /// running the subprocess. public var platformOptions: PlatformOptions diff --git a/SwiftKitCore/build.gradle b/SwiftKitCore/build.gradle index 7bab76e03..82874e20c 100644 --- a/SwiftKitCore/build.gradle +++ b/SwiftKitCore/build.gradle @@ -14,6 +14,7 @@ plugins { id("build-logic.java-application-conventions") + id("me.champeau.jmh") version "0.7.2" id("maven-publish") } @@ -45,12 +46,21 @@ java { languageVersion.set(JavaLanguageVersion.of(17)) } // Support Android 6+ (Java 7) - sourceCompatibility = JavaVersion.VERSION_1_7 - targetCompatibility = JavaVersion.VERSION_1_7 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } dependencies { - testImplementation 'junit:junit:4.13.2' + testImplementation(platform("org.junit:junit-bom:5.10.0")) + testImplementation("org.junit.jupiter:junit-jupiter") +} + +testing { + suites { + test { + useJUnitJupiter('5.10.3') + } + } } tasks.test { diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/NotImplementedError.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/NotImplementedError.java new file mode 100644 index 000000000..6956eaca4 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/NotImplementedError.java @@ -0,0 +1,24 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core; + +public class NotImplementedError extends AssertionError { + + private static final long serialVersionUID = 1L; + + public NotImplementedError(String message) { + super(message); + } +} \ No newline at end of file diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/Preconditions.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/Preconditions.java new file mode 100644 index 000000000..bd1e52b07 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/Preconditions.java @@ -0,0 +1,153 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package org.swift.swiftkit.core; + +import org.swift.swiftkit.core.annotations.Nullable; + +/** + * Collection of convenience functions to check argument preconditions. + *

+ * Partially based on {@code com.google.common.base.Preconditions}. + */ +public final class Preconditions { + private Preconditions() { + } + + public static void checkArgument(boolean expression) { + if (!expression) { + throw new IllegalArgumentException(); + } + } + + public static void checkArgument(boolean expression, @Nullable String format) { + if (!expression) { + throw new IllegalArgumentException(format); + } + } + + public static void checkArgument(boolean expression, @Nullable String format, + @Nullable Object arg1) { + if (!expression) { + throw new IllegalArgumentException(String.format(format, arg1)); + } + } + + public static void checkArgument(boolean expression, @Nullable String format, + @Nullable Object arg1, + @Nullable Object arg2) { + if (!expression) { + throw new IllegalArgumentException(String.format(format, arg1, arg2)); + } + } + + public static T checkNotNull(@Nullable T reference) { + if (reference == null) { + throw new NullPointerException(); + } + + return reference; + } + + public static T checkNotNull(@Nullable T reference, @Nullable String message) { + if (reference == null) { + throw new NullPointerException(message); + } + + return reference; + } + + /* + * All recent hotspots (as of 2009) *really* like to have the natural code + * + * if (guardExpression) { + * throw new BadException(messageExpression); + * } + * + * refactored so that messageExpression is moved to a separate String-returning method. + * + * if (guardExpression) { + * throw new BadException(badMsg(...)); + * } + * + * The alternative natural refactorings into void or Exception-returning methods are much slower. + * This is a big deal - we're talking factors of 2-8 in microbenchmarks, not just 10-20%. (This is + * a hotspot optimizer bug, which should be fixed, but that's a separate, big project). + * + * The coding pattern above is heavily used in java.util, e.g. in ArrayList. There is a + * RangeCheckMicroBenchmark in the JDK that was used to test this. + * + * But the methods in this class want to throw different exceptions, depending on the args, so it + * appears that this pattern is not directly applicable. But we can use the ridiculous, devious + * trick of throwing an exception in the middle of the construction of another exception. Hotspot + * is fine with that. + */ + + /** + * Ensures that {@code index} specifies a valid element in an array, list or string of size + * {@code size}. An element index may range from zero, inclusive, to {@code size}, exclusive. + * + * @param index a user-supplied index identifying an element of an array, list or string + * @param size the size of that array, list or string + * @return the value of {@code index} + * @throws IndexOutOfBoundsException if {@code index} is negative or is not less than {@code size} + * @throws IllegalArgumentException if {@code size} is negative + */ + public static int checkElementIndex(int index, int size) { + return checkElementIndex(index, size, "index"); + } + + /** + * Ensures that {@code index} specifies a valid element in an array, list or string of size + * {@code size}. An element index may range from zero, inclusive, to {@code size}, exclusive. + * + * @param index a user-supplied index identifying an element of an array, list or string + * @param size the size of that array, list or string + * @param desc the text to use to describe this index in an error message + * @return the value of {@code index} + * @throws IndexOutOfBoundsException if {@code index} is negative or is not less than {@code size} + * @throws IllegalArgumentException if {@code size} is negative + */ + public static int checkElementIndex(int index, int size, String desc) { + // Carefully optimized for execution by hotspot (explanatory comment above) + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException( + String.format("%s, index:%d, size:%d", desc, index, size)); + } + return index; + } + + public static void checkPositionIndexes(int start, int end, int size) { + // Carefully optimized for execution by hotspot (explanatory comment above) + if (start < 0 || end < start || end > size) { + throw new IndexOutOfBoundsException( + String.format("Start index:%d, end index:%d, size: %d", start, end, size)); + } + } + +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/NonNull.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/NonNull.java new file mode 100644 index 000000000..cad6cd8bf --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/NonNull.java @@ -0,0 +1,28 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.TYPE_USE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +// TODO: Consider depending on jspecify instead +@Documented +@Target(TYPE_USE) +@Retention(RUNTIME) +public @interface NonNull {} \ No newline at end of file diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Nullable.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Nullable.java new file mode 100644 index 000000000..c20ad884c --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Nullable.java @@ -0,0 +1,28 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core.annotations; + +import static java.lang.annotation.ElementType.TYPE_USE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +// TODO: Consider depending on jspecify instead +@Documented +@Target(TYPE_USE) +@Retention(RUNTIME) +public @interface Nullable {} \ No newline at end of file diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Unsigned.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Unsigned.java new file mode 100644 index 000000000..4bf8e3544 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/Unsigned.java @@ -0,0 +1,42 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core.annotations; + +import jdk.jfr.Description; +import jdk.jfr.Label; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; + +/** + * Value is of an unsigned numeric type. + *

+ * This annotation is used to annotate java integer primitives when their + * corresponding Swift type was actually unsigned, e.g. an {@code @Unsigned long} + * in a method signature corresponds to a Swift {@code UInt64} type, and therefore + * negative values reported by the signed {@code long} should instead be interpreted positive values, + * larger than {@code Long.MAX_VALUE} that are just not representable using a signed {@code long}. + */ +@Documented +@Label("Unsigned integer type") +@Description("Value should be interpreted as unsigned data type") +@Target({TYPE_USE, PARAMETER, FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Unsigned { +} diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueLayout.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueLayout.java index c0a751442..be883e051 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueLayout.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueLayout.java @@ -34,11 +34,19 @@ public static long addressByteSize() { } public static final ValueLayout.OfBoolean SWIFT_BOOL = ValueLayout.JAVA_BOOLEAN; + public static final ValueLayout.OfByte SWIFT_INT8 = ValueLayout.JAVA_BYTE; - public static final ValueLayout.OfChar SWIFT_UINT16 = ValueLayout.JAVA_CHAR; + public static final ValueLayout.OfByte SWIFT_UINT8 = SWIFT_INT8; + public static final ValueLayout.OfShort SWIFT_INT16 = ValueLayout.JAVA_SHORT; + public static final ValueLayout.OfChar SWIFT_UINT16 = ValueLayout.JAVA_CHAR; + public static final ValueLayout.OfInt SWIFT_INT32 = ValueLayout.JAVA_INT; + public static final ValueLayout.OfInt SWIFT_UINT32 = SWIFT_INT32; + public static final ValueLayout.OfLong SWIFT_INT64 = ValueLayout.JAVA_LONG; + public static final ValueLayout.OfLong SWIFT_UINT64 = SWIFT_INT64; + public static final ValueLayout.OfFloat SWIFT_FLOAT = ValueLayout.JAVA_FLOAT; public static final ValueLayout.OfDouble SWIFT_DOUBLE = ValueLayout.JAVA_DOUBLE; diff --git a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift index eebfdf4a9..0b8ca1d33 100644 --- a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift @@ -43,6 +43,7 @@ func assertLoweredFunction( translator.prepareForTranslation() let generator = FFMSwift2JavaGenerator( + config: config, translator: translator, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -128,6 +129,7 @@ func assertLoweredVariableAccessor( translator.prepareForTranslation() let generator = FFMSwift2JavaGenerator( + config: config, translator: translator, javaPackage: javaPackage, swiftOutputDirectory: "/fake", diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index ffefe9078..e975d2239 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -25,6 +25,7 @@ enum RenderKind { func assertOutput( dump: Bool = false, input: String, + config: Configuration? = nil, _ mode: JExtractGenerationMode, _ renderKind: RenderKind, swiftModuleName: String = "SwiftModule", @@ -36,8 +37,7 @@ func assertOutput( line: Int = #line, column: Int = #column ) throws { - var config = Configuration() - config.logLevel = .trace + var config = config ?? Configuration() config.swiftModule = swiftModuleName let translator = Swift2JavaTranslator(config: config) translator.dependenciesClasses = Array(javaClassLookupTable.keys) @@ -49,6 +49,7 @@ func assertOutput( switch mode { case .ffm: let generator = FFMSwift2JavaGenerator( + config: config, translator: translator, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -64,6 +65,7 @@ func assertOutput( case .jni: let generator = JNISwift2JavaGenerator( + config: config, translator: translator, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index 1678f91f7..a87294b05 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -47,6 +47,7 @@ final class FuncCallbackImportTests { let funcDecl = st.importedGlobalFuncs.first { $0.name == "callMe" }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -129,13 +130,13 @@ final class FuncCallbackImportTests { var config = Configuration() config.swiftModule = "__FakeModule" let st = Swift2JavaTranslator(config: config) - st.log.logLevel = .error try st.analyze(file: "Fake.swift", text: Self.class_interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.name == "callMeMore" }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -251,6 +252,7 @@ final class FuncCallbackImportTests { let funcDecl = st.importedGlobalFuncs.first { $0.name == "withBuffer" }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift index 6854b2603..a8d83a2a4 100644 --- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift +++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift @@ -246,6 +246,7 @@ extension FunctionDescriptorTests { }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: javaPackage, swiftOutputDirectory: "/fake", @@ -275,6 +276,7 @@ extension FunctionDescriptorTests { try st.analyze(file: "/fake/Sample.swiftinterface", text: interfaceFile) let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: javaPackage, swiftOutputDirectory: "/fake", diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift index cfe78fbd6..483d53f5a 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -18,78 +18,92 @@ import Testing @Suite struct JNIClassTests { let source = """ - public class MyClass { - let x: Int64 - let y: Int64 - - public static func method() {} - - public init(x: Int64, y: Int64) { - self.x = y - self.y = y - } - - public init() { - self.x = 0 - self.y = 0 + public class MyClass { + let x: Int64 + let y: Int64 + + public static func method() {} + + public init(x: Int64, y: Int64) { + self.x = y + self.y = y + } + + public init() { + self.x = 0 + self.y = 0 + } + + public func doSomething(x: Int64) {} + + public func copy() -> MyClass {} + public func isEqual(to other: MyClass) -> Bool {} } - - public func doSomething(x: Int64) {} - - public func copy() -> MyClass {} - public func isEqual(to other: MyClass) -> Bool {} - } - """ + """ @Test func generatesJavaClass() throws { - try assertOutput(input: source, .jni, .java, expectedChunks: [ - """ - // Generated by jextract-swift - // Swift module: SwiftModule + try assertOutput( + input: source, + .jni, .java, + expectedChunks: [ + """ + // Generated by jextract-swift + // Swift module: SwiftModule - package com.example.swift; - - import org.swift.swiftkit.core.*; - import org.swift.swiftkit.core.util.*; + package com.example.swift; - public final class MyClass extends JNISwiftInstance { - static final String LIB_NAME = "SwiftModule"; - - @SuppressWarnings("unused") - private static final boolean INITIALIZED_LIBS = initializeLibs(); - static boolean initializeLibs() { - System.loadLibrary(LIB_NAME); - return true; - } - - public MyClass(long selfPointer, SwiftArena swiftArena) { - super(selfPointer, swiftArena); - } - """, - """ - private static native void $destroy(long selfPointer); - """, - """ - @Override - protected Runnable $createDestroyFunction() { - long self$ = this.$memoryAddress(); - if (CallTraces.TRACE_DOWNCALLS) { - CallTraces.traceDowncall("MyClass.$createDestroyFunction", - "this", this, - "self", self$); - } - return new Runnable() { - @Override - public void run() { - if (CallTraces.TRACE_DOWNCALLS) { - CallTraces.traceDowncall("MyClass.$destroy", "self", self$); - } - MyClass.$destroy(self$); + import org.swift.swiftkit.core.*; + import org.swift.swiftkit.core.util.*; + """, + """ + public final class MyClass extends JNISwiftInstance { + static final String LIB_NAME = "SwiftModule"; + + @SuppressWarnings("unused") + private static final boolean INITIALIZED_LIBS = initializeLibs(); + static boolean initializeLibs() { + System.loadLibrary(LIB_NAME); + return true; + } + + public MyClass(long selfPointer, SwiftArena swiftArena) { + super(selfPointer, swiftArena); + } + """, + ]) + try assertOutput( + input: source, + .jni, .java, + expectedChunks: [ + """ + private static native void $destroy(long selfPointer); + """ + ]) + try assertOutput( + input: source, + .jni, .java, + expectedChunks: [ + """ + @Override + protected Runnable $createDestroyFunction() { + long self$ = this.$memoryAddress(); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("MyClass.$createDestroyFunction", + "this", this, + "self", self$); } - }; - """ - ]) + return new Runnable() { + @Override + public void run() { + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("MyClass.$destroy", "self", self$); + } + MyClass.$destroy(self$); + } + }; + """ + ]) } @Test @@ -101,18 +115,18 @@ struct JNIClassTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public static func method() - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public static func method() + * } + */ public static void method() { MyClass.$method(); } """, """ private static native void $method(); - """ + """, ] ) } @@ -169,7 +183,7 @@ struct JNIClassTests { """, """ private static native long $init(); - """ + """, ] ) } @@ -199,7 +213,7 @@ struct JNIClassTests { let resultBits$ = Int64(Int(bitPattern: result$)) return resultBits$.getJNIValue(in: environment!) } - """ + """, ] ) } @@ -251,7 +265,7 @@ struct JNIClassTests { """, """ private static native void $doSomething(long x, long self); - """ + """, ] ) } @@ -274,7 +288,7 @@ struct JNIClassTests { } self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!)) } - """, + """ ] ) } @@ -299,7 +313,7 @@ struct JNIClassTests { """, """ private static native long $copy(long self); - """ + """, ] ) } @@ -325,7 +339,7 @@ struct JNIClassTests { let resultBits$ = Int64(Int(bitPattern: result$)) return resultBits$.getJNIValue(in: environment!) } - """, + """ ] ) } @@ -350,7 +364,7 @@ struct JNIClassTests { """, """ private static native boolean $isEqual(long other, long self); - """ + """, ] ) } @@ -378,7 +392,7 @@ struct JNIClassTests { } return self$.pointee.isEqual(to: other$.pointee).getJNIValue(in: environment!) } - """, + """ ] ) } diff --git a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift index 4696253cf..be9cf0ce6 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift @@ -43,6 +43,10 @@ struct JNIModuleTests { package com.example.swift; + import org.swift.swiftkit.core.*; + import org.swift.swiftkit.core.util.*; + import org.swift.swiftkit.core.annotations.*; + public final class SwiftModule { static final String LIB_NAME = "SwiftModule"; @@ -81,6 +85,7 @@ struct JNIModuleTests { * public func takeIntegers(i1: Int8, i2: Int16, i3: Int32, i4: Int64) -> UInt16 * } */ + @Unsigned public static char takeIntegers(byte i1, short i2, int i3, long i4) { return SwiftModule.$takeIntegers(i1, i2, i3, i4); } diff --git a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift index a4084654c..09a8626d3 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift @@ -18,69 +18,81 @@ import Testing @Suite struct JNIStructTests { let source = """ - public struct MyStruct { - let x: Int64 - let y: Int64 - - public init(x: Int64, y: Int64) { - self.x = y - self.y = y + public struct MyStruct { + let x: Int64 + let y: Int64 + + public init(x: Int64, y: Int64) { + self.x = y + self.y = y + } + + public func doSomething(x: Int64) {} } - - public func doSomething(x: Int64) {} - } - """ + """ @Test func generatesJavaClass() throws { - try assertOutput(input: source, .jni, .java, expectedChunks: [ - """ - // Generated by jextract-swift - // Swift module: SwiftModule + try assertOutput( + input: source, .jni, .java, + expectedChunks: [ + """ + // Generated by jextract-swift + // Swift module: SwiftModule - package com.example.swift; - - import org.swift.swiftkit.core.*; - import org.swift.swiftkit.core.util.*; + package com.example.swift; - public final class MyStruct extends JNISwiftInstance { - static final String LIB_NAME = "SwiftModule"; - - @SuppressWarnings("unused") - private static final boolean INITIALIZED_LIBS = initializeLibs(); - static boolean initializeLibs() { - System.loadLibrary(LIB_NAME); - return true; - } - - public MyStruct(long selfPointer, SwiftArena swiftArena) { - super(selfPointer, swiftArena); - } - """, - """ - private static native void $destroy(long selfPointer); - """, - """ - @Override - protected Runnable $createDestroyFunction() { - long self$ = this.$memoryAddress(); - if (CallTraces.TRACE_DOWNCALLS) { - CallTraces.traceDowncall("MyStruct.$createDestroyFunction", - "this", this, - "self", self$); - } - return new Runnable() { - @Override - public void run() { - if (CallTraces.TRACE_DOWNCALLS) { - CallTraces.traceDowncall("MyStruct.$destroy", "self", self$); - } - MyStruct.$destroy(self$); + import org.swift.swiftkit.core.*; + import org.swift.swiftkit.core.util.*; + """,]) + try assertOutput(input: source, .jni, .java, expectedChunks: [ + """ + public final class MyStruct extends JNISwiftInstance { + static final String LIB_NAME = "SwiftModule"; + + @SuppressWarnings("unused") + private static final boolean INITIALIZED_LIBS = initializeLibs(); + static boolean initializeLibs() { + System.loadLibrary(LIB_NAME); + return true; } - }; - } - """ - ]) + + public MyStruct(long selfPointer, SwiftArena swiftArena) { + super(selfPointer, swiftArena); + } + """ + ]) + try assertOutput( + input: source, .jni, .java, + expectedChunks: [ + """ + private static native void $destroy(long selfPointer); + """ + ]) + try assertOutput( + input: source, .jni, .java, + expectedChunks: [ + """ + @Override + protected Runnable $createDestroyFunction() { + long self$ = this.$memoryAddress(); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("MyStruct.$createDestroyFunction", + "this", this, + "self", self$); + } + return new Runnable() { + @Override + public void run() { + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("MyStruct.$destroy", "self", self$); + } + MyStruct.$destroy(self$); + } + }; + } + """ + ]) } @Test @@ -175,7 +187,7 @@ struct JNIStructTests { """, """ private static native void $doSomething(long x, long self); - """ + """, ] ) } @@ -198,7 +210,7 @@ struct JNIStructTests { } self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!)) } - """, + """ ] ) } diff --git a/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift b/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift new file mode 100644 index 000000000..f4dbffed3 --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift @@ -0,0 +1,154 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import JavaKitConfigurationShared +import Testing + +final class JNIUnsignedNumberTests { + + @Test("Import: UInt16 (char)") + func jni_unsignedChar() throws { + try assertOutput( + input: "public func unsignedChar(_ arg: UInt16)", + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func unsignedChar(_ arg: UInt16) + * } + */ + public static void unsignedChar(@Unsigned char arg) { + SwiftModule.$unsignedChar(arg); + } + """, + """ + private static native void $unsignedChar(char arg); + """, + ] + ) + } + + @Test("Import: UInt32 (annotate)") + func jni_unsignedInt_annotate() throws { + var config = Configuration() + config.unsignedNumbersMode = .annotate + config.logLevel = .trace + + try assertOutput( + input: "public func unsignedInt(_ arg: UInt32)", + config: config, + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func unsignedInt(_ arg: UInt32) + * } + */ + public static void unsignedInt(@Unsigned int arg) { + SwiftModule.$unsignedInt(arg); + } + private static native void $unsignedInt(int arg); + """, + ] + ) + } + + @Test("Import: return UInt32 (default)") + func jni_returnUnsignedIntDefault() throws { + let config = Configuration() + + try assertOutput( + input: "public func returnUnsignedInt() -> UInt32", + config: config, + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func returnUnsignedInt() -> UInt32 + * } + */ + @Unsigned + public static int returnUnsignedInt() { + return SwiftModule.$returnUnsignedInt(); + } + private static native int $returnUnsignedInt(); + """, + ] + ) + } + + @Test("Import: return UInt64 (wrap, unsupported)") + func jni_return_unsignedLongWrap() throws { + var config = Configuration() + config.unsignedNumbersMode = .wrapGuava + + try assertOutput( + input: "public func returnUnsignedLong() -> UInt64", + config: config, + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + // we do not import in wrap mode + """ + public final class SwiftModule { + static final String LIB_NAME = "SwiftModule"; + + static { + System.loadLibrary(LIB_NAME); + } + + } + """, + ] + ) + } + + @Test("Import: take UInt64 return UInt32 (annotate)") + func jni_echo_unsignedLong_annotate() throws { + var config = Configuration() + config.unsignedNumbersMode = .annotate + + try assertOutput( + input: "public func unsignedLong(first: UInt64, second: UInt32) -> UInt32", + config: config, + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func unsignedLong(first: UInt64, second: UInt32) -> UInt32 + * } + */ + @Unsigned + public static int unsignedLong(@Unsigned long first, @Unsigned int second) { + return SwiftModule.$unsignedLong(first, second); + } + private static native int $unsignedLong(long first, int second); + """, + ] + ) + } +} diff --git a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift index 5757e8daa..9d2fcb227 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift @@ -20,6 +20,7 @@ struct JNIVariablesTests { let membersSource = """ public class MyClass { + public let someByte: UInt8 public let constant: Int64 public var mutable: Int64 public var computed: Int64 { diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift index 73357d6bd..fd885f1b5 100644 --- a/Tests/JExtractSwiftTests/MethodImportTests.swift +++ b/Tests/JExtractSwiftTests/MethodImportTests.swift @@ -73,6 +73,7 @@ final class MethodImportTests { try st.analyze(file: "Fake.swift", text: class_interfaceFile) let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -116,6 +117,7 @@ final class MethodImportTests { }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -157,6 +159,7 @@ final class MethodImportTests { }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -200,6 +203,7 @@ final class MethodImportTests { }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -243,6 +247,7 @@ final class MethodImportTests { }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -289,6 +294,7 @@ final class MethodImportTests { }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -331,6 +337,7 @@ final class MethodImportTests { }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -373,6 +380,7 @@ final class MethodImportTests { }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", @@ -417,6 +425,7 @@ final class MethodImportTests { }! let generator = FFMSwift2JavaGenerator( + config: config, translator: st, javaPackage: "com.example.swift", swiftOutputDirectory: "/fake", diff --git a/Tests/JExtractSwiftTests/UnsignedNumberTests.swift b/Tests/JExtractSwiftTests/UnsignedNumberTests.swift new file mode 100644 index 000000000..11b91e53d --- /dev/null +++ b/Tests/JExtractSwiftTests/UnsignedNumberTests.swift @@ -0,0 +1,268 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import JavaKitConfigurationShared +import Testing + +final class UnsignedNumberTests { + + @Test("Import: UInt16 (char)") + func unsignedChar() throws { + try assertOutput( + input: "public func unsignedChar(_ arg: UInt16)", + .ffm, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_unsignedChar__(uint16_t arg) + * } + */ + private static class swiftjava_SwiftModule_unsignedChar__ { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* arg: */SwiftValueLayout.SWIFT_UINT16 + ); + """, + """ + public static void unsignedChar(@Unsigned char arg) { + swiftjava_SwiftModule_unsignedChar__.call(arg); + } + """, + ] + ) + } + + @Test("Import: UInt32 (wrap)") + func unsignedInt() throws { + var config = Configuration() + config.unsignedNumbersMode = .wrapGuava + + try assertOutput( + input: "public func unsignedInt(_ arg: UInt32)", + config: config, + .ffm, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_unsignedInt__(uint32_t arg) + * } + */ + private static class swiftjava_SwiftModule_unsignedInt__ { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* arg: */SwiftValueLayout.SWIFT_UINT32 + ); + """, + """ + public static void unsignedInt(com.google.common.primitives.UnsignedInteger arg) { + swiftjava_SwiftModule_unsignedInt__.call(UnsignedNumbers.toPrimitive(arg)); + } + """, + ] + ) + } + + @Test("Import: UInt32 (annotate)") + func unsignedIntAnnotate() throws { + var config = Configuration() + config.unsignedNumbersMode = .annotate + + try assertOutput( + input: "public func unsignedInt(_ arg: UInt32)", + config: config, + .ffm, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_unsignedInt__(uint32_t arg) + * } + */ + private static class swiftjava_SwiftModule_unsignedInt__ { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* arg: */SwiftValueLayout.SWIFT_UINT32 + ); + """, + """ + public static void unsignedInt(@Unsigned int arg) { + swiftjava_SwiftModule_unsignedInt__.call(arg); + } + """, + ] + ) + } + + @Test("Import: return UInt32 (default)") + func returnUnsignedIntDefault() throws { + let config = Configuration() + + try assertOutput( + input: "public func returnUnsignedInt() -> UInt32", + config: config, + .ffm, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * uint32_t swiftjava_SwiftModule_returnUnsignedInt(void) + * } + */ + private static class swiftjava_SwiftModule_returnUnsignedInt { + private static final FunctionDescriptor DESC = FunctionDescriptor.of( + /* -> */SwiftValueLayout.SWIFT_UINT32 + ); + """, + """ + @Unsigned + public static int returnUnsignedInt() { + return swiftjava_SwiftModule_returnUnsignedInt.call(); + } + """, + ] + ) + } + + @Test("Import: return UInt64 (wrap)") + func return_unsignedLongWrap() throws { + var config = Configuration() + config.unsignedNumbersMode = .wrapGuava + + try assertOutput( + input: "public func returnUnsignedLong() -> UInt64", + config: config, + .ffm, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * uint64_t swiftjava_SwiftModule_returnUnsignedLong(void) + * } + */ + private static class swiftjava_SwiftModule_returnUnsignedLong { + private static final FunctionDescriptor DESC = FunctionDescriptor.of( + /* -> */SwiftValueLayout.SWIFT_UINT64 + ); + """, + """ + public static com.google.common.primitives.UnsignedLong returnUnsignedLong() { + return com.google.common.primitives.UnsignedLong.fromLongBits(swiftjava_SwiftModule_returnUnsignedLong.call()); + } + """, + ] + ) + } + + @Test("Import: return UInt64 (annotate)") + func return_unsignedLong_annotate() throws { + var config = Configuration() + config.unsignedNumbersMode = .annotate + + try assertOutput( + input: "public func returnUnsignedLong() -> UInt64", + config: config, + .ffm, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * uint64_t swiftjava_SwiftModule_returnUnsignedLong(void) + * } + */ + private static class swiftjava_SwiftModule_returnUnsignedLong { + private static final FunctionDescriptor DESC = FunctionDescriptor.of( + /* -> */SwiftValueLayout.SWIFT_UINT64 + ); + """, + """ + @Unsigned + public static long returnUnsignedLong() { + return swiftjava_SwiftModule_returnUnsignedLong.call(); + } + """, + ] + ) + } + + @Test("Import: take UInt64 (annotate)") + func take_unsignedLong_annotate() throws { + var config = Configuration() + config.unsignedNumbersMode = .annotate + + try assertOutput( + input: "public func takeUnsignedLong(arg: UInt64)", + config: config, + .ffm, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_takeUnsignedLong_arg(uint64_t arg) + * } + */ + private static class swiftjava_SwiftModule_takeUnsignedLong_arg { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* arg: */SwiftValueLayout.SWIFT_UINT64 + ); + """, + """ + public static void takeUnsignedLong(@Unsigned long arg) { + swiftjava_SwiftModule_takeUnsignedLong_arg.call(arg); + } + """, + ] + ) + } + + @Test("Import: take UInt64 return UInt32 (annotate)") + func echo_unsignedLong_annotate() throws { + var config = Configuration() + config.unsignedNumbersMode = .annotate + + try assertOutput( + input: "public func unsignedLong(first: UInt64, second: UInt32) -> UInt32", + config: config, + .ffm, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * {@snippet lang=c : + * uint32_t swiftjava_SwiftModule_unsignedLong_first_second(uint64_t first, uint32_t second) + * } + */ + private static class swiftjava_SwiftModule_unsignedLong_first_second { + private static final FunctionDescriptor DESC = FunctionDescriptor.of( + /* -> */SwiftValueLayout.SWIFT_UINT32 + /* first: */SwiftValueLayout.SWIFT_UINT64 + /* second: */SwiftValueLayout.SWIFT_UINT32 + ); + """, + """ + @Unsigned + public static int unsignedLong(@Unsigned long first, @Unsigned int second) { + return swiftjava_SwiftModule_unsignedLong_first_second.call(first, second); + } + """, + ] + ) + } +} From 2d89c613445a3baf41594529542dc167d4ebc115 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 1 Aug 2025 13:24:49 +0900 Subject: [PATCH 048/141] Fix: JNI + unsigned modes checking (#342) --- .../Commands/JExtractCommand.swift | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index 03ca0cd71..105ace456 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -86,10 +86,7 @@ extension SwiftJava.JExtractCommand { config.writeEmptyFiles = writeEmptyFiles config.unsignedNumbersMode = unsignedNumbers - guard checkModeCompatibility() else { - // check would have logged the reason for early exit. - return - } + try checkModeCompatibility() if let inputSwift = commonOptions.inputSwift { config.inputSwiftDirectory = inputSwift @@ -108,18 +105,15 @@ extension SwiftJava.JExtractCommand { } /// Check if the configured modes are compatible, and fail if not - func checkModeCompatibility() -> Bool { + func checkModeCompatibility() throws { if self.mode == .jni { switch self.unsignedNumbers { case .annotate: - print("Error: JNI mode does not support '\(JExtractUnsignedIntegerMode.wrapGuava)' Unsigned integer mode! \(Self.helpMessage)") - return false + throw IllegalModeCombinationError("JNI mode does not support '\(JExtractUnsignedIntegerMode.wrapGuava)' Unsigned integer mode! \(Self.helpMessage)") case .wrapGuava: () // OK } } - - return true } } @@ -140,5 +134,12 @@ extension SwiftJava.JExtractCommand { } +struct IllegalModeCombinationError: Error { + let message: String + init(_ message: String) { + self.message = message + } +} + extension JExtractGenerationMode: ExpressibleByArgument {} extension JExtractUnsignedIntegerMode: ExpressibleByArgument {} From f7a5082b3e2794637b327bc2792a7363042e23a3 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 1 Aug 2025 14:05:13 +0900 Subject: [PATCH 049/141] Mark Sendable types as @ThreadSafe (#341) --- .../FFM/FFMSwift2JavaGenerator.swift | 3 + ...t2JavaGenerator+JavaBindingsPrinting.swift | 3 + .../SwiftNominalTypeDeclaration.swift | 16 ++++++ .../swiftkit/core/annotations/ThreadSafe.java | 42 ++++++++++++++ Tests/JExtractSwiftTests/SendableTests.swift | 56 +++++++++++++++++++ 5 files changed, 120 insertions(+) create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/ThreadSafe.java create mode 100644 Tests/JExtractSwiftTests/SendableTests.swift diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index 299626213..3e60db148 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -278,6 +278,9 @@ extension FFMSwift2JavaGenerator { parentProtocol = "SwiftValue" } + if decl.swiftNominal.isSendable { + printer.print("@ThreadSafe // Sendable") + } printer.printBraceBlock("public final class \(decl.swiftNominal.name) extends FFMSwiftInstance implements \(parentProtocol)") { printer in // Constants diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 63b9dcd1e..b91588df2 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -171,6 +171,9 @@ extension JNISwift2JavaGenerator { private func printNominal( _ printer: inout CodePrinter, _ decl: ImportedNominalType, body: (inout CodePrinter) -> Void ) { + if decl.swiftNominal.isSendable { + printer.print("@ThreadSafe // Sendable") + } printer.printBraceBlock("public final class \(decl.swiftNominal.name) extends JNISwiftInstance") { printer in body(&printer) } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift index b377fd858..cef4e7311 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -85,6 +85,22 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { super.init(moduleName: moduleName, name: node.name.text) } + /// Returns true if this type conforms to `Sendable` and therefore is "threadsafe". + lazy var isSendable: Bool = { + // Check if Sendable is in the inheritance list + guard let inheritanceClause = self.syntax?.inheritanceClause else { + return false + } + + for inheritedType in inheritanceClause.inheritedTypes { + if inheritedType.type.trimmedDescription == "Sendable" { + return true + } + } + + return false + }() + /// Determine the known standard library type for this nominal type /// declaration. private func computeKnownStandardLibraryType() -> SwiftKnownTypeDeclKind? { diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/ThreadSafe.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/ThreadSafe.java new file mode 100644 index 000000000..2e62a8b66 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/annotations/ThreadSafe.java @@ -0,0 +1,42 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core.annotations; + +import jdk.jfr.Description; +import jdk.jfr.Label; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; + +/** + * Used to mark a type as thread-safe, i.e. no additional synchronization is necessary when accessing it + * from multiple threads. + * + *

In SwiftJava specifically, this attribute is applied when an extracted Swift type conforms to the Swift + * {@code Sendable} protocol, which is a compiler enforced mechanism to enforce thread-safety in Swift. + * + * @see Swift Sendable API documentation. + */ +@Documented +@Label("Thread-safe") +@Description("Value should be interpreted as safe to be shared across threads.") +@Target({TYPE_USE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ThreadSafe { +} diff --git a/Tests/JExtractSwiftTests/SendableTests.swift b/Tests/JExtractSwiftTests/SendableTests.swift new file mode 100644 index 000000000..d6d0d2d6e --- /dev/null +++ b/Tests/JExtractSwiftTests/SendableTests.swift @@ -0,0 +1,56 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import Testing + +final class SendableTests { + let source = + """ + public struct SendableStruct: Sendable {} + """ + + + @Test("Import: Sendable struct (ffm)") + func sendableStruct_ffm() throws { + + try assertOutput( + input: source, .ffm, .java, + expectedChunks: [ + """ + @ThreadSafe // Sendable + public final class SendableStruct extends FFMSwiftInstance implements SwiftValue { + static final String LIB_NAME = "SwiftModule"; + static final Arena LIBRARY_ARENA = Arena.ofAuto(); + """, + ] + ) + } + + @Test("Import: Sendable struct (jni)") + func sendableStruct_jni() throws { + + try assertOutput( + input: source, .jni, .java, + expectedChunks: [ + """ + @ThreadSafe // Sendable + public final class SendableStruct extends JNISwiftInstance { + static final String LIB_NAME = "SwiftModule"; + """, + ] + ) + } + +} \ No newline at end of file From 02de9675d1f227b2814d581b7b56a2c6f0d4f6d9 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 1 Aug 2025 15:12:15 +0900 Subject: [PATCH 050/141] Allow jextract to optionally import package/internal decls as well (#343) --- .../Convenience/SwiftSyntax+Extensions.swift | 69 ++++++++++++++++--- Sources/JExtractSwiftLib/Logger.swift | 17 +++++ .../Swift2JavaTranslator.swift | 4 +- .../JExtractSwiftLib/Swift2JavaVisitor.swift | 27 +++++--- .../Configuration.swift | 4 ++ .../GenerationMode.swift | 15 +++- .../Commands/JExtractCommand.swift | 5 ++ .../InternalExtractTests.swift | 49 +++++++++++++ 8 files changed, 171 insertions(+), 19 deletions(-) create mode 100644 Tests/JExtractSwiftTests/InternalExtractTests.swift diff --git a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift index da836d45b..e71300af7 100644 --- a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift @@ -51,23 +51,76 @@ extension DeclModifierSyntax { extension DeclModifierSyntax { var isPublic: Bool { switch self.name.tokenKind { - case .keyword(.private): return false - case .keyword(.fileprivate): return false - case .keyword(.internal): return false - case .keyword(.package): return false - case .keyword(.public): return true - case .keyword(.open): return true - default: return false + case .keyword(.private): false + case .keyword(.fileprivate): false + case .keyword(.internal): false + case .keyword(.package): false + case .keyword(.public): true + case .keyword(.open): true + default: false } } + + var isPackage: Bool { + switch self.name.tokenKind { + case .keyword(.private): false + case .keyword(.fileprivate): false + case .keyword(.internal): false + case .keyword(.package): true + case .keyword(.public): false + case .keyword(.open): false + default: false + } + } + + var isAtLeastPackage: Bool { + isPackage || isPublic + } + + var isInternal: Bool { + return switch self.name.tokenKind { + case .keyword(.private): false + case .keyword(.fileprivate): false + case .keyword(.internal): true + case .keyword(.package): false + case .keyword(.public): false + case .keyword(.open): false + default: false + } + } + + var isAtLeastInternal: Bool { + isInternal || isPackage || isPublic + } } extension WithModifiersSyntax { var isPublic: Bool { - self.modifiers.contains { modifier in + return self.modifiers.contains { modifier in modifier.isPublic } } + + var isAtLeastPackage: Bool { + if self.modifiers.isEmpty { + return false + } + + return self.modifiers.contains { modifier in + modifier.isAtLeastInternal + } + } + + var isAtLeastInternal: Bool { + if self.modifiers.isEmpty { + // we assume that default access level is internal + return true + } + + return self.modifiers.contains { modifier in + modifier.isAtLeastInternal + } + } } extension AttributeListSyntax.Element { diff --git a/Sources/JExtractSwiftLib/Logger.swift b/Sources/JExtractSwiftLib/Logger.swift index 541dbae42..5bffdc8ca 100644 --- a/Sources/JExtractSwiftLib/Logger.swift +++ b/Sources/JExtractSwiftLib/Logger.swift @@ -27,6 +27,23 @@ public struct Logger { self.logLevel = logLevel } + public func error( + _ message: @autoclosure () -> String, + metadata: [String: Any] = [:], + file: String = #fileID, + line: UInt = #line, + function: String = #function + ) { + guard logLevel <= .error else { + return + } + + let metadataString: String = + if metadata.isEmpty { "" } else { "\(metadata)" } + + print("[error][\(file):\(line)](\(function)) \(message()) \(metadataString)") + } + public func warning( _ message: @autoclosure () -> String, metadata: [String: Any] = [:], diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index 3e2359f27..300b979f7 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -200,7 +200,7 @@ extension Swift2JavaTranslator { _ nominalNode: some DeclGroupSyntax & NamedDeclSyntax & WithModifiersSyntax & WithAttributesSyntax, parent: ImportedNominalType? ) -> ImportedNominalType? { - if !nominalNode.shouldImport(log: log) { + if !nominalNode.shouldExtract(config: config, log: log) { return nil } @@ -225,7 +225,7 @@ extension Swift2JavaTranslator { guard swiftNominalDecl.moduleName == self.swiftModuleName else { return nil } - guard swiftNominalDecl.syntax!.shouldImport(log: log) else { + guard swiftNominalDecl.syntax!.shouldExtract(config: config, log: log) else { return nil } diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index 90181757f..3efddbfc6 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift @@ -15,9 +15,13 @@ import Foundation import SwiftParser import SwiftSyntax +import JavaKitConfigurationShared final class Swift2JavaVisitor { let translator: Swift2JavaTranslator + var config: Configuration { + self.translator.config + } init(translator: Swift2JavaTranslator) { self.translator = translator @@ -48,7 +52,7 @@ final class Swift2JavaVisitor { case .extensionDecl(let node): self.visit(extensionDecl: node, in: parent) case .typeAliasDecl: - break // TODO: Implement + break // TODO: Implement; https://github.com/swiftlang/swift-java/issues/338 case .associatedTypeDecl: break // TODO: Implement @@ -93,7 +97,7 @@ final class Swift2JavaVisitor { } func visit(functionDecl node: FunctionDeclSyntax, in typeContext: ImportedNominalType?) { - guard node.shouldImport(log: log) else { + guard node.shouldExtract(config: config, log: log) else { return } @@ -128,7 +132,7 @@ final class Swift2JavaVisitor { } func visit(variableDecl node: VariableDeclSyntax, in typeContext: ImportedNominalType?) { - guard node.shouldImport(log: log) else { + guard node.shouldExtract(config: config, log: log) else { return } @@ -182,7 +186,7 @@ final class Swift2JavaVisitor { self.log.info("Initializer must be within a current type; \(node)") return } - guard node.shouldImport(log: log) else { + guard node.shouldExtract(config: config, log: log) else { return } @@ -212,13 +216,20 @@ final class Swift2JavaVisitor { } extension DeclSyntaxProtocol where Self: WithModifiersSyntax & WithAttributesSyntax { - func shouldImport(log: Logger) -> Bool { - guard accessControlModifiers.contains(where: { $0.isPublic }) else { - log.trace("Skip import '\(self.qualifiedNameForDebug)': not public") + func shouldExtract(config: Configuration, log: Logger) -> Bool { + let meetsRequiredAccessLevel: Bool = + switch config.effectiveMinimumInputAccessLevelMode { + case .public: self.isPublic + case .package: self.isAtLeastPackage + case .internal: self.isAtLeastInternal + } + + guard meetsRequiredAccessLevel else { + log.debug("Skip import '\(self.qualifiedNameForDebug)': not at least \(config.effectiveMinimumInputAccessLevelMode)") return false } guard !attributes.contains(where: { $0.isJava }) else { - log.trace("Skip import '\(self.qualifiedNameForDebug)': is Java") + log.debug("Skip import '\(self.qualifiedNameForDebug)': is Java") return false } diff --git a/Sources/JavaKitConfigurationShared/Configuration.swift b/Sources/JavaKitConfigurationShared/Configuration.swift index c1ae7dd2b..0ff089da0 100644 --- a/Sources/JavaKitConfigurationShared/Configuration.swift +++ b/Sources/JavaKitConfigurationShared/Configuration.swift @@ -46,6 +46,10 @@ public struct Configuration: Codable { public var effectiveUnsignedNumbersMode: JExtractUnsignedIntegerMode { unsignedNumbersMode ?? .default } + public var minimumInputAccessLevelMode: JExtractMinimumAccessLevelMode? + public var effectiveMinimumInputAccessLevelMode: JExtractMinimumAccessLevelMode { + minimumInputAccessLevelMode ?? .default + } // ==== java 2 swift --------------------------------------------------------- diff --git a/Sources/JavaKitConfigurationShared/GenerationMode.swift b/Sources/JavaKitConfigurationShared/GenerationMode.swift index 190323a05..1feac411b 100644 --- a/Sources/JavaKitConfigurationShared/GenerationMode.swift +++ b/Sources/JavaKitConfigurationShared/GenerationMode.swift @@ -59,7 +59,20 @@ extension JExtractUnsignedIntegerMode { } } - public static var `default`: JExtractUnsignedIntegerMode { + public static var `default`: Self { .annotate } } + +/// The minimum access level which +public enum JExtractMinimumAccessLevelMode: String, Codable { + case `public` + case `package` + case `internal` +} + +extension JExtractMinimumAccessLevelMode { + public static var `default`: Self { + .public + } +} diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index 105ace456..54a997083 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -64,6 +64,9 @@ extension SwiftJava { @Option(help: "The mode of generation to use for the output files. Used with jextract mode. By default, unsigned Swift types are imported as their bit-width compatible signed Java counterparts, and annotated using the '@Unsigned' annotation. You may choose the 'wrap-guava' mode in order to import types as class wrapper types (`UnsignedInteger` et al) defined by the Google Guava library's `com.google.common.primitives' package. that ensure complete type-safety with regards to unsigned values, however they incur an allocation and performance overhead.") var unsignedNumbers: JExtractUnsignedIntegerMode = .default + @Option(help: "The lowest access level of Swift declarations that should be extracted, defaults to 'public'.") + var minimumInputAccessLevel: JExtractMinimumAccessLevelMode = .default + @Option( help: """ A swift-java configuration file for a given Swift module name on which this module depends, @@ -85,6 +88,7 @@ extension SwiftJava.JExtractCommand { config.outputSwiftDirectory = outputSwift config.writeEmptyFiles = writeEmptyFiles config.unsignedNumbersMode = unsignedNumbers + config.minimumInputAccessLevelMode = minimumInputAccessLevel try checkModeCompatibility() @@ -143,3 +147,4 @@ struct IllegalModeCombinationError: Error { extension JExtractGenerationMode: ExpressibleByArgument {} extension JExtractUnsignedIntegerMode: ExpressibleByArgument {} +extension JExtractMinimumAccessLevelMode: ExpressibleByArgument {} diff --git a/Tests/JExtractSwiftTests/InternalExtractTests.swift b/Tests/JExtractSwiftTests/InternalExtractTests.swift new file mode 100644 index 000000000..78e712363 --- /dev/null +++ b/Tests/JExtractSwiftTests/InternalExtractTests.swift @@ -0,0 +1,49 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import JavaKitConfigurationShared +import Testing + +final class InternalExtractTests { + let text = + """ + internal func catchMeIfYouCan() + """ + + @Test("Import: internal decl if configured") + func data_swiftThunk() throws { + var config = Configuration() + config.minimumInputAccessLevelMode = .internal + + try assertOutput( + input: text, + config: config, + .ffm, .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * internal func catchMeIfYouCan() + * } + */ + public static void catchMeIfYouCan() { + swiftjava_SwiftModule_catchMeIfYouCan.call(); + } + """, + ] + ) + } +} \ No newline at end of file From b2f42a170154aa080256ca5a11a857197a52cf81 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 1 Aug 2025 17:34:31 +0200 Subject: [PATCH 051/141] [JExtract/JNI] Support optionals in JNI mode (#340) --- .../Sources/MySwiftLibrary/Optionals.swift | 75 ++++ .../com/example/swift/MySwiftClassTest.java | 4 + .../java/com/example/swift/OptionalsTest.java | 116 +++++ .../Convenience/JavaType+Extensions.swift | 75 ++++ ...t2JavaGenerator+JavaBindingsPrinting.swift | 20 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 365 ++++++++++++++- ...wift2JavaGenerator+NativeTranslation.swift | 417 +++++++++++++++--- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 21 +- Sources/JExtractSwiftLib/JNI/JNIType.swift | 98 ---- .../Documentation.docc/SupportedFeatures.md | 3 +- .../JNI/JNIClassTests.swift | 12 +- .../JNI/JNIClosureTests.swift | 4 +- .../JNI/JNIJavaKitTests.swift | 10 +- .../JNI/JNIModuleTests.swift | 3 +- .../JNI/JNIOptionalTests.swift | 253 +++++++++++ .../JNI/JNIStructTests.swift | 3 +- .../JNI/JNIVariablesTests.swift | 33 +- 17 files changed, 1300 insertions(+), 212 deletions(-) create mode 100644 Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift create mode 100644 Samples/JExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java delete mode 100644 Sources/JExtractSwiftLib/JNI/JNIType.swift create mode 100644 Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift new file mode 100644 index 000000000..673ecb0b2 --- /dev/null +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift @@ -0,0 +1,75 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JavaKit + +public func optionalBool(input: Optional) -> Bool? { + return input +} + +public func optionalByte(input: Optional) -> Int8? { + return input +} + +public func optionalChar(input: Optional) -> UInt16? { + return input +} + +public func optionalShort(input: Optional) -> Int16? { + return input +} + +public func optionalInt(input: Optional) -> Int32? { + return input +} + +public func optionalLong(input: Optional) -> Int64? { + return input +} + +public func optionalFloat(input: Optional) -> Float? { + return input +} + +public func optionalDouble(input: Optional) -> Double? { + return input +} + +public func optionalString(input: Optional) -> String? { + return input +} + +public func optionalClass(input: Optional) -> MySwiftClass? { + return input +} + +public func optionalJavaKitLong(input: Optional) -> Int64? { + if let input { + return input.longValue() + } else { + return nil + } +} + +public func multipleOptionals( + input1: Optional, + input2: Optional, + input3: Optional, + input4: Optional, + input5: Optional, + input6: Optional, + input7: Optional +) -> Int64? { + return 1 +} diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java index f034b9040..e7de03add 100644 --- a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -17,6 +17,10 @@ import org.junit.jupiter.api.Test; import org.swift.swiftkit.core.ConfinedSwiftMemorySession; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.OptionalLong; + import static org.junit.jupiter.api.Assertions.*; public class MySwiftClassTest { diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java new file mode 100644 index 000000000..f7262ad4d --- /dev/null +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java @@ -0,0 +1,116 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.ConfinedSwiftMemorySession; + +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class OptionalsTest { + @Test + void optionalBool() { + assertEquals(Optional.empty(), MySwiftLibrary.optionalBool(Optional.empty())); + assertEquals(Optional.of(true), MySwiftLibrary.optionalBool(Optional.of(true))); + } + + @Test + void optionalByte() { + assertEquals(Optional.empty(), MySwiftLibrary.optionalByte(Optional.empty())); + assertEquals(Optional.of((byte) 1) , MySwiftLibrary.optionalByte(Optional.of((byte) 1))); + } + + @Test + void optionalChar() { + assertEquals(Optional.empty(), MySwiftLibrary.optionalChar(Optional.empty())); + assertEquals(Optional.of((char) 42), MySwiftLibrary.optionalChar(Optional.of((char) 42))); + } + + @Test + void optionalShort() { + assertEquals(Optional.empty(), MySwiftLibrary.optionalShort(Optional.empty())); + assertEquals(Optional.of((short) -250), MySwiftLibrary.optionalShort(Optional.of((short) -250))); + } + + @Test + void optionalInt() { + assertEquals(OptionalInt.empty(), MySwiftLibrary.optionalInt(OptionalInt.empty())); + assertEquals(OptionalInt.of(999), MySwiftLibrary.optionalInt(OptionalInt.of(999))); + } + + @Test + void optionalLong() { + assertEquals(OptionalLong.empty(), MySwiftLibrary.optionalLong(OptionalLong.empty())); + assertEquals(OptionalLong.of(999), MySwiftLibrary.optionalLong(OptionalLong.of(999))); + } + + @Test + void optionalFloat() { + assertEquals(Optional.empty(), MySwiftLibrary.optionalFloat(Optional.empty())); + assertEquals(Optional.of(3.14f), MySwiftLibrary.optionalFloat(Optional.of(3.14f))); + } + + @Test + void optionalDouble() { + assertEquals(OptionalDouble.empty(), MySwiftLibrary.optionalDouble(OptionalDouble.empty())); + assertEquals(OptionalDouble.of(2.718), MySwiftLibrary.optionalDouble(OptionalDouble.of(2.718))); + } + + @Test + void optionalString() { + assertEquals(Optional.empty(), MySwiftLibrary.optionalString(Optional.empty())); + assertEquals(Optional.of("Hello Swift!"), MySwiftLibrary.optionalString(Optional.of("Hello Swift!"))); + } + + @Test + void optionalClass() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftClass c = MySwiftClass.init(arena); + assertEquals(Optional.empty(), MySwiftLibrary.optionalClass(Optional.empty(), arena)); + Optional optionalClass = MySwiftLibrary.optionalClass(Optional.of(c), arena); + assertTrue(optionalClass.isPresent()); + assertEquals(c.getX(), optionalClass.get().getX()); + } + } + + @Test + void optionalJavaKitLong() { + assertEquals(OptionalLong.empty(), MySwiftLibrary.optionalJavaKitLong(Optional.empty())); + assertEquals(OptionalLong.of(99L), MySwiftLibrary.optionalJavaKitLong(Optional.of(99L))); + } + + @Test + void multipleOptionals() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftClass c = MySwiftClass.init(arena); + OptionalLong result = MySwiftLibrary.multipleOptionals( + Optional.of((byte) 1), + Optional.of((short) 42), + OptionalInt.of(50), + OptionalLong.of(1000L), + Optional.of("42"), + Optional.of(c), + Optional.of(true) + ); + assertEquals(result, OptionalLong.of(1L)); + } + } +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift index f9a67419d..cb849e795 100644 --- a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift @@ -36,4 +36,79 @@ extension JavaType { case .void: fatalError("There is no type signature for 'void'") } } + + /// Returns the next integral type with space for self and an additional byte. + var nextIntergralTypeWithSpaceForByte: (javaType: JavaType, swiftType: SwiftKnownTypeDeclKind, valueBytes: Int)? { + switch self { + case .boolean, .byte: (.short, .int16, 1) + case .char, .short: (.int, .int32, 2) + case .int: (.long, .int64, 4) + default: nil + } + } + + var optionalType: String? { + switch self { + case .boolean: "Optional" + case .byte: "Optional" + case .char: "Optional" + case .short: "Optional" + case .int: "OptionalInt" + case .long: "OptionalLong" + case .float: "Optional" + case .double: "OptionalDouble" + case .javaLangString: "Optional" + default: nil + } + } + + var optionalWrapperType: String? { + switch self { + case .boolean, .byte, .char, .short, .float, .javaLangString: "Optional" + case .int: "OptionalInt" + case .long: "OptionalLong" + case .double: "OptionalDouble" + default: nil + } + } + + var optionalPlaceholderValue: String? { + switch self { + case .boolean: "false" + case .byte: "(byte) 0" + case .char: "(char) 0" + case .short: "(short) 0" + case .int: "0" + case .long: "0L" + case .float: "0f" + case .double: "0.0" + case .array, .class: "null" + case .void: nil + } + } + + var jniCallMethodAName: String { + switch self { + case .boolean: "CallBooleanMethodA" + case .byte: "CallByteMethodA" + case .char: "CallCharMethodA" + case .short: "CallShortMethodA" + case .int: "CallIntMethodA" + case .long: "CallLongMethodA" + case .float: "CallFloatMethodA" + case .double: "CallDoubleMethodA" + case .void: "CallVoidMethodA" + default: "CallObjectMethodA" + } + } + + /// Returns whether this type returns `JavaValue` from JavaKit + var implementsJavaValue: Bool { + return switch self { + case .boolean, .byte, .char, .short, .int, .long, .float, .double, .void, .javaLangString: + true + default: + false + } + } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index b91588df2..0daf14de6 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +import JavaTypes // MARK: Defaults @@ -20,6 +21,7 @@ extension JNISwift2JavaGenerator { static let defaultJavaImports: Array = [ "org.swift.swiftkit.core.*", "org.swift.swiftkit.core.util.*", + "java.util.*", // NonNull, Unsigned and friends "org.swift.swiftkit.core.annotations.*", @@ -279,11 +281,15 @@ extension JNISwift2JavaGenerator { let translatedDecl = translatedDecl(for: decl)! // Will always call with valid decl let nativeSignature = translatedDecl.nativeFunctionSignature let resultType = nativeSignature.result.javaType - var parameters = nativeSignature.parameters - if let selfParameter = nativeSignature.selfParameter { - parameters.append(selfParameter) + var parameters = nativeSignature.parameters.flatMap(\.parameters) + if let selfParameter = nativeSignature.selfParameter?.parameters { + parameters += selfParameter } - let renderedParameters = parameters.map { "\($0.javaType) \($0.name)"}.joined(separator: ", ") + parameters += nativeSignature.result.outParameters + + let renderedParameters = parameters.map { javaParameter in + "\(javaParameter.type) \(javaParameter.name)" + }.joined(separator: ", ") printer.print("private static native \(resultType) \(translatedDecl.nativeFunctionName)(\(renderedParameters));") } @@ -308,6 +314,12 @@ extension JNISwift2JavaGenerator { arguments.append(lowered) } + // Indirect return receivers + for outParameter in translatedFunctionSignature.resultType.outParameters { + printer.print("\(outParameter.type) \(outParameter.name) = \(outParameter.allocation.render());") + arguments.append(outParameter.name) + } + //=== Part 3: Downcall. // TODO: If we always generate a native method and a "public" method, we can actually choose our own thunk names // using the registry? diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 5908cfb12..0db77ece9 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -190,14 +190,26 @@ extension JNISwift2JavaGenerator { let nominalTypeName = nominalType.nominalTypeDecl.name if let knownType = nominalType.nominalTypeDecl.knownTypeKind { - guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { - throw JavaTranslationError.unsupportedSwiftType(swiftType) + switch knownType { + case .optional: + guard let genericArgs = nominalType.genericArguments, genericArgs.count == 1 else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + return try translateOptionalParameter( + wrappedType: genericArgs[0], + parameterName: parameterName + ) + + default: + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + return TranslatedParameter( + parameter: JavaParameter(name: parameterName, type: javaType, annotations: parameterAnnotations), + conversion: .placeholder + ) } - - return TranslatedParameter( - parameter: JavaParameter(name: parameterName, type: javaType, annotations: parameterAnnotations), - conversion: .placeholder - ) } if nominalType.isJavaKitWrapper { @@ -237,13 +249,22 @@ extension JNISwift2JavaGenerator { conversion: .placeholder ) - case .metatype, .optional, .tuple, .existential, .opaque, .genericParameter: + case .optional(let wrapped): + return try translateOptionalParameter( + wrappedType: wrapped, + parameterName: parameterName + ) + + case .metatype, .tuple, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(swiftType) } } - func unsignedResultConversion(_ from: SwiftType, to javaType: JavaType, - mode: JExtractUnsignedIntegerMode) -> JavaNativeConversionStep { + func unsignedResultConversion( + _ from: SwiftType, + to javaType: JavaType, + mode: JExtractUnsignedIntegerMode + ) -> JavaNativeConversionStep { switch mode { case .annotate: return .placeholder // no conversions @@ -253,6 +274,75 @@ extension JNISwift2JavaGenerator { } } + func translateOptionalParameter( + wrappedType swiftType: SwiftType, + parameterName: String + ) throws -> TranslatedParameter { + let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config) + + switch swiftType { + case .nominal(let nominalType): + let nominalTypeName = nominalType.nominalTypeDecl.name + + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + guard let translatedClass = javaType.optionalType, let placeholderValue = javaType.optionalPlaceholderValue else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + return TranslatedParameter( + parameter: JavaParameter( + name: parameterName, + type: JavaType(className: translatedClass), + annotations: parameterAnnotations + ), + conversion: .commaSeparated([ + .isOptionalPresent, + .method(.placeholder, function: "orElse", arguments: [.constant(placeholderValue)]) + ]) + ) + } + + if nominalType.isJavaKitWrapper { + guard let javaType = nominalTypeName.parseJavaClassFromJavaKitName(in: self.javaClassLookupTable) else { + throw JavaTranslationError.wrappedJavaClassTranslationNotProvided(swiftType) + } + + return TranslatedParameter( + parameter: JavaParameter( + name: parameterName, + type: .class(package: nil, name: "Optional<\(javaType)>"), + annotations: parameterAnnotations + ), + conversion: .method( + .placeholder, + function: "orElse", + arguments: [.constant("null")] + ) + ) + } + + // Assume JExtract imported class + return TranslatedParameter( + parameter: JavaParameter( + name: parameterName, + type: .class(package: nil, name: "Optional<\(nominalTypeName)>"), + annotations: parameterAnnotations + ), + conversion: .method( + .method(.placeholder, function: "map", arguments: [.constant("\(nominalType)::$memoryAddress")]), + function: "orElse", + arguments: [.constant("0L")] + ) + ) + default: + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + } + func translate(swiftResult: SwiftResult) throws -> TranslatedResult { let swiftType = swiftResult.type @@ -262,15 +352,25 @@ extension JNISwift2JavaGenerator { switch swiftType { case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { - guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { - throw JavaTranslationError.unsupportedSwiftType(swiftType) + switch knownType { + case .optional: + guard let genericArgs = nominalType.genericArguments, genericArgs.count == 1 else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + return try translateOptionalResult(wrappedType: genericArgs[0]) + + default: + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + return TranslatedResult( + javaType: javaType, + annotations: resultAnnotations, + outParameters: [], + conversion: .placeholder + ) } - - return TranslatedResult( - javaType: javaType, - annotations: resultAnnotations, - conversion: .placeholder - ) } if nominalType.isJavaKitWrapper { @@ -282,13 +382,94 @@ extension JNISwift2JavaGenerator { return TranslatedResult( javaType: javaType, annotations: resultAnnotations, + outParameters: [], conversion: .constructSwiftValue(.placeholder, javaType) ) case .tuple([]): - return TranslatedResult(javaType: .void, conversion: .placeholder) + return TranslatedResult(javaType: .void, outParameters: [], conversion: .placeholder) + + case .optional(let wrapped): + return try translateOptionalResult(wrappedType: wrapped) - case .metatype, .optional, .tuple, .function, .existential, .opaque, .genericParameter: + case .metatype, .tuple, .function, .existential, .opaque, .genericParameter: + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + } + + func translateOptionalResult( + wrappedType swiftType: SwiftType + ) throws -> TranslatedResult { + let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config) + + switch swiftType { + case .nominal(let nominalType): + let nominalTypeName = nominalType.nominalTypeDecl.name + + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + guard let returnType = javaType.optionalType, let optionalClass = javaType.optionalWrapperType else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + // Check if we can fit the value and a discriminator byte in a primitive. + // so the return JNI value will be (value, discriminator) + if let nextIntergralTypeWithSpaceForByte = javaType.nextIntergralTypeWithSpaceForByte { + return TranslatedResult( + javaType: .class(package: nil, name: returnType), + annotations: parameterAnnotations, + outParameters: [], + conversion: .combinedValueToOptional( + .placeholder, + nextIntergralTypeWithSpaceForByte.javaType, + valueType: javaType, + valueSizeInBytes: nextIntergralTypeWithSpaceForByte.valueBytes, + optionalType: optionalClass + ) + ) + } else { + // Otherwise, we return the result as normal, but + // use an indirect return for the discriminator. + return TranslatedResult( + javaType: .class(package: nil, name: returnType), + annotations: parameterAnnotations, + outParameters: [ + OutParameter(name: "result_discriminator$", type: .array(.byte), allocation: .newArray(.byte, size: 1)) + ], + conversion: .toOptionalFromIndirectReturn( + discriminatorName: "result_discriminator$", + optionalClass: optionalClass, + javaType: javaType, + toValue: .placeholder + ) + ) + } + } + + guard !nominalType.isJavaKitWrapper else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + // We assume this is a JExtract class. + let returnType = JavaType.class(package: nil, name: "Optional<\(nominalTypeName)>") + return TranslatedResult( + javaType: returnType, + annotations: parameterAnnotations, + outParameters: [ + OutParameter(name: "result_discriminator$", type: .array(.byte), allocation: .newArray(.byte, size: 1)) + ], + conversion: .toOptionalFromIndirectReturn( + discriminatorName: "result_discriminator$", + optionalClass: "Optional", + javaType: .long, + toValue: .constructSwiftValue(.placeholder, .class(package: nil, name: nominalTypeName)) + ) + ) + + default: throw JavaTranslationError.unsupportedSwiftType(swiftType) } } @@ -348,10 +529,33 @@ extension JNISwift2JavaGenerator { /// Java annotations that should be propagated from the result type onto the method var annotations: [JavaAnnotation] = [] + let outParameters: [OutParameter] + /// Represents how to convert the Java native result into a user-facing result. let conversion: JavaNativeConversionStep } + struct OutParameter { + enum Allocation { + case newArray(JavaType, size: Int) + + func render() -> String { + switch self { + case .newArray(let javaType, let size): + "new \(javaType)[\(size)]" + } + } + } + + let name: String + let type: JavaType + let allocation: Allocation + + var javaParameter: JavaParameter { + JavaParameter(name: self.name, type: self.type) + } + } + /// Represent a Swift closure type in the user facing Java API. /// /// Closures are translated to named functional interfaces in Java. @@ -367,6 +571,11 @@ extension JNISwift2JavaGenerator { /// The value being converted case placeholder + case constant(String) + + // Convert the results of the inner steps to a comma separated list. + indirect case commaSeparated([JavaNativeConversionStep]) + /// `value.$memoryAddress()` indirect case valueMemoryAddress(JavaNativeConversionStep) @@ -375,6 +584,43 @@ extension JNISwift2JavaGenerator { indirect case call(JavaNativeConversionStep, function: String) + indirect case method(JavaNativeConversionStep, function: String, arguments: [JavaNativeConversionStep] = []) + + case isOptionalPresent + + indirect case combinedValueToOptional(JavaNativeConversionStep, JavaType, valueType: JavaType, valueSizeInBytes: Int, optionalType: String) + + indirect case ternary(JavaNativeConversionStep, thenExp: JavaNativeConversionStep, elseExp: JavaNativeConversionStep) + + indirect case equals(JavaNativeConversionStep, JavaNativeConversionStep) + + indirect case subscriptOf(JavaNativeConversionStep, arguments: [JavaNativeConversionStep]) + + static func toOptionalFromIndirectReturn( + discriminatorName: String, + optionalClass: String, + javaType: JavaType, + toValue valueConversion: JavaNativeConversionStep + ) -> JavaNativeConversionStep { + .aggregate( + name: "result$", + type: javaType, + [ + .ternary( + .equals( + .subscriptOf(.constant(discriminatorName), arguments: [.constant("0")]), + .constant("1") + ), + thenExp: .method(.constant(optionalClass), function: "of", arguments: [valueConversion]), + elseExp: .method(.constant(optionalClass), function: "empty") + ) + ] + ) + } + + /// Perform multiple conversions using the same input. + case aggregate(name: String, type: JavaType, [JavaNativeConversionStep]) + /// Returns the conversion string applied to the placeholder. func render(_ printer: inout CodePrinter, _ placeholder: String) -> String { // NOTE: 'printer' is used if the conversion wants to cause side-effects. @@ -383,6 +629,12 @@ extension JNISwift2JavaGenerator { case .placeholder: return placeholder + case .constant(let value): + return value + + case .commaSeparated(let list): + return list.map({ $0.render(&printer, placeholder)}).joined(separator: ", ") + case .valueMemoryAddress: return "\(placeholder).$memoryAddress()" @@ -394,13 +646,63 @@ extension JNISwift2JavaGenerator { let inner = inner.render(&printer, placeholder) return "\(function)(\(inner))" + + case .isOptionalPresent: + return "(byte) (\(placeholder).isPresent() ? 1 : 0)" + + case .method(let inner, let methodName, let arguments): + let inner = inner.render(&printer, placeholder) + let args = arguments.map { $0.render(&printer, placeholder) } + let argsStr = args.joined(separator: ", ") + return "\(inner).\(methodName)(\(argsStr))" + + case .combinedValueToOptional(let combined, let combinedType, let valueType, let valueSizeInBytes, let optionalType): + let combined = combined.render(&printer, placeholder) + printer.print( + """ + \(combinedType) combined$ = \(combined); + byte discriminator$ = (byte) (combined$ & 0xFF); + """ + ) + + if valueType == .boolean { + printer.print("boolean value$ = ((byte) (combined$ >> 8)) != 0;") + } else { + printer.print("\(valueType) value$ = (\(valueType)) (combined$ >> \(valueSizeInBytes * 8));") + } + + return "discriminator$ == 1 ? \(optionalType).of(value$) : \(optionalType).empty()" + + case .ternary(let cond, let thenExp, let elseExp): + let cond = cond.render(&printer, placeholder) + let thenExp = thenExp.render(&printer, placeholder) + let elseExp = elseExp.render(&printer, placeholder) + return "(\(cond)) ? \(thenExp) : \(elseExp)" + + case .equals(let lhs, let rhs): + let lhs = lhs.render(&printer, placeholder) + let rhs = rhs.render(&printer, placeholder) + return "\(lhs) == \(rhs)" + + case .subscriptOf(let inner, let arguments): + let inner = inner.render(&printer, placeholder) + let arguments = arguments.map { $0.render(&printer, placeholder) } + return "\(inner)[\(arguments.joined(separator: ", "))]" + + case .aggregate(let name, let type, let steps): + precondition(!steps.isEmpty, "Aggregate must contain steps") + printer.print("\(type) \(name) = \(placeholder);") + let steps = steps.map { + $0.render(&printer, name) + } + return steps.last! } } /// Whether the conversion uses SwiftArena. var requiresSwiftArena: Bool { switch self { - case .placeholder: + case .placeholder, .constant, .isOptionalPresent: return false case .constructSwiftValue: @@ -409,6 +711,27 @@ extension JNISwift2JavaGenerator { case .valueMemoryAddress(let inner): return inner.requiresSwiftArena + case .commaSeparated(let list): + return list.contains(where: { $0.requiresSwiftArena }) + + case .method(let inner, _, let args): + return inner.requiresSwiftArena || args.contains(where: \.requiresSwiftArena) + + case .combinedValueToOptional(let inner, _, _, _, _): + return inner.requiresSwiftArena + + case .ternary(let cond, let thenExp, let elseExp): + return cond.requiresSwiftArena || thenExp.requiresSwiftArena || elseExp.requiresSwiftArena + + case .equals(let lhs, let rhs): + return lhs.requiresSwiftArena || rhs.requiresSwiftArena + + case .subscriptOf(let inner, _): + return inner.requiresSwiftArena + + case .aggregate(_, _, let steps): + return steps.contains(where: \.requiresSwiftArena) + case .call(let inner, _): return inner.requiresSwiftArena } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 9f1113fc6..e7f7efbeb 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -72,16 +72,30 @@ extension JNISwift2JavaGenerator { let nominalTypeName = nominalType.nominalTypeDecl.name if let knownType = nominalType.nominalTypeDecl.knownTypeKind { - guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), - javaType.implementsJavaValue else { - throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) - } + switch knownType { + case .optional: + guard let genericArgs = nominalType.genericArguments, genericArgs.count == 1 else { + throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) + } + return try translateOptionalParameter( + wrappedType: genericArgs[0], + parameterName: parameterName + ) + + default: + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), + javaType.implementsJavaValue else { + throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) + } + + return NativeParameter( + parameters: [ + JavaParameter(name: parameterName, type: javaType) + ], + conversion: .initFromJNI(.placeholder, swiftType: swiftParameter.type) + ) - return NativeParameter( - name: parameterName, - javaType: javaType, - conversion: .initFromJNI(.placeholder, swiftType: swiftParameter.type) - ) + } } if nominalType.isJavaKitWrapper { @@ -90,23 +104,33 @@ extension JNISwift2JavaGenerator { } return NativeParameter( - name: parameterName, - javaType: javaType, - conversion: .initializeJavaKitWrapper(wrapperName: nominalTypeName) + parameters: [ + JavaParameter(name: parameterName, type: javaType) + ], + conversion: .initializeJavaKitWrapper( + .unwrapOptional( + .placeholder, + name: parameterName, + fatalErrorMessage: "\(parameterName) was null in call to \\(#function), but Swift requires non-optional!" + ), + wrapperName: nominalTypeName + ) ) } // JExtract classes are passed as the pointer. return NativeParameter( - name: parameterName, - javaType: .long, + parameters: [ + JavaParameter(name: parameterName, type: .long) + ], conversion: .pointee(.extractSwiftValue(.placeholder, swiftType: swiftParameter.type)) ) case .tuple([]): return NativeParameter( - name: parameterName, - javaType: .void, + parameters: [ + JavaParameter(name: parameterName, type: .void) + ], conversion: .placeholder ) @@ -124,19 +148,162 @@ extension JNISwift2JavaGenerator { let result = try translateClosureResult(fn.resultType) return NativeParameter( - name: parameterName, - javaType: .class(package: javaPackage, name: "\(parentName).\(methodName).\(parameterName)"), + parameters: [ + JavaParameter(name: parameterName, type: .class(package: javaPackage, name: "\(parentName).\(methodName).\(parameterName)")) + ], conversion: .closureLowering( parameters: parameters, result: result ) ) - case .metatype, .optional, .tuple, .existential, .opaque, .genericParameter: + case .optional(let wrapped): + return try translateOptionalParameter( + wrappedType: wrapped, + parameterName: parameterName + ) + + case .metatype, .tuple, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) } } + func translateOptionalParameter( + wrappedType swiftType: SwiftType, + parameterName: String + ) throws -> NativeParameter { + let discriminatorName = "\(parameterName)_discriminator" + let valueName = "\(parameterName)_value" + + switch swiftType { + case .nominal(let nominalType): + let nominalTypeName = nominalType.nominalTypeDecl.name + + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), + javaType.implementsJavaValue else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + return NativeParameter( + parameters: [ + JavaParameter(name: discriminatorName, type: .byte), + JavaParameter(name: valueName, type: javaType) + ], + conversion: .optionalLowering( + .initFromJNI(.placeholder, swiftType: swiftType), + discriminatorName: discriminatorName, + valueName: valueName + ) + ) + } + + if nominalType.isJavaKitWrapper { + guard let javaType = nominalTypeName.parseJavaClassFromJavaKitName(in: self.javaClassLookupTable) else { + throw JavaTranslationError.wrappedJavaClassTranslationNotProvided(swiftType) + } + + return NativeParameter( + parameters: [ + JavaParameter(name: parameterName, type: javaType) + ], + conversion: .optionalMap(.initializeJavaKitWrapper(.placeholder, wrapperName: nominalTypeName)) + ) + } + + // Assume JExtract wrapped class + return NativeParameter( + parameters: [JavaParameter(name: parameterName, type: .long)], + conversion: .pointee( + .optionalChain( + .extractSwiftValue( + .placeholder, + swiftType: swiftType, + allowNil: true + ) + ) + ) + ) + + default: + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + } + + func translateOptionalResult( + wrappedType swiftType: SwiftType + ) throws -> NativeResult { + switch swiftType { + case .nominal(let nominalType): + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), + javaType.implementsJavaValue else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + // Check if we can fit the value and a discriminator byte in a primitive. + // so the return JNI value will be (value, discriminator) + if let nextIntergralTypeWithSpaceForByte = javaType.nextIntergralTypeWithSpaceForByte { + return NativeResult( + javaType: nextIntergralTypeWithSpaceForByte.javaType, + conversion: .getJNIValue( + .optionalRaisingWidenIntegerType( + .placeholder, + valueType: javaType, + combinedSwiftType: nextIntergralTypeWithSpaceForByte.swiftType, + valueSizeInBytes: nextIntergralTypeWithSpaceForByte.valueBytes + ) + ), + outParameters: [] + ) + } else { + // Use indirect byte array to store discriminator + let discriminatorName = "result_discriminator$" + + return NativeResult( + javaType: javaType, + conversion: .optionalRaisingIndirectReturn( + .getJNIValue(.placeholder), + returnType: javaType, + discriminatorParameterName: discriminatorName, + placeholderValue: .member( + .constant("\(swiftType)"), + member: "jniPlaceholderValue" + ) + ), + outParameters: [ + JavaParameter(name: discriminatorName, type: .array(.byte)) + ] + ) + } + } + + guard !nominalType.isJavaKitWrapper else { + // TODO: Should be the same as above + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + + // Assume JExtract imported class + let discriminatorName = "result_discriminator$" + + return NativeResult( + javaType: .long, + conversion: .optionalRaisingIndirectReturn( + .getJNIValue(.allocateSwiftValue(name: "_result", swiftType: swiftType)), + returnType: .long, + discriminatorParameterName: discriminatorName, + placeholderValue: .constant("0") + ), + outParameters: [ + JavaParameter(name: discriminatorName, type: .array(.byte)) + ] + ) + + default: + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + } + func translateClosureResult( _ type: SwiftType ) throws -> NativeResult { @@ -151,7 +318,8 @@ extension JNISwift2JavaGenerator { // Only support primitives for now. return NativeResult( javaType: javaType, - conversion: .initFromJNI(.placeholder, swiftType: type) + conversion: .initFromJNI(.placeholder, swiftType: type), + outParameters: [] ) } @@ -161,7 +329,8 @@ extension JNISwift2JavaGenerator { case .tuple([]): return NativeResult( javaType: .void, - conversion: .placeholder + conversion: .placeholder, + outParameters: [] ) case .function, .metatype, .optional, .tuple, .existential, .opaque, .genericParameter: @@ -183,8 +352,9 @@ extension JNISwift2JavaGenerator { // Only support primitives for now. return NativeParameter( - name: parameterName, - javaType: javaType, + parameters: [ + JavaParameter(name: parameterName, type: javaType) + ], conversion: .getJValue(.placeholder) ) } @@ -203,17 +373,24 @@ extension JNISwift2JavaGenerator { switch swiftResult.type { case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { - guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { - throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + switch knownType { + case .optional: + guard let genericArgs = nominalType.genericArguments, genericArgs.count == 1 else { + throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + } + return try translateOptionalResult(wrappedType: genericArgs[0]) + + default: + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), javaType.implementsJavaValue else { + throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + } + + return NativeResult( + javaType: javaType, + conversion: .getJNIValue(.placeholder), + outParameters: [] + ) } - guard javaType.implementsJavaValue else { - throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) - } - - return NativeResult( - javaType: javaType, - conversion: .getJNIValue(.placeholder) - ) } if nominalType.isJavaKitWrapper { @@ -222,20 +399,23 @@ extension JNISwift2JavaGenerator { return NativeResult( javaType: .long, - conversion: .getJNIValue(.allocateSwiftValue(name: "result", swiftType: swiftResult.type)) + conversion: .getJNIValue(.allocateSwiftValue(name: "result", swiftType: swiftResult.type)), + outParameters: [] ) case .tuple([]): return NativeResult( javaType: .void, - conversion: .placeholder + conversion: .placeholder, + outParameters: [] ) - case .metatype, .optional, .tuple, .function, .existential, .opaque, .genericParameter: + case .optional(let wrapped): + return try translateOptionalResult(wrappedType: wrapped) + + case .metatype, .tuple, .function, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) } - - } } @@ -246,12 +426,9 @@ extension JNISwift2JavaGenerator { } struct NativeParameter { - let name: String - let javaType: JavaType - - var jniType: JNIType { - javaType.jniType - } + /// One Swift parameter can be lowered to multiple parameters. + /// E.g. 'Optional' as (descriptor, value) pair. + var parameters: [JavaParameter] /// Represents how to convert the JNI parameter to a Swift parameter let conversion: NativeSwiftConversionStep @@ -260,6 +437,9 @@ extension JNISwift2JavaGenerator { struct NativeResult { let javaType: JavaType let conversion: NativeSwiftConversionStep + + /// Out parameters for populating the indirect return values. + var outParameters: [JavaParameter] } /// Describes how to convert values between Java types and Swift through JNI @@ -267,6 +447,8 @@ extension JNISwift2JavaGenerator { /// The value being converted case placeholder + case constant(String) + /// `value.getJNIValue(in:)` indirect case getJNIValue(NativeSwiftConversionStep) @@ -277,7 +459,11 @@ extension JNISwift2JavaGenerator { indirect case initFromJNI(NativeSwiftConversionStep, swiftType: SwiftType) /// Extracts a swift type at a pointer given by a long. - indirect case extractSwiftValue(NativeSwiftConversionStep, swiftType: SwiftType) + indirect case extractSwiftValue( + NativeSwiftConversionStep, + swiftType: SwiftType, + allowNil: Bool = false + ) /// Allocate memory for a Swift value and outputs the pointer case allocateSwiftValue(name: String, swiftType: SwiftType) @@ -288,7 +474,23 @@ extension JNISwift2JavaGenerator { indirect case closureLowering(parameters: [NativeParameter], result: NativeResult) - case initializeJavaKitWrapper(wrapperName: String) + indirect case initializeJavaKitWrapper(NativeSwiftConversionStep, wrapperName: String) + + indirect case optionalLowering(NativeSwiftConversionStep, discriminatorName: String, valueName: String) + + indirect case optionalChain(NativeSwiftConversionStep) + + indirect case optionalRaisingWidenIntegerType(NativeSwiftConversionStep, valueType: JavaType, combinedSwiftType: SwiftKnownTypeDeclKind, valueSizeInBytes: Int) + + indirect case optionalRaisingIndirectReturn(NativeSwiftConversionStep, returnType: JavaType, discriminatorParameterName: String, placeholderValue: NativeSwiftConversionStep) + + indirect case method(NativeSwiftConversionStep, function: String, arguments: [(String?, NativeSwiftConversionStep)] = []) + + indirect case member(NativeSwiftConversionStep, member: String) + + indirect case optionalMap(NativeSwiftConversionStep) + + indirect case unwrapOptional(NativeSwiftConversionStep, name: String, fatalErrorMessage: String) /// Returns the conversion string applied to the placeholder. func render(_ printer: inout CodePrinter, _ placeholder: String) -> String { @@ -298,6 +500,9 @@ extension JNISwift2JavaGenerator { case .placeholder: return placeholder + case .constant(let value): + return value + case .getJNIValue(let inner): let inner = inner.render(&printer, placeholder) return "\(inner).getJNIValue(in: environment!)" @@ -310,18 +515,28 @@ extension JNISwift2JavaGenerator { let inner = inner.render(&printer, placeholder) return "\(swiftType)(fromJNI: \(inner), in: environment!)" - case .extractSwiftValue(let inner, let swiftType): + case .extractSwiftValue(let inner, let swiftType, let allowNil): let inner = inner.render(&printer, placeholder) + let pointerName = "\(inner)$" + if !allowNil { + printer.print(#"assert(\#(inner) != 0, "\#(inner) memory address was null")"#) + } printer.print( """ - assert(\(inner) != 0, "\(inner) memory address was null") let \(inner)Bits$ = Int(Int64(fromJNI: \(inner), in: environment!)) - guard let \(inner)$ = UnsafeMutablePointer<\(swiftType)>(bitPattern: \(inner)Bits$) else { - fatalError("\(inner) memory address was null in call to \\(#function)!") - } + let \(pointerName) = UnsafeMutablePointer<\(swiftType)>(bitPattern: \(inner)Bits$) """ ) - return "\(inner)$" + if !allowNil { + printer.print( + """ + guard let \(pointerName) else { + fatalError("\(inner) memory address was null in call to \\(#function)!") + } + """ + ) + } + return pointerName case .allocateSwiftValue(let name, let swiftType): let pointerName = "\(name)$" @@ -344,15 +559,17 @@ extension JNISwift2JavaGenerator { let methodSignature = MethodSignature( resultType: nativeResult.javaType, - parameterTypes: parameters.map(\.javaType) + parameterTypes: parameters.flatMap { $0.parameters.map(\.type) } ) - let closureParameters = !parameters.isEmpty ? "\(parameters.map(\.name).joined(separator: ", ")) in" : "" + let names = parameters.flatMap { $0.parameters.map(\.name) } + let closureParameters = !parameters.isEmpty ? "\(names.joined(separator: ", ")) in" : "" printer.print("{ \(closureParameters)") printer.indent() + // TODO: Add support for types that are lowered to multiple parameters in closures let arguments = parameters.map { - $0.conversion.render(&printer, $0.name) + $0.conversion.render(&printer, $0.parameters.first!.name) } printer.print( @@ -363,7 +580,7 @@ extension JNISwift2JavaGenerator { """ ) - let upcall = "environment!.interface.\(nativeResult.javaType.jniType.callMethodAName)(environment, \(placeholder), methodID$, arguments$)" + let upcall = "environment!.interface.\(nativeResult.javaType.jniCallMethodAName)(environment, \(placeholder), methodID$, arguments$)" let result = nativeResult.conversion.render(&printer, upcall) if nativeResult.javaType.isVoid { @@ -377,8 +594,92 @@ extension JNISwift2JavaGenerator { return printer.finalize() - case .initializeJavaKitWrapper(let wrapperName): - return "\(wrapperName)(javaThis: \(placeholder), environment: environment!)" + case .initializeJavaKitWrapper(let inner, let wrapperName): + let inner = inner.render(&printer, placeholder) + return "\(wrapperName)(javaThis: \(inner), environment: environment!)" + + case .optionalLowering(let valueConversion, let discriminatorName, let valueName): + let value = valueConversion.render(&printer, valueName) + return "\(discriminatorName) == 1 ? \(value) : nil" + + case .optionalChain(let inner): + let inner = inner.render(&printer, placeholder) + return "\(inner)?" + + case .optionalRaisingWidenIntegerType(let inner, let valueType, let combinedSwiftType, let valueSizeInBytes): + let inner = inner.render(&printer, placeholder) + let value = valueType == .boolean ? "$0 ? 1 : 0" : "$0" + let combinedSwiftTypeName = combinedSwiftType.moduleAndName.name + printer.print( + """ + let value$ = \(inner).map { + \(combinedSwiftTypeName)(\(value)) << \(valueSizeInBytes * 8) | \(combinedSwiftTypeName)(1) + } ?? 0 + """ + ) + return "value$" + + case .optionalRaisingIndirectReturn(let inner, let returnType, let discriminatorParameterName, let placeholderValue): + printer.print("let result$: \(returnType.jniTypeName)") + printer.printBraceBlock("if let innerResult$ = \(placeholder)") { printer in + let inner = inner.render(&printer, "innerResult$") + printer.print( + """ + result$ = \(inner) + var flag$ = Int8(1) + environment.interface.SetByteArrayRegion(environment, \(discriminatorParameterName), 0, 1, &flag$) + """ + ) + } + printer.printBraceBlock("else") { printer in + let placeholderValue = placeholderValue.render(&printer, placeholder) + printer.print( + """ + result$ = \(placeholderValue) + var flag$ = Int8(0) + environment.interface.SetByteArrayRegion(environment, \(discriminatorParameterName), 0, 1, &flag$) + """ + ) + } + + return "result$" + + case .method(let inner, let methodName, let arguments): + let inner = inner.render(&printer, placeholder) + let args = arguments.map { name, value in + let value = value.render(&printer, placeholder) + if let name { + return "\(name): \(value)" + } else { + return value + } + } + let argsStr = args.joined(separator: ", ") + return "\(inner).\(methodName)(\(argsStr))" + + case .member(let inner, let member): + let inner = inner.render(&printer, placeholder) + return "\(inner).\(member)" + + case .optionalMap(let inner): + var printer = CodePrinter() + printer.printBraceBlock("\(placeholder).map") { printer in + let inner = inner.render(&printer, "$0") + printer.print("return \(inner)") + } + return printer.finalize() + + case .unwrapOptional(let inner, let name, let fatalErrorMessage): + let unwrappedName = "\(name)_unwrapped$" + let inner = inner.render(&printer, placeholder) + printer.print( + """ + guard let \(unwrappedName) = \(inner) else { + fatalError("\(fatalErrorMessage)") + } + """ + ) + return unwrappedName } } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 8b910ffb3..ca580e602 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -125,18 +125,20 @@ extension JNISwift2JavaGenerator { } let nativeSignature = translatedDecl.nativeFunctionSignature - var parameters = nativeSignature.parameters + var parameters = nativeSignature.parameters.flatMap(\.parameters) if let selfParameter = nativeSignature.selfParameter { - parameters.append(selfParameter) + parameters += selfParameter.parameters } + parameters += nativeSignature.result.outParameters + printCDecl( &printer, javaMethodName: translatedDecl.nativeFunctionName, parentName: translatedDecl.parentName, - parameters: parameters.map { JavaParameter(name: $0.name, type: $0.javaType) }, - resultType: nativeSignature.result.javaType.jniType + parameters: parameters, + resultType: nativeSignature.result.javaType ) { printer in self.printFunctionDowncall(&printer, decl) } @@ -155,8 +157,9 @@ extension JNISwift2JavaGenerator { // Regular parameters. var arguments = [String]() - for parameter in nativeSignature.parameters { - let lowered = parameter.conversion.render(&printer, parameter.name) + for (idx, parameter) in nativeSignature.parameters.enumerated() { + let javaParameterName = translatedDecl.translatedFunctionSignature.parameters[idx].parameter.name + let lowered = parameter.conversion.render(&printer, javaParameterName) arguments.append(lowered) } @@ -230,7 +233,7 @@ extension JNISwift2JavaGenerator { javaMethodName: String, parentName: String, parameters: [JavaParameter], - resultType: JNIType, + resultType: JavaType, _ body: (inout CodePrinter) -> Void ) { let jniSignature = parameters.reduce(into: "") { signature, parameter in @@ -246,7 +249,7 @@ extension JNISwift2JavaGenerator { + jniSignature.escapedJNIIdentifier let translatedParameters = parameters.map { - "\($0.name): \($0.type.jniType)" + "\($0.name): \($0.type.jniTypeName)" } let thunkParameters = @@ -254,7 +257,7 @@ extension JNISwift2JavaGenerator { "environment: UnsafeMutablePointer!", "thisClass: jclass" ] + translatedParameters - let thunkReturnType = resultType != .void ? " -> \(resultType)" : "" + let thunkReturnType = resultType != .void ? " -> \(resultType.jniTypeName)" : "" // TODO: Think about function overloads printer.printBraceBlock( diff --git a/Sources/JExtractSwiftLib/JNI/JNIType.swift b/Sources/JExtractSwiftLib/JNI/JNIType.swift deleted file mode 100644 index cdedb0a17..000000000 --- a/Sources/JExtractSwiftLib/JNI/JNIType.swift +++ /dev/null @@ -1,98 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2025 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import JavaTypes - -/// Represents types that are able to be passed over a JNI boundary. -/// -/// - SeeAlso: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/types.html -enum JNIType { - case jboolean - case jfloat - case jdouble - case jbyte - case jchar - case jshort - case jint - case jlong - case void - case jstring - case jclass - case jthrowable - case jobject - case jbooleanArray - case jbyteArray - case jcharArray - case jshortArray - case jintArray - case jlongArray - case jfloatArray - case jdoubleArray - case jobjectArray - - var callMethodAName: String { - switch self { - case .jboolean: "CallBooleanMethodA" - case .jbyte: "CallByteMethodA" - case .jchar: "CallCharMethodA" - case .jshort: "CallShortMethodA" - case .jint: "CallIntMethodA" - case .jlong: "CallLongMethodA" - case .jfloat: "CallFloatMethodA" - case .jdouble: "CallDoubleMethodA" - case .void: "CallVoidMethodA" - case .jobject, .jstring, .jclass, .jthrowable: "CallObjectMethodA" - case .jbooleanArray, .jbyteArray, .jcharArray, .jshortArray, .jintArray, .jlongArray, .jfloatArray, .jdoubleArray, .jobjectArray: "CallObjectMethodA" - } - } -} - -extension JavaType { - var jniType: JNIType { - switch self { - case .boolean: .jboolean - case .byte: .jbyte - case .char: .jchar - case .short: .jshort - case .int: .jint - case .long: .jlong - case .float: .jfloat - case .double: .jdouble - case .void: .void - case .array(.boolean): .jbooleanArray - case .array(.byte): .jbyteArray - case .array(.char): .jcharArray - case .array(.short): .jshortArray - case .array(.int): .jintArray - case .array(.long): .jlongArray - case .array(.float): .jfloatArray - case .array(.double): .jdoubleArray - case .array: .jobjectArray - case .javaLangString: .jstring - case .javaLangClass: .jclass - case .javaLangThrowable: .jthrowable - case .class: .jobject - } - } - - /// Returns whether this type returns `JavaValue` from JavaKit - var implementsJavaValue: Bool { - return switch self { - case .boolean, .byte, .char, .short, .int, .long, .float, .double, .void, .javaLangString: - true - default: - false - } - } -} diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index b3d02276f..130c333bc 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -63,7 +63,8 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | `Foundation.Data`, `any Foundation.DataProtocol` | ✅ | ❌ | | Tuples: `(Int, String)`, `(A, B, C)` | ❌ | ❌ | | Protocols: `protocol`, existential parameters `any Collection` | ❌ | ❌ | -| Optional types: `Int?`, `AnyObject?` | ❌ | ❌ | +| Optional parameters: `func f(i: Int?, class: MyClass?)` | ✅ | ✅ | +| Optional return types: `func f() -> Int?`, `func g() -> MyClass?` | ❌ | ✅ | | Primitive types: `Bool`, `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `Float`, `Double` | ✅ | ✅ | | Parameters: JavaKit wrapped types `JavaLong`, `JavaInteger` | ❌ | ✅ | | Return values: JavaKit wrapped types `JavaLong`, `JavaInteger` | ❌ | ❌ | diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift index 483d53f5a..5b015f894 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -283,7 +283,8 @@ struct JNIClassTests { func Java_com_example_swift_MyClass__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, self: jlong) { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!)) @@ -331,7 +332,8 @@ struct JNIClassTests { func Java_com_example_swift_MyClass__00024copy__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } let result$ = UnsafeMutablePointer.allocate(capacity: 1) @@ -382,12 +384,14 @@ struct JNIClassTests { func Java_com_example_swift_MyClass__00024isEqual__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, other: jlong, self: jlong) -> jboolean { assert(other != 0, "other memory address was null") let otherBits$ = Int(Int64(fromJNI: other, in: environment!)) - guard let other$ = UnsafeMutablePointer(bitPattern: otherBits$) else { + let other$ = UnsafeMutablePointer(bitPattern: otherBits$) + guard let other$ else { fatalError("other memory address was null in call to \\(#function)!") } assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } return self$.pointee.isEqual(to: other$.pointee).getJNIValue(in: environment!) diff --git a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift index 47d7e35da..9a388da18 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift @@ -61,7 +61,7 @@ struct JNIClosureTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_SwiftModule__00024emptyClosure__Lcom_example_swift_SwiftModule_00024emptyClosure_00024closure_2") - func Java_com_example_swift_SwiftModule__00024emptyClosure__Lcom_example_swift_SwiftModule_00024emptyClosure_00024closure_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject) { + func Java_com_example_swift_SwiftModule__00024emptyClosure__Lcom_example_swift_SwiftModule_00024emptyClosure_00024closure_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject?) { SwiftModule.emptyClosure(closure: { let class$ = environment!.interface.GetObjectClass(environment, closure) let methodID$ = environment!.interface.GetMethodID(environment, class$, "apply", "()V")! @@ -113,7 +113,7 @@ struct JNIClosureTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_SwiftModule__00024closureWithArgumentsAndReturn__Lcom_example_swift_SwiftModule_00024closureWithArgumentsAndReturn_00024closure_2") - func Java_com_example_swift_SwiftModule__00024closureWithArgumentsAndReturn__Lcom_example_swift_SwiftModule_00024closureWithArgumentsAndReturn_00024closure_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject) { + func Java_com_example_swift_SwiftModule__00024closureWithArgumentsAndReturn__Lcom_example_swift_SwiftModule_00024closureWithArgumentsAndReturn_00024closure_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject?) { SwiftModule.closureWithArgumentsAndReturn(closure: { _0, _1 in let class$ = environment!.interface.GetObjectClass(environment, closure) let methodID$ = environment!.interface.GetMethodID(environment, class$, "apply", "(JZ)J")! diff --git a/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift b/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift index 0283780af..1f19c8f99 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift @@ -64,8 +64,14 @@ struct JNIJavaKitTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_SwiftModule__00024function__Ljava_lang_Long_2Ljava_lang_Integer_2J") - func Java_com_example_swift_SwiftModule__00024function__Ljava_lang_Long_2Ljava_lang_Integer_2J(environment: UnsafeMutablePointer!, thisClass: jclass, javaLong: jobject, javaInteger: jobject, int: jlong) { - SwiftModule.function(javaLong: JavaLong(javaThis: javaLong, environment: environment!), javaInteger: JavaInteger(javaThis: javaInteger, environment: environment!), int: Int64(fromJNI: int, in: environment!)) + func Java_com_example_swift_SwiftModule__00024function__Ljava_lang_Long_2Ljava_lang_Integer_2J(environment: UnsafeMutablePointer!, thisClass: jclass, javaLong: jobject?, javaInteger: jobject?, int: jlong) { + guard let javaLong_unwrapped$ = javaLong else { + fatalError("javaLong was null in call to \\(#function), but Swift requires non-optional!") + } + guard let javaInteger_unwrapped$ = javaInteger else { + fatalError("javaInteger was null in call to \\(#function), but Swift requires non-optional!") + } + SwiftModule.function(javaLong: JavaLong(javaThis: javaLong_unwrapped$, environment: environment!), javaInteger: JavaInteger(javaThis: javaInteger_unwrapped$, environment: environment!), int: Int64(fromJNI: int, in: environment!)) } """ ] diff --git a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift index be9cf0ce6..198276ba6 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift @@ -45,6 +45,7 @@ struct JNIModuleTests { import org.swift.swiftkit.core.*; import org.swift.swiftkit.core.util.*; + import java.util.*; import org.swift.swiftkit.core.annotations.*; public final class SwiftModule { @@ -176,7 +177,7 @@ struct JNIModuleTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_SwiftModule__00024copy__Ljava_lang_String_2") - func Java_com_example_swift_SwiftModule__00024copy__Ljava_lang_String_2(environment: UnsafeMutablePointer!, thisClass: jclass, string: jstring) -> jstring { + func Java_com_example_swift_SwiftModule__00024copy__Ljava_lang_String_2(environment: UnsafeMutablePointer!, thisClass: jclass, string: jstring?) -> jstring? { return SwiftModule.copy(String(fromJNI: string, in: environment!)).getJNIValue(in: environment!) } """, diff --git a/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift new file mode 100644 index 000000000..cd04660b0 --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift @@ -0,0 +1,253 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import Testing + +@Suite +struct JNIOptionalTests { + let source = + """ + class MyClass { } + + public func optionalSugar(_ arg: Int64?) -> Int32? + public func optionalExplicit(_ arg: Optional) -> Optional + public func optionalClass(_ arg: MyClass?) -> MyClass? + public func optionalJavaKitClass(_ arg: JavaLong?) + """ + + let classLookupTable = [ + "JavaLong": "java.lang.Long", + ] + + @Test + func optionalSugar_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + javaClassLookupTable: classLookupTable, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func optionalSugar(_ arg: Int64?) -> Int32? + * } + */ + public static OptionalInt optionalSugar(OptionalLong arg) { + long combined$ = SwiftModule.$optionalSugar((byte) (arg.isPresent() ? 1 : 0), arg.orElse(0L)); + byte discriminator$ = (byte) (combined$ & 0xFF); + int value$ = (int) (combined$ >> 32); + return discriminator$ == 1 ? OptionalInt.of(value$) : OptionalInt.empty(); + } + """, + """ + private static native long $optionalSugar(byte arg_discriminator, long arg_value); + """ + ] + ) + } + + @Test + func optionalSugar_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + javaClassLookupTable: classLookupTable, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024optionalSugar__BJ") + func Java_com_example_swift_SwiftModule__00024optionalSugar__BJ(environment: UnsafeMutablePointer!, thisClass: jclass, arg_discriminator: jbyte, arg_value: jlong) -> jlong { + let value$ = SwiftModule.optionalSugar(arg_discriminator == 1 ? Int64(fromJNI: arg_value, in: environment!) : nil).map { + Int64($0) << 32 | Int64(1) + } ?? 0 + return value$.getJNIValue(in: environment!) + } + """ + ] + ) + } + + @Test + func optionalExplicit_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + javaClassLookupTable: classLookupTable, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func optionalExplicit(_ arg: Optional) -> Optional + * } + */ + public static Optional optionalExplicit(Optional arg) { + byte[] result_discriminator$ = new byte[1]; + java.lang.String result$ = SwiftModule.$optionalExplicit((byte) (arg.isPresent() ? 1 : 0), arg.orElse(null), result_discriminator$); + return (result_discriminator$[0] == 1) ? Optional.of(result$) : Optional.empty(); + } + """, + """ + private static native java.lang.String $optionalExplicit(byte arg_discriminator, java.lang.String arg_value, byte[] result_discriminator$); + """ + ] + ) + } + + @Test + func optionalExplicit_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + javaClassLookupTable: classLookupTable, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024optionalExplicit__BLjava_lang_String_2_3B") + func Java_com_example_swift_SwiftModule__00024optionalExplicit__BLjava_lang_String_2_3B(environment: UnsafeMutablePointer!, thisClass: jclass, arg_discriminator: jbyte, arg_value: jstring?, result_discriminator$: jbyteArray?) -> jstring? { + let result$: jstring? + if let innerResult$ = SwiftModule.optionalExplicit(arg_discriminator == 1 ? String(fromJNI: arg_value, in: environment!) : nil) { + result$ = innerResult$.getJNIValue(in: environment!) + var flag$ = Int8(1) + environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) + } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:624 + else { + result$ = String.jniPlaceholderValue + var flag$ = Int8(0) + environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) + } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:634 + return result$ + } + """ + ] + ) + } + + @Test + func optionalClass_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + javaClassLookupTable: classLookupTable, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func optionalClass(_ arg: MyClass?) -> MyClass? + * } + */ + public static Optional optionalClass(Optional arg, SwiftArena swiftArena$) { + byte[] result_discriminator$ = new byte[1]; + long result$ = SwiftModule.$optionalClass(arg.map(MyClass::$memoryAddress).orElse(0L), result_discriminator$); + return (result_discriminator$[0] == 1) ? Optional.of(new MyClass(result$, swiftArena$)) : Optional.empty(); + } + """, + """ + private static native long $optionalClass(long arg, byte[] result_discriminator$); + """ + ] + ) + } + + @Test + func optionalClass_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + javaClassLookupTable: classLookupTable, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024optionalClass__J_3B") + func Java_com_example_swift_SwiftModule__00024optionalClass__J_3B(environment: UnsafeMutablePointer!, thisClass: jclass, arg: jlong, result_discriminator$: jbyteArray?) -> jlong { + let argBits$ = Int(Int64(fromJNI: arg, in: environment!)) + let arg$ = UnsafeMutablePointer(bitPattern: argBits$) + let result$: jlong + if let innerResult$ = SwiftModule.optionalClass(arg$?.pointee) { + let _result$ = UnsafeMutablePointer.allocate(capacity: 1) + _result$.initialize(to: innerResult$) + let _resultBits$ = Int64(Int(bitPattern: _result$)) + result$ = _resultBits$.getJNIValue(in: environment!) + var flag$ = Int8(1) + environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) + } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:624 + else { + result$ = 0 + var flag$ = Int8(0) + environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) + } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:634 + return result$ + } + """ + ] + ) + } + + @Test + func optionalJavaKitClass_javaBindings() throws { + try assertOutput( + input: source, + .jni, + .java, + javaClassLookupTable: classLookupTable, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func optionalJavaKitClass(_ arg: JavaLong?) + * } + */ + public static void optionalJavaKitClass(Optional arg) { + SwiftModule.$optionalJavaKitClass(arg.orElse(null)); + } + """, + """ + private static native void $optionalJavaKitClass(java.lang.Long arg); + """ + ] + ) + } + + @Test + func optionalJavaKitClass_swiftThunks() throws { + try assertOutput( + input: source, + .jni, + .swift, + detectChunkByInitialLines: 1, + javaClassLookupTable: classLookupTable, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024optionalJavaKitClass__Ljava_lang_Long_2") + func Java_com_example_swift_SwiftModule__00024optionalJavaKitClass__Ljava_lang_Long_2(environment: UnsafeMutablePointer!, thisClass: jclass, arg: jobject?) { + SwiftModule.optionalJavaKitClass(arg.map { + return JavaLong(javaThis: $0, environment: environment!) + } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:666 + ) + } + """ + ] + ) + } +} diff --git a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift index 09a8626d3..01a2e3c0f 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift @@ -205,7 +205,8 @@ struct JNIStructTests { func Java_com_example_swift_MyStruct__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, self: jlong) { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!)) diff --git a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift index 9d2fcb227..933e4f088 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift @@ -71,7 +71,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024getConstant__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } return self$.pointee.constant.getJNIValue(in: environment!) @@ -134,7 +135,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024getMutable__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } return self$.pointee.mutable.getJNIValue(in: environment!) @@ -145,7 +147,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024setMutable__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, self: jlong) { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } self$.pointee.mutable = Int64(fromJNI: newValue, in: environment!) @@ -194,7 +197,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024getComputed__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } return self$.pointee.computed.getJNIValue(in: environment!) @@ -243,7 +247,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024getComputedThrowing__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } do { @@ -311,7 +316,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024getGetterAndSetter__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } return self$.pointee.getterAndSetter.getJNIValue(in: environment!) @@ -322,7 +328,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, self: jlong) { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } self$.pointee.getterAndSetter = Int64(fromJNI: newValue, in: environment!) @@ -385,7 +392,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024isSomeBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jboolean { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } return self$.pointee.someBoolean.getJNIValue(in: environment!) @@ -396,7 +404,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, self: jlong) { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } self$.pointee.someBoolean = Bool(fromJNI: newValue, in: environment!) @@ -459,7 +468,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024isBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jboolean { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } return self$.pointee.isBoolean.getJNIValue(in: environment!) @@ -470,7 +480,8 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024setBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, self: jlong) { assert(self != 0, "self memory address was null") let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - guard let self$ = UnsafeMutablePointer(bitPattern: selfBits$) else { + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } self$.pointee.isBoolean = Bool(fromJNI: newValue, in: environment!) From 68f695a60c456ecdf91863dd563bc29f2f09dc62 Mon Sep 17 00:00:00 2001 From: "Sven A. Schmidt" Date: Sat, 2 Aug 2025 14:59:06 +0200 Subject: [PATCH 052/141] Update Package.swift (#345) --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 51608c319..bac55bd6d 100644 --- a/Package.swift +++ b/Package.swift @@ -32,7 +32,7 @@ func findJavaHome() -> String { } - if ProcessInfo.processInfo.environment["SPI_BUILD"] == "1" { + if ProcessInfo.processInfo.environment["SPI_PROCESSING"] == "1" { // just ignore that we're missing a JAVA_HOME when building in Swift Package Index return "" } From a3791cf7c94146a34f96fd9ad53efef9d58c2a1b Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 2 Aug 2025 22:36:21 -0400 Subject: [PATCH 053/141] [JavaKit] Add `JNIEnvPointer` and `JNINativeInterface_` for Android (#311) * [JavaKit] Add `JNIEnvPointer` for Android * [JavaKit] Fix `JNINativeInterface_` for Android --- Sources/JavaKit/JavaEnvironment.swift | 4 +++ .../JavaKitVM/JavaVirtualMachine.swift | 25 +++++++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/Sources/JavaKit/JavaEnvironment.swift b/Sources/JavaKit/JavaEnvironment.swift index 4895b32c7..422262dad 100644 --- a/Sources/JavaKit/JavaEnvironment.swift +++ b/Sources/JavaKit/JavaEnvironment.swift @@ -14,6 +14,10 @@ import JavaRuntime +#if canImport(Android) +typealias JNINativeInterface_ = JNINativeInterface +#endif + extension UnsafeMutablePointer { public var interface: JNINativeInterface_ { self.pointee!.pointee } } diff --git a/Sources/JavaKit/JavaKitVM/JavaVirtualMachine.swift b/Sources/JavaKit/JavaKitVM/JavaVirtualMachine.swift index 0800a89e2..edbddbb78 100644 --- a/Sources/JavaKit/JavaKitVM/JavaVirtualMachine.swift +++ b/Sources/JavaKit/JavaKitVM/JavaVirtualMachine.swift @@ -19,6 +19,11 @@ import Foundation #endif public typealias JavaVMPointer = UnsafeMutablePointer +#if canImport(Android) +typealias JNIEnvPointer = UnsafeMutablePointer +#else +typealias JNIEnvPointer = UnsafeMutableRawPointer +#endif public final class JavaVirtualMachine: @unchecked Sendable { /// The JNI version that we depend on. @@ -61,7 +66,7 @@ public final class JavaVirtualMachine: @unchecked Sendable { ) throws { self.classpath = classpath var jvm: JavaVMPointer? = nil - var environment: UnsafeMutableRawPointer? = nil + var environment: JNIEnvPointer? = nil var vmArgs = JavaVMInitArgs() vmArgs.version = JavaVirtualMachine.jniVersion vmArgs.ignoreUnrecognized = jboolean(ignoreUnrecognized ? JNI_TRUE : JNI_FALSE) @@ -161,12 +166,18 @@ extension JavaVirtualMachine { return environment.assumingMemoryBound(to: JNIEnv?.self) } +#if canImport(Android) + var jniEnv = environment?.assumingMemoryBound(to: JNIEnv?.self) +#else + var jniEnv = environment +#endif + // Attach the current thread to the JVM. let attachResult: jint if asDaemon { - attachResult = jvm.pointee!.pointee.AttachCurrentThreadAsDaemon(jvm, &environment, nil) + attachResult = jvm.pointee!.pointee.AttachCurrentThreadAsDaemon(jvm, &jniEnv, nil) } else { - attachResult = jvm.pointee!.pointee.AttachCurrentThread(jvm, &environment, nil) + attachResult = jvm.pointee!.pointee.AttachCurrentThread(jvm, &jniEnv, nil) } // If we failed to attach, report that. @@ -175,9 +186,13 @@ extension JavaVirtualMachine { throw attachError } - JavaVirtualMachine.destroyTLS.set(environment!) + JavaVirtualMachine.destroyTLS.set(jniEnv!) - return environment!.assumingMemoryBound(to: JNIEnv?.self) +#if canImport(Android) + return jniEnv! +#else + return jniEnv!.assumingMemoryBound(to: JNIEnv?.self) +#endif } /// Detach the current thread from the Java Virtual Machine. All Java From 1ac73e4518f988741c62dba7c7c17a4655fd6e00 Mon Sep 17 00:00:00 2001 From: "Sven A. Schmidt" Date: Mon, 4 Aug 2025 16:57:47 +0200 Subject: [PATCH 054/141] Update Package.swift (#347) --- Package.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index bac55bd6d..65737b2ba 100644 --- a/Package.swift +++ b/Package.swift @@ -32,8 +32,8 @@ func findJavaHome() -> String { } - if ProcessInfo.processInfo.environment["SPI_PROCESSING"] == "1" { - // just ignore that we're missing a JAVA_HOME when building in Swift Package Index + if ProcessInfo.processInfo.environment["SPI_PROCESSING"] == "1" && ProcessInfo.processInfo.environment["SPI_BUILD"] == nil { + // Just ignore that we're missing a JAVA_HOME when building in Swift Package Index during general processing where no Java is needed. However, do _not_ suppress the error during SPI's compatibility build stage where Java is required. return "" } fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.") From 8d41e6e263b568a9a3adacee0077ac1dfc03762a Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Tue, 5 Aug 2025 17:09:16 -0700 Subject: [PATCH 055/141] [JExtract] Fix importing extensions (#351) --- .../JExtractSwiftLib/Swift2JavaVisitor.swift | 4 +- .../ExtensionImportTests.swift | 48 +++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 Tests/JExtractSwiftTests/ExtensionImportTests.swift diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index 3efddbfc6..a933ff3e0 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Copyright (c) 2024-2025 Apple Inc. and the Swift.org project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -84,7 +84,7 @@ final class Swift2JavaVisitor { } func visit(extensionDecl node: ExtensionDeclSyntax, in parent: ImportedNominalType?) { - guard parent != nil else { + guard parent == nil else { // 'extension' in a nominal type is invalid. Ignore return } diff --git a/Tests/JExtractSwiftTests/ExtensionImportTests.swift b/Tests/JExtractSwiftTests/ExtensionImportTests.swift new file mode 100644 index 000000000..2bad2c7b8 --- /dev/null +++ b/Tests/JExtractSwiftTests/ExtensionImportTests.swift @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import Testing + +final class ExtensionImportTests { + let interfaceFile = + """ + extension MyStruct { + public func methodInExtension() {} + } + + public struct MyStruct {} + """ + + @Test("Import extensions: Swift thunks") + func data_swiftThunk() throws { + try assertOutput( + input: interfaceFile, .ffm, .swift, + expectedChunks: [ + """ + @_cdecl("swiftjava_getType_SwiftModule_MyStruct") + public func swiftjava_getType_SwiftModule_MyStruct() -> UnsafeMutableRawPointer /* Any.Type */ { + return unsafeBitCast(MyStruct.self, to: UnsafeMutableRawPointer.self) + } + """, + """ + @_cdecl("swiftjava_SwiftModule_MyStruct_methodInExtension") + public func swiftjava_SwiftModule_MyStruct_methodInExtension(_ self: UnsafeRawPointer) { + self.assumingMemoryBound(to: MyStruct.self).pointee.methodInExtension() + } + """ + ] + ) + } +} From 1e4c6ebd9aa7bd3bd095c9bd8b1f26634a016001 Mon Sep 17 00:00:00 2001 From: David Ko Date: Wed, 6 Aug 2025 19:12:49 -0400 Subject: [PATCH 056/141] replace Java2Swift.config with swift-java.config (#350) --- .../Documentation.docc/SwiftJavaCommandLineTool.md | 2 +- Sources/SwiftJavaTool/Commands/JExtractCommand.swift | 2 +- Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md index f83458d43..e8a3dfedb 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md @@ -57,7 +57,7 @@ OPTIONS: The name of the Swift module into which the resulting Swift types will be generated. --depends-on A swift-java configuration file for a given Swift module name on which this module depends, - e.g., JavaKitJar=Sources/JavaKitJar/Java2Swift.config. There should be one of these options + e.g., JavaKitJar=Sources/JavaKitJar/swift-java.config. There should be one of these options for each Swift module that this module depends on (transitively) that contains wrapped Java sources. --swift-native-implementation The names of Java classes whose declared native methods will be implemented in Swift. diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index 54a997083..88fbf7f3a 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -70,7 +70,7 @@ extension SwiftJava { @Option( help: """ A swift-java configuration file for a given Swift module name on which this module depends, - e.g., Sources/JavaKitJar/Java2Swift.config. There should be one of these options + e.g., Sources/JavaKitJar/swift-java.config. There should be one of these options for each Swift module that this module depends on (transitively) that contains wrapped Java sources. """ ) diff --git a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift index 4c5bd97b2..3e4a62af8 100644 --- a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift +++ b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift @@ -40,7 +40,7 @@ extension SwiftJava { @Option( help: """ A swift-java configuration file for a given Swift module name on which this module depends, - e.g., JavaKitJar=Sources/JavaKitJar/Java2Swift.config. There should be one of these options + e.g., JavaKitJar=Sources/JavaKitJar/swift-java.config. There should be one of these options for each Swift module that this module depends on (transitively) that contains wrapped Java sources. """ ) From f87fe2811359b70d5f1e6a2dc916e06b6802bd72 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 8 Aug 2025 00:13:13 +0200 Subject: [PATCH 057/141] [JExtract/JNI] Add `auto` arena to SwiftKitCore and add memory management options (#353) --- .../com/example/swift/HelloJava2SwiftJNI.java | 4 +- .../com/example/swift/MySwiftClassTest.java | 30 ++++---- .../com/example/swift/MySwiftStructTest.java | 7 +- .../java/com/example/swift/OptionalsTest.java | 6 +- ...t2JavaGenerator+JavaBindingsPrinting.swift | 32 ++++++-- .../Configuration.swift | 5 ++ .../GenerationMode.swift | 22 ++++++ .../Commands/JExtractCommand.swift | 9 +++ .../swiftkit/core/AutoSwiftMemorySession.java | 56 ++++++++++++++ .../core/ConfinedSwiftMemorySession.java | 6 +- .../swift/swiftkit/core/JNISwiftInstance.java | 9 +-- .../org/swift/swiftkit/core/SwiftArena.java | 13 +++- .../swiftkit/core/SwiftMemoryManagement.java | 19 +++++ .../swiftkit/core/ref/PhantomCleanable.java | 35 +++++++++ .../swift/swiftkit/core/ref/SwiftCleaner.java | 67 ++++++++++++++++ .../org/swift/swiftkit/AutoArenaTest.java | 57 ++++++++++++++ .../MemoryManagementModeTests.swift | 77 +++++++++++++++++++ 17 files changed, 411 insertions(+), 43 deletions(-) create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/AutoSwiftMemorySession.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftMemoryManagement.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/ref/PhantomCleanable.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/ref/SwiftCleaner.java create mode 100644 SwiftKitCore/src/test/java/org/swift/swiftkit/AutoArenaTest.java create mode 100644 Tests/JExtractSwiftTests/MemoryManagementModeTests.swift diff --git a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java index 21cad317a..3109f64e9 100644 --- a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java +++ b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java @@ -18,8 +18,8 @@ // Import javakit/swiftkit support libraries +import org.swift.swiftkit.core.SwiftArena; import org.swift.swiftkit.core.SwiftLibraries; -import org.swift.swiftkit.core.ConfinedSwiftMemorySession; public class HelloJava2SwiftJNI { @@ -41,7 +41,7 @@ static void examples() { MySwiftClass.method(); - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass myClass = MySwiftClass.init(10, 5, arena); MySwiftClass myClass2 = MySwiftClass.init(arena); diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java index e7de03add..2e9a7e623 100644 --- a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -15,7 +15,7 @@ package com.example.swift; import org.junit.jupiter.api.Test; -import org.swift.swiftkit.core.ConfinedSwiftMemorySession; +import org.swift.swiftkit.core.SwiftArena; import java.util.Optional; import java.util.OptionalInt; @@ -26,7 +26,7 @@ public class MySwiftClassTest { @Test void init_noParameters() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c = MySwiftClass.init(arena); assertNotNull(c); } @@ -34,7 +34,7 @@ void init_noParameters() { @Test void init_withParameters() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c = MySwiftClass.init(1337, 42, arena); assertNotNull(c); } @@ -42,7 +42,7 @@ void init_withParameters() { @Test void sum() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); assertEquals(30, c.sum()); } @@ -50,7 +50,7 @@ void sum() { @Test void xMultiplied() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); assertEquals(200, c.xMultiplied(10)); } @@ -58,7 +58,7 @@ void xMultiplied() { @Test void throwingFunction() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); Exception exception = assertThrows(Exception.class, () -> c.throwingFunction()); @@ -68,7 +68,7 @@ void throwingFunction() { @Test void constant() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); assertEquals(100, c.getConstant()); } @@ -76,7 +76,7 @@ void constant() { @Test void mutable() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); assertEquals(0, c.getMutable()); c.setMutable(42); @@ -86,7 +86,7 @@ void mutable() { @Test void product() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); assertEquals(200, c.getProduct()); } @@ -94,7 +94,7 @@ void product() { @Test void throwingVariable() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); Exception exception = assertThrows(Exception.class, () -> c.getThrowingVariable()); @@ -105,7 +105,7 @@ void throwingVariable() { @Test void mutableDividedByTwo() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); assertEquals(0, c.getMutableDividedByTwo()); c.setMutable(20); @@ -117,7 +117,7 @@ void mutableDividedByTwo() { @Test void isWarm() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); assertFalse(c.isWarm()); } @@ -125,7 +125,7 @@ void isWarm() { @Test void sumWithX() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c1 = MySwiftClass.init(20, 10, arena); MySwiftClass c2 = MySwiftClass.init(50, 10, arena); assertEquals(70, c1.sumX(c2)); @@ -134,7 +134,7 @@ void sumWithX() { @Test void copy() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c1 = MySwiftClass.init(20, 10, arena); MySwiftClass c2 = c1.copy(arena); @@ -146,7 +146,7 @@ void copy() { @Test void addXWithJavaLong() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c1 = MySwiftClass.init(20, 10, arena); Long javaLong = 50L; assertEquals(70, c1.addXWithJavaLong(javaLong)); diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java index 9eeaf0297..c2c1170b7 100644 --- a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java @@ -16,13 +16,14 @@ import org.junit.jupiter.api.Test; import org.swift.swiftkit.core.ConfinedSwiftMemorySession; +import org.swift.swiftkit.core.SwiftArena; import static org.junit.jupiter.api.Assertions.*; public class MySwiftStructTest { @Test void init() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftStruct s = MySwiftStruct.init(1337, 42, arena); assertEquals(1337, s.getCapacity()); assertEquals(42, s.getLen()); @@ -31,7 +32,7 @@ void init() { @Test void getAndSetLen() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftStruct s = MySwiftStruct.init(1337, 42, arena); s.setLen(100); assertEquals(100, s.getLen()); @@ -40,7 +41,7 @@ void getAndSetLen() { @Test void increaseCap() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftStruct s = MySwiftStruct.init(1337, 42, arena); long newCap = s.increaseCap(10); assertEquals(1347, newCap); diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java index f7262ad4d..d60ff6d5b 100644 --- a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java @@ -15,7 +15,7 @@ package com.example.swift; import org.junit.jupiter.api.Test; -import org.swift.swiftkit.core.ConfinedSwiftMemorySession; +import org.swift.swiftkit.core.SwiftArena; import java.util.Optional; import java.util.OptionalDouble; @@ -82,7 +82,7 @@ void optionalString() { @Test void optionalClass() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c = MySwiftClass.init(arena); assertEquals(Optional.empty(), MySwiftLibrary.optionalClass(Optional.empty(), arena)); Optional optionalClass = MySwiftLibrary.optionalClass(Optional.of(c), arena); @@ -99,7 +99,7 @@ void optionalJavaKitLong() { @Test void multipleOptionals() { - try (var arena = new ConfinedSwiftMemorySession()) { + try (var arena = SwiftArena.ofConfined()) { MySwiftClass c = MySwiftClass.init(arena); OptionalLong result = MySwiftLibrary.multipleOptionals( Optional.of((byte) 1), diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 0daf14de6..0920a89b0 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -247,29 +247,47 @@ extension JNISwift2JavaGenerator { guard let translatedDecl = translatedDecl(for: decl) else { fatalError("Decl was not translated, \(decl)") } + let translatedSignature = translatedDecl.translatedFunctionSignature var modifiers = ["public"] + if decl.isStatic || decl.isInitializer || !decl.hasParent { modifiers.append("static") } - let translatedSignature = translatedDecl.translatedFunctionSignature let resultType = translatedSignature.resultType.javaType - var parameters = translatedDecl.translatedFunctionSignature.parameters.map({ $0.parameter.renderParameter() }) - if translatedSignature.requiresSwiftArena { - parameters.append("SwiftArena swiftArena$") - } + var parameters = translatedDecl.translatedFunctionSignature.parameters.map { $0.parameter.renderParameter() } let throwsClause = decl.isThrowing ? " throws Exception" : "" var annotationsStr = translatedSignature.annotations.map({ $0.render() }).joined(separator: "\n") if !annotationsStr.isEmpty { annotationsStr += "\n" } - let modifiersStr = modifiers.joined(separator: " ") let parametersStr = parameters.joined(separator: ", ") + // Print default global arena variation + if config.effectiveMemoryManagementMode.requiresGlobalArena && translatedSignature.requiresSwiftArena { + printDeclDocumentation(&printer, decl) + printer.printBraceBlock( + "\(annotationsStr)\(modifiers.joined(separator: " ")) \(resultType) \(translatedDecl.name)(\(parametersStr))\(throwsClause)" + ) { printer in + let globalArenaName = "SwiftMemoryManagement.GLOBAL_SWIFT_JAVA_ARENA" + let arguments = translatedDecl.translatedFunctionSignature.parameters.map(\.parameter.name) + [globalArenaName] + let call = "\(translatedDecl.name)(\(arguments.joined(separator: ", ")))" + if translatedDecl.translatedFunctionSignature.resultType.javaType.isVoid { + printer.print("\(call);") + } else { + printer.print("return \(call);") + } + } + printer.println() + } + + if translatedSignature.requiresSwiftArena { + parameters.append("SwiftArena swiftArena$") + } printDeclDocumentation(&printer, decl) printer.printBraceBlock( - "\(annotationsStr)\(modifiersStr) \(resultType) \(translatedDecl.name)(\(parametersStr))\(throwsClause)" + "\(annotationsStr)\(modifiers.joined(separator: " ")) \(resultType) \(translatedDecl.name)(\(parameters.joined(separator: ", ")))\(throwsClause)" ) { printer in printDowncall(&printer, decl) } diff --git a/Sources/JavaKitConfigurationShared/Configuration.swift b/Sources/JavaKitConfigurationShared/Configuration.swift index 0ff089da0..2d9b43118 100644 --- a/Sources/JavaKitConfigurationShared/Configuration.swift +++ b/Sources/JavaKitConfigurationShared/Configuration.swift @@ -51,6 +51,11 @@ public struct Configuration: Codable { minimumInputAccessLevelMode ?? .default } + public var memoryManagementMode: JExtractMemoryManagementMode? + public var effectiveMemoryManagementMode: JExtractMemoryManagementMode { + memoryManagementMode ?? .default + } + // ==== java 2 swift --------------------------------------------------------- /// The Java class path that should be passed along to the swift-java tool. diff --git a/Sources/JavaKitConfigurationShared/GenerationMode.swift b/Sources/JavaKitConfigurationShared/GenerationMode.swift index 1feac411b..22fdd5f5a 100644 --- a/Sources/JavaKitConfigurationShared/GenerationMode.swift +++ b/Sources/JavaKitConfigurationShared/GenerationMode.swift @@ -76,3 +76,25 @@ extension JExtractMinimumAccessLevelMode { .public } } + + +/// Configures how memory should be managed by the user +public enum JExtractMemoryManagementMode: String, Codable { + /// Force users to provide an explicit `SwiftArena` to all calls that require them. + case explicit + + /// Provide both explicit `SwiftArena` support + /// and a default global automatic `SwiftArena` that will deallocate memory when the GC decides to. + case allowGlobalAutomatic + + public static var `default`: Self { + .explicit + } + + public var requiresGlobalArena: Bool { + switch self { + case .explicit: false + case .allowGlobalAutomatic: true + } + } +} diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index 88fbf7f3a..b32f34a08 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -67,6 +67,9 @@ extension SwiftJava { @Option(help: "The lowest access level of Swift declarations that should be extracted, defaults to 'public'.") var minimumInputAccessLevel: JExtractMinimumAccessLevelMode = .default + @Option(help: "The memory management mode to use for the generated code. By default, the user must explicitly provide `SwiftArena` to all calls that require it. By choosing `allow-automatic`, user can omit this parameter and a global GC-based arena will be used. `force-automatic` removes all explicit memory management.") + var memoryManagementMode: JExtractMemoryManagementMode = .default + @Option( help: """ A swift-java configuration file for a given Swift module name on which this module depends, @@ -89,6 +92,7 @@ extension SwiftJava.JExtractCommand { config.writeEmptyFiles = writeEmptyFiles config.unsignedNumbersMode = unsignedNumbers config.minimumInputAccessLevelMode = minimumInputAccessLevel + config.memoryManagementMode = memoryManagementMode try checkModeCompatibility() @@ -117,6 +121,10 @@ extension SwiftJava.JExtractCommand { case .wrapGuava: () // OK } + } else if self.mode == .ffm { + guard self.memoryManagementMode == .explicit else { + throw IllegalModeCombinationError("FFM mode does not support '\(self.memoryManagementMode)' memory management mode! \(Self.helpMessage)") + } } } } @@ -148,3 +156,4 @@ struct IllegalModeCombinationError: Error { extension JExtractGenerationMode: ExpressibleByArgument {} extension JExtractUnsignedIntegerMode: ExpressibleByArgument {} extension JExtractMinimumAccessLevelMode: ExpressibleByArgument {} +extension JExtractMemoryManagementMode: ExpressibleByArgument {} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/AutoSwiftMemorySession.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/AutoSwiftMemorySession.java new file mode 100644 index 000000000..36e732097 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/AutoSwiftMemorySession.java @@ -0,0 +1,56 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core; + + +import org.swift.swiftkit.core.ref.SwiftCleaner; + +import java.util.Objects; +import java.util.concurrent.ThreadFactory; + +/** + * A memory session which manages registered objects via the Garbage Collector. + * + *

When registered Java wrapper classes around native Swift instances {@link SwiftInstance}, + * are eligible for collection, this will trigger the cleanup of the native resources as well. + * + *

This memory session is LESS reliable than using a {@link ConfinedSwiftMemorySession} because + * the timing of when the native resources are cleaned up is somewhat undefined, and rely on the + * system GC. Meaning, that if an object nas been promoted to an old generation, there may be a + * long time between the resource no longer being referenced "in Java" and its native memory being released, + * and also the deinit of the Swift type being run. + * + *

This can be problematic for Swift applications which rely on quick release of resources, and may expect + * the deinits to run in expected and "quick" succession. + * + *

Whenever possible, prefer using an explicitly managed {@link SwiftArena}, such as {@link SwiftArena#ofConfined()}. + */ +final class AutoSwiftMemorySession implements SwiftArena { + private final SwiftCleaner swiftCleaner; + + public AutoSwiftMemorySession(ThreadFactory cleanerThreadFactory) { + this.swiftCleaner = SwiftCleaner.create(cleanerThreadFactory); + } + + @Override + public void register(SwiftInstance instance) { + Objects.requireNonNull(instance, "value"); + + // We make sure we don't capture `instance` in the + // cleanup action, so we can ignore the warning below. + var cleanupAction = instance.$createCleanup(); + swiftCleaner.register(instance, cleanupAction); + } +} \ No newline at end of file diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java index 7c6e80fb9..4383a6fe2 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java @@ -28,17 +28,13 @@ public class ConfinedSwiftMemorySession implements ClosableSwiftArena { final ConfinedResourceList resources; - public ConfinedSwiftMemorySession() { - this(Thread.currentThread()); - } - public ConfinedSwiftMemorySession(Thread owner) { this.owner = owner; this.state = new AtomicInteger(ACTIVE); this.resources = new ConfinedResourceList(); } - public void checkValid() throws RuntimeException { + void checkValid() throws RuntimeException { if (this.owner != null && this.owner != Thread.currentThread()) { throw new WrongThreadException(String.format("ConfinedSwift arena is confined to %s but was closed from %s!", this.owner, Thread.currentThread())); } else if (this.state.get() < ACTIVE) { diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java index 6b30ed2fc..95f1e5a0f 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java @@ -56,13 +56,8 @@ protected JNISwiftInstance(long selfPointer, SwiftArena arena) { @Override public SwiftInstanceCleanup $createCleanup() { - final AtomicBoolean statusDestroyedFlag = $statusDestroyedFlag(); - Runnable markAsDestroyed = new Runnable() { - @Override - public void run() { - statusDestroyedFlag.set(true); - } - }; + var statusDestroyedFlag = $statusDestroyedFlag(); + Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true); return new JNISwiftInstanceCleanup(this.$createDestroyFunction(), markAsDestroyed); } diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java index ed16d2501..3b6c46269 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java @@ -14,6 +14,8 @@ package org.swift.swiftkit.core; +import java.util.concurrent.ThreadFactory; + /** * A Swift arena manages Swift allocated memory for classes, structs, enums etc. * When an arena is closed, it will destroy all managed swift objects in a way appropriate to their type. @@ -21,12 +23,21 @@ *

A confined arena has an associated owner thread that confines some operations to * associated owner thread such as {@link ClosableSwiftArena#close()}. */ -public interface SwiftArena { +public interface SwiftArena { /** * Register a Swift object. * Its memory should be considered managed by this arena, and be destroyed when the arena is closed. */ void register(SwiftInstance instance); + + static ClosableSwiftArena ofConfined() { + return new ConfinedSwiftMemorySession(Thread.currentThread()); + } + + static SwiftArena ofAuto() { + ThreadFactory cleanerThreadFactory = r -> new Thread(r, "AutoSwiftArenaCleanerThread"); + return new AutoSwiftMemorySession(cleanerThreadFactory); + } } /** diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftMemoryManagement.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftMemoryManagement.java new file mode 100644 index 000000000..2b9a12092 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftMemoryManagement.java @@ -0,0 +1,19 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core; + +public class SwiftMemoryManagement { + public static final SwiftArena GLOBAL_SWIFT_JAVA_ARENA = SwiftArena.ofAuto(); +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ref/PhantomCleanable.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ref/PhantomCleanable.java new file mode 100644 index 000000000..2efcdae7d --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ref/PhantomCleanable.java @@ -0,0 +1,35 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core.ref; + +import java.lang.ref.PhantomReference; + +public class PhantomCleanable extends PhantomReference { + private final Runnable cleanupAction; + private final SwiftCleaner swiftCleaner; + + public PhantomCleanable(Object referent, SwiftCleaner swiftCleaner, Runnable cleanupAction) { + super(referent, swiftCleaner.referenceQueue); + this.cleanupAction = cleanupAction; + this.swiftCleaner = swiftCleaner; + swiftCleaner.list.add(this); + } + + public void cleanup() { + if (swiftCleaner.list.remove(this)) { + cleanupAction.run(); + } + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ref/SwiftCleaner.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ref/SwiftCleaner.java new file mode 100644 index 000000000..2a5b49f5e --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ref/SwiftCleaner.java @@ -0,0 +1,67 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core.ref; + +import java.lang.ref.ReferenceQueue; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ThreadFactory; + +public class SwiftCleaner implements Runnable { + final ReferenceQueue referenceQueue; + final List list; + + private SwiftCleaner() { + this.referenceQueue = new ReferenceQueue<>(); + this.list = Collections.synchronizedList(new LinkedList<>()); + } + + public static SwiftCleaner create(ThreadFactory threadFactory) { + SwiftCleaner swiftCleaner = new SwiftCleaner(); + swiftCleaner.start(threadFactory); + return swiftCleaner; + } + + void start(ThreadFactory threadFactory) { + // This makes sure the linked list is not empty when the thread starts, + // and the thread will run at least until the cleaner itself can be GCed. + new PhantomCleanable(this, this, () -> {}); + + Thread thread = threadFactory.newThread(this); + thread.setDaemon(true); + thread.start(); + } + + public void register(Object resourceHolder, Runnable cleaningAction) { + Objects.requireNonNull(resourceHolder, "resourceHolder"); + Objects.requireNonNull(cleaningAction, "cleaningAction"); + new PhantomCleanable(resourceHolder, this, cleaningAction); + } + + @Override + public void run() { + while (!list.isEmpty()) { + try { + PhantomCleanable removed = (PhantomCleanable) referenceQueue.remove(60 * 1000L); + removed.cleanup(); + } catch (Throwable e) { + // ignore exceptions from the cleanup action + // (including interruption of cleanup thread) + } + } + } +} diff --git a/SwiftKitCore/src/test/java/org/swift/swiftkit/AutoArenaTest.java b/SwiftKitCore/src/test/java/org/swift/swiftkit/AutoArenaTest.java new file mode 100644 index 000000000..b414962f5 --- /dev/null +++ b/SwiftKitCore/src/test/java/org/swift/swiftkit/AutoArenaTest.java @@ -0,0 +1,57 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.JNISwiftInstance; +import org.swift.swiftkit.core.SwiftArena; + +public class AutoArenaTest { + + @Test + @SuppressWarnings("removal") // System.runFinalization() will be removed + public void cleaner_releases_native_resource() { + SwiftArena arena = SwiftArena.ofAuto(); + + // This object is registered to the arena. + var object = new FakeSwiftInstance(arena); + var statusDestroyedFlag = object.$statusDestroyedFlag(); + + // Release the object and hope it gets GC-ed soon + + // noinspection UnusedAssignment + object = null; + + var i = 1_000; + while (!statusDestroyedFlag.get()) { + System.runFinalization(); + System.gc(); + + if (i-- < 1) { + throw new RuntimeException("Reference was not cleaned up! Did Cleaner not pick up the release?"); + } + } + } + + private static class FakeSwiftInstance extends JNISwiftInstance { + public FakeSwiftInstance(SwiftArena arena) { + super(1, arena); + } + + protected Runnable $createDestroyFunction() { + return () -> {}; + } + } +} diff --git a/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift b/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift new file mode 100644 index 000000000..4edf59c27 --- /dev/null +++ b/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift @@ -0,0 +1,77 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import JavaKitConfigurationShared +import Testing + +@Suite +struct MemoryManagementModeTests { + let text = + """ + class MyClass {} + + public func f() -> MyClass + """ + + @Test + func explicit() throws { + var config = Configuration() + config.memoryManagementMode = .explicit + + try assertOutput( + input: text, + config: config, + .jni, .java, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func f() -> MyClass + * } + */ + public static MyClass f(SwiftArena swiftArena$) { + return new MyClass(SwiftModule.$f(), swiftArena$); + } + """, + ] + ) + } + + @Test + func allowGlobalAutomatic() throws { + var config = Configuration() + config.memoryManagementMode = .allowGlobalAutomatic + + try assertOutput( + input: text, + config: config, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public static MyClass f() { + return f(SwiftMemoryManagement.GLOBAL_SWIFT_JAVA_ARENA); + } + """, + """ + public static MyClass f(SwiftArena swiftArena$) { + return new MyClass(SwiftModule.$f(), swiftArena$); + } + """, + ] + ) + } +} From d5f9c9059665550d4b696f2a345ba085bfdd11e9 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 8 Aug 2025 07:13:28 +0900 Subject: [PATCH 058/141] Update Dockerfile (#349) --- docker/Dockerfile | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 06b17a872..c3568b54b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -18,9 +18,13 @@ ENV LC_ALL=en_US.UTF-8 ENV LANG=en_US.UTF-8 ENV LANGUAGE=en_US.UTF-8 -COPY install_jdk.sh . - # JDK dependency -RUN bash -xc 'JDK_VENDOR=corretto ./install_jdk.sh' -ENV JAVA_HOME="/usr/lib/jvm/default-jdk" -ENV PATH="$PATH:/usr/lib/jvm/default-jdk/bin" +RUN curl -s "https://get.sdkman.io" | bash +RUN bash -c "source /root/.sdkman/bin/sdkman-init.sh && sdk install java 24.0.1-amzn" + +RUN curl -O https://download.swift.org/swiftly/linux/swiftly-$(uname -m).tar.gz && \ + tar zxf swiftly-$(uname -m).tar.gz && \ + ./swiftly init --quiet-shell-followup --assume-yes && \ + . "${SWIFTLY_HOME_DIR:-$HOME/.local/share/swiftly}/env.sh" && \ + hash -r + From 5dd3f36fa086339e1ff90bcad7cb433d0560a125 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 13 Aug 2025 11:54:56 +0900 Subject: [PATCH 059/141] gh: test with nightly swift builds (#357) --- .github/workflows/pull_request.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index b2338ed24..89ca42894 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -157,7 +157,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.1.2'] + swift_version: ['6.1.2', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] sample_app: [ # TODO: use a reusable-workflow to generate those names @@ -185,7 +185,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.1.2'] + swift_version: ['6.1.2'] # no nightly checks for macOS os_version: ['macos'] jdk_vendor: ['corretto'] sample_app: [ # TODO: use a reusable-workflow to generate those names From 69e487ba1871ff9f2f384ae9e0a547616dff128d Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Thu, 14 Aug 2025 18:28:23 +0900 Subject: [PATCH 060/141] jextract: hide memory location wrapping constructor, give it proper name (#360) --- .../JExtractSwiftLib/FFM/ConversionStep.swift | 2 +- ...wift2JavaGenerator+JavaBindingsPrinting.swift | 13 +++++++++++-- .../FFMSwift2JavaGenerator+JavaTranslation.swift | 5 ++++- .../FFM/FFMSwift2JavaGenerator.swift | 15 ++++++++++++++- ...wift2JavaGenerator+JavaBindingsPrinting.swift | 15 ++++++++++++++- .../JNISwift2JavaGenerator+JavaTranslation.swift | 13 ++++++++++--- Tests/JExtractSwiftTests/DataImportTests.swift | 7 ++++--- Tests/JExtractSwiftTests/JNI/JNIClassTests.swift | 16 +++++++++++----- .../JNI/JNIOptionalTests.swift | 2 +- .../JExtractSwiftTests/JNI/JNIStructTests.swift | 12 +++++++++--- .../MemoryManagementModeTests.swift | 4 ++-- Tests/JExtractSwiftTests/MethodImportTests.swift | 6 +++--- 12 files changed, 84 insertions(+), 26 deletions(-) diff --git a/Sources/JExtractSwiftLib/FFM/ConversionStep.swift b/Sources/JExtractSwiftLib/FFM/ConversionStep.swift index 656d9dd99..f59f739dd 100644 --- a/Sources/JExtractSwiftLib/FFM/ConversionStep.swift +++ b/Sources/JExtractSwiftLib/FFM/ConversionStep.swift @@ -123,7 +123,7 @@ enum ConversionStep: Equatable { // of splatting out text. let renderedArgumentList = renderedArguments.joined(separator: ", ") return "\(raw: type.description)(\(raw: renderedArgumentList))" - + case .tuplify(let elements): let renderedElements: [String] = elements.enumerated().map { (index, element) in element.asExprSyntax(placeholder: "\(placeholder)_\(index)", bodyItems: &bodyItems)!.description diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index b87492999..84a902755 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -407,6 +407,7 @@ extension FFMSwift2JavaGenerator { "arena$" } + // FIXME: use trailing$ convention let varName = outParameter.name.isEmpty ? "_result" : "_result_" + outParameter.name printer.print( @@ -463,7 +464,7 @@ extension FFMSwift2JavaGenerator.JavaConversionStep { switch self { case .placeholder, .explodedName, .constant, .readMemorySegment: return false - case .constructSwiftValue: + case .constructSwiftValue, .wrapMemoryAddressUnsafe: return true case .call(let inner, _, _), .cast(let inner, _), .construct(let inner, _), @@ -482,7 +483,11 @@ extension FFMSwift2JavaGenerator.JavaConversionStep { return false case .readMemorySegment: return true - case .cast(let inner, _), .construct(let inner, _), .constructSwiftValue(let inner, _), .swiftValueSelfSegment(let inner): + case .cast(let inner, _), + .construct(let inner, _), + .constructSwiftValue(let inner, _), + .swiftValueSelfSegment(let inner), + .wrapMemoryAddressUnsafe(let inner, _): return inner.requiresSwiftArena case .call(let inner, _, let withArena): return withArena || inner.requiresTemporaryArena @@ -522,6 +527,10 @@ extension FFMSwift2JavaGenerator.JavaConversionStep { let inner = inner.render(&printer, placeholder) return "new \(javaType.className!)(\(inner), swiftArena$)" + case .wrapMemoryAddressUnsafe(let inner, let javaType): + let inner = inner.render(&printer, placeholder) + return "\(javaType.className!).wrapMemoryAddressUnsafe(\(inner), swiftArena$)" + case .construct(let inner, let javaType): let inner = inner.render(&printer, placeholder) return "new \(javaType)(\(inner))" diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index f8b2e7e84..06a4d1713 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -684,7 +684,7 @@ extension FFMSwift2JavaGenerator { outParameters: [ JavaParameter(name: "", type: javaType) ], - conversion: .constructSwiftValue(.placeholder, javaType) + conversion: .wrapMemoryAddressUnsafe(.placeholder, javaType) ) case .tuple: @@ -732,6 +732,9 @@ extension FFMSwift2JavaGenerator { // Call 'new \(Type)(\(placeholder), swiftArena$)'. indirect case constructSwiftValue(JavaConversionStep, JavaType) + /// Call the `MyType.wrapMemoryAddressUnsafe` in order to wrap a memory address using the Java binding type + indirect case wrapMemoryAddressUnsafe(JavaConversionStep, JavaType) + // Construct the type using the placeholder as arguments. indirect case construct(JavaConversionStep, JavaType) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index 3e60db148..c66b0029b 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -216,9 +216,22 @@ extension FFMSwift2JavaGenerator { printer.print( """ - public \(decl.swiftNominal.name)(MemorySegment segment, AllocatingSwiftArena arena) { + private \(decl.swiftNominal.name)(MemorySegment segment, AllocatingSwiftArena arena) { super(segment, arena); } + + /** + * Assume that the passed {@code MemorySegment} represents a memory address of a {@link \(decl.swiftNominal.name)}. + *

+ * Warnings: + *

    + *
  • No checks are performed about the compatibility of the pointed at memory and the actual \(decl.swiftNominal.name) types.
  • + *
  • This operation does not copy, or retain, the pointed at pointer, so its lifetime must be ensured manually to be valid when wrapping.
  • + *
+ */ + public static \(decl.swiftNominal.name) wrapMemoryAddressUnsafe(MemorySegment selfPointer, AllocatingSwiftArena swiftArena) { + return new \(decl.swiftNominal.name)(selfPointer, swiftArena); + } """ ) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 0920a89b0..d3452cef0 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -117,9 +117,22 @@ extension JNISwift2JavaGenerator { printer.print( """ - public \(decl.swiftNominal.name)(long selfPointer, SwiftArena swiftArena) { + private \(decl.swiftNominal.name)(long selfPointer, SwiftArena swiftArena) { super(selfPointer, swiftArena); } + + /** + * Assume that the passed {@code long} represents a memory address of a {@link \(decl.swiftNominal.name)}. + *

+ * Warnings: + *

    + *
  • No checks are performed about the compatibility of the pointed at memory and the actual \(decl.swiftNominal.name) types.
  • + *
  • This operation does not copy, or retain, the pointed at pointer, so its lifetime must be ensured manually to be valid when wrapping.
  • + *
+ */ + public static \(decl.swiftNominal.name) wrapMemoryAddressUnsafe(long selfPointer, SwiftArena swiftArena) { + return new \(decl.swiftNominal.name)(selfPointer, swiftArena); + } """ ) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 0db77ece9..64ae23b7c 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -383,7 +383,7 @@ extension JNISwift2JavaGenerator { javaType: javaType, annotations: resultAnnotations, outParameters: [], - conversion: .constructSwiftValue(.placeholder, javaType) + conversion: .wrapMemoryAddressUnsafe(.placeholder, javaType) ) case .tuple([]): @@ -465,7 +465,7 @@ extension JNISwift2JavaGenerator { discriminatorName: "result_discriminator$", optionalClass: "Optional", javaType: .long, - toValue: .constructSwiftValue(.placeholder, .class(package: nil, name: nominalTypeName)) + toValue: .wrapMemoryAddressUnsafe(.placeholder, .class(package: nil, name: nominalTypeName)) ) ) @@ -582,6 +582,9 @@ extension JNISwift2JavaGenerator { /// Call `new \(Type)(\(placeholder), swiftArena$)` indirect case constructSwiftValue(JavaNativeConversionStep, JavaType) + /// Call the `MyType.wrapMemoryAddressUnsafe` in order to wrap a memory address using the Java binding type + indirect case wrapMemoryAddressUnsafe(JavaNativeConversionStep, JavaType) + indirect case call(JavaNativeConversionStep, function: String) indirect case method(JavaNativeConversionStep, function: String, arguments: [JavaNativeConversionStep] = []) @@ -641,6 +644,10 @@ extension JNISwift2JavaGenerator { case .constructSwiftValue(let inner, let javaType): let inner = inner.render(&printer, placeholder) return "new \(javaType.className!)(\(inner), swiftArena$)" + + case .wrapMemoryAddressUnsafe(let inner, let javaType): + let inner = inner.render(&printer, placeholder) + return "\(javaType.className!).wrapMemoryAddressUnsafe(\(inner), swiftArena$)" case .call(let inner, let function): let inner = inner.render(&printer, placeholder) @@ -705,7 +712,7 @@ extension JNISwift2JavaGenerator { case .placeholder, .constant, .isOptionalPresent: return false - case .constructSwiftValue: + case .constructSwiftValue, .wrapMemoryAddressUnsafe: return true case .valueMemoryAddress(let inner): diff --git a/Tests/JExtractSwiftTests/DataImportTests.swift b/Tests/JExtractSwiftTests/DataImportTests.swift index 7416779dc..cf4ed3872 100644 --- a/Tests/JExtractSwiftTests/DataImportTests.swift +++ b/Tests/JExtractSwiftTests/DataImportTests.swift @@ -86,7 +86,7 @@ final class DataImportTests { ] ) } - + @Test("Import Data: JavaBindings") func data_javaBindings() throws { @@ -167,7 +167,7 @@ final class DataImportTests { public static Data returnData(AllocatingSwiftArena swiftArena$) { MemorySegment _result = swiftArena$.allocate(Data.$LAYOUT); swiftjava_SwiftModule_returnData.call(_result); - return new Data(_result, swiftArena$); + return Data.wrapMemoryAddressUnsafe(_result, swiftArena$); } """, @@ -210,7 +210,7 @@ final class DataImportTests { public static Data init(java.lang.foreign.MemorySegment bytes, long count, AllocatingSwiftArena swiftArena$) { MemorySegment _result = swiftArena$.allocate(Data.$LAYOUT); swiftjava_SwiftModule_Data_init_bytes_count.call(bytes, count, _result); - return new Data(_result, swiftArena$); + return Data.wrapMemoryAddressUnsafe(_result, swiftArena$); } """, @@ -408,4 +408,5 @@ final class DataImportTests { ] ) } + } diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift index 5b015f894..1e33efcb7 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -66,11 +66,17 @@ struct JNIClassTests { System.loadLibrary(LIB_NAME); return true; } - - public MyClass(long selfPointer, SwiftArena swiftArena) { + """, + """ + private MyClass(long selfPointer, SwiftArena swiftArena) { super(selfPointer, swiftArena); } """, + """ + public static MyClass wrapMemoryAddressUnsafe(long selfPointer, SwiftArena swiftArena) { + return new MyClass(selfPointer, swiftArena); + } + """ ]) try assertOutput( input: source, @@ -164,7 +170,7 @@ struct JNIClassTests { * } */ public static MyClass init(long x, long y, SwiftArena swiftArena$) { - return new MyClass(MyClass.$init(x, y), swiftArena$); + return MyClass.wrapMemoryAddressUnsafe(MyClass.$init(x, y), swiftArena$); } """, """ @@ -175,7 +181,7 @@ struct JNIClassTests { * } */ public static MyClass init(SwiftArena swiftArena$) { - return new MyClass(MyClass.$init(), swiftArena$); + return MyClass.wrapMemoryAddressUnsafe(MyClass.$init(), swiftArena$); } """, """ @@ -309,7 +315,7 @@ struct JNIClassTests { * } */ public MyClass copy(SwiftArena swiftArena$) { - return new MyClass(MyClass.$copy(this.$memoryAddress()), swiftArena$); + return MyClass.wrapMemoryAddressUnsafe(MyClass.$copy(this.$memoryAddress()), swiftArena$); } """, """ diff --git a/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift index cd04660b0..f9c70ba4a 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift @@ -158,7 +158,7 @@ struct JNIOptionalTests { public static Optional optionalClass(Optional arg, SwiftArena swiftArena$) { byte[] result_discriminator$ = new byte[1]; long result$ = SwiftModule.$optionalClass(arg.map(MyClass::$memoryAddress).orElse(0L), result_discriminator$); - return (result_discriminator$[0] == 1) ? Optional.of(new MyClass(result$, swiftArena$)) : Optional.empty(); + return (result_discriminator$[0] == 1) ? Optional.of(MyClass.wrapMemoryAddressUnsafe(result$, swiftArena$)) : Optional.empty(); } """, """ diff --git a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift index 01a2e3c0f..59d7ee6bf 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift @@ -56,10 +56,16 @@ struct JNIStructTests { System.loadLibrary(LIB_NAME); return true; } - - public MyStruct(long selfPointer, SwiftArena swiftArena) { + """, + """ + private MyStruct(long selfPointer, SwiftArena swiftArena) { super(selfPointer, swiftArena); } + """, + """ + public static MyStruct wrapMemoryAddressUnsafe(long selfPointer, SwiftArena swiftArena) { + return new MyStruct(selfPointer, swiftArena); + } """ ]) try assertOutput( @@ -110,7 +116,7 @@ struct JNIStructTests { * } */ public static MyStruct init(long x, long y, SwiftArena swiftArena$) { - return new MyStruct(MyStruct.$init(x, y), swiftArena$); + return MyStruct.wrapMemoryAddressUnsafe(MyStruct.$init(x, y), swiftArena$); } """, """ diff --git a/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift b/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift index 4edf59c27..e64d43974 100644 --- a/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift +++ b/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift @@ -43,7 +43,7 @@ struct MemoryManagementModeTests { * } */ public static MyClass f(SwiftArena swiftArena$) { - return new MyClass(SwiftModule.$f(), swiftArena$); + return MyClass.wrapMemoryAddressUnsafe(SwiftModule.$f(), swiftArena$); } """, ] @@ -68,7 +68,7 @@ struct MemoryManagementModeTests { """, """ public static MyClass f(SwiftArena swiftArena$) { - return new MyClass(SwiftModule.$f(), swiftArena$); + return MyClass.wrapMemoryAddressUnsafe(SwiftModule.$f(), swiftArena$); } """, ] diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift index fd885f1b5..3aa7d394e 100644 --- a/Tests/JExtractSwiftTests/MethodImportTests.swift +++ b/Tests/JExtractSwiftTests/MethodImportTests.swift @@ -227,7 +227,7 @@ final class MethodImportTests { public static MySwiftClass globalReturnClass(AllocatingSwiftArena swiftArena$) { MemorySegment _result = swiftArena$.allocate(MySwiftClass.$LAYOUT); swiftjava___FakeModule_globalReturnClass.call(_result); - return new MySwiftClass(_result, swiftArena$); + return MySwiftClass.wrapMemoryAddressUnsafe(_result, swiftArena$); } """ ) @@ -404,7 +404,7 @@ final class MethodImportTests { public static MySwiftClass init(long len, long cap, AllocatingSwiftArena swiftArena$) { MemorySegment _result = swiftArena$.allocate(MySwiftClass.$LAYOUT); swiftjava___FakeModule_MySwiftClass_init_len_cap.call(len, cap, _result) - return new MySwiftClass(_result, swiftArena$); + return MySwiftClass.wrapMemoryAddressUnsafe(_result, swiftArena$); } """ ) @@ -449,7 +449,7 @@ final class MethodImportTests { public static MySwiftStruct init(long len, long cap, AllocatingSwiftArena swiftArena$) { MemorySegment _result = swiftArena$.allocate(MySwiftStruct.$LAYOUT); swiftjava___FakeModule_MySwiftStruct_init_len_cap.call(len, cap, _result) - return new MySwiftStruct(_result, swiftArena$); + return MySwiftStruct.wrapMemoryAddressUnsafe(_result, swiftArena$); } """ ) From a720622d62b3c7f2178f8b8f61e2594fb3cd1958 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 15 Aug 2025 11:15:43 +0900 Subject: [PATCH 061/141] fix javadoc formatting (#361) --- ...t2JavaGenerator+JavaBindingsPrinting.swift | 10 +-- .../JNI/JNIClassTests.swift | 40 +++++------ .../JNI/JNIClosureTests.swift | 20 +++--- .../JNI/JNIJavaKitTests.swift | 10 +-- .../JNI/JNIModuleTests.swift | 60 ++++++++-------- .../JNI/JNIOptionalTests.swift | 68 +++++++++---------- .../JNI/JNIStructTests.swift | 20 +++--- .../JNI/JNIUnsignedNumberTests.swift | 40 +++++------ .../MemoryManagementModeTests.swift | 16 ++--- 9 files changed, 142 insertions(+), 142 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index d3452cef0..6d82175fe 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -369,11 +369,11 @@ extension JNISwift2JavaGenerator { printer.print( """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * \(decl.signatureString) - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * \(decl.signatureString) + * } + */ """ ) } diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift index 1e33efcb7..30bb7433d 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -164,22 +164,22 @@ struct JNIClassTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public init(x: Int64, y: Int64) - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public init(x: Int64, y: Int64) + * } + */ public static MyClass init(long x, long y, SwiftArena swiftArena$) { return MyClass.wrapMemoryAddressUnsafe(MyClass.$init(x, y), swiftArena$); } """, """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public init() - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public init() + * } + */ public static MyClass init(SwiftArena swiftArena$) { return MyClass.wrapMemoryAddressUnsafe(MyClass.$init(), swiftArena$); } @@ -309,11 +309,11 @@ struct JNIClassTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func copy() -> MyClass - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func copy() -> MyClass + * } + */ public MyClass copy(SwiftArena swiftArena$) { return MyClass.wrapMemoryAddressUnsafe(MyClass.$copy(this.$memoryAddress()), swiftArena$); } @@ -361,11 +361,11 @@ struct JNIClassTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func isEqual(to other: MyClass) -> Bool - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func isEqual(to other: MyClass) -> Bool + * } + */ public boolean isEqual(MyClass other) { return MyClass.$isEqual(other.$memoryAddress(), this.$memoryAddress()); } diff --git a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift index 9a388da18..b374d24ec 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift @@ -36,11 +36,11 @@ struct JNIClosureTests { """, """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func emptyClosure(closure: () -> ()) - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func emptyClosure(closure: () -> ()) + * } + */ public static void emptyClosure(com.example.swift.SwiftModule.emptyClosure.closure closure) { SwiftModule.$emptyClosure(closure); } @@ -88,11 +88,11 @@ struct JNIClosureTests { """, """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func closureWithArgumentsAndReturn(closure: (Int64, Bool) -> Int64) - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func closureWithArgumentsAndReturn(closure: (Int64, Bool) -> Int64) + * } + */ public static void closureWithArgumentsAndReturn(com.example.swift.SwiftModule.closureWithArgumentsAndReturn.closure closure) { SwiftModule.$closureWithArgumentsAndReturn(closure); } diff --git a/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift b/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift index 1f19c8f99..ac6b83846 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift @@ -37,11 +37,11 @@ struct JNIJavaKitTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func function(javaLong: JavaLong, javaInteger: JavaInteger, int: Int64) - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func function(javaLong: JavaLong, javaInteger: JavaInteger, int: Int64) + * } + */ public static void function(java.lang.Long javaLong, java.lang.Integer javaInteger, long int) { SwiftModule.$function(javaLong, javaInteger, int); } diff --git a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift index 198276ba6..ad55f491e 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift @@ -67,11 +67,11 @@ struct JNIModuleTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func helloWorld() - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func helloWorld() + * } + */ public static void helloWorld() { SwiftModule.$helloWorld(); } @@ -81,11 +81,11 @@ struct JNIModuleTests { """, """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func takeIntegers(i1: Int8, i2: Int16, i3: Int32, i4: Int64) -> UInt16 - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func takeIntegers(i1: Int8, i2: Int16, i3: Int32, i4: Int64) -> UInt16 + * } + */ @Unsigned public static char takeIntegers(byte i1, short i2, int i3, long i4) { return SwiftModule.$takeIntegers(i1, i2, i3, i4); @@ -96,11 +96,11 @@ struct JNIModuleTests { """, """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func otherPrimitives(b: Bool, f: Float, d: Double) - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func otherPrimitives(b: Bool, f: Float, d: Double) + * } + */ public static void otherPrimitives(boolean b, float f, double d) { SwiftModule.$otherPrimitives(b, f, d); } @@ -151,11 +151,11 @@ struct JNIModuleTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func copy(_ string: String) -> String - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func copy(_ string: String) -> String + * } + */ public static java.lang.String copy(java.lang.String string) { return SwiftModule.$copy(string); } @@ -194,11 +194,11 @@ struct JNIModuleTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func methodA() throws - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func methodA() throws + * } + */ public static void methodA() throws Exception { SwiftModule.$methodA(); } @@ -208,11 +208,11 @@ struct JNIModuleTests { """, """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func methodB() throws -> Int64 - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func methodB() throws -> Int64 + * } + */ public static long methodB() throws Exception { return SwiftModule.$methodB(); } diff --git a/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift index f9c70ba4a..2186e2279 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift @@ -46,12 +46,12 @@ struct JNIOptionalTests { * public func optionalSugar(_ arg: Int64?) -> Int32? * } */ - public static OptionalInt optionalSugar(OptionalLong arg) { - long combined$ = SwiftModule.$optionalSugar((byte) (arg.isPresent() ? 1 : 0), arg.orElse(0L)); - byte discriminator$ = (byte) (combined$ & 0xFF); - int value$ = (int) (combined$ >> 32); - return discriminator$ == 1 ? OptionalInt.of(value$) : OptionalInt.empty(); - } + public static OptionalInt optionalSugar(OptionalLong arg) { + long combined$ = SwiftModule.$optionalSugar((byte) (arg.isPresent() ? 1 : 0), arg.orElse(0L)); + byte discriminator$ = (byte) (combined$ & 0xFF); + int value$ = (int) (combined$ >> 32); + return discriminator$ == 1 ? OptionalInt.of(value$) : OptionalInt.empty(); + } """, """ private static native long $optionalSugar(byte arg_discriminator, long arg_value); @@ -92,16 +92,16 @@ struct JNIOptionalTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func optionalExplicit(_ arg: Optional) -> Optional - * } - */ - public static Optional optionalExplicit(Optional arg) { - byte[] result_discriminator$ = new byte[1]; - java.lang.String result$ = SwiftModule.$optionalExplicit((byte) (arg.isPresent() ? 1 : 0), arg.orElse(null), result_discriminator$); - return (result_discriminator$[0] == 1) ? Optional.of(result$) : Optional.empty(); - } + * Downcall to Swift: + * {@snippet lang=swift : + * public func optionalExplicit(_ arg: Optional) -> Optional + * } + */ + public static Optional optionalExplicit(Optional arg) { + byte[] result_discriminator$ = new byte[1]; + java.lang.String result$ = SwiftModule.$optionalExplicit((byte) (arg.isPresent() ? 1 : 0), arg.orElse(null), result_discriminator$); + return (result_discriminator$[0] == 1) ? Optional.of(result$) : Optional.empty(); + } """, """ private static native java.lang.String $optionalExplicit(byte arg_discriminator, java.lang.String arg_value, byte[] result_discriminator$); @@ -150,16 +150,16 @@ struct JNIOptionalTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func optionalClass(_ arg: MyClass?) -> MyClass? - * } - */ - public static Optional optionalClass(Optional arg, SwiftArena swiftArena$) { - byte[] result_discriminator$ = new byte[1]; - long result$ = SwiftModule.$optionalClass(arg.map(MyClass::$memoryAddress).orElse(0L), result_discriminator$); - return (result_discriminator$[0] == 1) ? Optional.of(MyClass.wrapMemoryAddressUnsafe(result$, swiftArena$)) : Optional.empty(); - } + * Downcall to Swift: + * {@snippet lang=swift : + * public func optionalClass(_ arg: MyClass?) -> MyClass? + * } + */ + public static Optional optionalClass(Optional arg, SwiftArena swiftArena$) { + byte[] result_discriminator$ = new byte[1]; + long result$ = SwiftModule.$optionalClass(arg.map(MyClass::$memoryAddress).orElse(0L), result_discriminator$); + return (result_discriminator$[0] == 1) ? Optional.of(MyClass.wrapMemoryAddressUnsafe(result$, swiftArena$)) : Optional.empty(); + } """, """ private static native long $optionalClass(long arg, byte[] result_discriminator$); @@ -213,14 +213,14 @@ struct JNIOptionalTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func optionalJavaKitClass(_ arg: JavaLong?) - * } - */ - public static void optionalJavaKitClass(Optional arg) { - SwiftModule.$optionalJavaKitClass(arg.orElse(null)); - } + * Downcall to Swift: + * {@snippet lang=swift : + * public func optionalJavaKitClass(_ arg: JavaLong?) + * } + */ + public static void optionalJavaKitClass(Optional arg) { + SwiftModule.$optionalJavaKitClass(arg.orElse(null)); + } """, """ private static native void $optionalJavaKitClass(java.lang.Long arg); diff --git a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift index 59d7ee6bf..7f54fa7e1 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift @@ -110,11 +110,11 @@ struct JNIStructTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public init(x: Int64, y: Int64) - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public init(x: Int64, y: Int64) + * } + */ public static MyStruct init(long x, long y, SwiftArena swiftArena$) { return MyStruct.wrapMemoryAddressUnsafe(MyStruct.$init(x, y), swiftArena$); } @@ -182,11 +182,11 @@ struct JNIStructTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func doSomething(x: Int64) - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func doSomething(x: Int64) + * } + */ public void doSomething(long x) { MyStruct.$doSomething(x, this.$memoryAddress()); } diff --git a/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift b/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift index f4dbffed3..bfae447a9 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift @@ -27,11 +27,11 @@ final class JNIUnsignedNumberTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func unsignedChar(_ arg: UInt16) - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func unsignedChar(_ arg: UInt16) + * } + */ public static void unsignedChar(@Unsigned char arg) { SwiftModule.$unsignedChar(arg); } @@ -57,11 +57,11 @@ final class JNIUnsignedNumberTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func unsignedInt(_ arg: UInt32) - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func unsignedInt(_ arg: UInt32) + * } + */ public static void unsignedInt(@Unsigned int arg) { SwiftModule.$unsignedInt(arg); } @@ -83,11 +83,11 @@ final class JNIUnsignedNumberTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func returnUnsignedInt() -> UInt32 - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func returnUnsignedInt() -> UInt32 + * } + */ @Unsigned public static int returnUnsignedInt() { return SwiftModule.$returnUnsignedInt(); @@ -137,11 +137,11 @@ final class JNIUnsignedNumberTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func unsignedLong(first: UInt64, second: UInt32) -> UInt32 - * } - */ + * Downcall to Swift: + * {@snippet lang=swift : + * public func unsignedLong(first: UInt64, second: UInt32) -> UInt32 + * } + */ @Unsigned public static int unsignedLong(@Unsigned long first, @Unsigned int second) { return SwiftModule.$unsignedLong(first, second); diff --git a/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift b/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift index e64d43974..3ce839b9b 100644 --- a/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift +++ b/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift @@ -37,14 +37,14 @@ struct MemoryManagementModeTests { expectedChunks: [ """ /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func f() -> MyClass - * } - */ - public static MyClass f(SwiftArena swiftArena$) { - return MyClass.wrapMemoryAddressUnsafe(SwiftModule.$f(), swiftArena$); - } + * Downcall to Swift: + * {@snippet lang=swift : + * public func f() -> MyClass + * } + */ + public static MyClass f(SwiftArena swiftArena$) { + return MyClass.wrapMemoryAddressUnsafe(SwiftModule.$f(), swiftArena$); + } """, ] ) From 94cb7ecdf905ff1450de1d44b6f145460807020e Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 15 Aug 2025 04:59:20 +0200 Subject: [PATCH 062/141] Add "..." wildcard syntax to output assertions in JExtract tests (#362) Co-authored-by: Konrad `ktoso` Malawski --- .../Asserts/TextAssertions.swift | 65 +++++++++++++---- .../JNI/JNIVariablesTests.swift | 72 ++++--------------- 2 files changed, 64 insertions(+), 73 deletions(-) diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index e975d2239..1f768fb2d 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -124,18 +124,49 @@ func assertOutput( continue } - for (no, (g, e)) in zip(gotLines.dropFirst(matchingOutputOffset), expectedLines).enumerated() { - if g.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).count == 0 - || e.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).count == 0 { - continue - } + var currentExpectedLine: Int = 0 + var currentGotLine: Int = matchingOutputOffset + + outer: while currentExpectedLine < expectedLines.count { + let expectedLine = expectedLines[currentExpectedLine].trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) - let ge = g.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) - let ee = e.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) - if ge.commonPrefix(with: ee) != ee { - diffLineNumbers.append(no + matchingOutputOffset) + if expectedLine == "..." { + // Ignore the rest of output, if last line is placeholder. + guard currentExpectedLine != (expectedLines.count - 1) else { + break + } - #expect(ge == ee, sourceLocation: sourceLocation) + let nextLine = expectedLines[currentExpectedLine + 1].trimmingCharacters(in: .whitespacesAndNewlines) + while currentGotLine < gotLines.count { + let gottenLine = gotLines[currentGotLine].trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + if gottenLine.commonPrefix(with: nextLine) == nextLine { + currentExpectedLine += 2 + break + } else if nextLine == "..." { + // Skip any following "..." + currentExpectedLine += 1 + break + } else { + currentGotLine += 1 + } + + if currentGotLine == gotLines.count { + diffLineNumbers.append(currentExpectedLine + matchingOutputOffset + 1) + Issue.record("Expected to find '\(nextLine)' after wildcard, but end of file reached.", sourceLocation: sourceLocation) + break outer + } + } + + currentGotLine += 1 + } else { + let gottenLine = gotLines[currentGotLine].trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + if gottenLine.commonPrefix(with: expectedLine) != expectedLine { + diffLineNumbers.append(currentExpectedLine + matchingOutputOffset) + + #expect(gottenLine == expectedLine, sourceLocation: sourceLocation) + } + currentGotLine += 1 + currentExpectedLine += 1 } } @@ -155,9 +186,17 @@ func assertOutput( print("==== ---------------------------------------------------------------") print("Got output:") let printFromLineNo = matchingOutputOffset - let printToLineNo = matchingOutputOffset + expectedLines.count - for (n, g) in gotLines.enumerated() where n >= printFromLineNo && n <= printToLineNo { - print("\(n): \(g)".red(if: diffLineNumbers.contains(n))) + for (n, g) in gotLines.enumerated() where n >= printFromLineNo { + let baseLine = "\(n): \(g)" + var line = baseLine + if diffLineNumbers.contains(n) { + line += "\n" + let leadingCount = "\(n): ".count + let message = "\(String(repeating: " ", count: leadingCount))\(String(repeating: "^", count: 8)) EXPECTED MATCH OR SEARCHING FROM HERE " + line += "\(message)\(String(repeating: "^", count: max(0, line.count - message.count)))" + line = line.red + } + print(line) } print("==== ---------------------------------------------------------------\n") } diff --git a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift index 933e4f088..c9d313a50 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift @@ -69,12 +69,7 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024getConstant__J") func Java_com_example_swift_MyClass__00024getConstant__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { - assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - let self$ = UnsafeMutablePointer(bitPattern: selfBits$) - guard let self$ else { - fatalError("self memory address was null in call to \\(#function)!") - } + ... return self$.pointee.constant.getJNIValue(in: environment!) } """ @@ -134,11 +129,12 @@ struct JNIVariablesTests { @_cdecl("Java_com_example_swift_MyClass__00024getMutable__J") func Java_com_example_swift_MyClass__00024getMutable__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) + ... let self$ = UnsafeMutablePointer(bitPattern: selfBits$) guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } + ... return self$.pointee.mutable.getJNIValue(in: environment!) } """, @@ -146,11 +142,7 @@ struct JNIVariablesTests { @_cdecl("Java_com_example_swift_MyClass__00024setMutable__JJ") func Java_com_example_swift_MyClass__00024setMutable__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, self: jlong) { assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - let self$ = UnsafeMutablePointer(bitPattern: selfBits$) - guard let self$ else { - fatalError("self memory address was null in call to \\(#function)!") - } + ... self$.pointee.mutable = Int64(fromJNI: newValue, in: environment!) } """ @@ -195,12 +187,7 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024getComputed__J") func Java_com_example_swift_MyClass__00024getComputed__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { - assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - let self$ = UnsafeMutablePointer(bitPattern: selfBits$) - guard let self$ else { - fatalError("self memory address was null in call to \\(#function)!") - } + ... return self$.pointee.computed.getJNIValue(in: environment!) } """, @@ -245,12 +232,7 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024getComputedThrowing__J") func Java_com_example_swift_MyClass__00024getComputedThrowing__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { - assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - let self$ = UnsafeMutablePointer(bitPattern: selfBits$) - guard let self$ else { - fatalError("self memory address was null in call to \\(#function)!") - } + ... do { return try self$.pointee.computedThrowing.getJNIValue(in: environment!) } catch { @@ -314,24 +296,14 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024getGetterAndSetter__J") func Java_com_example_swift_MyClass__00024getGetterAndSetter__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { - assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - let self$ = UnsafeMutablePointer(bitPattern: selfBits$) - guard let self$ else { - fatalError("self memory address was null in call to \\(#function)!") - } + ... return self$.pointee.getterAndSetter.getJNIValue(in: environment!) } """, """ @_cdecl("Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ") func Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, self: jlong) { - assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - let self$ = UnsafeMutablePointer(bitPattern: selfBits$) - guard let self$ else { - fatalError("self memory address was null in call to \\(#function)!") - } + ... self$.pointee.getterAndSetter = Int64(fromJNI: newValue, in: environment!) } """ @@ -390,24 +362,14 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024isSomeBoolean__J") func Java_com_example_swift_MyClass__00024isSomeBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jboolean { - assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - let self$ = UnsafeMutablePointer(bitPattern: selfBits$) - guard let self$ else { - fatalError("self memory address was null in call to \\(#function)!") - } + ... return self$.pointee.someBoolean.getJNIValue(in: environment!) } """, """ @_cdecl("Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ") func Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, self: jlong) { - assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - let self$ = UnsafeMutablePointer(bitPattern: selfBits$) - guard let self$ else { - fatalError("self memory address was null in call to \\(#function)!") - } + ... self$.pointee.someBoolean = Bool(fromJNI: newValue, in: environment!) } """ @@ -466,24 +428,14 @@ struct JNIVariablesTests { """ @_cdecl("Java_com_example_swift_MyClass__00024isBoolean__J") func Java_com_example_swift_MyClass__00024isBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jboolean { - assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - let self$ = UnsafeMutablePointer(bitPattern: selfBits$) - guard let self$ else { - fatalError("self memory address was null in call to \\(#function)!") - } + ... return self$.pointee.isBoolean.getJNIValue(in: environment!) } """, """ @_cdecl("Java_com_example_swift_MyClass__00024setBoolean__ZJ") func Java_com_example_swift_MyClass__00024setBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, self: jlong) { - assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) - let self$ = UnsafeMutablePointer(bitPattern: selfBits$) - guard let self$ else { - fatalError("self memory address was null in call to \\(#function)!") - } + ... self$.pointee.isBoolean = Bool(fromJNI: newValue, in: environment!) } """ From c3ecd548a85e8b1f2d809399e7b5f10e4215c9e3 Mon Sep 17 00:00:00 2001 From: David Ko Date: Thu, 14 Aug 2025 23:00:24 -0400 Subject: [PATCH 063/141] Add function documentation (#363) Co-authored-by: Konrad `ktoso` Malawski --- .gitignore | 3 +++ .../Commands/ResolveCommand.swift | 25 ++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 28dfbae29..d4e1c5ec4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ .DS_Store .build .idea +.vscode Packages xcuserdata/ DerivedData/ @@ -16,6 +17,7 @@ bin/ BuildLogic/out/ .index-build .build-vscode +**/.vscode # Ignore gradle build artifacts .gradle @@ -45,3 +47,4 @@ Package.resolved */**/*.swiftdeps */**/*.swiftdeps~ */**/.docc-build/ + diff --git a/Sources/SwiftJavaTool/Commands/ResolveCommand.swift b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift index 3eb01fe75..c492e7889 100644 --- a/Sources/SwiftJavaTool/Commands/ResolveCommand.swift +++ b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift @@ -89,6 +89,15 @@ extension SwiftJava.ResolveCommand { resolvedClasspath: dependenciesClasspath) } + + /// Resolves Java dependencies from swift-java.config and returns classpath information. + /// + /// - Parameters: + /// - swiftModule: module name from --swift-module. e.g.: --swift-module MySwiftModule + /// - dependencies: parsed maven-style dependency descriptors (groupId:artifactId:version) + /// from Sources/MySwiftModule/swift-java.config "dependencies" array. + /// + /// - Throws: func resolveDependencies( swiftModule: String, dependencies: [JavaDependencyDescriptor] ) async throws -> ResolvedDependencyClasspath { @@ -108,6 +117,11 @@ extension SwiftJava.ResolveCommand { return ResolvedDependencyClasspath(for: dependencies, classpath: dependenciesClasspath) } + + /// Resolves maven-style dependencies from swift-java.config under temporary project directory. + /// + /// - Parameter dependencies: maven-style dependencies to resolve + /// - Returns: Colon-separated classpath func resolveDependencies(dependencies: [JavaDependencyDescriptor]) async -> String { let workDir = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) .appendingPathComponent(".build") @@ -158,6 +172,7 @@ extension SwiftJava.ResolveCommand { } } + /// Creates Gradle project files (build.gradle, settings.gradle.kts) in temporary directory. func printGradleProject(directory: URL, dependencies: [JavaDependencyDescriptor]) throws { let buildGradle = directory .appendingPathComponent("build.gradle", isDirectory: false) @@ -189,7 +204,14 @@ extension SwiftJava.ResolveCommand { """ try settingsGradleText.write(to: settingsGradle, atomically: true, encoding: .utf8) } - + + /// Creates {MySwiftModule}.swift.classpath in the --output-directory. + /// + /// - Parameters: + /// - swiftModule: Swift module name for classpath filename (--swift-module value) + /// - outputDirectory: Directory path for classpath file (--output-directory value) + /// - resolvedClasspath: Complete dependency classpath information + /// mutating func writeSwiftJavaClasspathFile( swiftModule: String, outputDirectory: String, @@ -218,6 +240,7 @@ extension SwiftJava.ResolveCommand { return camelCased } + // copy gradlew & gradle.bat from root, throws error if there is no gradle setup. func copyGradlew(to resolverWorkDirectory: URL) throws { var searchDir = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) From 91d21328c7d9776108e979a82f8faf786f44b54d Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 15 Aug 2025 12:36:46 +0900 Subject: [PATCH 064/141] gh action: also run test jobs with nightly (#358) --- .github/actions/prepare_env/action.yml | 3 +++ .github/workflows/pull_request.yml | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/actions/prepare_env/action.yml b/.github/actions/prepare_env/action.yml index 0e4a1ca37..3fdd5d4b1 100644 --- a/.github/actions/prepare_env/action.yml +++ b/.github/actions/prepare_env/action.yml @@ -4,6 +4,9 @@ description: 'Prepare the CI environment by installing Swift and selected JDK et runs: using: composite steps: + - name: Check Swift version + shell: bash + run: swift -version - name: Set up JDK ${{ matrix.jdk_version }} uses: actions/setup-java@v4 with: diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 89ca42894..b2d8ee7c2 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -45,7 +45,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['6.1.2'] + swift_version: ['6.1.2', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -115,7 +115,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.1.2'] + swift_version: ['6.1.2', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -185,7 +185,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.1.2'] # no nightly checks for macOS + swift_version: ['6.1.2'] # no nightly testing on macOS os_version: ['macos'] jdk_vendor: ['corretto'] sample_app: [ # TODO: use a reusable-workflow to generate those names From e29deed3ad7bf0de7463509adb51b425f4ed510a Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 15 Aug 2025 20:15:39 +0900 Subject: [PATCH 065/141] Allow config if to auto load native libraries with -Dswift-java.auto-load-libraries=false (#364) --- .../FFM/FFMSwift2JavaGenerator.swift | 13 ++++---- .../swift/swiftkit/core/SwiftLibraries.java | 31 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index c66b0029b..9c9154a49 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -194,8 +194,8 @@ extension FFMSwift2JavaGenerator { @SuppressWarnings("unused") private static final boolean INITIALIZED_LIBS = initializeLibs(); static boolean initializeLibs() { - System.loadLibrary(SwiftLibraries.STDLIB_DYLIB_NAME); - System.loadLibrary("SwiftKitSwift"); + System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFT_CORE); + System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFTKITSWIFT); System.loadLibrary(LIB_NAME); return true; } @@ -343,10 +343,11 @@ extension FFMSwift2JavaGenerator { """ static final SymbolLookup SYMBOL_LOOKUP = getSymbolLookup(); private static SymbolLookup getSymbolLookup() { - // Ensure Swift and our Lib are loaded during static initialization of the class. - SwiftLibraries.loadLibrary("swiftCore"); - SwiftLibraries.loadLibrary("SwiftKitSwift"); - SwiftLibraries.loadLibrary(LIB_NAME); + if (SwiftLibraries.AUTO_LOAD_LIBS) { + System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFT_CORE); + System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFTKITSWIFT); + System.loadLibrary(LIB_NAME); + } if (PlatformUtils.isMacOS()) { return SymbolLookup.libraryLookup(System.mapLibraryName(LIB_NAME), LIBRARY_ARENA) diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java index 3230e52a7..a093cc504 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java @@ -24,18 +24,28 @@ public final class SwiftLibraries { - public static final String STDLIB_DYLIB_NAME = "swiftCore"; - public static final String SWIFTKITSWIFT_DYLIB_NAME = "SwiftKitSwift"; + // Library names of core Swift and SwiftKit + public static final String LIB_NAME_SWIFT_CORE = "swiftCore"; + public static final String LIB_NAME_SWIFT_CONCURRENCY = "swift_Concurrency"; + public static final String LIB_NAME_SWIFTKITSWIFT = "SwiftKitSwift"; - private static final String STDLIB_MACOS_DYLIB_PATH = "/usr/lib/swift/libswiftCore.dylib"; + /** + * Allows for configuration if jextracted types should automatically attempt to load swiftCore and the library type is from. + *

+ * If all libraries you need to load are available in paths passed to {@code -Djava.library.path} this should work correctly, + * however if attempting to load libraries from e.g. the jar as a resource, you may want to disable this. + */ + public static final boolean AUTO_LOAD_LIBS = System.getProperty("swift-java.auto-load-libraries") == null ? + true + : Boolean.getBoolean("swiftkit.auto-load-libraries"); @SuppressWarnings("unused") private static final boolean INITIALIZED_LIBS = loadLibraries(false); public static boolean loadLibraries(boolean loadSwiftKit) { - System.loadLibrary(STDLIB_DYLIB_NAME); + System.loadLibrary(LIB_NAME_SWIFTKITSWIFT); if (loadSwiftKit) { - System.loadLibrary(SWIFTKITSWIFT_DYLIB_NAME); + System.loadLibrary(LIB_NAME_SWIFTKITSWIFT); } return true; } @@ -43,17 +53,6 @@ public static boolean loadLibraries(boolean loadSwiftKit) { // ==== ------------------------------------------------------------------------------------------------------------ // Loading libraries - public static void loadLibrary(String libname) { - // TODO: avoid concurrent loadResource calls; one load is enough esp since we cause File IO when we do that - try { - // try to load a dylib from our classpath, e.g. when we included it in our jar - loadResourceLibrary(libname); - } catch (UnsatisfiedLinkError | RuntimeException e) { - // fallback to plain system path loading - System.loadLibrary(libname); - } - } - public static void loadResourceLibrary(String libname) { String resourceName = PlatformUtils.dynamicLibraryName(libname); if (CallTraces.TRACE_DOWNCALLS) { From 66f7a15b6ec915faf0fc3755649356fd935fc987 Mon Sep 17 00:00:00 2001 From: "Sven A. Schmidt" Date: Fri, 15 Aug 2025 14:29:19 +0200 Subject: [PATCH 066/141] Update .spi.yml (#367) --- .spi.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.spi.yml b/.spi.yml index 58345f251..e7159a311 100644 --- a/.spi.yml +++ b/.spi.yml @@ -6,3 +6,5 @@ builder: JavaKit, SwiftKitSwift ] + # Drop this version pinning once 6.2 is released and docs are built with 6.2 by default to prevent it staying on 6.2 forever. + swift_version: 6.2 From f8de1460314bd68727fcb3e6a888acd0d4698ac4 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sat, 16 Aug 2025 16:46:00 +0200 Subject: [PATCH 067/141] [JExtract/JNI] Import enums (#346) --- .../Sources/MySwiftLibrary/Alignment.swift | 18 + .../Sources/MySwiftLibrary/Vehicle.swift | 63 ++++ .../Sources/MySwiftLibrary/swift-java.config | 1 + .../java/com/example/swift/EnumBenchmark.java | 60 ++++ .../com/example/swift/AlignmentEnumTest.java | 41 +++ .../com/example/swift/VehicleEnumTest.java | 206 ++++++++++++ .../Convenience/String+Extensions.swift | 8 + .../Convenience/SwiftSyntax+Extensions.swift | 2 + ...Swift2JavaGenerator+FunctionLowering.swift | 4 + ...MSwift2JavaGenerator+JavaTranslation.swift | 2 +- Sources/JExtractSwiftLib/ImportedDecls.swift | 53 +++ Sources/JExtractSwiftLib/JNI/JNICaching.swift | 31 ++ ...t2JavaGenerator+JavaBindingsPrinting.swift | 116 ++++++- ...ISwift2JavaGenerator+JavaTranslation.swift | 302 ++++++++++++++--- ...wift2JavaGenerator+NativeTranslation.swift | 49 ++- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 159 ++++++++- .../JNI/JNISwift2JavaGenerator.swift | 1 + .../JExtractSwiftLib/Swift2JavaVisitor.swift | 88 ++++- .../SwiftTypes/SwiftEnumCaseParameter.swift | 32 ++ .../SwiftTypes/SwiftFunctionSignature.swift | 28 +- .../SwiftNominalTypeDeclaration.swift | 8 + .../SwiftTypes/SwiftParameter.swift | 10 + .../SwiftTypes/SwiftType.swift | 13 + .../JavaKit/Helpers/_JNIMethodIDCache.swift | 59 ++++ Sources/JavaTypes/Mangling.swift | 7 +- .../Documentation.docc/SupportedFeatures.md | 113 ++++++- .../JExtractSwiftTests/JNI/JNIEnumTests.swift | 313 ++++++++++++++++++ .../JNI/JNIOptionalTests.swift | 34 +- 28 files changed, 1702 insertions(+), 119 deletions(-) create mode 100644 Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Alignment.swift create mode 100644 Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Vehicle.swift create mode 100644 Samples/JExtractJNISampleApp/src/jmh/java/com/example/swift/EnumBenchmark.java create mode 100644 Samples/JExtractJNISampleApp/src/test/java/com/example/swift/AlignmentEnumTest.java create mode 100644 Samples/JExtractJNISampleApp/src/test/java/com/example/swift/VehicleEnumTest.java create mode 100644 Sources/JExtractSwiftLib/JNI/JNICaching.swift create mode 100644 Sources/JExtractSwiftLib/SwiftTypes/SwiftEnumCaseParameter.swift create mode 100644 Sources/JavaKit/Helpers/_JNIMethodIDCache.swift create mode 100644 Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Alignment.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Alignment.swift new file mode 100644 index 000000000..760c564b9 --- /dev/null +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Alignment.swift @@ -0,0 +1,18 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +public enum Alignment: String { + case horizontal + case vertical +} diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Vehicle.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Vehicle.swift new file mode 100644 index 000000000..9d9155add --- /dev/null +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Vehicle.swift @@ -0,0 +1,63 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +public enum Vehicle { + case bicycle + case car(String, trailer: String?) + case motorbike(String, horsePower: Int64, helmets: Int32?) + indirect case transformer(front: Vehicle, back: Vehicle) + case boat(passengers: Int32?, length: Int16?) + + public init?(name: String) { + switch name { + case "bicycle": self = .bicycle + case "car": self = .car("Unknown", trailer: nil) + case "motorbike": self = .motorbike("Unknown", horsePower: 0, helmets: nil) + case "boat": self = .boat(passengers: nil, length: nil) + default: return nil + } + } + + public var name: String { + switch self { + case .bicycle: "bicycle" + case .car: "car" + case .motorbike: "motorbike" + case .transformer: "transformer" + case .boat: "boat" + } + } + + public func isFasterThan(other: Vehicle) -> Bool { + switch (self, other) { + case (.bicycle, .bicycle), (.bicycle, .car), (.bicycle, .motorbike), (.bicycle, .transformer): false + case (.car, .bicycle): true + case (.car, .motorbike), (.car, .transformer), (.car, .car): false + case (.motorbike, .bicycle), (.motorbike, .car): true + case (.motorbike, .motorbike), (.motorbike, .transformer): false + case (.transformer, .bicycle), (.transformer, .car), (.transformer, .motorbike): true + case (.transformer, .transformer): false + default: false + } + } + + public mutating func upgrade() { + switch self { + case .bicycle: self = .car("Unknown", trailer: nil) + case .car: self = .motorbike("Unknown", horsePower: 0, helmets: nil) + case .motorbike: self = .transformer(front: .car("BMW", trailer: nil), back: self) + case .transformer, .boat: break + } + } +} diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config index bb637f34f..be44c2fd7 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config @@ -1,4 +1,5 @@ { "javaPackage": "com.example.swift", "mode": "jni", + "logLevel": ["debug"] } diff --git a/Samples/JExtractJNISampleApp/src/jmh/java/com/example/swift/EnumBenchmark.java b/Samples/JExtractJNISampleApp/src/jmh/java/com/example/swift/EnumBenchmark.java new file mode 100644 index 000000000..d3de624be --- /dev/null +++ b/Samples/JExtractJNISampleApp/src/jmh/java/com/example/swift/EnumBenchmark.java @@ -0,0 +1,60 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; +import org.swift.swiftkit.core.ClosableSwiftArena; +import org.swift.swiftkit.core.ConfinedSwiftMemorySession; +import org.swift.swiftkit.core.SwiftArena; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.AverageTime) +@Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) +@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@Fork(value = 3, jvmArgsAppend = { "--enable-native-access=ALL-UNNAMED" }) +public class EnumBenchmark { + + @State(Scope.Benchmark) + public static class BenchmarkState { + ClosableSwiftArena arena; + Vehicle vehicle; + + @Setup(Level.Trial) + public void beforeAll() { + arena = SwiftArena.ofConfined(); + vehicle = Vehicle.motorbike("Yamaha", 900, OptionalInt.empty(), arena); + } + + @TearDown(Level.Trial) + public void afterAll() { + arena.close(); + } + } + + @Benchmark + public Vehicle.Motorbike getAssociatedValues(BenchmarkState state, Blackhole bh) { + Vehicle.Motorbike motorbike = state.vehicle.getAsMotorbike().orElseThrow(); + bh.consume(motorbike.arg0()); + bh.consume(motorbike.horsePower()); + return motorbike; + } +} \ No newline at end of file diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/AlignmentEnumTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/AlignmentEnumTest.java new file mode 100644 index 000000000..6be85c75d --- /dev/null +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/AlignmentEnumTest.java @@ -0,0 +1,41 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.ConfinedSwiftMemorySession; +import org.swift.swiftkit.core.SwiftArena; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +public class AlignmentEnumTest { + @Test + void rawValue() { + try (var arena = SwiftArena.ofConfined()) { + Optional invalid = Alignment.init("invalid", arena); + assertFalse(invalid.isPresent()); + + Optional horizontal = Alignment.init("horizontal", arena); + assertTrue(horizontal.isPresent()); + assertEquals("horizontal", horizontal.get().getRawValue()); + + Optional vertical = Alignment.init("vertical", arena); + assertTrue(vertical.isPresent()); + assertEquals("vertical", vertical.get().getRawValue()); + } + } +} \ No newline at end of file diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/VehicleEnumTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/VehicleEnumTest.java new file mode 100644 index 000000000..c533603a2 --- /dev/null +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/VehicleEnumTest.java @@ -0,0 +1,206 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.ConfinedSwiftMemorySession; +import org.swift.swiftkit.core.SwiftArena; + +import java.lang.foreign.Arena; +import java.util.Optional; +import java.util.OptionalInt; + +import static org.junit.jupiter.api.Assertions.*; + +public class VehicleEnumTest { + @Test + void bicycle() { + try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.bicycle(arena); + assertNotNull(vehicle); + } + } + + @Test + void car() { + try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.car("Porsche 911", Optional.empty(), arena); + assertNotNull(vehicle); + } + } + + @Test + void motorbike() { + try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.motorbike("Yamaha", 750, OptionalInt.empty(), arena); + assertNotNull(vehicle); + } + } + + @Test + void initName() { + try (var arena = SwiftArena.ofConfined()) { + assertFalse(Vehicle.init("bus", arena).isPresent()); + Optional vehicle = Vehicle.init("car", arena); + assertTrue(vehicle.isPresent()); + assertNotNull(vehicle.get()); + } + } + + @Test + void nameProperty() { + try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.bicycle(arena); + assertEquals("bicycle", vehicle.getName()); + } + } + + @Test + void isFasterThan() { + try (var arena = SwiftArena.ofConfined()) { + Vehicle bicycle = Vehicle.bicycle(arena); + Vehicle car = Vehicle.car("Porsche 911", Optional.empty(), arena); + assertFalse(bicycle.isFasterThan(car)); + assertTrue(car.isFasterThan(bicycle)); + } + } + + @Test + void upgrade() { + try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.bicycle(arena); + assertEquals("bicycle", vehicle.getName()); + vehicle.upgrade(); + assertEquals("car", vehicle.getName()); + vehicle.upgrade(); + assertEquals("motorbike", vehicle.getName()); + } + } + + @Test + void getAsBicycle() { + try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.bicycle(arena); + Vehicle.Bicycle bicycle = vehicle.getAsBicycle().orElseThrow(); + assertNotNull(bicycle); + } + } + + @Test + void getAsCar() { + try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.car("BMW", Optional.empty(), arena); + Vehicle.Car car = vehicle.getAsCar().orElseThrow(); + assertEquals("BMW", car.arg0()); + + vehicle = Vehicle.car("BMW", Optional.of("Long trailer"), arena); + car = vehicle.getAsCar().orElseThrow(); + assertEquals("Long trailer", car.trailer().orElseThrow()); + } + } + + @Test + void getAsMotorbike() { + try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.motorbike("Yamaha", 750, OptionalInt.empty(), arena); + Vehicle.Motorbike motorbike = vehicle.getAsMotorbike().orElseThrow(); + assertEquals("Yamaha", motorbike.arg0()); + assertEquals(750, motorbike.horsePower()); + assertEquals(OptionalInt.empty(), motorbike.helmets()); + + vehicle = Vehicle.motorbike("Yamaha", 750, OptionalInt.of(2), arena); + motorbike = vehicle.getAsMotorbike().orElseThrow(); + assertEquals(OptionalInt.of(2), motorbike.helmets()); + } + } + + @Test + void getAsTransformer() { + try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.transformer(Vehicle.bicycle(arena), Vehicle.car("BMW", Optional.empty(), arena), arena); + Vehicle.Transformer transformer = vehicle.getAsTransformer(arena).orElseThrow(); + assertTrue(transformer.front().getAsBicycle().isPresent()); + assertEquals("BMW", transformer.back().getAsCar().orElseThrow().arg0()); + } + } + + @Test + void getAsBoat() { + try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.boat(OptionalInt.of(10), Optional.of((short) 1), arena); + Vehicle.Boat boat = vehicle.getAsBoat().orElseThrow(); + assertEquals(OptionalInt.of(10), boat.passengers()); + assertEquals(Optional.of((short) 1), boat.length()); + } + } + + @Test + void associatedValuesAreCopied() { + try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.car("BMW", Optional.empty(), arena); + Vehicle.Car car = vehicle.getAsCar().orElseThrow(); + assertEquals("BMW", car.arg0()); + vehicle.upgrade(); + Vehicle.Motorbike motorbike = vehicle.getAsMotorbike().orElseThrow(); + assertNotNull(motorbike); + // Motorbike should still remain + assertEquals("BMW", car.arg0()); + } + } + + @Test + void getDiscriminator() { + try (var arena = SwiftArena.ofConfined()) { + assertEquals(Vehicle.Discriminator.BICYCLE, Vehicle.bicycle(arena).getDiscriminator()); + assertEquals(Vehicle.Discriminator.CAR, Vehicle.car("BMW", Optional.empty(), arena).getDiscriminator()); + assertEquals(Vehicle.Discriminator.MOTORBIKE, Vehicle.motorbike("Yamaha", 750, OptionalInt.empty(), arena).getDiscriminator()); + assertEquals(Vehicle.Discriminator.TRANSFORMER, Vehicle.transformer(Vehicle.bicycle(arena), Vehicle.bicycle(arena), arena).getDiscriminator()); + } + } + + @Test + void getCase() { + try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.bicycle(arena); + Vehicle.Case caseElement = vehicle.getCase(arena); + assertInstanceOf(Vehicle.Bicycle.class, caseElement); + } + } + + @Test + void switchGetCase() { + try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.car("BMW", Optional.empty(), arena); + switch (vehicle.getCase(arena)) { + case Vehicle.Bicycle b: + fail("Was bicycle"); + break; + case Vehicle.Car car: + assertEquals("BMW", car.arg0()); + break; + case Vehicle.Motorbike motorbike: + fail("Was motorbike"); + break; + case Vehicle.Transformer transformer: + fail("Was transformer"); + break; + case Vehicle.Boat b: + fail("Was boat"); + break; + } + } + } + +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift index 0f5cfeac6..25c463661 100644 --- a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift @@ -24,6 +24,14 @@ extension String { return "\(f.uppercased())\(String(dropFirst()))" } + var firstCharacterLowercased: String { + guard let f = first else { + return self + } + + return "\(f.lowercased())\(String(dropFirst()))" + } + /// Returns whether the string is of the format `isX` var hasJavaBooleanNamingConvention: Bool { guard self.hasPrefix("is"), self.count > 2 else { diff --git a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift index e71300af7..3719d99d6 100644 --- a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift @@ -253,6 +253,8 @@ extension DeclSyntaxProtocol { } )) .triviaSanitizedDescription + case .enumCaseDecl(let node): + node.triviaSanitizedDescription default: fatalError("unimplemented \(self.kind)") } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index 085a6331b..0a61708e6 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -835,6 +835,10 @@ extension LoweredFunctionSignature { case .setter: assert(paramExprs.count == 1) resultExpr = "\(callee) = \(paramExprs[0])" + + case .enumCase: + // This should not be called, but let's fatalError. + fatalError("Enum cases are not supported with FFM.") } // Lower the result. diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 06a4d1713..57f947a8c 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -146,7 +146,7 @@ extension FFMSwift2JavaGenerator { let javaName = switch decl.apiKind { case .getter: decl.javaGetterName case .setter: decl.javaSetterName - case .function, .initializer: decl.name + case .function, .initializer, .enumCase: decl.name } // Signature. diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 4c4d9c0f4..5655af006 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -22,6 +22,7 @@ package enum SwiftAPIKind { case initializer case getter case setter + case enumCase } /// Describes a Swift nominal type (e.g., a class, struct, enum) that has been @@ -32,6 +33,7 @@ package class ImportedNominalType: ImportedDecl { package var initializers: [ImportedFunc] = [] package var methods: [ImportedFunc] = [] package var variables: [ImportedFunc] = [] + package var cases: [ImportedEnumCase] = [] init(swiftNominal: SwiftNominalTypeDeclaration) { self.swiftNominal = swiftNominal @@ -46,6 +48,56 @@ package class ImportedNominalType: ImportedDecl { } } +public final class ImportedEnumCase: ImportedDecl, CustomStringConvertible { + /// The case name + public var name: String + + /// The enum parameters + var parameters: [SwiftEnumCaseParameter] + + var swiftDecl: any DeclSyntaxProtocol + + var enumType: SwiftNominalType + + /// A function that represents the Swift static "initializer" for cases + var caseFunction: ImportedFunc + + init( + name: String, + parameters: [SwiftEnumCaseParameter], + swiftDecl: any DeclSyntaxProtocol, + enumType: SwiftNominalType, + caseFunction: ImportedFunc + ) { + self.name = name + self.parameters = parameters + self.swiftDecl = swiftDecl + self.enumType = enumType + self.caseFunction = caseFunction + } + + public var description: String { + """ + ImportedEnumCase { + name: \(name), + parameters: \(parameters), + swiftDecl: \(swiftDecl), + enumType: \(enumType), + caseFunction: \(caseFunction) + } + """ + } +} + +extension ImportedEnumCase: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(self)) + } + public static func == (lhs: ImportedEnumCase, rhs: ImportedEnumCase) -> Bool { + return lhs === rhs + } +} + public final class ImportedFunc: ImportedDecl, CustomStringConvertible { /// Swift module name (e.g. the target name where a type or function was declared) public var module: String @@ -113,6 +165,7 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible { let prefix = switch self.apiKind { case .getter: "getter:" case .setter: "setter:" + case .enumCase: "case:" case .function, .initializer: "" } diff --git a/Sources/JExtractSwiftLib/JNI/JNICaching.swift b/Sources/JExtractSwiftLib/JNI/JNICaching.swift new file mode 100644 index 000000000..cdb13e3fc --- /dev/null +++ b/Sources/JExtractSwiftLib/JNI/JNICaching.swift @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +enum JNICaching { + static func cacheName(for type: ImportedNominalType) -> String { + cacheName(for: type.swiftNominal.name) + } + + static func cacheName(for type: SwiftNominalType) -> String { + cacheName(for: type.nominalTypeDecl.name) + } + + private static func cacheName(for name: String) -> String { + "_JNI_\(name)" + } + + static func cacheMemberName(for enumCase: ImportedEnumCase) -> String { + "\(enumCase.enumType.nominalTypeDecl.name.firstCharacterLowercased)\(enumCase.name.firstCharacterUppercased)Cache" + } +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 6d82175fe..9351252e5 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -138,6 +138,11 @@ extension JNISwift2JavaGenerator { printer.println() + if decl.swiftNominal.kind == .enum { + printEnumHelpers(&printer, decl) + printer.println() + } + for initializer in decl.initializers { printFunctionDowncallMethods(&printer, initializer) printer.println() @@ -200,6 +205,85 @@ extension JNISwift2JavaGenerator { } } + private func printEnumHelpers(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + printEnumDiscriminator(&printer, decl) + printer.println() + printEnumCaseInterface(&printer, decl) + printer.println() + printEnumStaticInitializers(&printer, decl) + printer.println() + printEnumCases(&printer, decl) + } + + private func printEnumDiscriminator(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + printer.printBraceBlock("public enum Discriminator") { printer in + printer.print( + decl.cases.map { $0.name.uppercased() }.joined(separator: ",\n") + ) + } + + // TODO: Consider whether all of these "utility" functions can be printed using our existing printing logic. + printer.printBraceBlock("public Discriminator getDiscriminator()") { printer in + printer.print("return Discriminator.values()[$getDiscriminator(this.$memoryAddress())];") + } + printer.print("private static native int $getDiscriminator(long self);") + } + + private func printEnumCaseInterface(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + printer.print("public sealed interface Case {}") + printer.println() + + let requiresSwiftArena = decl.cases.compactMap { + self.translatedEnumCase(for: $0) + }.contains(where: \.requiresSwiftArena) + + printer.printBraceBlock("public Case getCase(\(requiresSwiftArena ? "SwiftArena swiftArena$" : ""))") { printer in + printer.print("Discriminator discriminator = this.getDiscriminator();") + printer.printBraceBlock("switch (discriminator)") { printer in + for enumCase in decl.cases { + guard let translatedCase = self.translatedEnumCase(for: enumCase) else { + continue + } + let arenaArgument = translatedCase.requiresSwiftArena ? "swiftArena$" : "" + printer.print("case \(enumCase.name.uppercased()): return this.getAs\(enumCase.name.firstCharacterUppercased)(\(arenaArgument)).orElseThrow();") + } + } + printer.print(#"throw new RuntimeException("Unknown discriminator value " + discriminator);"#) + } + } + + private func printEnumStaticInitializers(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + for enumCase in decl.cases { + printFunctionDowncallMethods(&printer, enumCase.caseFunction) + } + } + + private func printEnumCases(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + for enumCase in decl.cases { + guard let translatedCase = self.translatedEnumCase(for: enumCase) else { + return + } + + let members = translatedCase.translatedValues.map { + $0.parameter.renderParameter() + } + + let caseName = enumCase.name.firstCharacterUppercased + + // Print record + printer.printBraceBlock("public record \(caseName)(\(members.joined(separator: ", "))) implements Case") { printer in + let nativeParameters = zip(translatedCase.translatedValues, translatedCase.parameterConversions).flatMap { value, conversion in + ["\(conversion.native.javaType) \(value.parameter.name)"] + } + + printer.print("record $NativeParameters(\(nativeParameters.joined(separator: ", "))) {}") + } + + self.printJavaBindingWrapperMethod(&printer, translatedCase.getAsCaseFunction) + printer.println() + } + } + private func printFunctionDowncallMethods( _ printer: inout CodePrinter, _ decl: ImportedFunc @@ -260,17 +344,23 @@ extension JNISwift2JavaGenerator { guard let translatedDecl = translatedDecl(for: decl) else { fatalError("Decl was not translated, \(decl)") } - let translatedSignature = translatedDecl.translatedFunctionSignature + printJavaBindingWrapperMethod(&printer, translatedDecl, importedFunc: decl) + } + private func printJavaBindingWrapperMethod( + _ printer: inout CodePrinter, + _ translatedDecl: TranslatedFunctionDecl, + importedFunc: ImportedFunc? = nil + ) { var modifiers = ["public"] - - if decl.isStatic || decl.isInitializer || !decl.hasParent { + if translatedDecl.isStatic { modifiers.append("static") } + let translatedSignature = translatedDecl.translatedFunctionSignature let resultType = translatedSignature.resultType.javaType var parameters = translatedDecl.translatedFunctionSignature.parameters.map { $0.parameter.renderParameter() } - let throwsClause = decl.isThrowing ? " throws Exception" : "" + let throwsClause = translatedDecl.isThrowing ? " throws Exception" : "" var annotationsStr = translatedSignature.annotations.map({ $0.render() }).joined(separator: "\n") if !annotationsStr.isEmpty { annotationsStr += "\n" } @@ -279,7 +369,9 @@ extension JNISwift2JavaGenerator { // Print default global arena variation if config.effectiveMemoryManagementMode.requiresGlobalArena && translatedSignature.requiresSwiftArena { - printDeclDocumentation(&printer, decl) + if let importedFunc { + printDeclDocumentation(&printer, importedFunc) + } printer.printBraceBlock( "\(annotationsStr)\(modifiers.joined(separator: " ")) \(resultType) \(translatedDecl.name)(\(parametersStr))\(throwsClause)" ) { printer in @@ -298,18 +390,19 @@ extension JNISwift2JavaGenerator { if translatedSignature.requiresSwiftArena { parameters.append("SwiftArena swiftArena$") } - printDeclDocumentation(&printer, decl) + if let importedFunc { + printDeclDocumentation(&printer, importedFunc) + } printer.printBraceBlock( "\(annotationsStr)\(modifiers.joined(separator: " ")) \(resultType) \(translatedDecl.name)(\(parameters.joined(separator: ", ")))\(throwsClause)" ) { printer in - printDowncall(&printer, decl) + printDowncall(&printer, translatedDecl) } - printNativeFunction(&printer, decl) + printNativeFunction(&printer, translatedDecl) } - private func printNativeFunction(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - let translatedDecl = translatedDecl(for: decl)! // Will always call with valid decl + private func printNativeFunction(_ printer: inout CodePrinter, _ translatedDecl: TranslatedFunctionDecl) { let nativeSignature = translatedDecl.nativeFunctionSignature let resultType = nativeSignature.result.javaType var parameters = nativeSignature.parameters.flatMap(\.parameters) @@ -327,9 +420,8 @@ extension JNISwift2JavaGenerator { private func printDowncall( _ printer: inout CodePrinter, - _ decl: ImportedFunc + _ translatedDecl: TranslatedFunctionDecl ) { - let translatedDecl = translatedDecl(for: decl)! // We will only call this method if we can translate the decl. let translatedFunctionSignature = translatedDecl.translatedFunctionSignature // Regular parameters. diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 64ae23b7c..0e169ec69 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -41,12 +41,121 @@ extension JNISwift2JavaGenerator { return translated } + func translatedEnumCase( + for decl: ImportedEnumCase + ) -> TranslatedEnumCase? { + if let cached = translatedEnumCases[decl] { + return cached + } + + let translated: TranslatedEnumCase? + do { + let translation = JavaTranslation( + config: config, + swiftModuleName: swiftModuleName, + javaPackage: self.javaPackage, + javaClassLookupTable: self.javaClassLookupTable + ) + translated = try translation.translate(enumCase: decl) + } catch { + self.logger.debug("Failed to translate: '\(decl.swiftDecl.qualifiedNameForDebug)'; \(error)") + translated = nil + } + + translatedEnumCases[decl] = translated + return translated + } + struct JavaTranslation { let config: Configuration let swiftModuleName: String let javaPackage: String let javaClassLookupTable: JavaClassLookupTable + func translate(enumCase: ImportedEnumCase) throws -> TranslatedEnumCase { + let nativeTranslation = NativeJavaTranslation( + config: self.config, + javaPackage: self.javaPackage, + javaClassLookupTable: self.javaClassLookupTable + ) + + let methodName = "" // TODO: Used for closures, replace with better name? + let parentName = "" // TODO: Used for closures, replace with better name? + + let translatedValues = try self.translateParameters( + enumCase.parameters.map { ($0.name, $0.type) }, + methodName: methodName, + parentName: parentName + ) + + let conversions = try enumCase.parameters.enumerated().map { idx, parameter in + let resultName = parameter.name ?? "arg\(idx)" + let result = SwiftResult(convention: .direct, type: parameter.type) + var translatedResult = try self.translate(swiftResult: result, resultName: resultName) + translatedResult.conversion = .replacingPlaceholder(translatedResult.conversion, placeholder: "$nativeParameters.\(resultName)") + let nativeResult = try nativeTranslation.translate(swiftResult: result, resultName: resultName) + return (translated: translatedResult, native: nativeResult) + } + + let caseName = enumCase.name.firstCharacterUppercased + let enumName = enumCase.enumType.nominalTypeDecl.name + let nativeParametersType = JavaType.class(package: nil, name: "\(caseName).$NativeParameters") + let getAsCaseName = "getAs\(caseName)" + // If the case has no parameters, we can skip the native call. + let constructRecordConversion = JavaNativeConversionStep.method(.constant("Optional"), function: "of", arguments: [ + .constructJavaClass( + .commaSeparated(conversions.map(\.translated.conversion)), + .class(package: nil,name: caseName) + ) + ]) + let getAsCaseFunction = TranslatedFunctionDecl( + name: getAsCaseName, + isStatic: false, + isThrowing: false, + nativeFunctionName: "$\(getAsCaseName)", + parentName: enumName, + functionTypes: [], + translatedFunctionSignature: TranslatedFunctionSignature( + selfParameter: TranslatedParameter( + parameter: JavaParameter(name: "self", type: .long), + conversion: .aggregate( + [ + .ifStatement(.constant("getDiscriminator() != Discriminator.\(caseName.uppercased())"), thenExp: .constant("return Optional.empty();")), + .valueMemoryAddress(.placeholder) + ] + ) + ), + parameters: [], + resultType: TranslatedResult( + javaType: .class(package: nil, name: "Optional<\(caseName)>"), + outParameters: conversions.flatMap(\.translated.outParameters), + conversion: enumCase.parameters.isEmpty ? constructRecordConversion : .aggregate(variable: ("$nativeParameters", nativeParametersType), [constructRecordConversion]) + ) + ), + nativeFunctionSignature: NativeFunctionSignature( + selfParameter: NativeParameter( + parameters: [JavaParameter(name: "self", type: .long)], + conversion: .extractSwiftValue(.placeholder, swiftType: .nominal(enumCase.enumType), allowNil: false) + ), + parameters: [], + result: NativeResult( + javaType: nativeParametersType, + conversion: .placeholder, + outParameters: conversions.flatMap(\.native.outParameters) + ) + ) + ) + + return TranslatedEnumCase( + name: enumCase.name.firstCharacterUppercased, + enumName: enumCase.enumType.nominalTypeDecl.name, + original: enumCase, + translatedValues: translatedValues, + parameterConversions: conversions, + getAsCaseFunction: getAsCaseFunction + ) + } + func translate(_ decl: ImportedFunc) throws -> TranslatedFunctionDecl { let nativeTranslation = NativeJavaTranslation( config: self.config, @@ -61,7 +170,7 @@ extension JNISwift2JavaGenerator { let javaName = switch decl.apiKind { case .getter: decl.javaGetterName case .setter: decl.javaSetterName - case .function, .initializer: decl.name + case .function, .initializer, .enumCase: decl.name } // Swift -> Java @@ -98,6 +207,8 @@ extension JNISwift2JavaGenerator { return TranslatedFunctionDecl( name: javaName, + isStatic: decl.isStatic || !decl.hasParent || decl.isInitializer, + isThrowing: decl.isThrowing, nativeFunctionName: "$\(javaName)", parentName: parentName, functionTypes: funcTypes, @@ -136,31 +247,47 @@ extension JNISwift2JavaGenerator { methodName: String, parentName: String ) throws -> TranslatedFunctionSignature { - let parameters = try functionSignature.parameters.enumerated().map { idx, param in - let parameterName = param.parameterName ?? "arg\(idx))" + let parameters = try translateParameters( + functionSignature.parameters.map { ($0.parameterName, $0.type )}, + methodName: methodName, + parentName: parentName + ) + + // 'self' + let selfParameter = try self.translateSelfParameter(functionSignature.selfParameter, methodName: methodName, parentName: parentName) + + let resultType = try translate(swiftResult: functionSignature.result) + + return TranslatedFunctionSignature( + selfParameter: selfParameter, + parameters: parameters, + resultType: resultType + ) + } + + func translateParameters( + _ parameters: [(name: String?, type: SwiftType)], + methodName: String, + parentName: String + ) throws -> [TranslatedParameter] { + try parameters.enumerated().map { idx, param in + let parameterName = param.name ?? "arg\(idx)" return try translateParameter(swiftType: param.type, parameterName: parameterName, methodName: methodName, parentName: parentName) } + } + func translateSelfParameter(_ selfParameter: SwiftSelfParameter?, methodName: String, parentName: String) throws -> TranslatedParameter? { // 'self' - let selfParameter: TranslatedParameter? - if case .instance(let swiftSelf) = functionSignature.selfParameter { - selfParameter = try self.translateParameter( + if case .instance(let swiftSelf) = selfParameter { + return try self.translateParameter( swiftType: swiftSelf.type, parameterName: swiftSelf.parameterName ?? "self", methodName: methodName, parentName: parentName ) } else { - selfParameter = nil + return nil } - - let resultType = try translate(swiftResult: functionSignature.result) - - return TranslatedFunctionSignature( - selfParameter: selfParameter, - parameters: parameters, - resultType: resultType - ) } func translateParameter( @@ -343,7 +470,7 @@ extension JNISwift2JavaGenerator { } } - func translate(swiftResult: SwiftResult) throws -> TranslatedResult { + func translate(swiftResult: SwiftResult, resultName: String = "result") throws -> TranslatedResult { let swiftType = swiftResult.type // If the result type should cause any annotations on the method, include them here. @@ -357,7 +484,7 @@ extension JNISwift2JavaGenerator { guard let genericArgs = nominalType.genericArguments, genericArgs.count == 1 else { throw JavaTranslationError.unsupportedSwiftType(swiftType) } - return try translateOptionalResult(wrappedType: genericArgs[0]) + return try translateOptionalResult(wrappedType: genericArgs[0], resultName: resultName) default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { @@ -390,7 +517,7 @@ extension JNISwift2JavaGenerator { return TranslatedResult(javaType: .void, outParameters: [], conversion: .placeholder) case .optional(let wrapped): - return try translateOptionalResult(wrappedType: wrapped) + return try translateOptionalResult(wrappedType: wrapped, resultName: resultName) case .metatype, .tuple, .function, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(swiftType) @@ -398,8 +525,11 @@ extension JNISwift2JavaGenerator { } func translateOptionalResult( - wrappedType swiftType: SwiftType + wrappedType swiftType: SwiftType, + resultName: String = "result" ) throws -> TranslatedResult { + let discriminatorName = "\(resultName)$_discriminator$" + let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config) switch swiftType { @@ -425,6 +555,7 @@ extension JNISwift2JavaGenerator { conversion: .combinedValueToOptional( .placeholder, nextIntergralTypeWithSpaceForByte.javaType, + resultName: resultName, valueType: javaType, valueSizeInBytes: nextIntergralTypeWithSpaceForByte.valueBytes, optionalType: optionalClass @@ -437,13 +568,14 @@ extension JNISwift2JavaGenerator { javaType: .class(package: nil, name: returnType), annotations: parameterAnnotations, outParameters: [ - OutParameter(name: "result_discriminator$", type: .array(.byte), allocation: .newArray(.byte, size: 1)) + OutParameter(name: discriminatorName, type: .array(.byte), allocation: .newArray(.byte, size: 1)) ], conversion: .toOptionalFromIndirectReturn( - discriminatorName: "result_discriminator$", + discriminatorName: .combinedName(component: "discriminator$"), optionalClass: optionalClass, javaType: javaType, - toValue: .placeholder + toValue: .placeholder, + resultName: resultName ) ) } @@ -459,13 +591,14 @@ extension JNISwift2JavaGenerator { javaType: returnType, annotations: parameterAnnotations, outParameters: [ - OutParameter(name: "result_discriminator$", type: .array(.byte), allocation: .newArray(.byte, size: 1)) + OutParameter(name: discriminatorName, type: .array(.byte), allocation: .newArray(.byte, size: 1)) ], conversion: .toOptionalFromIndirectReturn( - discriminatorName: "result_discriminator$", + discriminatorName: .combinedName(component: "discriminator$"), optionalClass: "Optional", javaType: .long, - toValue: .wrapMemoryAddressUnsafe(.placeholder, .class(package: nil, name: nominalTypeName)) + toValue: .wrapMemoryAddressUnsafe(.placeholder, .class(package: nil, name: nominalTypeName)), + resultName: resultName ) ) @@ -475,10 +608,38 @@ extension JNISwift2JavaGenerator { } } + struct TranslatedEnumCase { + /// The corresponding Java case class (CamelCased) + let name: String + + /// The name of the translated enum + let enumName: String + + /// The oringinal enum case. + let original: ImportedEnumCase + + /// A list of the translated associated values + let translatedValues: [TranslatedParameter] + + /// A list of parameter conversions + let parameterConversions: [(translated: TranslatedResult, native: NativeResult)] + + let getAsCaseFunction: TranslatedFunctionDecl + + /// Returns whether the parameters require an arena + var requiresSwiftArena: Bool { + parameterConversions.contains(where: \.translated.conversion.requiresSwiftArena) + } + } + struct TranslatedFunctionDecl { /// Java function name let name: String + let isStatic: Bool + + let isThrowing: Bool + /// The name of the native function let nativeFunctionName: String @@ -532,7 +693,7 @@ extension JNISwift2JavaGenerator { let outParameters: [OutParameter] /// Represents how to convert the Java native result into a user-facing result. - let conversion: JavaNativeConversionStep + var conversion: JavaNativeConversionStep } struct OutParameter { @@ -573,6 +734,9 @@ extension JNISwift2JavaGenerator { case constant(String) + /// `input_component` + case combinedName(component: String) + // Convert the results of the inner steps to a comma separated list. indirect case commaSeparated([JavaNativeConversionStep]) @@ -582,6 +746,9 @@ extension JNISwift2JavaGenerator { /// Call `new \(Type)(\(placeholder), swiftArena$)` indirect case constructSwiftValue(JavaNativeConversionStep, JavaType) + /// Call `new \(Type)(\(placeholder))` + indirect case constructJavaClass(JavaNativeConversionStep, JavaType) + /// Call the `MyType.wrapMemoryAddressUnsafe` in order to wrap a memory address using the Java binding type indirect case wrapMemoryAddressUnsafe(JavaNativeConversionStep, JavaType) @@ -591,7 +758,7 @@ extension JNISwift2JavaGenerator { case isOptionalPresent - indirect case combinedValueToOptional(JavaNativeConversionStep, JavaType, valueType: JavaType, valueSizeInBytes: Int, optionalType: String) + indirect case combinedValueToOptional(JavaNativeConversionStep, JavaType, resultName: String, valueType: JavaType, valueSizeInBytes: Int, optionalType: String) indirect case ternary(JavaNativeConversionStep, thenExp: JavaNativeConversionStep, elseExp: JavaNativeConversionStep) @@ -600,18 +767,18 @@ extension JNISwift2JavaGenerator { indirect case subscriptOf(JavaNativeConversionStep, arguments: [JavaNativeConversionStep]) static func toOptionalFromIndirectReturn( - discriminatorName: String, + discriminatorName: JavaNativeConversionStep, optionalClass: String, javaType: JavaType, - toValue valueConversion: JavaNativeConversionStep + toValue valueConversion: JavaNativeConversionStep, + resultName: String ) -> JavaNativeConversionStep { .aggregate( - name: "result$", - type: javaType, + variable: (name: "\(resultName)$", type: javaType), [ .ternary( .equals( - .subscriptOf(.constant(discriminatorName), arguments: [.constant("0")]), + .subscriptOf(discriminatorName, arguments: [.constant("0")]), .constant("1") ), thenExp: .method(.constant(optionalClass), function: "of", arguments: [valueConversion]), @@ -622,7 +789,12 @@ extension JNISwift2JavaGenerator { } /// Perform multiple conversions using the same input. - case aggregate(name: String, type: JavaType, [JavaNativeConversionStep]) + case aggregate(variable: (name: String, type: JavaType)? = nil, [JavaNativeConversionStep]) + + indirect case ifStatement(JavaNativeConversionStep, thenExp: JavaNativeConversionStep, elseExp: JavaNativeConversionStep? = nil) + + /// Access a member of the value + indirect case replacingPlaceholder(JavaNativeConversionStep, placeholder: String) /// Returns the conversion string applied to the placeholder. func render(_ printer: inout CodePrinter, _ placeholder: String) -> String { @@ -635,6 +807,9 @@ extension JNISwift2JavaGenerator { case .constant(let value): return value + case .combinedName(let component): + return "\(placeholder)_\(component)" + case .commaSeparated(let list): return list.map({ $0.render(&printer, placeholder)}).joined(separator: ", ") @@ -649,6 +824,10 @@ extension JNISwift2JavaGenerator { let inner = inner.render(&printer, placeholder) return "\(javaType.className!).wrapMemoryAddressUnsafe(\(inner), swiftArena$)" + case .constructJavaClass(let inner, let javaType): + let inner = inner.render(&printer, placeholder) + return "new \(javaType.className!)(\(inner))" + case .call(let inner, let function): let inner = inner.render(&printer, placeholder) return "\(function)(\(inner))" @@ -663,22 +842,22 @@ extension JNISwift2JavaGenerator { let argsStr = args.joined(separator: ", ") return "\(inner).\(methodName)(\(argsStr))" - case .combinedValueToOptional(let combined, let combinedType, let valueType, let valueSizeInBytes, let optionalType): + case .combinedValueToOptional(let combined, let combinedType, let resultName, let valueType, let valueSizeInBytes, let optionalType): let combined = combined.render(&printer, placeholder) printer.print( """ - \(combinedType) combined$ = \(combined); - byte discriminator$ = (byte) (combined$ & 0xFF); + \(combinedType) \(resultName)_combined$ = \(combined); + byte \(resultName)_discriminator$ = (byte) (\(resultName)_combined$ & 0xFF); """ ) if valueType == .boolean { - printer.print("boolean value$ = ((byte) (combined$ >> 8)) != 0;") + printer.print("boolean \(resultName)_value$ = ((byte) (\(resultName)_combined$ >> 8)) != 0;") } else { - printer.print("\(valueType) value$ = (\(valueType)) (combined$ >> \(valueSizeInBytes * 8));") + printer.print("\(valueType) \(resultName)_value$ = (\(valueType)) (\(resultName)_combined$ >> \(valueSizeInBytes * 8));") } - return "discriminator$ == 1 ? \(optionalType).of(value$) : \(optionalType).empty()" + return "\(resultName)_discriminator$ == 1 ? \(optionalType).of(\(resultName)_value$) : \(optionalType).empty()" case .ternary(let cond, let thenExp, let elseExp): let cond = cond.render(&printer, placeholder) @@ -696,25 +875,50 @@ extension JNISwift2JavaGenerator { let arguments = arguments.map { $0.render(&printer, placeholder) } return "\(inner)[\(arguments.joined(separator: ", "))]" - case .aggregate(let name, let type, let steps): + case .aggregate(let variable, let steps): precondition(!steps.isEmpty, "Aggregate must contain steps") - printer.print("\(type) \(name) = \(placeholder);") + let toExplode: String + if let variable { + printer.print("\(variable.type) \(variable.name) = \(placeholder);") + toExplode = variable.name + } else { + toExplode = placeholder + } let steps = steps.map { - $0.render(&printer, name) + $0.render(&printer, toExplode) } return steps.last! + + case .ifStatement(let cond, let thenExp, let elseExp): + let cond = cond.render(&printer, placeholder) + printer.printBraceBlock("if (\(cond))") { printer in + printer.print(thenExp.render(&printer, placeholder)) + } + if let elseExp { + printer.printBraceBlock("else") { printer in + printer.print(elseExp.render(&printer, placeholder)) + } + } + + return "" + + case .replacingPlaceholder(let inner, let placeholder): + return inner.render(&printer, placeholder) } } /// Whether the conversion uses SwiftArena. var requiresSwiftArena: Bool { switch self { - case .placeholder, .constant, .isOptionalPresent: + case .placeholder, .constant, .isOptionalPresent, .combinedName: return false case .constructSwiftValue, .wrapMemoryAddressUnsafe: return true + case .constructJavaClass(let inner, _): + return inner.requiresSwiftArena + case .valueMemoryAddress(let inner): return inner.requiresSwiftArena @@ -724,7 +928,7 @@ extension JNISwift2JavaGenerator { case .method(let inner, _, let args): return inner.requiresSwiftArena || args.contains(where: \.requiresSwiftArena) - case .combinedValueToOptional(let inner, _, _, _, _): + case .combinedValueToOptional(let inner, _, _, _, _, _): return inner.requiresSwiftArena case .ternary(let cond, let thenExp, let elseExp): @@ -736,11 +940,17 @@ extension JNISwift2JavaGenerator { case .subscriptOf(let inner, _): return inner.requiresSwiftArena - case .aggregate(_, _, let steps): + case .aggregate(_, let steps): return steps.contains(where: \.requiresSwiftArena) + case .ifStatement(let cond, let thenExp, let elseExp): + return cond.requiresSwiftArena || thenExp.requiresSwiftArena || (elseExp?.requiresSwiftArena ?? false) + case .call(let inner, _): - return inner.requiresSwiftArena + return inner.requiresSwiftArena + + case .replacingPlaceholder(let inner, _): + return inner.requiresSwiftArena } } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index e7f7efbeb..f2e9522bb 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -61,6 +61,23 @@ extension JNISwift2JavaGenerator { ) } + func translateParameters( + _ parameters: [SwiftParameter], + translatedParameters: [TranslatedParameter], + methodName: String, + parentName: String + ) throws -> [NativeParameter] { + try zip(translatedParameters, parameters).map { translatedParameter, swiftParameter in + let parameterName = translatedParameter.parameter.name + return try translate( + swiftParameter: swiftParameter, + parameterName: parameterName, + methodName: methodName, + parentName: parentName + ) + } + } + func translate( swiftParameter: SwiftParameter, parameterName: String, @@ -231,8 +248,11 @@ extension JNISwift2JavaGenerator { } func translateOptionalResult( - wrappedType swiftType: SwiftType + wrappedType swiftType: SwiftType, + resultName: String = "result" ) throws -> NativeResult { + let discriminatorName = "\(resultName)_discriminator$" + switch swiftType { case .nominal(let nominalType): if let knownType = nominalType.nominalTypeDecl.knownTypeKind { @@ -249,6 +269,7 @@ extension JNISwift2JavaGenerator { conversion: .getJNIValue( .optionalRaisingWidenIntegerType( .placeholder, + resultName: resultName, valueType: javaType, combinedSwiftType: nextIntergralTypeWithSpaceForByte.swiftType, valueSizeInBytes: nextIntergralTypeWithSpaceForByte.valueBytes @@ -258,7 +279,6 @@ extension JNISwift2JavaGenerator { ) } else { // Use indirect byte array to store discriminator - let discriminatorName = "result_discriminator$" return NativeResult( javaType: javaType, @@ -284,8 +304,6 @@ extension JNISwift2JavaGenerator { } // Assume JExtract imported class - let discriminatorName = "result_discriminator$" - return NativeResult( javaType: .long, conversion: .optionalRaisingIndirectReturn( @@ -368,7 +386,8 @@ extension JNISwift2JavaGenerator { } func translate( - swiftResult: SwiftResult + swiftResult: SwiftResult, + resultName: String = "result" ) throws -> NativeResult { switch swiftResult.type { case .nominal(let nominalType): @@ -378,7 +397,7 @@ extension JNISwift2JavaGenerator { guard let genericArgs = nominalType.genericArguments, genericArgs.count == 1 else { throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) } - return try translateOptionalResult(wrappedType: genericArgs[0]) + return try translateOptionalResult(wrappedType: genericArgs[0], resultName: resultName) default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), javaType.implementsJavaValue else { @@ -399,7 +418,7 @@ extension JNISwift2JavaGenerator { return NativeResult( javaType: .long, - conversion: .getJNIValue(.allocateSwiftValue(name: "result", swiftType: swiftResult.type)), + conversion: .getJNIValue(.allocateSwiftValue(name: resultName, swiftType: swiftResult.type)), outParameters: [] ) @@ -411,7 +430,7 @@ extension JNISwift2JavaGenerator { ) case .optional(let wrapped): - return try translateOptionalResult(wrappedType: wrapped) + return try translateOptionalResult(wrappedType: wrapped, resultName: resultName) case .metatype, .tuple, .function, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) @@ -449,6 +468,9 @@ extension JNISwift2JavaGenerator { case constant(String) + /// `input_component` + case combinedName(component: String) + /// `value.getJNIValue(in:)` indirect case getJNIValue(NativeSwiftConversionStep) @@ -480,7 +502,7 @@ extension JNISwift2JavaGenerator { indirect case optionalChain(NativeSwiftConversionStep) - indirect case optionalRaisingWidenIntegerType(NativeSwiftConversionStep, valueType: JavaType, combinedSwiftType: SwiftKnownTypeDeclKind, valueSizeInBytes: Int) + indirect case optionalRaisingWidenIntegerType(NativeSwiftConversionStep, resultName: String, valueType: JavaType, combinedSwiftType: SwiftKnownTypeDeclKind, valueSizeInBytes: Int) indirect case optionalRaisingIndirectReturn(NativeSwiftConversionStep, returnType: JavaType, discriminatorParameterName: String, placeholderValue: NativeSwiftConversionStep) @@ -503,6 +525,9 @@ extension JNISwift2JavaGenerator { case .constant(let value): return value + case .combinedName(let component): + return "\(placeholder)_\(component)" + case .getJNIValue(let inner): let inner = inner.render(&printer, placeholder) return "\(inner).getJNIValue(in: environment!)" @@ -606,18 +631,18 @@ extension JNISwift2JavaGenerator { let inner = inner.render(&printer, placeholder) return "\(inner)?" - case .optionalRaisingWidenIntegerType(let inner, let valueType, let combinedSwiftType, let valueSizeInBytes): + case .optionalRaisingWidenIntegerType(let inner, let resultName, let valueType, let combinedSwiftType, let valueSizeInBytes): let inner = inner.render(&printer, placeholder) let value = valueType == .boolean ? "$0 ? 1 : 0" : "$0" let combinedSwiftTypeName = combinedSwiftType.moduleAndName.name printer.print( """ - let value$ = \(inner).map { + let \(resultName)_value$ = \(inner).map { \(combinedSwiftTypeName)(\(value)) << \(valueSizeInBytes * 8) | \(combinedSwiftTypeName)(1) } ?? 0 """ ) - return "value$" + return "\(resultName)_value$" case .optionalRaisingIndirectReturn(let inner, let returnType, let discriminatorParameterName, let placeholderValue): printer.print("let result$: \(returnType.jniTypeName)") diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index ca580e602..6b8c435a7 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -80,6 +80,15 @@ extension JNISwift2JavaGenerator { } } + private func printJNICache(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + printer.printBraceBlock("enum \(JNICaching.cacheName(for: type))") { printer in + for enumCase in type.cases { + guard let translatedCase = translatedEnumCase(for: enumCase) else { continue } + printer.print("static let \(JNICaching.cacheMemberName(for: enumCase)) = \(renderEnumCaseCacheInit(translatedCase))") + } + } + } + private func printGlobalSwiftThunkSources(_ printer: inout CodePrinter) throws { printHeader(&printer) @@ -97,11 +106,24 @@ extension JNISwift2JavaGenerator { private func printNominalTypeThunks(_ printer: inout CodePrinter, _ type: ImportedNominalType) throws { printHeader(&printer) + printJNICache(&printer, type) + printer.println() + for initializer in type.initializers { printSwiftFunctionThunk(&printer, initializer) printer.println() } + if type.swiftNominal.kind == .enum { + printEnumDiscriminator(&printer, type) + printer.println() + + for enumCase in type.cases { + printEnumCase(&printer, enumCase) + printer.println() + } + } + for method in type.methods { printSwiftFunctionThunk(&printer, method) printer.println() @@ -115,6 +137,92 @@ extension JNISwift2JavaGenerator { printDestroyFunctionThunk(&printer, type) } + private func printEnumDiscriminator(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + let selfPointerParam = JavaParameter(name: "selfPointer", type: .long) + printCDecl( + &printer, + javaMethodName: "$getDiscriminator", + parentName: type.swiftNominal.name, + parameters: [selfPointerParam], + resultType: .int + ) { printer in + let selfPointer = self.printSelfJLongToUnsafeMutablePointer( + &printer, + swiftParentName: type.swiftNominal.name, + selfPointerParam + ) + printer.printBraceBlock("switch (\(selfPointer).pointee)") { printer in + for (idx, enumCase) in type.cases.enumerated() { + printer.print("case .\(enumCase.name): return \(idx)") + } + } + } + } + + private func printEnumCase(_ printer: inout CodePrinter, _ enumCase: ImportedEnumCase) { + guard let translatedCase = self.translatedEnumCase(for: enumCase) else { + return + } + + // Print static case initializer + printSwiftFunctionThunk(&printer, enumCase.caseFunction) + printer.println() + + // Print getAsCase method + if !translatedCase.translatedValues.isEmpty { + printEnumGetAsCaseThunk(&printer, translatedCase) + } + } + + private func renderEnumCaseCacheInit(_ enumCase: TranslatedEnumCase) -> String { + let nativeParametersClassName = "\(javaPackagePath)/\(enumCase.enumName)$\(enumCase.name)$$NativeParameters" + let methodSignature = MethodSignature(resultType: .void, parameterTypes: enumCase.parameterConversions.map(\.native.javaType)) + let methods = #"[.init(name: "", signature: "\#(methodSignature.mangledName)")]"# + + return #"_JNIMethodIDCache(environment: try! JavaVirtualMachine.shared().environment(), className: "\#(nativeParametersClassName)", methods: \#(methods))"# + } + + private func printEnumGetAsCaseThunk( + _ printer: inout CodePrinter, + _ enumCase: TranslatedEnumCase + ) { + printCDecl( + &printer, + enumCase.getAsCaseFunction + ) { printer in + let selfPointer = enumCase.getAsCaseFunction.nativeFunctionSignature.selfParameter!.conversion.render(&printer, "self") + let caseNames = enumCase.original.parameters.enumerated().map { idx, parameter in + parameter.name ?? "_\(idx)" + } + let caseNamesWithLet = caseNames.map { "let \($0)" } + let methodSignature = MethodSignature(resultType: .void, parameterTypes: enumCase.parameterConversions.map(\.native.javaType)) + printer.print( + """ + guard case .\(enumCase.original.name)(\(caseNamesWithLet.joined(separator: ", "))) = \(selfPointer).pointee else { + fatalError("Expected enum case '\(enumCase.original.name)', but was '\\(\(selfPointer).pointee)'!") + } + let cache$ = \(JNICaching.cacheName(for: enumCase.original.enumType)).\(JNICaching.cacheMemberName(for: enumCase.original)) + let class$ = cache$.javaClass + let method$ = _JNIMethodIDCache.Method(name: "", signature: "\(methodSignature.mangledName)") + let constructorID$ = cache$[method$] + """ + ) + let upcallArguments = zip(enumCase.parameterConversions, caseNames).map { conversion, caseName in + // '0' is treated the same as a null pointer. + let nullConversion = !conversion.native.javaType.isPrimitive ? " ?? 0" : "" + let result = conversion.native.conversion.render(&printer, caseName) + return "\(result)\(nullConversion)" + } + printer.print( + """ + return withVaList([\(upcallArguments.joined(separator: ", "))]) { + return environment.interface.NewObjectV(environment, class$, constructorID$, $0) + } + """ + ) + } + } + private func printSwiftFunctionThunk( _ printer: inout CodePrinter, _ decl: ImportedFunc @@ -124,21 +232,9 @@ extension JNISwift2JavaGenerator { return } - let nativeSignature = translatedDecl.nativeFunctionSignature - var parameters = nativeSignature.parameters.flatMap(\.parameters) - - if let selfParameter = nativeSignature.selfParameter { - parameters += selfParameter.parameters - } - - parameters += nativeSignature.result.outParameters - printCDecl( &printer, - javaMethodName: translatedDecl.nativeFunctionName, - parentName: translatedDecl.parentName, - parameters: parameters, - resultType: nativeSignature.result.javaType + translatedDecl ) { printer in self.printFunctionDowncall(&printer, decl) } @@ -190,6 +286,18 @@ extension JNISwift2JavaGenerator { .joined(separator: ", ") result = "\(tryClause)\(callee).\(decl.name)(\(downcallArguments))" + case .enumCase: + let downcallArguments = zip( + decl.functionSignature.parameters, + arguments + ).map { originalParam, argument in + let label = originalParam.argumentLabel.map { "\($0): " } ?? "" + return "\(label)\(argument)" + } + + let associatedValues = !downcallArguments.isEmpty ? "(\(downcallArguments.joined(separator: ", ")))" : "" + result = "\(callee).\(decl.name)\(associatedValues)" + case .getter: result = "\(tryClause)\(callee).\(decl.name)" @@ -228,6 +336,31 @@ extension JNISwift2JavaGenerator { } } + private func printCDecl( + _ printer: inout CodePrinter, + _ translatedDecl: TranslatedFunctionDecl, + _ body: (inout CodePrinter) -> Void + ) { + let nativeSignature = translatedDecl.nativeFunctionSignature + var parameters = nativeSignature.parameters.flatMap(\.parameters) + + if let selfParameter = nativeSignature.selfParameter { + parameters += selfParameter.parameters + } + + parameters += nativeSignature.result.outParameters + + printCDecl( + &printer, + javaMethodName: translatedDecl.nativeFunctionName, + parentName: translatedDecl.parentName, + parameters: parameters, + resultType: nativeSignature.result.javaType + ) { printer in + body(&printer) + } + } + private func printCDecl( _ printer: inout CodePrinter, javaMethodName: String, diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index c5f3a5b67..60ec1b8b8 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -39,6 +39,7 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { /// Cached Java translation result. 'nil' indicates failed translation. var translatedDecls: [ImportedFunc: TranslatedFunctionDecl] = [:] + var translatedEnumCases: [ImportedEnumCase: TranslatedEnumCase] = [:] /// Because we need to write empty files for SwiftPM, keep track which files we didn't write yet, /// and write an empty file for those. diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index a933ff3e0..bc6ac0323 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift @@ -46,7 +46,7 @@ final class Swift2JavaVisitor { case .structDecl(let node): self.visit(nominalDecl: node, in: parent) case .enumDecl(let node): - self.visit(nominalDecl: node, in: parent) + self.visit(enumDecl: node, in: parent) case .protocolDecl(let node): self.visit(nominalDecl: node, in: parent) case .extensionDecl(let node): @@ -65,6 +65,8 @@ final class Swift2JavaVisitor { case .subscriptDecl: // TODO: Implement break + case .enumCaseDecl(let node): + self.visit(enumCaseDecl: node, in: parent) default: break @@ -83,6 +85,12 @@ final class Swift2JavaVisitor { } } + func visit(enumDecl node: EnumDeclSyntax, in parent: ImportedNominalType?) { + self.visit(nominalDecl: node, in: parent) + + self.synthesizeRawRepresentableConformance(enumDecl: node, in: parent) + } + func visit(extensionDecl node: ExtensionDeclSyntax, in parent: ImportedNominalType?) { guard parent == nil else { // 'extension' in a nominal type is invalid. Ignore @@ -131,6 +139,49 @@ final class Swift2JavaVisitor { } } + func visit(enumCaseDecl node: EnumCaseDeclSyntax, in typeContext: ImportedNominalType?) { + guard let typeContext else { + self.log.info("Enum case must be within a current type; \(node)") + return + } + + do { + for caseElement in node.elements { + self.log.debug("Import case \(caseElement.name) of enum \(node.qualifiedNameForDebug)") + + let parameters = try caseElement.parameterClause?.parameters.map { + try SwiftEnumCaseParameter($0, lookupContext: translator.lookupContext) + } + + let signature = try SwiftFunctionSignature( + caseElement, + enclosingType: typeContext.swiftType, + lookupContext: translator.lookupContext + ) + + let caseFunction = ImportedFunc( + module: translator.swiftModuleName, + swiftDecl: node, + name: caseElement.name.text, + apiKind: .enumCase, + functionSignature: signature + ) + + let importedCase = ImportedEnumCase( + name: caseElement.name.text, + parameters: parameters ?? [], + swiftDecl: node, + enumType: SwiftNominalType(nominalTypeDecl: typeContext.swiftNominal), + caseFunction: caseFunction + ) + + typeContext.cases.append(importedCase) + } + } catch { + self.log.debug("Failed to import: \(node.qualifiedNameForDebug); \(error)") + } + } + func visit(variableDecl node: VariableDeclSyntax, in typeContext: ImportedNominalType?) { guard node.shouldExtract(config: config, log: log) else { return @@ -213,6 +264,32 @@ final class Swift2JavaVisitor { typeContext.initializers.append(imported) } + + private func synthesizeRawRepresentableConformance(enumDecl node: EnumDeclSyntax, in parent: ImportedNominalType?) { + guard let imported = translator.importedNominalType(node, parent: parent) else { + return + } + + if let firstInheritanceType = imported.swiftNominal.firstInheritanceType, + let inheritanceType = try? SwiftType( + firstInheritanceType, + lookupContext: translator.lookupContext + ), + inheritanceType.isRawTypeCompatible + { + if !imported.variables.contains(where: { $0.name == "rawValue" && $0.functionSignature.result.type != inheritanceType }) { + let decl: DeclSyntax = "public var rawValue: \(raw: inheritanceType.description) { get }" + self.visit(decl: decl, in: imported) + } + + imported.variables.first?.signatureString + + if !imported.initializers.contains(where: { $0.functionSignature.parameters.count == 1 && $0.functionSignature.parameters.first?.parameterName == "rawValue" && $0.functionSignature.parameters.first?.type == inheritanceType }) { + let decl: DeclSyntax = "public init?(rawValue: \(raw: inheritanceType))" + self.visit(decl: decl, in: imported) + } + } + } } extension DeclSyntaxProtocol where Self: WithModifiersSyntax & WithAttributesSyntax { @@ -233,15 +310,6 @@ extension DeclSyntaxProtocol where Self: WithModifiersSyntax & WithAttributesSyn return false } - if let node = self.as(InitializerDeclSyntax.self) { - let isFailable = node.optionalMark != nil - - if isFailable { - log.warning("Skip import '\(self.qualifiedNameForDebug)': failable initializer") - return false - } - } - return true } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftEnumCaseParameter.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftEnumCaseParameter.swift new file mode 100644 index 000000000..55682152d --- /dev/null +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftEnumCaseParameter.swift @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftSyntax + +struct SwiftEnumCaseParameter: Equatable { + var name: String? + var type: SwiftType +} + +extension SwiftEnumCaseParameter { + init( + _ node: EnumCaseParameterSyntax, + lookupContext: SwiftTypeLookupContext + ) throws { + self.init( + name: node.firstName?.identifier?.name, + type: try SwiftType(node.type, lookupContext: lookupContext) + ) + } +} diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift index 85b961101..a5b01bee3 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift @@ -71,11 +71,6 @@ extension SwiftFunctionSignature { throw SwiftFunctionTranslationError.missingEnclosingType(node) } - // We do not yet support failable initializers. - if node.optionalMark != nil { - throw SwiftFunctionTranslationError.failableInitializer(node) - } - let (genericParams, genericRequirements) = try Self.translateGenericParameters( parameterClause: node.genericParameterClause, whereClause: node.genericWhereClause, @@ -86,16 +81,37 @@ extension SwiftFunctionSignature { lookupContext: lookupContext ) + let type = node.optionalMark != nil ? .optional(enclosingType) : enclosingType + self.init( selfParameter: .initializer(enclosingType), parameters: parameters, - result: SwiftResult(convention: .direct, type: enclosingType), + result: SwiftResult(convention: .direct, type: type), effectSpecifiers: effectSpecifiers, genericParameters: genericParams, genericRequirements: genericRequirements ) } + init( + _ node: EnumCaseElementSyntax, + enclosingType: SwiftType, + lookupContext: SwiftTypeLookupContext + ) throws { + let parameters = try node.parameterClause?.parameters.map { param in + try SwiftParameter(param, lookupContext: lookupContext) + } + + self.init( + selfParameter: .initializer(enclosingType), + parameters: parameters ?? [], + result: SwiftResult(convention: .direct, type: enclosingType), + effectSpecifiers: [], + genericParameters: [], + genericRequirements: [] + ) + } + init( _ node: FunctionDeclSyntax, enclosingType: SwiftType?, diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift index cef4e7311..335979a44 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -85,6 +85,14 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { super.init(moduleName: moduleName, name: node.name.text) } + lazy var firstInheritanceType: TypeSyntax? = { + guard let firstInheritanceType = self.syntax?.inheritanceClause?.inheritedTypes.first else { + return nil + } + + return firstInheritanceType.type + }() + /// Returns true if this type conforms to `Sendable` and therefore is "threadsafe". lazy var isSendable: Bool = { // Check if Sendable is in the inheritance list diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParameter.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParameter.swift index 75d165e9b..63f7d75bc 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParameter.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParameter.swift @@ -57,6 +57,16 @@ enum SwiftParameterConvention: Equatable { case `inout` } +extension SwiftParameter { + init(_ node: EnumCaseParameterSyntax, lookupContext: SwiftTypeLookupContext) throws { + self.convention = .byValue + self.type = try SwiftType(node.type, lookupContext: lookupContext) + self.argumentLabel = nil + self.parameterName = node.firstName?.identifier?.name + self.argumentLabel = node.firstName?.identifier?.name + } +} + extension SwiftParameter { init(_ node: FunctionParameterSyntax, lookupContext: SwiftTypeLookupContext) throws { // Determine the convention. The default is by-value, but there are diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index 3cc14406a..58bb65c39 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -103,6 +103,19 @@ enum SwiftType: Equatable { default: false } } + + var isRawTypeCompatible: Bool { + switch self { + case .nominal(let nominal): + switch nominal.nominalTypeDecl.knownTypeKind { + case .int, .uint, .int8, .uint8, .int16, .uint16, .int32, .uint32, .int64, .uint64, .float, .double, .string: + true + default: + false + } + default: false + } + } } extension SwiftType: CustomStringConvertible { diff --git a/Sources/JavaKit/Helpers/_JNIMethodIDCache.swift b/Sources/JavaKit/Helpers/_JNIMethodIDCache.swift new file mode 100644 index 000000000..a67d225f5 --- /dev/null +++ b/Sources/JavaKit/Helpers/_JNIMethodIDCache.swift @@ -0,0 +1,59 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +/// A cache used to hold references for JNI method and classes. +/// +/// This type is used internally in by the outputted JExtract wrappers +/// to improve performance of any JNI lookups. +public final class _JNIMethodIDCache: Sendable { + public struct Method: Hashable { + public let name: String + public let signature: String + + public init(name: String, signature: String) { + self.name = name + self.signature = signature + } + } + + nonisolated(unsafe) let _class: jclass? + nonisolated(unsafe) let methods: [Method: jmethodID] + + public var javaClass: jclass { + self._class! + } + + public init(environment: UnsafeMutablePointer!, className: String, methods: [Method]) { + guard let clazz = environment.interface.FindClass(environment, className) else { + fatalError("Class \(className) could not be found!") + } + self._class = environment.interface.NewGlobalRef(environment, clazz)! + self.methods = methods.reduce(into: [:]) { (result, method) in + if let methodID = environment.interface.GetMethodID(environment, clazz, method.name, method.signature) { + result[method] = methodID + } else { + fatalError("Method \(method.signature) with signature \(method.signature) not found in class \(className)") + } + } + } + + + public subscript(_ method: Method) -> jmethodID? { + methods[method] + } + + public func cleanup(environment: UnsafeMutablePointer!) { + environment.interface.DeleteGlobalRef(environment, self._class) + } +} diff --git a/Sources/JavaTypes/Mangling.swift b/Sources/JavaTypes/Mangling.swift index 74e2e0b83..f0dbd4840 100644 --- a/Sources/JavaTypes/Mangling.swift +++ b/Sources/JavaTypes/Mangling.swift @@ -36,7 +36,7 @@ extension JavaType { case .void: "V" case .array(let elementType): "[" + elementType.mangledName case .class(package: let package, name: let name): - "L\(package!).\(name);".replacingPeriodsWithSlashes() + "L\(package!).\(name.replacingPeriodsWithDollars());".replacingPeriodsWithSlashes() } } } @@ -145,4 +145,9 @@ extension StringProtocol { fileprivate func replacingSlashesWithPeriods() -> String { return String(self.map { $0 == "/" ? "." as Character : $0 }) } + + /// Return the string after replacing all of the periods (".") with slashes ("$"). + fileprivate func replacingPeriodsWithDollars() -> String { + return String(self.map { $0 == "." ? "$" as Character : $0 }) + } } diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 130c333bc..c5de6c454 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -49,7 +49,8 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Initializers: `class`, `struct` | ✅ | ✅ | | Optional Initializers / Throwing Initializers | ❌ | ❌ | | Deinitializers: `class`, `struct` | ✅ | ✅ | -| `enum`, `actor` | ❌ | ❌ | +| `enum` | ❌ | ✅ | +| `actor` | ❌ | ❌ | | Global Swift `func` | ✅ | ✅ | | Class/struct member `func` | ✅ | ✅ | | Throwing functions: `func x() throws` | ❌ | ✅ | @@ -157,3 +158,113 @@ you are expected to add a Guava dependency to your Java project. | `Double` | `double` | > Note: The `wrap-guava` mode is currently only available in FFM mode of jextract. + +### Enums + +> Note: Enums are currently only supported in JNI mode. + +Swift enums are extracted into a corresponding Java `class`. To support associated values +all cases are also extracted as Java `record`s. + +Consider the following Swift enum: +```swift +public enum Vehicle { + case car(String) + case bicycle(maker: String) +} +``` +You can then instantiate a case of `Vehicle` by using one of the static methods: +```java +try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.car("BMW", arena); + Optional car = vehicle.getAsCar(); + assertEquals("BMW", car.orElseThrow().arg0()); +} +``` +As you can see above, to access the associated values of a case you can call one of the +`getAsX` methods that will return an Optional record with the associated values. +```java +try (var arena = SwiftArena.ofConfined()) { + Vehicle vehicle = Vehicle.bycicle("My Brand", arena); + Optional car = vehicle.getAsCar(); + assertFalse(car.isPresent()); + + Optional bicycle = vehicle.getAsBicycle(); + assertEquals("My Brand", bicycle.orElseThrow().maker()); +} +``` + +#### Switching and pattern matching + +If you only need to switch on the case and not access any associated values, +you can use the `getDiscriminator()` method: +```java +Vehicle vehicle = ...; +switch (vehicle.getDiscriminator()) { + case BICYCLE: + System.out.println("I am a bicycle!"); + break + case CAR: + System.out.println("I am a car!"); + break +} +``` +If you also want access to the associated values, you have various options +depending on the Java version you are using. +If you are running Java 21+ you can use [pattern matching for switch](https://openjdk.org/jeps/441): +```java +Vehicle vehicle = ...; +switch (vehicle.getCase()) { + case Vehicle.Bicycle b: + System.out.println("Bicycle maker: " + b.maker()); + break + case Vehicle.Car c: + System.out.println("Car: " + c.arg0()); + break +} +``` +For Java 16+ you can use [pattern matching for instanceof](https://openjdk.org/jeps/394) +```java +Vehicle vehicle = ...; +Vehicle.Case case = vehicle.getCase(); +if (case instanceof Vehicle.Bicycle b) { + System.out.println("Bicycle maker: " + b.maker()); +} else if(case instanceof Vehicle.Car c) { + System.out.println("Car: " + c.arg0()); +} +``` +For any previous Java versions you can resort to casting the `Case` to the expected type: +```java +Vehicle vehicle = ...; +Vehicle.Case case = vehicle.getCase(); +if (case instanceof Vehicle.Bicycle) { + Vehicle.Bicycle b = (Vehicle.Bicycle) case; + System.out.println("Bicycle maker: " + b.maker()); +} else if(case instanceof Vehicle.Car) { + Vehicle.Car c = (Vehicle.Car) case; + System.out.println("Car: " + c.arg0()); +} +``` + +#### RawRepresentable enums + +JExtract also supports extracting enums that conform to `RawRepresentable` +by giving access to an optional initializer and the `rawValue` variable. +Consider the following example: +```swift +public enum Alignment: String { + case horizontal + case vertical +} +``` +you can then initialize `Alignment` from a `String` and also retrieve back its `rawValue`: +```java +try (var arena = SwiftArena.ofConfined()) { + Optional alignment = Alignment.init("horizontal", arena); + assertEqual(HORIZONTAL, alignment.orElseThrow().getDiscriminator()); + assertEqual("horizontal", alignment.orElseThrow().getRawValue()); +} +``` + + + diff --git a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift new file mode 100644 index 000000000..477650437 --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift @@ -0,0 +1,313 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import Testing + +@Suite +struct JNIEnumTests { + let source = """ + public enum MyEnum { + case first + case second(String) + case third(x: Int64, y: Int32) + } + """ + + @Test + func generatesJavaClass() throws { + try assertOutput( + input: source, + .jni, .java, + expectedChunks: [ + """ + // Generated by jextract-swift + // Swift module: SwiftModule + + package com.example.swift; + + import org.swift.swiftkit.core.*; + import org.swift.swiftkit.core.util.*; + """, + """ + public final class MyEnum extends JNISwiftInstance { + static final String LIB_NAME = "SwiftModule"; + + @SuppressWarnings("unused") + private static final boolean INITIALIZED_LIBS = initializeLibs(); + static boolean initializeLibs() { + System.loadLibrary(LIB_NAME); + return true; + } + """, + """ + private MyEnum(long selfPointer, SwiftArena swiftArena) { + super(selfPointer, swiftArena); + } + """, + """ + public static MyEnum wrapMemoryAddressUnsafe(long selfPointer, SwiftArena swiftArena) { + return new MyEnum(selfPointer, swiftArena); + } + """, + """ + private static native void $destroy(long selfPointer); + """, + """ + @Override + protected Runnable $createDestroyFunction() { + long self$ = this.$memoryAddress(); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("MyEnum.$createDestroyFunction", + "this", this, + "self", self$); + } + return new Runnable() { + @Override + public void run() { + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("MyEnum.$destroy", "self", self$); + } + MyEnum.$destroy(self$); + } + }; + """ + ]) + } + + @Test + func generatesDiscriminator_java() throws { + try assertOutput( + input: source, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public enum Discriminator { + FIRST, + SECOND, + THIRD + } + """, + """ + public Discriminator getDiscriminator() { + return Discriminator.values()[$getDiscriminator(this.$memoryAddress())]; + } + """, + """ + private static native int $getDiscriminator(long self); + """ + ]) + } + + @Test + func generatesDiscriminator_swift() throws { + try assertOutput( + input: source, + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyEnum__00024getDiscriminator__J") + func Java_com_example_swift_MyEnum__00024getDiscriminator__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jint { + ... + switch (self$.pointee) { + case .first: return 0 + case .second: return 1 + case .third: return 2 + } + } + """ + ]) + } + + @Test + func generatesCases_java() throws { + try assertOutput( + input: source, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public sealed interface Case {} + """, + """ + public Case getCase() { + Discriminator discriminator = this.getDiscriminator(); + switch (discriminator) { + case FIRST: return this.getAsFirst().orElseThrow(); + case SECOND: return this.getAsSecond().orElseThrow(); + case THIRD: return this.getAsThird().orElseThrow(); + } + throw new RuntimeException("Unknown discriminator value " + discriminator); + } + """, + """ + public record First() implements Case { + record $NativeParameters() {} + } + """, + """ + public record Second(java.lang.String arg0) implements Case { + record $NativeParameters(java.lang.String arg0) {} + } + """, + """ + public record Third(long x, int y) implements Case { + record $NativeParameters(long x, int y) {} + } + """ + ]) + } + + @Test + func generatesCaseInitializers_java() throws { + try assertOutput( + input: source, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public static MyEnum first(SwiftArena swiftArena$) { + return MyEnum.wrapMemoryAddressUnsafe(MyEnum.$first(), swiftArena$); + } + """, + """ + public static MyEnum second(java.lang.String arg0, SwiftArena swiftArena$) { + return MyEnum.wrapMemoryAddressUnsafe(MyEnum.$second(arg0), swiftArena$); + } + """, + """ + public static MyEnum third(long x, int y, SwiftArena swiftArena$) { + return MyEnum.wrapMemoryAddressUnsafe(MyEnum.$third(x, y), swiftArena$); + } + """ + ]) + } + + @Test + func generatesCaseInitializers_swift() throws { + try assertOutput( + input: source, + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyEnum__00024first__") + func Java_com_example_swift_MyEnum__00024first__(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong { + let result$ = UnsafeMutablePointer.allocate(capacity: 1) + result$.initialize(to: MyEnum.first) + let resultBits$ = Int64(Int(bitPattern: result$)) + return resultBits$.getJNIValue(in: environment!) + } + """, + """ + @_cdecl("Java_com_example_swift_MyEnum__00024second__Ljava_lang_String_2") + func Java_com_example_swift_MyEnum__00024second__Ljava_lang_String_2(environment: UnsafeMutablePointer!, thisClass: jclass, arg0: jstring?) -> jlong { + let result$ = UnsafeMutablePointer.allocate(capacity: 1) + result$.initialize(to: MyEnum.second(String(fromJNI: arg0, in: environment!))) + let resultBits$ = Int64(Int(bitPattern: result$)) + return resultBits$.getJNIValue(in: environment!) + } + """, + """ + @_cdecl("Java_com_example_swift_MyEnum__00024third__JI") + func Java_com_example_swift_MyEnum__00024third__JI(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jint) -> jlong { + let result$ = UnsafeMutablePointer.allocate(capacity: 1) + result$.initialize(to: MyEnum.third(x: Int64(fromJNI: x, in: environment!), y: Int32(fromJNI: y, in: environment!))) + let resultBits$ = Int64(Int(bitPattern: result$)) + return resultBits$.getJNIValue(in: environment!) + } + """ + ]) + } + + @Test + func generatesGetAsCase_java() throws { + try assertOutput( + input: source, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public Optional getAsFirst() { + if (getDiscriminator() != Discriminator.FIRST) { + return Optional.empty(); + } + return Optional.of(new First()); + } + """, + """ + public Optional getAsSecond() { + if (getDiscriminator() != Discriminator.SECOND) { + return Optional.empty(); + } + Second.$NativeParameters $nativeParameters = MyEnum.$getAsSecond(this.$memoryAddress()); + return Optional.of(new Second($nativeParameters.arg0)); + } + """, + """ + public Optional getAsThird() { + if (getDiscriminator() != Discriminator.THIRD) { + return Optional.empty(); + } + Third.$NativeParameters $nativeParameters = MyEnum.$getAsThird(this.$memoryAddress()); + return Optional.of(new Third($nativeParameters.x, $nativeParameters.y)); + } + """ + ]) + } + + @Test + func generatesGetAsCase_swift() throws { + try assertOutput( + input: source, + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyEnum__00024getAsSecond__J") + func Java_com_example_swift_MyEnum__00024getAsSecond__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jobject? { + ... + guard case .second(let _0) = self$.pointee else { + fatalError("Expected enum case 'second', but was '\\(self$.pointee)'!") + } + let cache$ = _JNI_MyEnum.myEnumSecondCache + let class$ = cache$.javaClass + let method$ = _JNIMethodIDCache.Method(name: "", signature: "(Ljava/lang/String;)V") + let constructorID$ = cache$[method$] + return withVaList([_0.getJNIValue(in: environment!) ?? 0]) { + return environment.interface.NewObjectV(environment, class$, constructorID$, $0) + } + } + """, + """ + @_cdecl("Java_com_example_swift_MyEnum__00024getAsThird__J") + func Java_com_example_swift_MyEnum__00024getAsThird__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jobject? { + ... + guard case .third(let x, let y) = self$.pointee else { + fatalError("Expected enum case 'third', but was '\\(self$.pointee)'!") + } + let cache$ = _JNI_MyEnum.myEnumThirdCache + let class$ = cache$.javaClass + let method$ = _JNIMethodIDCache.Method(name: "", signature: "(JI)V") + let constructorID$ = cache$[method$] + return withVaList([x.getJNIValue(in: environment!), y.getJNIValue(in: environment!)]) { + return environment.interface.NewObjectV(environment, class$, constructorID$, $0) + } + } + """ + ]) + } +} diff --git a/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift index 2186e2279..548a2eacc 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift @@ -47,10 +47,10 @@ struct JNIOptionalTests { * } */ public static OptionalInt optionalSugar(OptionalLong arg) { - long combined$ = SwiftModule.$optionalSugar((byte) (arg.isPresent() ? 1 : 0), arg.orElse(0L)); - byte discriminator$ = (byte) (combined$ & 0xFF); - int value$ = (int) (combined$ >> 32); - return discriminator$ == 1 ? OptionalInt.of(value$) : OptionalInt.empty(); + long result_combined$ = SwiftModule.$optionalSugar((byte) (arg.isPresent() ? 1 : 0), arg.orElse(0L)); + byte result_discriminator$ = (byte) (result_combined$ & 0xFF); + int result_value$ = (int) (result_combined$ >> 32); + return result_discriminator$ == 1 ? OptionalInt.of(result_value$) : OptionalInt.empty(); } """, """ @@ -72,10 +72,10 @@ struct JNIOptionalTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024optionalSugar__BJ") func Java_com_example_swift_SwiftModule__00024optionalSugar__BJ(environment: UnsafeMutablePointer!, thisClass: jclass, arg_discriminator: jbyte, arg_value: jlong) -> jlong { - let value$ = SwiftModule.optionalSugar(arg_discriminator == 1 ? Int64(fromJNI: arg_value, in: environment!) : nil).map { + let result_value$ = SwiftModule.optionalSugar(arg_discriminator == 1 ? Int64(fromJNI: arg_value, in: environment!) : nil).map { Int64($0) << 32 | Int64(1) } ?? 0 - return value$.getJNIValue(in: environment!) + return result_value$.getJNIValue(in: environment!) } """ ] @@ -98,9 +98,9 @@ struct JNIOptionalTests { * } */ public static Optional optionalExplicit(Optional arg) { - byte[] result_discriminator$ = new byte[1]; - java.lang.String result$ = SwiftModule.$optionalExplicit((byte) (arg.isPresent() ? 1 : 0), arg.orElse(null), result_discriminator$); - return (result_discriminator$[0] == 1) ? Optional.of(result$) : Optional.empty(); + byte[] result$_discriminator$ = new byte[1]; + java.lang.String result$ = SwiftModule.$optionalExplicit((byte) (arg.isPresent() ? 1 : 0), arg.orElse(null), result$_discriminator$); + return (result$_discriminator$[0] == 1) ? Optional.of(result$) : Optional.empty(); } """, """ @@ -127,12 +127,12 @@ struct JNIOptionalTests { result$ = innerResult$.getJNIValue(in: environment!) var flag$ = Int8(1) environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) - } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:624 + } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:649 else { result$ = String.jniPlaceholderValue var flag$ = Int8(0) environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) - } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:634 + } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:659 return result$ } """ @@ -156,9 +156,9 @@ struct JNIOptionalTests { * } */ public static Optional optionalClass(Optional arg, SwiftArena swiftArena$) { - byte[] result_discriminator$ = new byte[1]; - long result$ = SwiftModule.$optionalClass(arg.map(MyClass::$memoryAddress).orElse(0L), result_discriminator$); - return (result_discriminator$[0] == 1) ? Optional.of(MyClass.wrapMemoryAddressUnsafe(result$, swiftArena$)) : Optional.empty(); + byte[] result$_discriminator$ = new byte[1]; + long result$ = SwiftModule.$optionalClass(arg.map(MyClass::$memoryAddress).orElse(0L), result$_discriminator$); + return (result$_discriminator$[0] == 1) ? Optional.of(MyClass.wrapMemoryAddressUnsafe(result$, swiftArena$)) : Optional.empty(); } """, """ @@ -190,12 +190,12 @@ struct JNIOptionalTests { result$ = _resultBits$.getJNIValue(in: environment!) var flag$ = Int8(1) environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) - } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:624 + } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:649 else { result$ = 0 var flag$ = Int8(0) environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) - } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:634 + } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:659 return result$ } """ @@ -243,7 +243,7 @@ struct JNIOptionalTests { func Java_com_example_swift_SwiftModule__00024optionalJavaKitClass__Ljava_lang_Long_2(environment: UnsafeMutablePointer!, thisClass: jclass, arg: jobject?) { SwiftModule.optionalJavaKitClass(arg.map { return JavaLong(javaThis: $0, environment: environment!) - } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:666 + } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:691 ) } """ From 612f04e48c430d4e38df21c51e95d52de31c08d3 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 20 Aug 2025 16:22:52 +0900 Subject: [PATCH 068/141] Disable SPI docs for jni dependent targets (#369) docc plugin can't handle targets which need additional libraries, so we're blocked on generating docs for them in SPI until https://github.com/swiftlang/swift-docc-plugin/issues/112 is resolved --- .spi.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.spi.yml b/.spi.yml index e7159a311..f4074e0e3 100644 --- a/.spi.yml +++ b/.spi.yml @@ -2,9 +2,7 @@ version: 1 builder: configs: - documentation_targets: [ - SwiftJavaDocumentation, - JavaKit, - SwiftKitSwift + SwiftJavaDocumentation ] # Drop this version pinning once 6.2 is released and docs are built with 6.2 by default to prevent it staying on 6.2 forever. swift_version: 6.2 From 1d2014849d325ea4efc6141114d54ee36ce3fcd1 Mon Sep 17 00:00:00 2001 From: David Ko Date: Mon, 25 Aug 2025 04:11:55 -0400 Subject: [PATCH 069/141] Update on SwiftJavaDocumentation (#372) Co-authored-by: Konrad `ktoso` Malawski --- .../Documentation.docc/SupportedFeatures.md | 4 ++-- .../Documentation.docc/index.md | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index c5de6c454..8d1931496 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -33,10 +33,10 @@ Java `native` functions. JavaKit simplifies the type conversions | Java `abstract class` | TODO | | Java `enum` | ❌ | | Java methods: `static`, member | ✅ `@JavaMethod` | -| **This list is very work in progress** | | +| **This list is very work in progress** | | -### JExtract: Java -> Swift +### JExtract – calling Swift from Java SwiftJava's `swift-java jextract` tool automates generating Java bindings from Swift sources. diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/index.md b/Sources/SwiftJavaDocumentation/Documentation.docc/index.md index 7c695a710..460f396d5 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/index.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/index.md @@ -11,9 +11,21 @@ Please refer to articles about the specific direction of interoperability you ar ### Getting started -TODO: Some general intro +**SwiftJava** provides a set of tools and libraries to enable Java and Swift interoperability. It allows developers to generate bindings to either language from the other, by using either source generation (for Java consuming Swift code) or a combination of Swift macros and source generation (for Swift consuming Java libraries). -If you prefer a video introduction, you may want to this +The generated code is highly efficient and less error-prone than manually mapping, and also guarantees memory safety across the boundaries between the languages. + +Reasons why you might want to reach for Swift and Java interoperability include, but are not limited to, the following scenarios: +- Incremental adoption of Swift in an existing Java codebase +- Reuse existing libraries which exist in one ecosystem, but don't have a direct equivalent in the other + +SwiftJava is offering several core libraries which support language interoperability: +- `JavaKit` (Swift -> Java) - JNI-based support library and Swift macros +- `SwiftKit` (Java -> Swift) - Support library for Java calling Swift code (either using JNI or FFM) +- `swift-java` - command line tool; Supports source generation and also dependency management operations +- Build tool integration - SwiftPM Plugin + +If you prefer a video introduction, you may want to watch this [Explore Swift and Java interoperability](https://www.youtube.com/watch?v=QSHO-GUGidA) WWDC 2025 session, which is a quick overview of all the features and approaches offered by SwiftJava. @@ -25,7 +37,7 @@ which is a quick overview of all the features and approaches offered by SwiftJav - -### Source generation +### Source Generation - - From caa97a4e841ef9859fe93b8c754422e1cb73bd7a Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Tue, 26 Aug 2025 07:26:47 +0900 Subject: [PATCH 070/141] Reorganize and rename modules; Centralize around SwiftJava prefix and naming (#366) --- .github/workflows/pull_request.yml | 8 +- .../JavaApiCallBenchmarks.swift | 4 +- Benchmarks/Package.swift | 6 +- Package.swift | 137 ++++++++++-------- .../JExtractSwiftPlugin.swift | 2 +- .../JavaCompilerPlugin.swift | 2 +- .../PluginsShared/JavaKitConfigurationShared | 1 - .../SwiftJavaConfigurationShared | 1 + Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift | 2 +- README.md | 2 +- Samples/JavaDependencySampleApp/Package.swift | 16 +- .../Sources/JavaDependencySample/main.swift | 8 +- Samples/JavaKitSampleApp/Package.swift | 6 +- .../JavaKitExample/JavaKitExample.swift | 4 +- Samples/JavaProbablyPrime/Package.swift | 4 +- .../Sources/JavaProbablyPrime/prime.swift | 2 +- Samples/JavaSieve/Package.swift | 8 +- .../JavaSieve/Sources/JavaSieve/main.swift | 2 +- .../Package.swift | 6 +- .../Sources/MySwiftLibrary/MySwiftClass.swift | 0 .../MySwiftLibrary/MySwiftLibrary.swift | 0 .../MySwiftLibrary/MySwiftStruct.swift | 0 .../jni/JNIImplementations.swift | 4 +- .../Sources/MySwiftLibrary/swift-java.config | 0 .../build.gradle | 0 .../ci-validate.sh | 0 .../gradle.properties | 0 .../gradlew | 0 .../gradlew.bat | 0 .../swiftkit/ffm/JavaToSwiftBenchmark.java | 0 .../swiftkit/ffm/StringPassingBenchmark.java | 0 .../com/example/swift/HelloJava2Swift.java | 0 .../com/example/swift/DataImportTest.java | 0 .../com/example/swift/MySwiftClassTest.java | 0 .../com/example/swift/MySwiftLibraryTest.java | 0 .../com/example/swift/OptionalImportTest.java | 0 .../example/swift/UnsignedNumbersTest.java | 0 .../swift/swiftkitffm/MySwiftClassTest.java | 0 .../swift/swiftkitffm/MySwiftStructTest.java | 0 .../org/swift/swiftkitffm/SwiftArenaTest.java | 0 .../Package.swift | 4 +- .../Sources/MySwiftLibrary/Alignment.swift | 0 .../Sources/MySwiftLibrary/Closures.swift | 0 .../Sources/MySwiftLibrary/MySwiftClass.swift | 2 +- .../MySwiftLibrary/MySwiftLibrary.swift | 0 .../MySwiftLibrary/MySwiftStruct.swift | 0 .../Sources/MySwiftLibrary/Optionals.swift | 2 +- .../Sources/MySwiftLibrary/Vehicle.swift | 0 .../Sources/MySwiftLibrary/swift-java.config | 0 .../build.gradle | 0 .../ci-validate.sh | 0 .../gradle.properties | 0 .../gradlew | 0 .../gradlew.bat | 0 .../com/example/swift/HelloJava2SwiftJNI.java | 0 .../com/example/swift/AlignmentEnumTest.java | 0 .../java/com/example/swift/ClosuresTest.java | 0 .../com/example/swift/MySwiftClassTest.java | 0 .../com/example/swift/MySwiftLibraryTest.java | 0 .../com/example/swift/MySwiftStructTest.java | 0 .../java/com/example/swift/OptionalsTest.java | 0 .../com/example/swift/VehicleEnumTest.java | 0 Sources/{JavaRuntime => CJNI}/dummy.c | 0 .../JavaRuntime.h => CJNI/include/CJNI.h} | 6 +- Sources/CJNI/include/module.modulemap | 4 + .../Common/TypeAnnotations.swift | 2 +- .../Configuration+Extensions.swift | 4 +- .../Convenience/String+Extensions.swift | 2 +- ...MSwift2JavaGenerator+JavaTranslation.swift | 2 +- .../FFM/FFMSwift2JavaGenerator.swift | 2 +- .../JNI/JNIJavaTypeTranslator.swift | 2 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 2 +- ...wift2JavaGenerator+NativeTranslation.swift | 2 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 4 +- .../JNI/JNISwift2JavaGenerator.swift | 2 +- Sources/JExtractSwiftLib/Logger.swift | 4 +- Sources/JExtractSwiftLib/Swift2Java.swift | 4 +- .../Swift2JavaTranslator.swift | 2 +- .../JExtractSwiftLib/Swift2JavaVisitor.swift | 2 +- .../DependencyResolver.swift | 6 +- .../swift-java.config | 2 +- Sources/JavaRuntime/include/module.modulemap | 4 - .../generated/BufferedInputStream.swift | 4 +- .../JavaIO}/generated/Charset.swift | 4 +- .../JavaIO}/generated/Closeable.swift | 4 +- .../JavaIO}/generated/File.swift | 4 +- .../JavaIO}/generated/FileDescriptor.swift | 4 +- .../JavaIO}/generated/FileReader.swift | 4 +- .../JavaIO}/generated/Flushable.swift | 4 +- .../JavaIO}/generated/InputStream.swift | 4 +- .../JavaIO}/generated/InputStreamReader.swift | 4 +- .../JavaIO}/generated/OutputStream.swift | 4 +- .../JavaIO}/generated/Path.swift | 4 +- .../JavaIO}/generated/Readable.swift | 4 +- .../JavaIO}/generated/Reader.swift | 4 +- .../JavaIO}/generated/StringReader.swift | 4 +- .../JavaIO}/generated/WatchService.swift | 4 +- .../JavaIO}/generated/Writer.swift | 4 +- .../JavaIO}/swift-java.config | 0 .../Constructor+Utilities.swift | 0 .../Executable+Utilities.swift | 2 +- .../JavaLangReflect}/Field+Utilities.swift | 0 .../JavaClass+Reflection.swift | 2 +- .../JavaLangReflect}/Method+Utilities.swift | 0 .../generated/AccessibleObject.swift | 4 +- .../generated/AnnotatedType.swift | 4 +- .../generated/Annotation.swift | 4 +- .../generated/Constructor.swift | 4 +- .../generated/Executable.swift | 6 +- .../JavaLangReflect}/generated/Field.swift | 6 +- .../generated/GenericArrayType.swift | 4 +- .../generated/GenericDeclaration.swift | 4 +- .../JavaLangReflect}/generated/Method.swift | 4 +- .../generated/Parameter.swift | 6 +- .../generated/ParameterizedType.swift | 4 +- .../JavaLangReflect}/generated/Type.swift | 4 +- .../generated/TypeVariable.swift | 4 +- .../generated/WildcardType.swift | 4 +- .../JavaLangReflect}/swift-java.config | 0 .../JavaNet}/generated/URI.swift | 4 +- .../JavaNet}/generated/URL.swift | 4 +- .../JavaNet}/generated/URLClassLoader.swift | 6 +- .../JavaNet}/generated/URLConnection.swift | 4 +- .../JavaNet}/swift-java.config | 0 .../JavaUtil}/JavaEnumeration+Sequence.swift | 0 .../JavaUtil}/JavaIterator+Iterator.swift | 2 +- .../JavaUtil}/List+Sequence.swift | 0 .../JavaUtil}/generated/ArrayDeque.swift | 4 +- .../JavaUtil}/generated/ArrayList.swift | 4 +- .../JavaUtil}/generated/BitSet.swift | 4 +- .../JavaUtil}/generated/Enumeration.swift | 4 +- .../JavaUtil}/generated/HashMap.swift | 4 +- .../JavaUtil}/generated/HashSet.swift | 4 +- .../JavaUtil}/generated/JavaCollection.swift | 4 +- .../JavaUtil}/generated/JavaDictionary.swift | 4 +- .../JavaUtil}/generated/JavaIterator.swift | 4 +- .../JavaUtil}/generated/JavaSet.swift | 4 +- .../JavaUtil}/generated/LinkedList.swift | 4 +- .../JavaUtil}/generated/List.swift | 4 +- .../JavaUtil}/generated/ListIterator.swift | 4 +- .../JavaUtil}/generated/PriorityQueue.swift | 4 +- .../JavaUtil}/generated/Queue.swift | 4 +- .../JavaUtil}/generated/RandomAccess.swift | 4 +- .../JavaUtil}/generated/Stack.swift | 4 +- .../JavaUtil}/generated/TreeMap.swift | 4 +- .../JavaUtil}/generated/TreeSet.swift | 4 +- .../JavaUtil}/swift-java.config | 0 .../generated/JavaBiConsumer.swift | 4 +- .../generated/JavaBiFunction.swift | 4 +- .../generated/JavaBiPredicate.swift | 4 +- .../generated/JavaBinaryOperator.swift | 4 +- .../generated/JavaBooleanSupplier.swift | 4 +- .../generated/JavaConsumer.swift | 4 +- .../generated/JavaDoubleBinaryOperator.swift | 4 +- .../generated/JavaDoubleConsumer.swift | 4 +- .../generated/JavaDoubleFunction.swift | 4 +- .../generated/JavaDoublePredicate.swift | 4 +- .../generated/JavaDoubleSupplier.swift | 4 +- .../generated/JavaDoubleToIntFunction.swift | 4 +- .../generated/JavaDoubleToLongFunction.swift | 4 +- .../generated/JavaDoubleUnaryOperator.swift | 4 +- .../generated/JavaFunction.swift | 4 +- .../generated/JavaIntBinaryOperator.swift | 4 +- .../generated/JavaIntConsumer.swift | 4 +- .../generated/JavaIntFunction.swift | 4 +- .../generated/JavaIntPredicate.swift | 4 +- .../generated/JavaIntSupplier.swift | 4 +- .../generated/JavaIntToDoubleFunction.swift | 4 +- .../generated/JavaIntToLongFunction.swift | 4 +- .../generated/JavaIntUnaryOperator.swift | 4 +- .../generated/JavaLongBinaryOperator.swift | 4 +- .../generated/JavaLongConsumer.swift | 4 +- .../generated/JavaLongFunction.swift | 4 +- .../generated/JavaLongPredicate.swift | 4 +- .../generated/JavaLongSupplier.swift | 4 +- .../generated/JavaLongToDoubleFunction.swift | 4 +- .../generated/JavaLongToIntFunction.swift | 4 +- .../generated/JavaLongUnaryOperator.swift | 4 +- .../generated/JavaObjDoubleConsumer.swift | 4 +- .../generated/JavaObjIntConsumer.swift | 4 +- .../generated/JavaObjLongConsumer.swift | 4 +- .../generated/JavaPredicate.swift | 4 +- .../generated/JavaSupplier.swift | 4 +- .../generated/JavaToDoubleBiFunction.swift | 4 +- .../generated/JavaToDoubleFunction.swift | 4 +- .../generated/JavaToIntBiFunction.swift | 4 +- .../generated/JavaToIntFunction.swift | 4 +- .../generated/JavaToLongBiFunction.swift | 4 +- .../generated/JavaToLongFunction.swift | 4 +- .../generated/JavaUnaryOperator.swift | 4 +- .../JavaUtilFunction}/swift-java.config | 0 .../JavaUtilJar}/generated/Attributes.swift | 6 +- .../JavaUtilJar}/generated/JarEntry.swift | 4 +- .../JavaUtilJar}/generated/JarFile.swift | 6 +- .../generated/JarInputStream.swift | 4 +- .../generated/JarOutputStream.swift | 4 +- .../JavaUtilJar}/generated/Manifest.swift | 4 +- .../JavaUtilJar}/generated/ZipEntry.swift | 4 +- .../JavaUtilJar}/swift-java.config | 0 .../JavaStdlib/README_PACKAGE_CONVENTION.md | 13 ++ .../AnyJavaObject.swift | 2 +- .../BridgedValues/JavaValue+Array.swift | 0 .../BridgedValues/JavaValue+Bool.swift | 0 .../JavaValue+FloatingPoint.swift | 0 .../BridgedValues/JavaValue+Integers.swift | 0 .../BridgedValues/JavaValue+String.swift | 0 .../Documentation.docc/JavaKit.md | 2 +- .../Exceptions/Exception+Error.swift | 0 .../Exceptions/ExceptionHandling.swift | 0 .../Exceptions/Throwable+Error.swift | 0 .../SwiftJava/Helpers/_JNIMethodIDCache.swift | 59 ++++++++ .../JavaClass+Initialization.swift | 2 +- .../JavaEnvironment.swift | 2 +- .../JavaKitVM/JavaVirtualMachine.swift | 0 .../JavaKitVM/LockedState.swift | 0 .../JavaKitVM/ThreadLocalStorage.swift | 0 .../JavaObject+Inheritance.swift | 2 +- .../JavaObject+MethodCalls.swift | 2 +- .../JavaObjectHolder.swift | 2 +- .../JavaRuntime+Reexport.swift | 2 +- .../{JavaKit => SwiftJava}/JavaValue.swift | 2 +- Sources/{JavaKit => SwiftJava}/Macros.swift | 14 +- .../Optional+JavaObject.swift | 2 +- .../Optional+JavaOptional.swift | 0 .../generated/Appendable.swift | 2 +- .../generated/CharSequence.swift | 2 +- .../generated/Exception.swift | 2 +- .../generated/JavaArray.swift | 2 +- .../generated/JavaBoolean.swift | 2 +- .../generated/JavaByte.swift | 2 +- .../generated/JavaCharacter.swift | 2 +- .../generated/JavaClass.swift | 2 +- .../generated/JavaClassLoader.swift | 2 +- .../generated/JavaDouble.swift | 2 +- .../generated/JavaError.swift | 2 +- .../generated/JavaFloat.swift | 2 +- .../generated/JavaInteger.swift | 2 +- .../generated/JavaLong.swift | 2 +- .../generated/JavaNumber.swift | 2 +- .../generated/JavaObject.swift | 2 +- .../generated/JavaOptional.swift | 2 +- .../generated/JavaOptionalDouble.swift | 2 +- .../generated/JavaOptionalInt.swift | 2 +- .../generated/JavaOptionalLong.swift | 2 +- .../generated/JavaShort.swift | 2 +- .../generated/JavaString.swift | 2 +- .../generated/JavaVoid.swift | 2 +- .../generated/RuntimeException.swift | 2 +- .../generated/Throwable.swift | 2 +- .../{JavaKit => SwiftJava}/swift-java.config | 0 .../Configuration.swift | 0 .../GenerationMode.swift | 0 .../GradleDependencyParsing.swift | 0 .../SwiftJavaCommandLineTool.md | 8 +- .../GenerationMode.swift | 0 .../ImplementsJavaMacro.swift | 0 .../JavaClassMacro.swift | 0 .../JavaFieldMacro.swift | 0 .../JavaMethodMacro.swift | 0 .../MacroErrors.swift | 0 .../SwiftJNIMacrosPlugin.swift} | 2 +- .../SwiftSyntaxUtils.swift | 0 .../TerminalColors.swift | 0 .../Commands/ConfigureCommand.swift | 14 +- .../Commands/JExtractCommand.swift | 12 +- .../Commands/ResolveCommand.swift | 14 +- .../Commands/WrapJavaCommand.swift | 10 +- Sources/SwiftJavaTool/CommonOptions.swift | 12 +- .../SwiftJavaTool/Java/JavaClassLoader.swift | 8 +- Sources/SwiftJavaTool/String+Extensions.swift | 10 +- Sources/SwiftJavaTool/SwiftJava.swift | 14 +- .../SwiftJavaBaseAsyncParsableCommand.swift | 14 +- .../JavaClassTranslator.swift | 4 +- .../JavaTranslator+Configuration.swift | 2 +- .../JavaTranslator+Validation.swift | 0 .../JavaTranslator.swift | 12 +- .../MethodVariance.swift | 4 +- .../OptionalKind.swift | 0 .../StringExtras.swift | 0 .../TranslationError.swift | 2 +- .../swiftkit/ffm/FFMSwiftInstanceCleanup.java | 4 +- .../org/swift/swiftkit/ffm/SwiftRuntime.java | 27 ++++ .../Asserts/LoweringAssertions.swift | 2 +- .../Asserts/TextAssertions.swift | 2 +- .../FuncCallbackImportTests.swift | 2 +- .../FunctionDescriptorImportTests.swift | 2 +- .../InternalExtractTests.swift | 2 +- .../JNI/JNIUnsignedNumberTests.swift | 2 +- .../MemoryManagementModeTests.swift | 2 +- .../MethodImportTests.swift | 2 +- .../UnsignedNumberTests.swift | 2 +- .../GradleDependencyParsingTests.swift | 2 +- .../JavaClassMacroTests.swift | 2 +- .../BasicRuntimeTests.swift | 4 +- .../Java2SwiftTests.swift | 90 ++++++------ .../JavaTranslatorValidationTests.swift | 2 +- 296 files changed, 641 insertions(+), 527 deletions(-) delete mode 120000 Plugins/PluginsShared/JavaKitConfigurationShared create mode 120000 Plugins/PluginsShared/SwiftJavaConfigurationShared rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/Package.swift (93%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/Sources/MySwiftLibrary/MySwiftClass.swift (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/Sources/MySwiftLibrary/MySwiftLibrary.swift (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/Sources/MySwiftLibrary/MySwiftStruct.swift (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/Sources/MySwiftLibrary/jni/JNIImplementations.swift (97%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/Sources/MySwiftLibrary/swift-java.config (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/build.gradle (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractFFMSampleApp}/ci-validate.sh (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractFFMSampleApp}/gradle.properties (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractFFMSampleApp}/gradlew (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractFFMSampleApp}/gradlew.bat (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/src/jmh/java/org/swift/swiftkit/ffm/JavaToSwiftBenchmark.java (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/src/jmh/java/org/swift/swiftkit/ffm/StringPassingBenchmark.java (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/src/main/java/com/example/swift/HelloJava2Swift.java (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/src/test/java/com/example/swift/DataImportTest.java (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/src/test/java/com/example/swift/MySwiftClassTest.java (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/src/test/java/com/example/swift/MySwiftLibraryTest.java (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/src/test/java/com/example/swift/OptionalImportTest.java (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/src/test/java/com/example/swift/UnsignedNumbersTest.java (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/src/test/java/org/swift/swiftkitffm/MySwiftClassTest.java (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/src/test/java/org/swift/swiftkitffm/MySwiftStructTest.java (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractFFMSampleApp}/src/test/java/org/swift/swiftkitffm/SwiftArenaTest.java (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/Package.swift (94%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/Sources/MySwiftLibrary/Alignment.swift (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/Sources/MySwiftLibrary/Closures.swift (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/Sources/MySwiftLibrary/MySwiftClass.swift (99%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/Sources/MySwiftLibrary/MySwiftLibrary.swift (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/Sources/MySwiftLibrary/MySwiftStruct.swift (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/Sources/MySwiftLibrary/Optionals.swift (99%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/Sources/MySwiftLibrary/Vehicle.swift (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/Sources/MySwiftLibrary/swift-java.config (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/build.gradle (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractJNISampleApp}/ci-validate.sh (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractJNISampleApp}/gradle.properties (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractJNISampleApp}/gradlew (100%) rename Samples/{SwiftKitSampleApp => SwiftJavaExtractJNISampleApp}/gradlew.bat (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/src/main/java/com/example/swift/HelloJava2SwiftJNI.java (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/src/test/java/com/example/swift/AlignmentEnumTest.java (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/src/test/java/com/example/swift/ClosuresTest.java (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/src/test/java/com/example/swift/MySwiftClassTest.java (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/src/test/java/com/example/swift/MySwiftLibraryTest.java (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/src/test/java/com/example/swift/MySwiftStructTest.java (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/src/test/java/com/example/swift/OptionalsTest.java (100%) rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/src/test/java/com/example/swift/VehicleEnumTest.java (100%) rename Sources/{JavaRuntime => CJNI}/dummy.c (100%) rename Sources/{JavaRuntime/include/JavaRuntime.h => CJNI/include/CJNI.h} (85%) create mode 100644 Sources/CJNI/include/module.modulemap delete mode 100644 Sources/JavaRuntime/include/module.modulemap rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/BufferedInputStream.swift (96%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/Charset.swift (96%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/Closeable.swift (82%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/File.swift (98%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/FileDescriptor.swift (94%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/FileReader.swift (95%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/Flushable.swift (82%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/InputStream.swift (97%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/InputStreamReader.swift (96%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/OutputStream.swift (95%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/Path.swift (98%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/Readable.swift (77%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/Reader.swift (96%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/StringReader.swift (95%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/WatchService.swift (85%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/generated/Writer.swift (97%) rename Sources/{JavaKitIO => JavaStdlib/JavaIO}/swift-java.config (100%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/Constructor+Utilities.swift (100%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/Executable+Utilities.swift (98%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/Field+Utilities.swift (100%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/JavaClass+Reflection.swift (98%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/Method+Utilities.swift (100%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/generated/AccessibleObject.swift (97%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/generated/AnnotatedType.swift (96%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/generated/Annotation.swift (91%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/generated/Constructor.swift (98%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/generated/Executable.swift (96%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/generated/Field.swift (97%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/generated/GenericArrayType.swift (89%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/generated/GenericDeclaration.swift (96%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/generated/Method.swift (98%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/generated/Parameter.swift (95%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/generated/ParameterizedType.swift (91%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/generated/Type.swift (83%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/generated/TypeVariable.swift (96%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/generated/WildcardType.swift (90%) rename Sources/{JavaKitReflection => JavaStdlib/JavaLangReflect}/swift-java.config (100%) rename Sources/{JavaKitNetwork => JavaStdlib/JavaNet}/generated/URI.swift (98%) rename Sources/{JavaKitNetwork => JavaStdlib/JavaNet}/generated/URL.swift (97%) rename Sources/{JavaKitNetwork => JavaStdlib/JavaNet}/generated/URLClassLoader.swift (93%) rename Sources/{JavaKitNetwork => JavaStdlib/JavaNet}/generated/URLConnection.swift (99%) rename Sources/{JavaKitNetwork => JavaStdlib/JavaNet}/swift-java.config (100%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/JavaEnumeration+Sequence.swift (100%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/JavaIterator+Iterator.swift (97%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/List+Sequence.swift (100%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/ArrayDeque.swift (98%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/ArrayList.swift (98%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/BitSet.swift (98%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/Enumeration.swift (90%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/HashMap.swift (98%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/HashSet.swift (97%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/JavaCollection.swift (97%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/JavaDictionary.swift (95%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/JavaIterator.swift (89%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/JavaSet.swift (99%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/LinkedList.swift (99%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/List.swift (99%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/ListIterator.swift (94%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/PriorityQueue.swift (97%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/Queue.swift (97%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/RandomAccess.swift (79%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/Stack.swift (93%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/TreeMap.swift (98%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/generated/TreeSet.swift (97%) rename Sources/{JavaKitCollection => JavaStdlib/JavaUtil}/swift-java.config (100%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaBiConsumer.swift (91%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaBiFunction.swift (92%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaBiPredicate.swift (94%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaBinaryOperator.swift (93%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaBooleanSupplier.swift (85%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaConsumer.swift (90%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaDoubleBinaryOperator.swift (87%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaDoubleConsumer.swift (89%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaDoubleFunction.swift (86%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaDoublePredicate.swift (92%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaDoubleSupplier.swift (85%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaDoubleToIntFunction.swift (86%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaDoubleToLongFunction.swift (86%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaDoubleUnaryOperator.swift (94%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaFunction.swift (95%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaIntBinaryOperator.swift (87%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaIntConsumer.swift (88%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaIntFunction.swift (86%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaIntPredicate.swift (92%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaIntSupplier.swift (84%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaIntToDoubleFunction.swift (86%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaIntToLongFunction.swift (86%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaIntUnaryOperator.swift (93%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaLongBinaryOperator.swift (87%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaLongConsumer.swift (88%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaLongFunction.swift (86%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaLongPredicate.swift (92%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaLongSupplier.swift (84%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaLongToDoubleFunction.swift (86%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaLongToIntFunction.swift (86%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaLongUnaryOperator.swift (93%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaObjDoubleConsumer.swift (87%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaObjIntConsumer.swift (87%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaObjLongConsumer.swift (87%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaPredicate.swift (96%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaSupplier.swift (85%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaToDoubleBiFunction.swift (89%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaToDoubleFunction.swift (87%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaToIntBiFunction.swift (88%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaToIntFunction.swift (87%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaToLongBiFunction.swift (88%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaToLongFunction.swift (87%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/generated/JavaUnaryOperator.swift (95%) rename Sources/{JavaKitFunction => JavaStdlib/JavaUtilFunction}/swift-java.config (100%) rename Sources/{JavaKitJar => JavaStdlib/JavaUtilJar}/generated/Attributes.swift (98%) rename Sources/{JavaKitJar => JavaStdlib/JavaUtilJar}/generated/JarEntry.swift (98%) rename Sources/{JavaKitJar => JavaStdlib/JavaUtilJar}/generated/JarFile.swift (98%) rename Sources/{JavaKitJar => JavaStdlib/JavaUtilJar}/generated/JarInputStream.swift (98%) rename Sources/{JavaKitJar => JavaStdlib/JavaUtilJar}/generated/JarOutputStream.swift (98%) rename Sources/{JavaKitJar => JavaStdlib/JavaUtilJar}/generated/Manifest.swift (95%) rename Sources/{JavaKitJar => JavaStdlib/JavaUtilJar}/generated/ZipEntry.swift (99%) rename Sources/{JavaKitJar => JavaStdlib/JavaUtilJar}/swift-java.config (100%) create mode 100644 Sources/JavaStdlib/README_PACKAGE_CONVENTION.md rename Sources/{JavaKit => SwiftJava}/AnyJavaObject.swift (99%) rename Sources/{JavaKit => SwiftJava}/BridgedValues/JavaValue+Array.swift (100%) rename Sources/{JavaKit => SwiftJava}/BridgedValues/JavaValue+Bool.swift (100%) rename Sources/{JavaKit => SwiftJava}/BridgedValues/JavaValue+FloatingPoint.swift (100%) rename Sources/{JavaKit => SwiftJava}/BridgedValues/JavaValue+Integers.swift (100%) rename Sources/{JavaKit => SwiftJava}/BridgedValues/JavaValue+String.swift (100%) rename Sources/{JavaKit => SwiftJava}/Documentation.docc/JavaKit.md (99%) rename Sources/{JavaKit => SwiftJava}/Exceptions/Exception+Error.swift (100%) rename Sources/{JavaKit => SwiftJava}/Exceptions/ExceptionHandling.swift (100%) rename Sources/{JavaKit => SwiftJava}/Exceptions/Throwable+Error.swift (100%) create mode 100644 Sources/SwiftJava/Helpers/_JNIMethodIDCache.swift rename Sources/{JavaKit => SwiftJava}/JavaClass+Initialization.swift (98%) rename Sources/{JavaKit => SwiftJava}/JavaEnvironment.swift (97%) rename Sources/{JavaKit => SwiftJava}/JavaKitVM/JavaVirtualMachine.swift (100%) rename Sources/{JavaKit => SwiftJava}/JavaKitVM/LockedState.swift (100%) rename Sources/{JavaKit => SwiftJava}/JavaKitVM/ThreadLocalStorage.swift (100%) rename Sources/{JavaKit => SwiftJava}/JavaObject+Inheritance.swift (98%) rename Sources/{JavaKit => SwiftJava}/JavaObject+MethodCalls.swift (99%) rename Sources/{JavaKit => SwiftJava}/JavaObjectHolder.swift (98%) rename Sources/{JavaKit => SwiftJava}/JavaRuntime+Reexport.swift (94%) rename Sources/{JavaKit => SwiftJava}/JavaValue.swift (99%) rename Sources/{JavaKit => SwiftJava}/Macros.swift (90%) rename Sources/{JavaKit => SwiftJava}/Optional+JavaObject.swift (99%) rename Sources/{JavaKit => SwiftJava}/Optional+JavaOptional.swift (100%) rename Sources/{JavaKit => SwiftJava}/generated/Appendable.swift (95%) rename Sources/{JavaKit => SwiftJava}/generated/CharSequence.swift (96%) rename Sources/{JavaKit => SwiftJava}/generated/Exception.swift (96%) rename Sources/{JavaKit => SwiftJava}/generated/JavaArray.swift (99%) rename Sources/{JavaKit => SwiftJava}/generated/JavaBoolean.swift (98%) rename Sources/{JavaKit => SwiftJava}/generated/JavaByte.swift (99%) rename Sources/{JavaKit => SwiftJava}/generated/JavaCharacter.swift (99%) rename Sources/{JavaKit => SwiftJava}/generated/JavaClass.swift (99%) rename Sources/{JavaKit => SwiftJava}/generated/JavaClassLoader.swift (99%) rename Sources/{JavaKit => SwiftJava}/generated/JavaDouble.swift (99%) rename Sources/{JavaKit => SwiftJava}/generated/JavaError.swift (96%) rename Sources/{JavaKit => SwiftJava}/generated/JavaFloat.swift (99%) rename Sources/{JavaKit => SwiftJava}/generated/JavaInteger.swift (99%) rename Sources/{JavaKit => SwiftJava}/generated/JavaLong.swift (99%) rename Sources/{JavaKit => SwiftJava}/generated/JavaNumber.swift (96%) rename Sources/{JavaKit => SwiftJava}/generated/JavaObject.swift (97%) rename Sources/{JavaKit => SwiftJava}/generated/JavaOptional.swift (98%) rename Sources/{JavaKit => SwiftJava}/generated/JavaOptionalDouble.swift (98%) rename Sources/{JavaKit => SwiftJava}/generated/JavaOptionalInt.swift (97%) rename Sources/{JavaKit => SwiftJava}/generated/JavaOptionalLong.swift (97%) rename Sources/{JavaKit => SwiftJava}/generated/JavaShort.swift (99%) rename Sources/{JavaKit => SwiftJava}/generated/JavaString.swift (99%) rename Sources/{JavaKit => SwiftJava}/generated/JavaVoid.swift (92%) rename Sources/{JavaKit => SwiftJava}/generated/RuntimeException.swift (96%) rename Sources/{JavaKit => SwiftJava}/generated/Throwable.swift (98%) rename Sources/{JavaKit => SwiftJava}/swift-java.config (100%) rename Sources/{JavaKitConfigurationShared => SwiftJavaConfigurationShared}/Configuration.swift (100%) rename Sources/{JavaKitConfigurationShared => SwiftJavaConfigurationShared}/GenerationMode.swift (100%) rename Sources/{JavaKitConfigurationShared => SwiftJavaConfigurationShared}/GradleDependencyParsing.swift (100%) rename Sources/{JavaKitMacros => SwiftJavaMacros}/GenerationMode.swift (100%) rename Sources/{JavaKitMacros => SwiftJavaMacros}/ImplementsJavaMacro.swift (100%) rename Sources/{JavaKitMacros => SwiftJavaMacros}/JavaClassMacro.swift (100%) rename Sources/{JavaKitMacros => SwiftJavaMacros}/JavaFieldMacro.swift (100%) rename Sources/{JavaKitMacros => SwiftJavaMacros}/JavaMethodMacro.swift (100%) rename Sources/{JavaKitMacros => SwiftJavaMacros}/MacroErrors.swift (100%) rename Sources/{JavaKitMacros/JavaKitMacrosPlugin.swift => SwiftJavaMacros/SwiftJNIMacrosPlugin.swift} (93%) rename Sources/{JavaKitMacros => SwiftJavaMacros}/SwiftSyntaxUtils.swift (100%) rename Sources/{JavaKitShared => SwiftJavaShared}/TerminalColors.swift (100%) rename Sources/{SwiftJavaLib => SwiftJavaToolLib}/JavaClassTranslator.swift (99%) rename Sources/{SwiftJavaLib => SwiftJavaToolLib}/JavaTranslator+Configuration.swift (97%) rename Sources/{SwiftJavaLib => SwiftJavaToolLib}/JavaTranslator+Validation.swift (100%) rename Sources/{SwiftJavaLib => SwiftJavaToolLib}/JavaTranslator.swift (98%) rename Sources/{SwiftJavaLib => SwiftJavaToolLib}/MethodVariance.swift (98%) rename Sources/{SwiftJavaLib => SwiftJavaToolLib}/OptionalKind.swift (100%) rename Sources/{SwiftJavaLib => SwiftJavaToolLib}/StringExtras.swift (100%) rename Sources/{SwiftJavaLib => SwiftJavaToolLib}/TranslationError.swift (97%) rename Tests/{JavaKitConfigurationSharedTests => SwiftJavaConfigurationSharedTests}/GradleDependencyParsingTests.swift (97%) rename Tests/{JavaKitMacroTests => SwiftJavaMacrosTests}/JavaClassMacroTests.swift (99%) rename Tests/{JavaKitTests => SwiftJavaTests}/BasicRuntimeTests.swift (98%) rename Tests/{SwiftJavaTests => SwiftJavaToolLibTests}/Java2SwiftTests.swift (89%) rename Tests/{SwiftJavaTests => SwiftJavaToolLibTests}/JavaTranslatorValidationTests.swift (98%) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index b2d8ee7c2..0a2b6949b 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -166,8 +166,8 @@ jobs: 'JavaProbablyPrime', 'JavaSieve', 'SwiftAndJavaJarSampleLib', - 'SwiftKitSampleApp', - 'JExtractJNISampleApp', + 'SwiftJavaExtractFFMSampleApp', + 'SwiftJavaExtractJNISampleApp', ] container: image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} @@ -194,8 +194,8 @@ jobs: 'JavaProbablyPrime', 'JavaSieve', 'SwiftAndJavaJarSampleLib', - 'SwiftKitSampleApp', - 'JExtractJNISampleApp', + 'SwiftJavaExtractFFMSampleApp', + 'SwiftJavaExtractJNISampleApp', ] steps: - uses: actions/checkout@v4 diff --git a/Benchmarks/Benchmarks/JavaApiCallBenchmarks/JavaApiCallBenchmarks.swift b/Benchmarks/Benchmarks/JavaApiCallBenchmarks/JavaApiCallBenchmarks.swift index 25c670747..d52d31330 100644 --- a/Benchmarks/Benchmarks/JavaApiCallBenchmarks/JavaApiCallBenchmarks.swift +++ b/Benchmarks/Benchmarks/JavaApiCallBenchmarks/JavaApiCallBenchmarks.swift @@ -14,8 +14,8 @@ import Benchmark import Foundation -import JavaKit -import JavaKitNetwork +import SwiftJava +import JavaNet @MainActor let benchmarks = { var jvm: JavaVirtualMachine { diff --git a/Benchmarks/Package.swift b/Benchmarks/Package.swift index b8fbe580e..955378fc8 100644 --- a/Benchmarks/Package.swift +++ b/Benchmarks/Package.swift @@ -51,9 +51,9 @@ let package = Package( .executableTarget( name: "JavaApiCallBenchmarks", dependencies: [ - .product(name: "JavaRuntime", package: "swift-java"), - .product(name: "JavaKit", package: "swift-java"), - .product(name: "JavaKitNetwork", package: "swift-java"), + .product(name: "CJNI", package: "swift-java"), + .product(name: "SwiftJava", package: "swift-java"), + .product(name: "JavaNet", package: "swift-java"), .product(name: "Benchmark", package: "package-benchmark"), ], path: "Benchmarks/JavaApiCallBenchmarks", diff --git a/Package.swift b/Package.swift index 65737b2ba..a30b9510b 100644 --- a/Package.swift +++ b/Package.swift @@ -94,45 +94,45 @@ let package = Package( .macOS(.v15) ], products: [ - // ==== JavaKit (i.e. calling Java directly Swift utilities) + // ==== SwiftJava (i.e. calling Java directly Swift utilities) .library( - name: "JavaKit", - targets: ["JavaKit"] + name: "SwiftJava", + targets: ["SwiftJava"] ), .library( - name: "JavaRuntime", - targets: ["JavaRuntime"] + name: "CJNI", + targets: ["CJNI"] ), .library( - name: "JavaKitCollection", - targets: ["JavaKitCollection"] + name: "JavaUtil", + targets: ["JavaUtil"] ), .library( - name: "JavaKitFunction", - targets: ["JavaKitFunction"] + name: "JavaUtilFunction", + targets: ["JavaUtilFunction"] ), .library( - name: "JavaKitJar", - targets: ["JavaKitJar"] + name: "JavaUtilJar", + targets: ["JavaUtilJar"] ), .library( - name: "JavaKitNetwork", - targets: ["JavaKitNetwork"] + name: "JavaNet", + targets: ["JavaNet"] ), .library( - name: "JavaKitIO", - targets: ["JavaKitIO"] + name: "JavaIO", + targets: ["JavaIO"] ), .library( - name: "JavaKitReflection", - targets: ["JavaKitReflection"] + name: "JavaLangReflect", + targets: ["JavaLangReflect"] ), .library( @@ -212,13 +212,13 @@ let package = Package( .target( name: "SwiftJavaDocumentation", dependencies: [ - "JavaKit", + "SwiftJava", "SwiftKitSwift", ] ), .macro( - name: "JavaKitMacros", + name: "SwiftJavaMacros", dependencies: [ .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), .product(name: "SwiftCompilerPlugin", package: "swift-syntax"), @@ -235,12 +235,12 @@ let package = Package( ), .target( - name: "JavaKit", + name: "SwiftJava", dependencies: [ - "JavaRuntime", - "JavaKitMacros", + "CJNI", + "SwiftJavaMacros", "JavaTypes", - "JavaKitConfigurationShared", // for Configuration reading at runtime + "SwiftJavaConfigurationShared", // for Configuration reading at runtime ], exclude: ["swift-java.config"], swiftSettings: [ @@ -268,8 +268,9 @@ let package = Package( ] ), .target( - name: "JavaKitCollection", - dependencies: ["JavaKit"], + name: "JavaUtil", + dependencies: ["SwiftJava"], + path: "Sources/JavaStdlib/JavaUtil", exclude: ["swift-java.config"], swiftSettings: [ .swiftLanguageMode(.v5), @@ -277,8 +278,9 @@ let package = Package( ] ), .target( - name: "JavaKitFunction", - dependencies: ["JavaKit"], + name: "JavaUtilFunction", + dependencies: ["SwiftJava"], + path: "Sources/JavaStdlib/JavaUtilFunction", exclude: ["swift-java.config"], swiftSettings: [ .swiftLanguageMode(.v5), @@ -286,8 +288,9 @@ let package = Package( ] ), .target( - name: "JavaKitJar", - dependencies: ["JavaKit", "JavaKitCollection"], + name: "JavaUtilJar", + dependencies: ["SwiftJava", "JavaUtil"], + path: "Sources/JavaStdlib/JavaUtilJar", exclude: ["swift-java.config"], swiftSettings: [ .swiftLanguageMode(.v5), @@ -295,8 +298,9 @@ let package = Package( ] ), .target( - name: "JavaKitNetwork", - dependencies: ["JavaKit", "JavaKitCollection"], + name: "JavaNet", + dependencies: ["SwiftJava", "JavaUtil"], + path: "Sources/JavaStdlib/JavaNet", exclude: ["swift-java.config"], swiftSettings: [ .swiftLanguageMode(.v5), @@ -304,8 +308,9 @@ let package = Package( ] ), .target( - name: "JavaKitIO", - dependencies: ["JavaKit", "JavaKitCollection"], + name: "JavaIO", + dependencies: ["SwiftJava", "JavaUtil"], + path: "Sources/JavaStdlib/JavaIO", exclude: ["swift-java.config"], swiftSettings: [ .swiftLanguageMode(.v5), @@ -313,8 +318,9 @@ let package = Package( ] ), .target( - name: "JavaKitReflection", - dependencies: ["JavaKit", "JavaKitCollection"], + name: "JavaLangReflect", + dependencies: ["SwiftJava", "JavaUtil"], + path: "Sources/JavaStdlib/JavaLangReflect", exclude: ["swift-java.config"], swiftSettings: [ .swiftLanguageMode(.v5), @@ -353,7 +359,7 @@ let package = Package( ), .target( - name: "JavaRuntime", + name: "CJNI", swiftSettings: [ .swiftLanguageMode(.v5), .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) @@ -361,26 +367,26 @@ let package = Package( ), .target( - name: "JavaKitConfigurationShared" + name: "SwiftJavaConfigurationShared" ), .target( - name: "JavaKitShared" + name: "SwiftJavaShared" ), .target( - name: "SwiftJavaLib", + name: "SwiftJavaToolLib", dependencies: [ .product(name: "SwiftBasicFormat", package: "swift-syntax"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), - "JavaKit", - "JavaKitJar", - "JavaKitReflection", - "JavaKitNetwork", + "SwiftJava", + "JavaUtilJar", + "JavaLangReflect", + "JavaNet", "JavaTypes", - "JavaKitShared", - "JavaKitConfigurationShared", + "SwiftJavaShared", + "SwiftJavaConfigurationShared", // .product(name: "Subprocess", package: "swift-subprocess") "_Subprocess", ], @@ -399,13 +405,13 @@ let package = Package( .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), .product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "SystemPackage", package: "swift-system"), - "JavaKit", - "JavaKitJar", - "JavaKitNetwork", - "SwiftJavaLib", + "SwiftJava", + "JavaUtilJar", + "JavaNet", + "SwiftJavaToolLib", "JExtractSwiftLib", - "JavaKitShared", - "JavaKitConfigurationShared", + "SwiftJavaShared", + "SwiftJavaConfigurationShared", ], swiftSettings: [ .swiftLanguageMode(.v5), @@ -427,8 +433,8 @@ let package = Package( .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), .product(name: "ArgumentParser", package: "swift-argument-parser"), "JavaTypes", - "JavaKitShared", - "JavaKitConfigurationShared", + "SwiftJavaShared", + "SwiftJavaConfigurationShared", ], swiftSettings: [ .swiftLanguageMode(.v5), @@ -445,8 +451,11 @@ let package = Package( ), .testTarget( - name: "JavaKitTests", - dependencies: ["JavaKit", "JavaKitNetwork"], + name: "SwiftJavaTests", + dependencies: [ + "SwiftJava", + "JavaNet" + ], swiftSettings: [ .swiftLanguageMode(.v5), .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) @@ -455,16 +464,18 @@ let package = Package( .testTarget( name: "JavaTypesTests", - dependencies: ["JavaTypes"], + dependencies: [ + "JavaTypes" + ], swiftSettings: [ .swiftLanguageMode(.v5) ] ), .testTarget( - name: "JavaKitMacroTests", + name: "SwiftJavaMacrosTests", dependencies: [ - "JavaKitMacros", + "SwiftJavaMacros", .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"), ], swiftSettings: [ @@ -473,8 +484,10 @@ let package = Package( ), .testTarget( - name: "SwiftJavaTests", - dependencies: ["SwiftJavaLib"], + name: "SwiftJavaToolLibTests", + dependencies: [ + "SwiftJavaToolLib" + ], swiftSettings: [ .swiftLanguageMode(.v5), .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) @@ -482,8 +495,8 @@ let package = Package( ), .testTarget( - name: "JavaKitConfigurationSharedTests", - dependencies: ["JavaKitConfigurationShared"], + name: "SwiftJavaConfigurationSharedTests", + dependencies: ["SwiftJavaConfigurationShared"], swiftSettings: [ .swiftLanguageMode(.v5), .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) diff --git a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift index 2778cebeb..49ed3c4da 100644 --- a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift +++ b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift @@ -43,7 +43,7 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { let sourceDir = target.directory.string - // The name of the configuration file JavaKit.config from the target for + // The name of the configuration file SwiftJava.config from the target for // which we are generating Swift wrappers for Java classes. let configFile = URL(filePath: sourceDir).appending(path: "swift-java.config") let configuration = try readConfiguration(sourceDir: "\(sourceDir)") diff --git a/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift b/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift index 559551809..e12e13e81 100644 --- a/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift +++ b/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift @@ -32,7 +32,7 @@ struct JavaCompilerBuildToolPlugin: BuildToolPlugin { // so we cannot eliminate this deprecation warning. let sourceDir = target.directory.string - // The name of the configuration file JavaKit.config from the target for + // The name of the configuration file SwiftJava.config from the target for // which we are generating Swift wrappers for Java classes. let configFile = URL(filePath: sourceDir).appending(path: "swift-java.config") let config: Configuration? diff --git a/Plugins/PluginsShared/JavaKitConfigurationShared b/Plugins/PluginsShared/JavaKitConfigurationShared deleted file mode 120000 index d5c765dfc..000000000 --- a/Plugins/PluginsShared/JavaKitConfigurationShared +++ /dev/null @@ -1 +0,0 @@ -../../Sources/JavaKitConfigurationShared \ No newline at end of file diff --git a/Plugins/PluginsShared/SwiftJavaConfigurationShared b/Plugins/PluginsShared/SwiftJavaConfigurationShared new file mode 120000 index 000000000..2af5fd01e --- /dev/null +++ b/Plugins/PluginsShared/SwiftJavaConfigurationShared @@ -0,0 +1 @@ +../../Sources/SwiftJavaConfigurationShared \ No newline at end of file diff --git a/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift index cbbbe4252..57641eff9 100644 --- a/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift +++ b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift @@ -34,7 +34,7 @@ struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { // so we cannot eliminate this deprecation warning. let sourceDir = target.directory.string - // The name of the configuration file JavaKit.config from the target for + // The name of the configuration file SwiftJava.config from the target for // which we are generating Swift wrappers for Java classes. let configFile = URL(filePath: sourceDir) .appending(path: SwiftJavaConfigFileName) diff --git a/README.md b/README.md index 940b478c9..9f676905a 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,7 @@ To view the rendered docc documentation you can use the docc preview command: xcrun docc preview Sources/SwiftJavaDocumentation/Documentation.docc # OR JavaKit to view JavaKit documentation: -# xcrun docc preview Sources/JavaKit/Documentation.docc +# xcrun docc preview Sources/SwiftJNI/Documentation.docc # ======================================== # Starting Local Preview Server diff --git a/Samples/JavaDependencySampleApp/Package.swift b/Samples/JavaDependencySampleApp/Package.swift index 506b8c93e..c5ae97c7d 100644 --- a/Samples/JavaDependencySampleApp/Package.swift +++ b/Samples/JavaDependencySampleApp/Package.swift @@ -64,9 +64,9 @@ let package = Package( .executableTarget( name: "JavaDependencySample", dependencies: [ - .product(name: "JavaKit", package: "swift-java"), - .product(name: "JavaRuntime", package: "swift-java"), - .product(name: "JavaKitFunction", package: "swift-java"), + .product(name: "SwiftJava", package: "swift-java"), + .product(name: "CJNI", package: "swift-java"), + .product(name: "JavaUtilFunction", package: "swift-java"), "JavaCommonsCSV" ], exclude: ["swift-java.config"], @@ -82,11 +82,11 @@ let package = Package( .target( name: "JavaCommonsCSV", dependencies: [ - .product(name: "JavaKit", package: "swift-java"), - .product(name: "JavaKitFunction", package: "swift-java"), - .product(name: "JavaKitCollection", package: "swift-java"), - .product(name: "JavaKitIO", package: "swift-java"), - .product(name: "JavaKitNetwork", package: "swift-java"), + .product(name: "SwiftJava", package: "swift-java"), + .product(name: "JavaUtilFunction", package: "swift-java"), + .product(name: "JavaUtil", package: "swift-java"), + .product(name: "JavaIO", package: "swift-java"), + .product(name: "JavaNet", package: "swift-java"), ], exclude: ["swift-java.config"], swiftSettings: [ diff --git a/Samples/JavaDependencySampleApp/Sources/JavaDependencySample/main.swift b/Samples/JavaDependencySampleApp/Sources/JavaDependencySample/main.swift index c75cf5537..13ea6eed3 100644 --- a/Samples/JavaDependencySampleApp/Sources/JavaDependencySample/main.swift +++ b/Samples/JavaDependencySampleApp/Sources/JavaDependencySample/main.swift @@ -12,10 +12,10 @@ // //===----------------------------------------------------------------------===// -import JavaKit -import JavaKitFunction -import JavaKitIO -import JavaKitConfigurationShared +import SwiftJava +import JavaUtilFunction +import JavaIO +import SwiftJavaConfigurationShared import Foundation // Import the commons-csv library wrapper: diff --git a/Samples/JavaKitSampleApp/Package.swift b/Samples/JavaKitSampleApp/Package.swift index 1b5458190..32451e925 100644 --- a/Samples/JavaKitSampleApp/Package.swift +++ b/Samples/JavaKitSampleApp/Package.swift @@ -65,9 +65,9 @@ let package = Package( .target( name: "JavaKitExample", dependencies: [ - .product(name: "JavaKit", package: "swift-java"), - .product(name: "JavaKitFunction", package: "swift-java"), - .product(name: "JavaKitJar", package: "swift-java"), + .product(name: "SwiftJava", package: "swift-java"), + .product(name: "JavaUtilFunction", package: "swift-java"), + .product(name: "JavaUtilJar", package: "swift-java"), ], swiftSettings: [ .swiftLanguageMode(.v5), diff --git a/Samples/JavaKitSampleApp/Sources/JavaKitExample/JavaKitExample.swift b/Samples/JavaKitSampleApp/Sources/JavaKitExample/JavaKitExample.swift index f074fee6a..44f811d30 100644 --- a/Samples/JavaKitSampleApp/Sources/JavaKitExample/JavaKitExample.swift +++ b/Samples/JavaKitSampleApp/Sources/JavaKitExample/JavaKitExample.swift @@ -12,8 +12,8 @@ // //===----------------------------------------------------------------------===// -import JavaKit -import JavaKitFunction +import SwiftJava +import JavaUtilFunction enum SwiftWrappedError: Error { case message(String) diff --git a/Samples/JavaProbablyPrime/Package.swift b/Samples/JavaProbablyPrime/Package.swift index 9f0ecff24..3ebf8fcb5 100644 --- a/Samples/JavaProbablyPrime/Package.swift +++ b/Samples/JavaProbablyPrime/Package.swift @@ -29,8 +29,8 @@ let package = Package( .executableTarget( name: "JavaProbablyPrime", dependencies: [ - .product(name: "JavaKitCollection", package: "swift-java"), - .product(name: "JavaKit", package: "swift-java"), + .product(name: "JavaUtil", package: "swift-java"), + .product(name: "SwiftJava", package: "swift-java"), .product(name: "ArgumentParser", package: "swift-argument-parser"), ], swiftSettings: [ diff --git a/Samples/JavaProbablyPrime/Sources/JavaProbablyPrime/prime.swift b/Samples/JavaProbablyPrime/Sources/JavaProbablyPrime/prime.swift index 07f701bdb..c070c87ad 100644 --- a/Samples/JavaProbablyPrime/Sources/JavaProbablyPrime/prime.swift +++ b/Samples/JavaProbablyPrime/Sources/JavaProbablyPrime/prime.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import ArgumentParser -import JavaKit +import SwiftJava @main struct ProbablyPrime: ParsableCommand { diff --git a/Samples/JavaSieve/Package.swift b/Samples/JavaSieve/Package.swift index ee61a0214..c34d83318 100644 --- a/Samples/JavaSieve/Package.swift +++ b/Samples/JavaSieve/Package.swift @@ -54,8 +54,8 @@ let package = Package( .target( name: "JavaMath", dependencies: [ - .product(name: "JavaKit", package: "swift-java"), - .product(name: "JavaKitJar", package: "swift-java"), + .product(name: "SwiftJava", package: "swift-java"), + .product(name: "JavaUtilJar", package: "swift-java"), ], exclude: ["swift-java.config"], swiftSettings: [ @@ -72,8 +72,8 @@ let package = Package( name: "JavaSieve", dependencies: [ "JavaMath", - .product(name: "JavaKit", package: "swift-java"), - .product(name: "JavaKitCollection", package: "swift-java"), + .product(name: "SwiftJava", package: "swift-java"), + .product(name: "JavaUtil", package: "swift-java"), ], exclude: ["swift-java.config"], swiftSettings: [ diff --git a/Samples/JavaSieve/Sources/JavaSieve/main.swift b/Samples/JavaSieve/Sources/JavaSieve/main.swift index feec792d1..470ee5c79 100644 --- a/Samples/JavaSieve/Sources/JavaSieve/main.swift +++ b/Samples/JavaSieve/Sources/JavaSieve/main.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaKit +import SwiftJava import JavaMath let jvm = try JavaVirtualMachine.shared() diff --git a/Samples/SwiftKitSampleApp/Package.swift b/Samples/SwiftJavaExtractFFMSampleApp/Package.swift similarity index 93% rename from Samples/SwiftKitSampleApp/Package.swift rename to Samples/SwiftJavaExtractFFMSampleApp/Package.swift index 8501062be..30630a01a 100644 --- a/Samples/SwiftKitSampleApp/Package.swift +++ b/Samples/SwiftJavaExtractFFMSampleApp/Package.swift @@ -41,7 +41,7 @@ let javaIncludePath = "\(javaHome)/include" #endif let package = Package( - name: "SwiftKitSampleApp", + name: "SwiftJavaExtractFFMSampleApp", platforms: [ .macOS(.v15), .iOS(.v18), @@ -63,8 +63,8 @@ let package = Package( .target( name: "MySwiftLibrary", dependencies: [ - .product(name: "JavaKit", package: "swift-java"), - .product(name: "JavaRuntime", package: "swift-java"), + .product(name: "SwiftJava", package: "swift-java"), + .product(name: "CJNI", package: "swift-java"), .product(name: "SwiftKitSwift", package: "swift-java"), ], exclude: [ diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift similarity index 100% rename from Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift rename to Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift similarity index 100% rename from Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift rename to Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift similarity index 100% rename from Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift rename to Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/jni/JNIImplementations.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/jni/JNIImplementations.swift similarity index 97% rename from Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/jni/JNIImplementations.swift rename to Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/jni/JNIImplementations.swift index ef0e2e582..481265d50 100644 --- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/jni/JNIImplementations.swift +++ b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/jni/JNIImplementations.swift @@ -12,8 +12,8 @@ // //===----------------------------------------------------------------------===// -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("com.example.swift.HelloJava2Swift") open class HelloJava2Swift: JavaObject { diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/swift-java.config b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/swift-java.config similarity index 100% rename from Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/swift-java.config rename to Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/swift-java.config diff --git a/Samples/SwiftKitSampleApp/build.gradle b/Samples/SwiftJavaExtractFFMSampleApp/build.gradle similarity index 100% rename from Samples/SwiftKitSampleApp/build.gradle rename to Samples/SwiftJavaExtractFFMSampleApp/build.gradle diff --git a/Samples/JExtractJNISampleApp/ci-validate.sh b/Samples/SwiftJavaExtractFFMSampleApp/ci-validate.sh similarity index 100% rename from Samples/JExtractJNISampleApp/ci-validate.sh rename to Samples/SwiftJavaExtractFFMSampleApp/ci-validate.sh diff --git a/Samples/JExtractJNISampleApp/gradle.properties b/Samples/SwiftJavaExtractFFMSampleApp/gradle.properties similarity index 100% rename from Samples/JExtractJNISampleApp/gradle.properties rename to Samples/SwiftJavaExtractFFMSampleApp/gradle.properties diff --git a/Samples/JExtractJNISampleApp/gradlew b/Samples/SwiftJavaExtractFFMSampleApp/gradlew similarity index 100% rename from Samples/JExtractJNISampleApp/gradlew rename to Samples/SwiftJavaExtractFFMSampleApp/gradlew diff --git a/Samples/JExtractJNISampleApp/gradlew.bat b/Samples/SwiftJavaExtractFFMSampleApp/gradlew.bat similarity index 100% rename from Samples/JExtractJNISampleApp/gradlew.bat rename to Samples/SwiftJavaExtractFFMSampleApp/gradlew.bat diff --git a/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/ffm/JavaToSwiftBenchmark.java b/Samples/SwiftJavaExtractFFMSampleApp/src/jmh/java/org/swift/swiftkit/ffm/JavaToSwiftBenchmark.java similarity index 100% rename from Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/ffm/JavaToSwiftBenchmark.java rename to Samples/SwiftJavaExtractFFMSampleApp/src/jmh/java/org/swift/swiftkit/ffm/JavaToSwiftBenchmark.java diff --git a/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/ffm/StringPassingBenchmark.java b/Samples/SwiftJavaExtractFFMSampleApp/src/jmh/java/org/swift/swiftkit/ffm/StringPassingBenchmark.java similarity index 100% rename from Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/ffm/StringPassingBenchmark.java rename to Samples/SwiftJavaExtractFFMSampleApp/src/jmh/java/org/swift/swiftkit/ffm/StringPassingBenchmark.java diff --git a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftJavaExtractFFMSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java similarity index 100% rename from Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java rename to Samples/SwiftJavaExtractFFMSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/DataImportTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/DataImportTest.java similarity index 100% rename from Samples/SwiftKitSampleApp/src/test/java/com/example/swift/DataImportTest.java rename to Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/DataImportTest.java diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java similarity index 100% rename from Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java rename to Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java similarity index 100% rename from Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java rename to Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/OptionalImportTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/OptionalImportTest.java similarity index 100% rename from Samples/SwiftKitSampleApp/src/test/java/com/example/swift/OptionalImportTest.java rename to Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/OptionalImportTest.java diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedNumbersTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/UnsignedNumbersTest.java similarity index 100% rename from Samples/SwiftKitSampleApp/src/test/java/com/example/swift/UnsignedNumbersTest.java rename to Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/UnsignedNumbersTest.java diff --git a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftClassTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftClassTest.java similarity index 100% rename from Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftClassTest.java rename to Samples/SwiftJavaExtractFFMSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftClassTest.java diff --git a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftStructTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftStructTest.java similarity index 100% rename from Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftStructTest.java rename to Samples/SwiftJavaExtractFFMSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftStructTest.java diff --git a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/SwiftArenaTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/org/swift/swiftkitffm/SwiftArenaTest.java similarity index 100% rename from Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkitffm/SwiftArenaTest.java rename to Samples/SwiftJavaExtractFFMSampleApp/src/test/java/org/swift/swiftkitffm/SwiftArenaTest.java diff --git a/Samples/JExtractJNISampleApp/Package.swift b/Samples/SwiftJavaExtractJNISampleApp/Package.swift similarity index 94% rename from Samples/JExtractJNISampleApp/Package.swift rename to Samples/SwiftJavaExtractJNISampleApp/Package.swift index 8c7ce8563..0d2d2fa86 100644 --- a/Samples/JExtractJNISampleApp/Package.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Package.swift @@ -60,8 +60,8 @@ let package = Package( .target( name: "MySwiftLibrary", dependencies: [ - .product(name: "JavaKit", package: "swift-java"), - .product(name: "JavaRuntime", package: "swift-java"), + .product(name: "SwiftJava", package: "swift-java"), + .product(name: "CJNI", package: "swift-java"), .product(name: "SwiftKitSwift", package: "swift-java"), ], exclude: [ diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Alignment.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Alignment.swift similarity index 100% rename from Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Alignment.swift rename to Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Alignment.swift diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift similarity index 100% rename from Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift rename to Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Closures.swift diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift similarity index 99% rename from Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift rename to Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index 9a78c3c27..4b334a5e6 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaKit +import SwiftJava public class MySwiftClass { public let x: Int64 diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift similarity index 100% rename from Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift rename to Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift similarity index 100% rename from Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift rename to Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift similarity index 99% rename from Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift rename to Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift index 673ecb0b2..3e1221021 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaKit +import SwiftJava public func optionalBool(input: Optional) -> Bool? { return input diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Vehicle.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Vehicle.swift similarity index 100% rename from Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/Vehicle.swift rename to Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Vehicle.swift diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config similarity index 100% rename from Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config rename to Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config diff --git a/Samples/JExtractJNISampleApp/build.gradle b/Samples/SwiftJavaExtractJNISampleApp/build.gradle similarity index 100% rename from Samples/JExtractJNISampleApp/build.gradle rename to Samples/SwiftJavaExtractJNISampleApp/build.gradle diff --git a/Samples/SwiftKitSampleApp/ci-validate.sh b/Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh similarity index 100% rename from Samples/SwiftKitSampleApp/ci-validate.sh rename to Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh diff --git a/Samples/SwiftKitSampleApp/gradle.properties b/Samples/SwiftJavaExtractJNISampleApp/gradle.properties similarity index 100% rename from Samples/SwiftKitSampleApp/gradle.properties rename to Samples/SwiftJavaExtractJNISampleApp/gradle.properties diff --git a/Samples/SwiftKitSampleApp/gradlew b/Samples/SwiftJavaExtractJNISampleApp/gradlew similarity index 100% rename from Samples/SwiftKitSampleApp/gradlew rename to Samples/SwiftJavaExtractJNISampleApp/gradlew diff --git a/Samples/SwiftKitSampleApp/gradlew.bat b/Samples/SwiftJavaExtractJNISampleApp/gradlew.bat similarity index 100% rename from Samples/SwiftKitSampleApp/gradlew.bat rename to Samples/SwiftJavaExtractJNISampleApp/gradlew.bat diff --git a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java b/Samples/SwiftJavaExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java similarity index 100% rename from Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java rename to Samples/SwiftJavaExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/AlignmentEnumTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AlignmentEnumTest.java similarity index 100% rename from Samples/JExtractJNISampleApp/src/test/java/com/example/swift/AlignmentEnumTest.java rename to Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AlignmentEnumTest.java diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java similarity index 100% rename from Samples/JExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java rename to Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ClosuresTest.java diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java similarity index 100% rename from Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java rename to Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java similarity index 100% rename from Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java rename to Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java similarity index 100% rename from Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java rename to Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java similarity index 100% rename from Samples/JExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java rename to Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/VehicleEnumTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/VehicleEnumTest.java similarity index 100% rename from Samples/JExtractJNISampleApp/src/test/java/com/example/swift/VehicleEnumTest.java rename to Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/VehicleEnumTest.java diff --git a/Sources/JavaRuntime/dummy.c b/Sources/CJNI/dummy.c similarity index 100% rename from Sources/JavaRuntime/dummy.c rename to Sources/CJNI/dummy.c diff --git a/Sources/JavaRuntime/include/JavaRuntime.h b/Sources/CJNI/include/CJNI.h similarity index 85% rename from Sources/JavaRuntime/include/JavaRuntime.h rename to Sources/CJNI/include/CJNI.h index 02bf548e4..bef6e7ffd 100644 --- a/Sources/JavaRuntime/include/JavaRuntime.h +++ b/Sources/CJNI/include/CJNI.h @@ -12,9 +12,9 @@ // //===----------------------------------------------------------------------===// -#ifndef Swift_JavaRuntime_h -#define Swift_JavaRuntime_h +#ifndef Swift_CJNI_h +#define Swift_CJNI_h #include -#endif /* Swift_JavaRuntime_h */ +#endif /* Swift_CJNI_h */ diff --git a/Sources/CJNI/include/module.modulemap b/Sources/CJNI/include/module.modulemap new file mode 100644 index 000000000..c71f30c28 --- /dev/null +++ b/Sources/CJNI/include/module.modulemap @@ -0,0 +1,4 @@ +module CJNI { + umbrella header "CJNI.h" + export * +} diff --git a/Sources/JExtractSwiftLib/Common/TypeAnnotations.swift b/Sources/JExtractSwiftLib/Common/TypeAnnotations.swift index 70a86e820..0896e4be6 100644 --- a/Sources/JExtractSwiftLib/Common/TypeAnnotations.swift +++ b/Sources/JExtractSwiftLib/Common/TypeAnnotations.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import JavaTypes -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared /// Determine if the given type needs any extra annotations that should be included /// in Java sources when the corresponding Java type is rendered. diff --git a/Sources/JExtractSwiftLib/Configuration+Extensions.swift b/Sources/JExtractSwiftLib/Configuration+Extensions.swift index d85cf4472..042d86100 100644 --- a/Sources/JExtractSwiftLib/Configuration+Extensions.swift +++ b/Sources/JExtractSwiftLib/Configuration+Extensions.swift @@ -12,8 +12,8 @@ // //===----------------------------------------------------------------------===// -import JavaKitConfigurationShared // TODO: this should become SwiftJavaConfigurationShared -import JavaTypes // TODO: this should become SwiftJavaConfigurationShared +import SwiftJavaConfigurationShared +import JavaTypes extension Configuration { public var effectiveUnsignedNumericsMode: UnsignedNumericsMode { diff --git a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift index 25c463661..82ce5c1c0 100644 --- a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift @@ -66,7 +66,7 @@ extension String { .joined() } - /// Looks up self as a JavaKit wrapped class name and converts it + /// Looks up self as a SwiftJava wrapped class name and converts it /// into a `JavaType.class` if it exists in `lookupTable`. func parseJavaClassFromJavaKitName(in lookupTable: [String: String]) -> JavaType? { guard let canonicalJavaName = lookupTable[self] else { diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 57f947a8c..00fa60e74 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import JavaTypes -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared extension FFMSwift2JavaGenerator { func translatedDecl( diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index 9c9154a49..c9a5028b8 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -15,7 +15,7 @@ import JavaTypes import SwiftSyntax import SwiftSyntaxBuilder -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared import struct Foundation.URL package class FFMSwift2JavaGenerator: Swift2JavaGenerator { diff --git a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift index fe10ef72d..983396ac9 100644 --- a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import JavaTypes -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared enum JNIJavaTypeTranslator { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 0e169ec69..073883a2c 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import JavaTypes -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared extension JNISwift2JavaGenerator { func translatedDecl( diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index f2e9522bb..03273e684 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import JavaTypes -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared extension JNISwift2JavaGenerator { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 6b8c435a7..536797838 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -408,8 +408,8 @@ extension JNISwift2JavaGenerator { """ // Generated by swift-java - import JavaKit - import JavaRuntime + import SwiftJava + import CJNI """ ) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index 60ec1b8b8..f9b99f6b5 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import JavaTypes -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared /// A table that where keys are Swift class names and the values are /// the fully qualified canoical names. diff --git a/Sources/JExtractSwiftLib/Logger.swift b/Sources/JExtractSwiftLib/Logger.swift index 5bffdc8ca..6ef046517 100644 --- a/Sources/JExtractSwiftLib/Logger.swift +++ b/Sources/JExtractSwiftLib/Logger.swift @@ -15,7 +15,7 @@ import Foundation import SwiftSyntax import ArgumentParser -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared // Placeholder for some better logger, we could depend on swift-log public struct Logger { @@ -114,7 +114,7 @@ public struct Logger { } extension Logger { - public typealias Level = JavaKitConfigurationShared.LogLevel + public typealias Level = SwiftJavaConfigurationShared.LogLevel } extension Logger.Level: ExpressibleByArgument { diff --git a/Sources/JExtractSwiftLib/Swift2Java.swift b/Sources/JExtractSwiftLib/Swift2Java.swift index 5a73e3288..e62719937 100644 --- a/Sources/JExtractSwiftLib/Swift2Java.swift +++ b/Sources/JExtractSwiftLib/Swift2Java.swift @@ -15,8 +15,8 @@ import Foundation import SwiftSyntax import SwiftSyntaxBuilder -import JavaKitShared -import JavaKitConfigurationShared // TODO: this should become SwiftJavaConfigurationShared +import SwiftJavaShared +import SwiftJavaConfigurationShared public struct SwiftToJava { let config: Configuration diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index 300b979f7..02049ed4b 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -16,7 +16,7 @@ import Foundation import JavaTypes import SwiftBasicFormat import SwiftParser -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared import SwiftSyntax /// Takes swift interfaces and translates them into Java used to access those. diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index bc6ac0323..9b183cb6b 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift @@ -15,7 +15,7 @@ import Foundation import SwiftParser import SwiftSyntax -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared final class Swift2JavaVisitor { let translator: Swift2JavaTranslator diff --git a/Sources/JavaKitDependencyResolver/DependencyResolver.swift b/Sources/JavaKitDependencyResolver/DependencyResolver.swift index 697e3d2a4..1ecc8dc58 100644 --- a/Sources/JavaKitDependencyResolver/DependencyResolver.swift +++ b/Sources/JavaKitDependencyResolver/DependencyResolver.swift @@ -12,10 +12,10 @@ // //===----------------------------------------------------------------------===// -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI -@JavaInterface("org.swift.javakit.dependencies.DependencyResolver") +@JavaInterface("org.swift.jni.dependencies.DependencyResolver") public struct DependencyResolver { } diff --git a/Sources/JavaKitDependencyResolver/swift-java.config b/Sources/JavaKitDependencyResolver/swift-java.config index 6ea5aa879..3803b5f0a 100644 --- a/Sources/JavaKitDependencyResolver/swift-java.config +++ b/Sources/JavaKitDependencyResolver/swift-java.config @@ -1,5 +1,5 @@ { "dependencies": [ - ":JavaKit", + ":SwiftJNI", ] } diff --git a/Sources/JavaRuntime/include/module.modulemap b/Sources/JavaRuntime/include/module.modulemap deleted file mode 100644 index 2c0d4a982..000000000 --- a/Sources/JavaRuntime/include/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module JavaRuntime { - umbrella header "JavaRuntime.h" - export * -} diff --git a/Sources/JavaKitIO/generated/BufferedInputStream.swift b/Sources/JavaStdlib/JavaIO/generated/BufferedInputStream.swift similarity index 96% rename from Sources/JavaKitIO/generated/BufferedInputStream.swift rename to Sources/JavaStdlib/JavaIO/generated/BufferedInputStream.swift index 8ea95eeb2..b4a134949 100644 --- a/Sources/JavaKitIO/generated/BufferedInputStream.swift +++ b/Sources/JavaStdlib/JavaIO/generated/BufferedInputStream.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.io.BufferedInputStream") open class BufferedInputStream: InputStream { diff --git a/Sources/JavaKitIO/generated/Charset.swift b/Sources/JavaStdlib/JavaIO/generated/Charset.swift similarity index 96% rename from Sources/JavaKitIO/generated/Charset.swift rename to Sources/JavaStdlib/JavaIO/generated/Charset.swift index fda054eff..f3b9126c2 100644 --- a/Sources/JavaKitIO/generated/Charset.swift +++ b/Sources/JavaStdlib/JavaIO/generated/Charset.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.nio.charset.Charset") open class Charset: JavaObject { diff --git a/Sources/JavaKitIO/generated/Closeable.swift b/Sources/JavaStdlib/JavaIO/generated/Closeable.swift similarity index 82% rename from Sources/JavaKitIO/generated/Closeable.swift rename to Sources/JavaStdlib/JavaIO/generated/Closeable.swift index 6da8e2a91..9e68c58bd 100644 --- a/Sources/JavaKitIO/generated/Closeable.swift +++ b/Sources/JavaStdlib/JavaIO/generated/Closeable.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.io.Closeable") public struct Closeable { diff --git a/Sources/JavaKitIO/generated/File.swift b/Sources/JavaStdlib/JavaIO/generated/File.swift similarity index 98% rename from Sources/JavaKitIO/generated/File.swift rename to Sources/JavaStdlib/JavaIO/generated/File.swift index 5cbdb70ea..11831b5e8 100644 --- a/Sources/JavaKitIO/generated/File.swift +++ b/Sources/JavaStdlib/JavaIO/generated/File.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.io.File") open class File: JavaObject { diff --git a/Sources/JavaKitIO/generated/FileDescriptor.swift b/Sources/JavaStdlib/JavaIO/generated/FileDescriptor.swift similarity index 94% rename from Sources/JavaKitIO/generated/FileDescriptor.swift rename to Sources/JavaStdlib/JavaIO/generated/FileDescriptor.swift index 81490e460..36f1686c4 100644 --- a/Sources/JavaKitIO/generated/FileDescriptor.swift +++ b/Sources/JavaStdlib/JavaIO/generated/FileDescriptor.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.io.FileDescriptor") open class FileDescriptor: JavaObject { diff --git a/Sources/JavaKitIO/generated/FileReader.swift b/Sources/JavaStdlib/JavaIO/generated/FileReader.swift similarity index 95% rename from Sources/JavaKitIO/generated/FileReader.swift rename to Sources/JavaStdlib/JavaIO/generated/FileReader.swift index 5254bdd03..ed0898c70 100644 --- a/Sources/JavaKitIO/generated/FileReader.swift +++ b/Sources/JavaStdlib/JavaIO/generated/FileReader.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.io.FileReader") open class FileReader: InputStreamReader { diff --git a/Sources/JavaKitIO/generated/Flushable.swift b/Sources/JavaStdlib/JavaIO/generated/Flushable.swift similarity index 82% rename from Sources/JavaKitIO/generated/Flushable.swift rename to Sources/JavaStdlib/JavaIO/generated/Flushable.swift index daf621f63..59854369f 100644 --- a/Sources/JavaKitIO/generated/Flushable.swift +++ b/Sources/JavaStdlib/JavaIO/generated/Flushable.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.io.Flushable") public struct Flushable { diff --git a/Sources/JavaKitIO/generated/InputStream.swift b/Sources/JavaStdlib/JavaIO/generated/InputStream.swift similarity index 97% rename from Sources/JavaKitIO/generated/InputStream.swift rename to Sources/JavaStdlib/JavaIO/generated/InputStream.swift index 971a610ba..d521f2651 100644 --- a/Sources/JavaKitIO/generated/InputStream.swift +++ b/Sources/JavaStdlib/JavaIO/generated/InputStream.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.io.InputStream", implements: Closeable.self) open class InputStream: JavaObject { diff --git a/Sources/JavaKitIO/generated/InputStreamReader.swift b/Sources/JavaStdlib/JavaIO/generated/InputStreamReader.swift similarity index 96% rename from Sources/JavaKitIO/generated/InputStreamReader.swift rename to Sources/JavaStdlib/JavaIO/generated/InputStreamReader.swift index 2766aebab..faa04edae 100644 --- a/Sources/JavaKitIO/generated/InputStreamReader.swift +++ b/Sources/JavaStdlib/JavaIO/generated/InputStreamReader.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.io.InputStreamReader") open class InputStreamReader: Reader { diff --git a/Sources/JavaKitIO/generated/OutputStream.swift b/Sources/JavaStdlib/JavaIO/generated/OutputStream.swift similarity index 95% rename from Sources/JavaKitIO/generated/OutputStream.swift rename to Sources/JavaStdlib/JavaIO/generated/OutputStream.swift index e169bfd0c..1d3ec3566 100644 --- a/Sources/JavaKitIO/generated/OutputStream.swift +++ b/Sources/JavaStdlib/JavaIO/generated/OutputStream.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.io.OutputStream", implements: Closeable.self, Flushable.self) open class OutputStream: JavaObject { diff --git a/Sources/JavaKitIO/generated/Path.swift b/Sources/JavaStdlib/JavaIO/generated/Path.swift similarity index 98% rename from Sources/JavaKitIO/generated/Path.swift rename to Sources/JavaStdlib/JavaIO/generated/Path.swift index e93d03814..87956f105 100644 --- a/Sources/JavaKitIO/generated/Path.swift +++ b/Sources/JavaStdlib/JavaIO/generated/Path.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.nio.file.Path") public struct Path { diff --git a/Sources/JavaKitIO/generated/Readable.swift b/Sources/JavaStdlib/JavaIO/generated/Readable.swift similarity index 77% rename from Sources/JavaKitIO/generated/Readable.swift rename to Sources/JavaStdlib/JavaIO/generated/Readable.swift index 168259897..25f482217 100644 --- a/Sources/JavaKitIO/generated/Readable.swift +++ b/Sources/JavaStdlib/JavaIO/generated/Readable.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.lang.Readable") public struct Readable { diff --git a/Sources/JavaKitIO/generated/Reader.swift b/Sources/JavaStdlib/JavaIO/generated/Reader.swift similarity index 96% rename from Sources/JavaKitIO/generated/Reader.swift rename to Sources/JavaStdlib/JavaIO/generated/Reader.swift index 2f6cdfe2c..e133f7419 100644 --- a/Sources/JavaKitIO/generated/Reader.swift +++ b/Sources/JavaStdlib/JavaIO/generated/Reader.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.io.Reader", implements: Readable.self, Closeable.self) open class Reader: JavaObject { diff --git a/Sources/JavaKitIO/generated/StringReader.swift b/Sources/JavaStdlib/JavaIO/generated/StringReader.swift similarity index 95% rename from Sources/JavaKitIO/generated/StringReader.swift rename to Sources/JavaStdlib/JavaIO/generated/StringReader.swift index e2af1166c..aa3efd388 100644 --- a/Sources/JavaKitIO/generated/StringReader.swift +++ b/Sources/JavaStdlib/JavaIO/generated/StringReader.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.io.StringReader") open class StringReader: Reader { diff --git a/Sources/JavaKitIO/generated/WatchService.swift b/Sources/JavaStdlib/JavaIO/generated/WatchService.swift similarity index 85% rename from Sources/JavaKitIO/generated/WatchService.swift rename to Sources/JavaStdlib/JavaIO/generated/WatchService.swift index 20bca06fc..0b44c4fe2 100644 --- a/Sources/JavaKitIO/generated/WatchService.swift +++ b/Sources/JavaStdlib/JavaIO/generated/WatchService.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.nio.file.WatchService", extends: Closeable.self) public struct WatchService { diff --git a/Sources/JavaKitIO/generated/Writer.swift b/Sources/JavaStdlib/JavaIO/generated/Writer.swift similarity index 97% rename from Sources/JavaKitIO/generated/Writer.swift rename to Sources/JavaStdlib/JavaIO/generated/Writer.swift index 5e3fdff2a..7712a1c0c 100644 --- a/Sources/JavaKitIO/generated/Writer.swift +++ b/Sources/JavaStdlib/JavaIO/generated/Writer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.io.Writer", implements: Appendable.self, Closeable.self, Flushable.self) open class Writer: JavaObject { diff --git a/Sources/JavaKitIO/swift-java.config b/Sources/JavaStdlib/JavaIO/swift-java.config similarity index 100% rename from Sources/JavaKitIO/swift-java.config rename to Sources/JavaStdlib/JavaIO/swift-java.config diff --git a/Sources/JavaKitReflection/Constructor+Utilities.swift b/Sources/JavaStdlib/JavaLangReflect/Constructor+Utilities.swift similarity index 100% rename from Sources/JavaKitReflection/Constructor+Utilities.swift rename to Sources/JavaStdlib/JavaLangReflect/Constructor+Utilities.swift diff --git a/Sources/JavaKitReflection/Executable+Utilities.swift b/Sources/JavaStdlib/JavaLangReflect/Executable+Utilities.swift similarity index 98% rename from Sources/JavaKitReflection/Executable+Utilities.swift rename to Sources/JavaStdlib/JavaLangReflect/Executable+Utilities.swift index 2b0a8a2db..bf70f1146 100644 --- a/Sources/JavaKitReflection/Executable+Utilities.swift +++ b/Sources/JavaStdlib/JavaLangReflect/Executable+Utilities.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaKit +import SwiftJava extension Executable { /// Whether this executable throws any checked exception. diff --git a/Sources/JavaKitReflection/Field+Utilities.swift b/Sources/JavaStdlib/JavaLangReflect/Field+Utilities.swift similarity index 100% rename from Sources/JavaKitReflection/Field+Utilities.swift rename to Sources/JavaStdlib/JavaLangReflect/Field+Utilities.swift diff --git a/Sources/JavaKitReflection/JavaClass+Reflection.swift b/Sources/JavaStdlib/JavaLangReflect/JavaClass+Reflection.swift similarity index 98% rename from Sources/JavaKitReflection/JavaClass+Reflection.swift rename to Sources/JavaStdlib/JavaLangReflect/JavaClass+Reflection.swift index 1f2120e00..29e24be5b 100644 --- a/Sources/JavaKitReflection/JavaClass+Reflection.swift +++ b/Sources/JavaStdlib/JavaLangReflect/JavaClass+Reflection.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaKit +import SwiftJava // TODO: We should be able to autogenerate this as an extension based on // knowing that JavaClass was defined elsewhere. diff --git a/Sources/JavaKitReflection/Method+Utilities.swift b/Sources/JavaStdlib/JavaLangReflect/Method+Utilities.swift similarity index 100% rename from Sources/JavaKitReflection/Method+Utilities.swift rename to Sources/JavaStdlib/JavaLangReflect/Method+Utilities.swift diff --git a/Sources/JavaKitReflection/generated/AccessibleObject.swift b/Sources/JavaStdlib/JavaLangReflect/generated/AccessibleObject.swift similarity index 97% rename from Sources/JavaKitReflection/generated/AccessibleObject.swift rename to Sources/JavaStdlib/JavaLangReflect/generated/AccessibleObject.swift index 84dcc4c23..c118f78b8 100644 --- a/Sources/JavaKitReflection/generated/AccessibleObject.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/AccessibleObject.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.lang.reflect.AccessibleObject") open class AccessibleObject: JavaObject { diff --git a/Sources/JavaKitReflection/generated/AnnotatedType.swift b/Sources/JavaStdlib/JavaLangReflect/generated/AnnotatedType.swift similarity index 96% rename from Sources/JavaKitReflection/generated/AnnotatedType.swift rename to Sources/JavaStdlib/JavaLangReflect/generated/AnnotatedType.swift index ca9b9994e..cc5b418c4 100644 --- a/Sources/JavaKitReflection/generated/AnnotatedType.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/AnnotatedType.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.lang.reflect.AnnotatedType") public struct AnnotatedType { diff --git a/Sources/JavaKitReflection/generated/Annotation.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Annotation.swift similarity index 91% rename from Sources/JavaKitReflection/generated/Annotation.swift rename to Sources/JavaStdlib/JavaLangReflect/generated/Annotation.swift index 269c0f9e1..66ab49cce 100644 --- a/Sources/JavaKitReflection/generated/Annotation.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Annotation.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.lang.annotation.Annotation") public struct Annotation { diff --git a/Sources/JavaKitReflection/generated/Constructor.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Constructor.swift similarity index 98% rename from Sources/JavaKitReflection/generated/Constructor.swift rename to Sources/JavaStdlib/JavaLangReflect/generated/Constructor.swift index 263d767d1..76f49f118 100644 --- a/Sources/JavaKitReflection/generated/Constructor.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Constructor.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.lang.reflect.Constructor") open class Constructor: Executable { diff --git a/Sources/JavaKitReflection/generated/Executable.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift similarity index 96% rename from Sources/JavaKitReflection/generated/Executable.swift rename to Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift index 6a1f5b75c..31799784e 100644 --- a/Sources/JavaKitReflection/generated/Executable.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift @@ -1,7 +1,7 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaKitCollection -import JavaRuntime +import SwiftJava +import JavaUtil +import CJNI @JavaClass("java.lang.reflect.Executable", implements: GenericDeclaration.self) open class Executable: AccessibleObject { diff --git a/Sources/JavaKitReflection/generated/Field.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Field.swift similarity index 97% rename from Sources/JavaKitReflection/generated/Field.swift rename to Sources/JavaStdlib/JavaLangReflect/generated/Field.swift index c67e56ffd..2c4a49e9a 100644 --- a/Sources/JavaKitReflection/generated/Field.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Field.swift @@ -1,7 +1,7 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaKitCollection -import JavaRuntime +import SwiftJava +import JavaUtil +import CJNI @JavaClass("java.lang.reflect.Field") open class Field: AccessibleObject { diff --git a/Sources/JavaKitReflection/generated/GenericArrayType.swift b/Sources/JavaStdlib/JavaLangReflect/generated/GenericArrayType.swift similarity index 89% rename from Sources/JavaKitReflection/generated/GenericArrayType.swift rename to Sources/JavaStdlib/JavaLangReflect/generated/GenericArrayType.swift index 6f96a0d23..75ad2c752 100644 --- a/Sources/JavaKitReflection/generated/GenericArrayType.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/GenericArrayType.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.lang.reflect.GenericArrayType", extends: Type.self) public struct GenericArrayType { diff --git a/Sources/JavaKitReflection/generated/GenericDeclaration.swift b/Sources/JavaStdlib/JavaLangReflect/generated/GenericDeclaration.swift similarity index 96% rename from Sources/JavaKitReflection/generated/GenericDeclaration.swift rename to Sources/JavaStdlib/JavaLangReflect/generated/GenericDeclaration.swift index 7a86bd8ac..bec84aa58 100644 --- a/Sources/JavaKitReflection/generated/GenericDeclaration.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/GenericDeclaration.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.lang.reflect.GenericDeclaration") public struct GenericDeclaration { diff --git a/Sources/JavaKitReflection/generated/Method.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Method.swift similarity index 98% rename from Sources/JavaKitReflection/generated/Method.swift rename to Sources/JavaStdlib/JavaLangReflect/generated/Method.swift index 46183f4f0..4489cf24b 100644 --- a/Sources/JavaKitReflection/generated/Method.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Method.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.lang.reflect.Method") open class Method: Executable { diff --git a/Sources/JavaKitReflection/generated/Parameter.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Parameter.swift similarity index 95% rename from Sources/JavaKitReflection/generated/Parameter.swift rename to Sources/JavaStdlib/JavaLangReflect/generated/Parameter.swift index 30f2d6282..6dea46fd0 100644 --- a/Sources/JavaKitReflection/generated/Parameter.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Parameter.swift @@ -1,7 +1,7 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaKitCollection -import JavaRuntime +import SwiftJava +import JavaUtil +import CJNI @JavaClass("java.lang.reflect.Parameter") open class Parameter: JavaObject { diff --git a/Sources/JavaKitReflection/generated/ParameterizedType.swift b/Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift similarity index 91% rename from Sources/JavaKitReflection/generated/ParameterizedType.swift rename to Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift index 3e4137148..a427a2a5a 100644 --- a/Sources/JavaKitReflection/generated/ParameterizedType.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.lang.reflect.ParameterizedType", extends: Type.self) public struct ParameterizedType { diff --git a/Sources/JavaKitReflection/generated/Type.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Type.swift similarity index 83% rename from Sources/JavaKitReflection/generated/Type.swift rename to Sources/JavaStdlib/JavaLangReflect/generated/Type.swift index ea6376cb4..05e8687ec 100644 --- a/Sources/JavaKitReflection/generated/Type.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Type.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.lang.reflect.Type") public struct Type { diff --git a/Sources/JavaKitReflection/generated/TypeVariable.swift b/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift similarity index 96% rename from Sources/JavaKitReflection/generated/TypeVariable.swift rename to Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift index c925cdcf3..e6a7899ce 100644 --- a/Sources/JavaKitReflection/generated/TypeVariable.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.lang.reflect.TypeVariable", extends: Type.self) public struct TypeVariable { diff --git a/Sources/JavaKitReflection/generated/WildcardType.swift b/Sources/JavaStdlib/JavaLangReflect/generated/WildcardType.swift similarity index 90% rename from Sources/JavaKitReflection/generated/WildcardType.swift rename to Sources/JavaStdlib/JavaLangReflect/generated/WildcardType.swift index e1551f506..2514495a1 100644 --- a/Sources/JavaKitReflection/generated/WildcardType.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/WildcardType.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.lang.reflect.WildcardType", extends: Type.self) public struct WildcardType { diff --git a/Sources/JavaKitReflection/swift-java.config b/Sources/JavaStdlib/JavaLangReflect/swift-java.config similarity index 100% rename from Sources/JavaKitReflection/swift-java.config rename to Sources/JavaStdlib/JavaLangReflect/swift-java.config diff --git a/Sources/JavaKitNetwork/generated/URI.swift b/Sources/JavaStdlib/JavaNet/generated/URI.swift similarity index 98% rename from Sources/JavaKitNetwork/generated/URI.swift rename to Sources/JavaStdlib/JavaNet/generated/URI.swift index 000e29caa..f1a6c0788 100644 --- a/Sources/JavaKitNetwork/generated/URI.swift +++ b/Sources/JavaStdlib/JavaNet/generated/URI.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.net.URI") open class URI: JavaObject { diff --git a/Sources/JavaKitNetwork/generated/URL.swift b/Sources/JavaStdlib/JavaNet/generated/URL.swift similarity index 97% rename from Sources/JavaKitNetwork/generated/URL.swift rename to Sources/JavaStdlib/JavaNet/generated/URL.swift index ed0dee389..9140a1d9e 100644 --- a/Sources/JavaKitNetwork/generated/URL.swift +++ b/Sources/JavaStdlib/JavaNet/generated/URL.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.net.URL") open class URL: JavaObject { diff --git a/Sources/JavaKitNetwork/generated/URLClassLoader.swift b/Sources/JavaStdlib/JavaNet/generated/URLClassLoader.swift similarity index 93% rename from Sources/JavaKitNetwork/generated/URLClassLoader.swift rename to Sources/JavaStdlib/JavaNet/generated/URLClassLoader.swift index eac9c14c0..1750f1970 100644 --- a/Sources/JavaKitNetwork/generated/URLClassLoader.swift +++ b/Sources/JavaStdlib/JavaNet/generated/URLClassLoader.swift @@ -1,7 +1,7 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaKitCollection -import JavaRuntime +import SwiftJava +import JavaUtil +import CJNI @JavaClass("java.net.URLClassLoader") open class URLClassLoader: JavaObject { diff --git a/Sources/JavaKitNetwork/generated/URLConnection.swift b/Sources/JavaStdlib/JavaNet/generated/URLConnection.swift similarity index 99% rename from Sources/JavaKitNetwork/generated/URLConnection.swift rename to Sources/JavaStdlib/JavaNet/generated/URLConnection.swift index 94fd6f523..6a93edd17 100644 --- a/Sources/JavaKitNetwork/generated/URLConnection.swift +++ b/Sources/JavaStdlib/JavaNet/generated/URLConnection.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.net.URLConnection") public struct URLConnection { diff --git a/Sources/JavaKitNetwork/swift-java.config b/Sources/JavaStdlib/JavaNet/swift-java.config similarity index 100% rename from Sources/JavaKitNetwork/swift-java.config rename to Sources/JavaStdlib/JavaNet/swift-java.config diff --git a/Sources/JavaKitCollection/JavaEnumeration+Sequence.swift b/Sources/JavaStdlib/JavaUtil/JavaEnumeration+Sequence.swift similarity index 100% rename from Sources/JavaKitCollection/JavaEnumeration+Sequence.swift rename to Sources/JavaStdlib/JavaUtil/JavaEnumeration+Sequence.swift diff --git a/Sources/JavaKitCollection/JavaIterator+Iterator.swift b/Sources/JavaStdlib/JavaUtil/JavaIterator+Iterator.swift similarity index 97% rename from Sources/JavaKitCollection/JavaIterator+Iterator.swift rename to Sources/JavaStdlib/JavaUtil/JavaIterator+Iterator.swift index add2a48b4..ad4275270 100644 --- a/Sources/JavaKitCollection/JavaIterator+Iterator.swift +++ b/Sources/JavaStdlib/JavaUtil/JavaIterator+Iterator.swift @@ -11,7 +11,7 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// -import JavaKit +import SwiftJava extension JavaIterator: IteratorProtocol { public typealias Element = E diff --git a/Sources/JavaKitCollection/List+Sequence.swift b/Sources/JavaStdlib/JavaUtil/List+Sequence.swift similarity index 100% rename from Sources/JavaKitCollection/List+Sequence.swift rename to Sources/JavaStdlib/JavaUtil/List+Sequence.swift diff --git a/Sources/JavaKitCollection/generated/ArrayDeque.swift b/Sources/JavaStdlib/JavaUtil/generated/ArrayDeque.swift similarity index 98% rename from Sources/JavaKitCollection/generated/ArrayDeque.swift rename to Sources/JavaStdlib/JavaUtil/generated/ArrayDeque.swift index 79a1e20bf..f0d257afb 100644 --- a/Sources/JavaKitCollection/generated/ArrayDeque.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/ArrayDeque.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.ArrayDeque") open class ArrayDeque: JavaObject { diff --git a/Sources/JavaKitCollection/generated/ArrayList.swift b/Sources/JavaStdlib/JavaUtil/generated/ArrayList.swift similarity index 98% rename from Sources/JavaKitCollection/generated/ArrayList.swift rename to Sources/JavaStdlib/JavaUtil/generated/ArrayList.swift index 6748640b3..b39ada568 100644 --- a/Sources/JavaKitCollection/generated/ArrayList.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/ArrayList.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.ArrayList", implements: List.self, RandomAccess.self) open class ArrayList: JavaObject { diff --git a/Sources/JavaKitCollection/generated/BitSet.swift b/Sources/JavaStdlib/JavaUtil/generated/BitSet.swift similarity index 98% rename from Sources/JavaKitCollection/generated/BitSet.swift rename to Sources/JavaStdlib/JavaUtil/generated/BitSet.swift index d5211c28b..2d89ba5a4 100644 --- a/Sources/JavaKitCollection/generated/BitSet.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/BitSet.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.BitSet") open class BitSet: JavaObject { diff --git a/Sources/JavaKitCollection/generated/Enumeration.swift b/Sources/JavaStdlib/JavaUtil/generated/Enumeration.swift similarity index 90% rename from Sources/JavaKitCollection/generated/Enumeration.swift rename to Sources/JavaStdlib/JavaUtil/generated/Enumeration.swift index 492cc3b2a..0229e16d4 100644 --- a/Sources/JavaKitCollection/generated/Enumeration.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/Enumeration.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.Enumeration") public struct Enumeration { diff --git a/Sources/JavaKitCollection/generated/HashMap.swift b/Sources/JavaStdlib/JavaUtil/generated/HashMap.swift similarity index 98% rename from Sources/JavaKitCollection/generated/HashMap.swift rename to Sources/JavaStdlib/JavaUtil/generated/HashMap.swift index 424dfbb9a..6fd38bc4a 100644 --- a/Sources/JavaKitCollection/generated/HashMap.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/HashMap.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.HashMap") open class HashMap: JavaObject { diff --git a/Sources/JavaKitCollection/generated/HashSet.swift b/Sources/JavaStdlib/JavaUtil/generated/HashSet.swift similarity index 97% rename from Sources/JavaKitCollection/generated/HashSet.swift rename to Sources/JavaStdlib/JavaUtil/generated/HashSet.swift index 97b15dffe..f7c37d21e 100644 --- a/Sources/JavaKitCollection/generated/HashSet.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/HashSet.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.HashSet", implements: JavaSet.self) open class HashSet: JavaObject { diff --git a/Sources/JavaKitCollection/generated/JavaCollection.swift b/Sources/JavaStdlib/JavaUtil/generated/JavaCollection.swift similarity index 97% rename from Sources/JavaKitCollection/generated/JavaCollection.swift rename to Sources/JavaStdlib/JavaUtil/generated/JavaCollection.swift index 26ca6827e..4d1c8a811 100644 --- a/Sources/JavaKitCollection/generated/JavaCollection.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/JavaCollection.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.Collection") public struct JavaCollection { diff --git a/Sources/JavaKitCollection/generated/JavaDictionary.swift b/Sources/JavaStdlib/JavaUtil/generated/JavaDictionary.swift similarity index 95% rename from Sources/JavaKitCollection/generated/JavaDictionary.swift rename to Sources/JavaStdlib/JavaUtil/generated/JavaDictionary.swift index 43ded9154..85ee9bebb 100644 --- a/Sources/JavaKitCollection/generated/JavaDictionary.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/JavaDictionary.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.Dictionary") open class JavaDictionary: JavaObject { diff --git a/Sources/JavaKitCollection/generated/JavaIterator.swift b/Sources/JavaStdlib/JavaUtil/generated/JavaIterator.swift similarity index 89% rename from Sources/JavaKitCollection/generated/JavaIterator.swift rename to Sources/JavaStdlib/JavaUtil/generated/JavaIterator.swift index 382c44e53..1038a5e1b 100644 --- a/Sources/JavaKitCollection/generated/JavaIterator.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/JavaIterator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.Iterator") public struct JavaIterator { diff --git a/Sources/JavaKitCollection/generated/JavaSet.swift b/Sources/JavaStdlib/JavaUtil/generated/JavaSet.swift similarity index 99% rename from Sources/JavaKitCollection/generated/JavaSet.swift rename to Sources/JavaStdlib/JavaUtil/generated/JavaSet.swift index d924d2d17..7fec8e7ac 100644 --- a/Sources/JavaKitCollection/generated/JavaSet.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/JavaSet.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.Set", extends: JavaCollection.self) public struct JavaSet { diff --git a/Sources/JavaKitCollection/generated/LinkedList.swift b/Sources/JavaStdlib/JavaUtil/generated/LinkedList.swift similarity index 99% rename from Sources/JavaKitCollection/generated/LinkedList.swift rename to Sources/JavaStdlib/JavaUtil/generated/LinkedList.swift index 25abfbbf0..9f27a3e16 100644 --- a/Sources/JavaKitCollection/generated/LinkedList.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/LinkedList.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.LinkedList") public struct LinkedList { diff --git a/Sources/JavaKitCollection/generated/List.swift b/Sources/JavaStdlib/JavaUtil/generated/List.swift similarity index 99% rename from Sources/JavaKitCollection/generated/List.swift rename to Sources/JavaStdlib/JavaUtil/generated/List.swift index b264f88eb..1d745ec3b 100644 --- a/Sources/JavaKitCollection/generated/List.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/List.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.List") public struct List { diff --git a/Sources/JavaKitCollection/generated/ListIterator.swift b/Sources/JavaStdlib/JavaUtil/generated/ListIterator.swift similarity index 94% rename from Sources/JavaKitCollection/generated/ListIterator.swift rename to Sources/JavaStdlib/JavaUtil/generated/ListIterator.swift index 7b2731605..a643efa73 100644 --- a/Sources/JavaKitCollection/generated/ListIterator.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/ListIterator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.ListIterator", extends: JavaIterator.self) public struct ListIterator { diff --git a/Sources/JavaKitCollection/generated/PriorityQueue.swift b/Sources/JavaStdlib/JavaUtil/generated/PriorityQueue.swift similarity index 97% rename from Sources/JavaKitCollection/generated/PriorityQueue.swift rename to Sources/JavaStdlib/JavaUtil/generated/PriorityQueue.swift index 14e50f31b..13fc36292 100644 --- a/Sources/JavaKitCollection/generated/PriorityQueue.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/PriorityQueue.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.PriorityQueue") open class PriorityQueue: JavaObject { diff --git a/Sources/JavaKitCollection/generated/Queue.swift b/Sources/JavaStdlib/JavaUtil/generated/Queue.swift similarity index 97% rename from Sources/JavaKitCollection/generated/Queue.swift rename to Sources/JavaStdlib/JavaUtil/generated/Queue.swift index 44373fce8..1d51d7c40 100644 --- a/Sources/JavaKitCollection/generated/Queue.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/Queue.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.Queue", extends: JavaCollection.self) public struct Queue { diff --git a/Sources/JavaKitCollection/generated/RandomAccess.swift b/Sources/JavaStdlib/JavaUtil/generated/RandomAccess.swift similarity index 79% rename from Sources/JavaKitCollection/generated/RandomAccess.swift rename to Sources/JavaStdlib/JavaUtil/generated/RandomAccess.swift index 0084ceede..e0fcb3907 100644 --- a/Sources/JavaKitCollection/generated/RandomAccess.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/RandomAccess.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.RandomAccess") public struct RandomAccess { diff --git a/Sources/JavaKitCollection/generated/Stack.swift b/Sources/JavaStdlib/JavaUtil/generated/Stack.swift similarity index 93% rename from Sources/JavaKitCollection/generated/Stack.swift rename to Sources/JavaStdlib/JavaUtil/generated/Stack.swift index 867e9d443..636ed03be 100644 --- a/Sources/JavaKitCollection/generated/Stack.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/Stack.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.Stack") open class Stack: JavaObject { diff --git a/Sources/JavaKitCollection/generated/TreeMap.swift b/Sources/JavaStdlib/JavaUtil/generated/TreeMap.swift similarity index 98% rename from Sources/JavaKitCollection/generated/TreeMap.swift rename to Sources/JavaStdlib/JavaUtil/generated/TreeMap.swift index 7796d555e..4bf41ed37 100644 --- a/Sources/JavaKitCollection/generated/TreeMap.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/TreeMap.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.TreeMap") open class TreeMap: JavaObject { diff --git a/Sources/JavaKitCollection/generated/TreeSet.swift b/Sources/JavaStdlib/JavaUtil/generated/TreeSet.swift similarity index 97% rename from Sources/JavaKitCollection/generated/TreeSet.swift rename to Sources/JavaStdlib/JavaUtil/generated/TreeSet.swift index 7e1807abb..cbfe8979b 100644 --- a/Sources/JavaKitCollection/generated/TreeSet.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/TreeSet.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.TreeSet") open class TreeSet: JavaObject { diff --git a/Sources/JavaKitCollection/swift-java.config b/Sources/JavaStdlib/JavaUtil/swift-java.config similarity index 100% rename from Sources/JavaKitCollection/swift-java.config rename to Sources/JavaStdlib/JavaUtil/swift-java.config diff --git a/Sources/JavaKitFunction/generated/JavaBiConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiConsumer.swift similarity index 91% rename from Sources/JavaKitFunction/generated/JavaBiConsumer.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiConsumer.swift index c5f5b7bfa..a8f331279 100644 --- a/Sources/JavaKitFunction/generated/JavaBiConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.BiConsumer") public struct JavaBiConsumer { diff --git a/Sources/JavaKitFunction/generated/JavaBiFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiFunction.swift similarity index 92% rename from Sources/JavaKitFunction/generated/JavaBiFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiFunction.swift index edecbff58..d83e74a83 100644 --- a/Sources/JavaKitFunction/generated/JavaBiFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.BiFunction") public struct JavaBiFunction { diff --git a/Sources/JavaKitFunction/generated/JavaBiPredicate.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiPredicate.swift similarity index 94% rename from Sources/JavaKitFunction/generated/JavaBiPredicate.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiPredicate.swift index 5fbc145ef..f1870aa49 100644 --- a/Sources/JavaKitFunction/generated/JavaBiPredicate.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiPredicate.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.BiPredicate") public struct JavaBiPredicate { diff --git a/Sources/JavaKitFunction/generated/JavaBinaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBinaryOperator.swift similarity index 93% rename from Sources/JavaKitFunction/generated/JavaBinaryOperator.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaBinaryOperator.swift index 3d0b0cd5d..a03e9addf 100644 --- a/Sources/JavaKitFunction/generated/JavaBinaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBinaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface( "java.util.function.BinaryOperator", diff --git a/Sources/JavaKitFunction/generated/JavaBooleanSupplier.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBooleanSupplier.swift similarity index 85% rename from Sources/JavaKitFunction/generated/JavaBooleanSupplier.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaBooleanSupplier.swift index eef960252..d6ce66de8 100644 --- a/Sources/JavaKitFunction/generated/JavaBooleanSupplier.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBooleanSupplier.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.BooleanSupplier") public struct JavaBooleanSupplier { diff --git a/Sources/JavaKitFunction/generated/JavaConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaConsumer.swift similarity index 90% rename from Sources/JavaKitFunction/generated/JavaConsumer.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaConsumer.swift index 91eac1063..f8e64b2f7 100644 --- a/Sources/JavaKitFunction/generated/JavaConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.Consumer") public struct JavaConsumer { diff --git a/Sources/JavaKitFunction/generated/JavaDoubleBinaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleBinaryOperator.swift similarity index 87% rename from Sources/JavaKitFunction/generated/JavaDoubleBinaryOperator.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleBinaryOperator.swift index 0415ed90e..ee82194d9 100644 --- a/Sources/JavaKitFunction/generated/JavaDoubleBinaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleBinaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.DoubleBinaryOperator") public struct JavaDoubleBinaryOperator { diff --git a/Sources/JavaKitFunction/generated/JavaDoubleConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleConsumer.swift similarity index 89% rename from Sources/JavaKitFunction/generated/JavaDoubleConsumer.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleConsumer.swift index 9c3225847..a4a5a48f2 100644 --- a/Sources/JavaKitFunction/generated/JavaDoubleConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.DoubleConsumer") public struct JavaDoubleConsumer { diff --git a/Sources/JavaKitFunction/generated/JavaDoubleFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleFunction.swift similarity index 86% rename from Sources/JavaKitFunction/generated/JavaDoubleFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleFunction.swift index 4ff4f3ef9..96879af49 100644 --- a/Sources/JavaKitFunction/generated/JavaDoubleFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.DoubleFunction") public struct JavaDoubleFunction { diff --git a/Sources/JavaKitFunction/generated/JavaDoublePredicate.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoublePredicate.swift similarity index 92% rename from Sources/JavaKitFunction/generated/JavaDoublePredicate.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoublePredicate.swift index b2add42f1..27ac5861a 100644 --- a/Sources/JavaKitFunction/generated/JavaDoublePredicate.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoublePredicate.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.DoublePredicate") public struct JavaDoublePredicate { diff --git a/Sources/JavaKitFunction/generated/JavaDoubleSupplier.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleSupplier.swift similarity index 85% rename from Sources/JavaKitFunction/generated/JavaDoubleSupplier.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleSupplier.swift index 184a4353c..807668bcb 100644 --- a/Sources/JavaKitFunction/generated/JavaDoubleSupplier.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleSupplier.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.DoubleSupplier") public struct JavaDoubleSupplier { diff --git a/Sources/JavaKitFunction/generated/JavaDoubleToIntFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleToIntFunction.swift similarity index 86% rename from Sources/JavaKitFunction/generated/JavaDoubleToIntFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleToIntFunction.swift index ae4ab0b3c..4de7ab7f1 100644 --- a/Sources/JavaKitFunction/generated/JavaDoubleToIntFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleToIntFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.DoubleToIntFunction") public struct JavaDoubleToIntFunction { diff --git a/Sources/JavaKitFunction/generated/JavaDoubleToLongFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleToLongFunction.swift similarity index 86% rename from Sources/JavaKitFunction/generated/JavaDoubleToLongFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleToLongFunction.swift index b0c9f9d62..492dbfa67 100644 --- a/Sources/JavaKitFunction/generated/JavaDoubleToLongFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleToLongFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.DoubleToLongFunction") public struct JavaDoubleToLongFunction { diff --git a/Sources/JavaKitFunction/generated/JavaDoubleUnaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleUnaryOperator.swift similarity index 94% rename from Sources/JavaKitFunction/generated/JavaDoubleUnaryOperator.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleUnaryOperator.swift index eb29139a2..9db463ee6 100644 --- a/Sources/JavaKitFunction/generated/JavaDoubleUnaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleUnaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.DoubleUnaryOperator") public struct JavaDoubleUnaryOperator { diff --git a/Sources/JavaKitFunction/generated/JavaFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaFunction.swift similarity index 95% rename from Sources/JavaKitFunction/generated/JavaFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaFunction.swift index 744461a96..6e2361d79 100644 --- a/Sources/JavaKitFunction/generated/JavaFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.Function") public struct JavaFunction { diff --git a/Sources/JavaKitFunction/generated/JavaIntBinaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntBinaryOperator.swift similarity index 87% rename from Sources/JavaKitFunction/generated/JavaIntBinaryOperator.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntBinaryOperator.swift index 458885fe5..f7a56f184 100644 --- a/Sources/JavaKitFunction/generated/JavaIntBinaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntBinaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.IntBinaryOperator") public struct JavaIntBinaryOperator { diff --git a/Sources/JavaKitFunction/generated/JavaIntConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntConsumer.swift similarity index 88% rename from Sources/JavaKitFunction/generated/JavaIntConsumer.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntConsumer.swift index 704a45138..62130b888 100644 --- a/Sources/JavaKitFunction/generated/JavaIntConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.IntConsumer") public struct JavaIntConsumer { diff --git a/Sources/JavaKitFunction/generated/JavaIntFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntFunction.swift similarity index 86% rename from Sources/JavaKitFunction/generated/JavaIntFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntFunction.swift index cd7e7219a..c3fcdf1ec 100644 --- a/Sources/JavaKitFunction/generated/JavaIntFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.IntFunction") public struct JavaIntFunction { diff --git a/Sources/JavaKitFunction/generated/JavaIntPredicate.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntPredicate.swift similarity index 92% rename from Sources/JavaKitFunction/generated/JavaIntPredicate.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntPredicate.swift index 9580ef874..2ca8fcc0d 100644 --- a/Sources/JavaKitFunction/generated/JavaIntPredicate.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntPredicate.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.IntPredicate") public struct JavaIntPredicate { diff --git a/Sources/JavaKitFunction/generated/JavaIntSupplier.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntSupplier.swift similarity index 84% rename from Sources/JavaKitFunction/generated/JavaIntSupplier.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntSupplier.swift index f0fae86aa..05155e793 100644 --- a/Sources/JavaKitFunction/generated/JavaIntSupplier.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntSupplier.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.IntSupplier") public struct JavaIntSupplier { diff --git a/Sources/JavaKitFunction/generated/JavaIntToDoubleFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntToDoubleFunction.swift similarity index 86% rename from Sources/JavaKitFunction/generated/JavaIntToDoubleFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntToDoubleFunction.swift index 0a52e5b7e..f3efcc6d9 100644 --- a/Sources/JavaKitFunction/generated/JavaIntToDoubleFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntToDoubleFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.IntToDoubleFunction") public struct JavaIntToDoubleFunction { diff --git a/Sources/JavaKitFunction/generated/JavaIntToLongFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntToLongFunction.swift similarity index 86% rename from Sources/JavaKitFunction/generated/JavaIntToLongFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntToLongFunction.swift index 395dc8761..1bbe989c5 100644 --- a/Sources/JavaKitFunction/generated/JavaIntToLongFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntToLongFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.IntToLongFunction") public struct JavaIntToLongFunction { diff --git a/Sources/JavaKitFunction/generated/JavaIntUnaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntUnaryOperator.swift similarity index 93% rename from Sources/JavaKitFunction/generated/JavaIntUnaryOperator.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntUnaryOperator.swift index 221b3a97a..04ed0b2b8 100644 --- a/Sources/JavaKitFunction/generated/JavaIntUnaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntUnaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.IntUnaryOperator") public struct JavaIntUnaryOperator { diff --git a/Sources/JavaKitFunction/generated/JavaLongBinaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongBinaryOperator.swift similarity index 87% rename from Sources/JavaKitFunction/generated/JavaLongBinaryOperator.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongBinaryOperator.swift index 88035edc4..1f3ca7915 100644 --- a/Sources/JavaKitFunction/generated/JavaLongBinaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongBinaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.LongBinaryOperator") public struct JavaLongBinaryOperator { diff --git a/Sources/JavaKitFunction/generated/JavaLongConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongConsumer.swift similarity index 88% rename from Sources/JavaKitFunction/generated/JavaLongConsumer.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongConsumer.swift index b0ef6013b..82ae2a3bf 100644 --- a/Sources/JavaKitFunction/generated/JavaLongConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.LongConsumer") public struct JavaLongConsumer { diff --git a/Sources/JavaKitFunction/generated/JavaLongFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongFunction.swift similarity index 86% rename from Sources/JavaKitFunction/generated/JavaLongFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongFunction.swift index 2d9a75597..eeed2bd81 100644 --- a/Sources/JavaKitFunction/generated/JavaLongFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.LongFunction") public struct JavaLongFunction { diff --git a/Sources/JavaKitFunction/generated/JavaLongPredicate.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongPredicate.swift similarity index 92% rename from Sources/JavaKitFunction/generated/JavaLongPredicate.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongPredicate.swift index 3ed020eb6..77f1293e6 100644 --- a/Sources/JavaKitFunction/generated/JavaLongPredicate.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongPredicate.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.LongPredicate") public struct JavaLongPredicate { diff --git a/Sources/JavaKitFunction/generated/JavaLongSupplier.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongSupplier.swift similarity index 84% rename from Sources/JavaKitFunction/generated/JavaLongSupplier.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongSupplier.swift index 03ad63033..60e5421d7 100644 --- a/Sources/JavaKitFunction/generated/JavaLongSupplier.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongSupplier.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.LongSupplier") public struct JavaLongSupplier { diff --git a/Sources/JavaKitFunction/generated/JavaLongToDoubleFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongToDoubleFunction.swift similarity index 86% rename from Sources/JavaKitFunction/generated/JavaLongToDoubleFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongToDoubleFunction.swift index 99b56ba32..a115477c0 100644 --- a/Sources/JavaKitFunction/generated/JavaLongToDoubleFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongToDoubleFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.LongToDoubleFunction") public struct JavaLongToDoubleFunction { diff --git a/Sources/JavaKitFunction/generated/JavaLongToIntFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongToIntFunction.swift similarity index 86% rename from Sources/JavaKitFunction/generated/JavaLongToIntFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongToIntFunction.swift index 2c952a174..984dbda31 100644 --- a/Sources/JavaKitFunction/generated/JavaLongToIntFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongToIntFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.LongToIntFunction") public struct JavaLongToIntFunction { diff --git a/Sources/JavaKitFunction/generated/JavaLongUnaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongUnaryOperator.swift similarity index 93% rename from Sources/JavaKitFunction/generated/JavaLongUnaryOperator.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongUnaryOperator.swift index fe99e3380..7e379b0af 100644 --- a/Sources/JavaKitFunction/generated/JavaLongUnaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongUnaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.LongUnaryOperator") public struct JavaLongUnaryOperator { diff --git a/Sources/JavaKitFunction/generated/JavaObjDoubleConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjDoubleConsumer.swift similarity index 87% rename from Sources/JavaKitFunction/generated/JavaObjDoubleConsumer.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjDoubleConsumer.swift index cf1ac236f..5c19ff03c 100644 --- a/Sources/JavaKitFunction/generated/JavaObjDoubleConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjDoubleConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.ObjDoubleConsumer") public struct JavaObjDoubleConsumer { diff --git a/Sources/JavaKitFunction/generated/JavaObjIntConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjIntConsumer.swift similarity index 87% rename from Sources/JavaKitFunction/generated/JavaObjIntConsumer.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjIntConsumer.swift index 6c1e3b1ea..a511db83e 100644 --- a/Sources/JavaKitFunction/generated/JavaObjIntConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjIntConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.ObjIntConsumer") public struct JavaObjIntConsumer { diff --git a/Sources/JavaKitFunction/generated/JavaObjLongConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjLongConsumer.swift similarity index 87% rename from Sources/JavaKitFunction/generated/JavaObjLongConsumer.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjLongConsumer.swift index 5486e9102..a2c32cfa1 100644 --- a/Sources/JavaKitFunction/generated/JavaObjLongConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjLongConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.ObjLongConsumer") public struct JavaObjLongConsumer { diff --git a/Sources/JavaKitFunction/generated/JavaPredicate.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaPredicate.swift similarity index 96% rename from Sources/JavaKitFunction/generated/JavaPredicate.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaPredicate.swift index 9c953e3f6..a2de359aa 100644 --- a/Sources/JavaKitFunction/generated/JavaPredicate.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaPredicate.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.Predicate") public struct JavaPredicate { diff --git a/Sources/JavaKitFunction/generated/JavaSupplier.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaSupplier.swift similarity index 85% rename from Sources/JavaKitFunction/generated/JavaSupplier.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaSupplier.swift index b44740dd8..a751ae793 100644 --- a/Sources/JavaKitFunction/generated/JavaSupplier.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaSupplier.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.Supplier") public struct JavaSupplier { diff --git a/Sources/JavaKitFunction/generated/JavaToDoubleBiFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToDoubleBiFunction.swift similarity index 89% rename from Sources/JavaKitFunction/generated/JavaToDoubleBiFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaToDoubleBiFunction.swift index 22ccf62c3..739f1f178 100644 --- a/Sources/JavaKitFunction/generated/JavaToDoubleBiFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToDoubleBiFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.ToDoubleBiFunction") public struct JavaToDoubleBiFunction { diff --git a/Sources/JavaKitFunction/generated/JavaToDoubleFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToDoubleFunction.swift similarity index 87% rename from Sources/JavaKitFunction/generated/JavaToDoubleFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaToDoubleFunction.swift index bf1c1d37a..acdf7adb0 100644 --- a/Sources/JavaKitFunction/generated/JavaToDoubleFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToDoubleFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.ToDoubleFunction") public struct JavaToDoubleFunction { diff --git a/Sources/JavaKitFunction/generated/JavaToIntBiFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToIntBiFunction.swift similarity index 88% rename from Sources/JavaKitFunction/generated/JavaToIntBiFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaToIntBiFunction.swift index 6ebc0ff18..e1fdcc0af 100644 --- a/Sources/JavaKitFunction/generated/JavaToIntBiFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToIntBiFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.ToIntBiFunction") public struct JavaToIntBiFunction { diff --git a/Sources/JavaKitFunction/generated/JavaToIntFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToIntFunction.swift similarity index 87% rename from Sources/JavaKitFunction/generated/JavaToIntFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaToIntFunction.swift index c960913d6..4d963c57e 100644 --- a/Sources/JavaKitFunction/generated/JavaToIntFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToIntFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.ToIntFunction") public struct JavaToIntFunction { diff --git a/Sources/JavaKitFunction/generated/JavaToLongBiFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToLongBiFunction.swift similarity index 88% rename from Sources/JavaKitFunction/generated/JavaToLongBiFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaToLongBiFunction.swift index 93d0fc471..fb42c3e4e 100644 --- a/Sources/JavaKitFunction/generated/JavaToLongBiFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToLongBiFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.ToLongBiFunction") public struct JavaToLongBiFunction { diff --git a/Sources/JavaKitFunction/generated/JavaToLongFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToLongFunction.swift similarity index 87% rename from Sources/JavaKitFunction/generated/JavaToLongFunction.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaToLongFunction.swift index f4fd57675..e34b5bd0d 100644 --- a/Sources/JavaKitFunction/generated/JavaToLongFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToLongFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface("java.util.function.ToLongFunction") public struct JavaToLongFunction { diff --git a/Sources/JavaKitFunction/generated/JavaUnaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaUnaryOperator.swift similarity index 95% rename from Sources/JavaKitFunction/generated/JavaUnaryOperator.swift rename to Sources/JavaStdlib/JavaUtilFunction/generated/JavaUnaryOperator.swift index 232283ba1..a31be86ea 100644 --- a/Sources/JavaKitFunction/generated/JavaUnaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaUnaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaInterface( "java.util.function.UnaryOperator", extends: JavaFunction.self) diff --git a/Sources/JavaKitFunction/swift-java.config b/Sources/JavaStdlib/JavaUtilFunction/swift-java.config similarity index 100% rename from Sources/JavaKitFunction/swift-java.config rename to Sources/JavaStdlib/JavaUtilFunction/swift-java.config diff --git a/Sources/JavaKitJar/generated/Attributes.swift b/Sources/JavaStdlib/JavaUtilJar/generated/Attributes.swift similarity index 98% rename from Sources/JavaKitJar/generated/Attributes.swift rename to Sources/JavaStdlib/JavaUtilJar/generated/Attributes.swift index f173c98d9..710ce6407 100644 --- a/Sources/JavaKitJar/generated/Attributes.swift +++ b/Sources/JavaStdlib/JavaUtilJar/generated/Attributes.swift @@ -1,7 +1,7 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaKitCollection -import JavaRuntime +import SwiftJava +import JavaUtil +import CJNI @JavaClass("java.util.jar.Attributes") open class Attributes: JavaObject { diff --git a/Sources/JavaKitJar/generated/JarEntry.swift b/Sources/JavaStdlib/JavaUtilJar/generated/JarEntry.swift similarity index 98% rename from Sources/JavaKitJar/generated/JarEntry.swift rename to Sources/JavaStdlib/JavaUtilJar/generated/JarEntry.swift index 3a90d6a8f..7140cb974 100644 --- a/Sources/JavaKitJar/generated/JarEntry.swift +++ b/Sources/JavaStdlib/JavaUtilJar/generated/JarEntry.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.jar.JarEntry") open class JarEntry: ZipEntry { diff --git a/Sources/JavaKitJar/generated/JarFile.swift b/Sources/JavaStdlib/JavaUtilJar/generated/JarFile.swift similarity index 98% rename from Sources/JavaKitJar/generated/JarFile.swift rename to Sources/JavaStdlib/JavaUtilJar/generated/JarFile.swift index d5ea99697..db84e6264 100644 --- a/Sources/JavaKitJar/generated/JarFile.swift +++ b/Sources/JavaStdlib/JavaUtilJar/generated/JarFile.swift @@ -1,7 +1,7 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaKitCollection -import JavaRuntime +import SwiftJava +import JavaUtil +import CJNI @JavaClass("java.util.jar.JarFile") open class JarFile: JavaObject { diff --git a/Sources/JavaKitJar/generated/JarInputStream.swift b/Sources/JavaStdlib/JavaUtilJar/generated/JarInputStream.swift similarity index 98% rename from Sources/JavaKitJar/generated/JarInputStream.swift rename to Sources/JavaStdlib/JavaUtilJar/generated/JarInputStream.swift index f6d121d90..7d2ce4233 100644 --- a/Sources/JavaKitJar/generated/JarInputStream.swift +++ b/Sources/JavaStdlib/JavaUtilJar/generated/JarInputStream.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.jar.JarInputStream") open class JarInputStream: JavaObject { diff --git a/Sources/JavaKitJar/generated/JarOutputStream.swift b/Sources/JavaStdlib/JavaUtilJar/generated/JarOutputStream.swift similarity index 98% rename from Sources/JavaKitJar/generated/JarOutputStream.swift rename to Sources/JavaStdlib/JavaUtilJar/generated/JarOutputStream.swift index e13dc9734..79a7c14ec 100644 --- a/Sources/JavaKitJar/generated/JarOutputStream.swift +++ b/Sources/JavaStdlib/JavaUtilJar/generated/JarOutputStream.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.jar.JarOutputStream") open class JarOutputStream: JavaObject { diff --git a/Sources/JavaKitJar/generated/Manifest.swift b/Sources/JavaStdlib/JavaUtilJar/generated/Manifest.swift similarity index 95% rename from Sources/JavaKitJar/generated/Manifest.swift rename to Sources/JavaStdlib/JavaUtilJar/generated/Manifest.swift index 101af57c6..b43b9e85d 100644 --- a/Sources/JavaKitJar/generated/Manifest.swift +++ b/Sources/JavaStdlib/JavaUtilJar/generated/Manifest.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.jar.Manifest") open class Manifest: JavaObject { diff --git a/Sources/JavaKitJar/generated/ZipEntry.swift b/Sources/JavaStdlib/JavaUtilJar/generated/ZipEntry.swift similarity index 99% rename from Sources/JavaKitJar/generated/ZipEntry.swift rename to Sources/JavaStdlib/JavaUtilJar/generated/ZipEntry.swift index 6a0fbd201..e276ae2a0 100644 --- a/Sources/JavaKitJar/generated/ZipEntry.swift +++ b/Sources/JavaStdlib/JavaUtilJar/generated/ZipEntry.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaKit -import JavaRuntime +import SwiftJava +import CJNI @JavaClass("java.util.zip.ZipEntry") open class ZipEntry: JavaObject { diff --git a/Sources/JavaKitJar/swift-java.config b/Sources/JavaStdlib/JavaUtilJar/swift-java.config similarity index 100% rename from Sources/JavaKitJar/swift-java.config rename to Sources/JavaStdlib/JavaUtilJar/swift-java.config diff --git a/Sources/JavaStdlib/README_PACKAGE_CONVENTION.md b/Sources/JavaStdlib/README_PACKAGE_CONVENTION.md new file mode 100644 index 000000000..ab65fa173 --- /dev/null +++ b/Sources/JavaStdlib/README_PACKAGE_CONVENTION.md @@ -0,0 +1,13 @@ +# Extracted Java Modules + +This directory contains Swift bindings for common Java standard library packages. +These pre-built bindings to solve a circular dependency problem - SwiftJava tools need these types to process and generate other bindings. + +You can also use these bindings directly in your SwiftJava programs to call Java classes without having to generate wrappers each time. + +The naming follows this pattern: Java package names become Swift target names. Example: `java.lang.util` becomes `JavaLangUtil`. + +Since Swift doesn't have namespaces like Java, all types appear at the top level in Swift. To avoid naming conflicts, +some types may be prefixed with 'J' (e.g. `JList` to avoid confusion with Swift native types). + +To see which Java types are included and any naming changes, check the `swift-java.config` file in each module. \ No newline at end of file diff --git a/Sources/JavaKit/AnyJavaObject.swift b/Sources/SwiftJava/AnyJavaObject.swift similarity index 99% rename from Sources/JavaKit/AnyJavaObject.swift rename to Sources/SwiftJava/AnyJavaObject.swift index 5e0a88d03..02b9d70b2 100644 --- a/Sources/JavaKit/AnyJavaObject.swift +++ b/Sources/SwiftJava/AnyJavaObject.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaRuntime +import CJNI /// Protocol that describes Swift types that are bridged to a Java class type. /// diff --git a/Sources/JavaKit/BridgedValues/JavaValue+Array.swift b/Sources/SwiftJava/BridgedValues/JavaValue+Array.swift similarity index 100% rename from Sources/JavaKit/BridgedValues/JavaValue+Array.swift rename to Sources/SwiftJava/BridgedValues/JavaValue+Array.swift diff --git a/Sources/JavaKit/BridgedValues/JavaValue+Bool.swift b/Sources/SwiftJava/BridgedValues/JavaValue+Bool.swift similarity index 100% rename from Sources/JavaKit/BridgedValues/JavaValue+Bool.swift rename to Sources/SwiftJava/BridgedValues/JavaValue+Bool.swift diff --git a/Sources/JavaKit/BridgedValues/JavaValue+FloatingPoint.swift b/Sources/SwiftJava/BridgedValues/JavaValue+FloatingPoint.swift similarity index 100% rename from Sources/JavaKit/BridgedValues/JavaValue+FloatingPoint.swift rename to Sources/SwiftJava/BridgedValues/JavaValue+FloatingPoint.swift diff --git a/Sources/JavaKit/BridgedValues/JavaValue+Integers.swift b/Sources/SwiftJava/BridgedValues/JavaValue+Integers.swift similarity index 100% rename from Sources/JavaKit/BridgedValues/JavaValue+Integers.swift rename to Sources/SwiftJava/BridgedValues/JavaValue+Integers.swift diff --git a/Sources/JavaKit/BridgedValues/JavaValue+String.swift b/Sources/SwiftJava/BridgedValues/JavaValue+String.swift similarity index 100% rename from Sources/JavaKit/BridgedValues/JavaValue+String.swift rename to Sources/SwiftJava/BridgedValues/JavaValue+String.swift diff --git a/Sources/JavaKit/Documentation.docc/JavaKit.md b/Sources/SwiftJava/Documentation.docc/JavaKit.md similarity index 99% rename from Sources/JavaKit/Documentation.docc/JavaKit.md rename to Sources/SwiftJava/Documentation.docc/JavaKit.md index fe0f50ba2..a050e88be 100644 --- a/Sources/JavaKit/Documentation.docc/JavaKit.md +++ b/Sources/SwiftJava/Documentation.docc/JavaKit.md @@ -200,7 +200,7 @@ let primes = sieveClass.findPrimes(100) // returns a List? Putting it all together, we can define a main program in `Sources/JavaSieve/main.swift` that looks like this: ```swift -import JavaKit +import SwiftJNI let jvm = try JavaVirtualMachine.shared(classpath: ["QuadraticSieve-1.0.jar"]) do { diff --git a/Sources/JavaKit/Exceptions/Exception+Error.swift b/Sources/SwiftJava/Exceptions/Exception+Error.swift similarity index 100% rename from Sources/JavaKit/Exceptions/Exception+Error.swift rename to Sources/SwiftJava/Exceptions/Exception+Error.swift diff --git a/Sources/JavaKit/Exceptions/ExceptionHandling.swift b/Sources/SwiftJava/Exceptions/ExceptionHandling.swift similarity index 100% rename from Sources/JavaKit/Exceptions/ExceptionHandling.swift rename to Sources/SwiftJava/Exceptions/ExceptionHandling.swift diff --git a/Sources/JavaKit/Exceptions/Throwable+Error.swift b/Sources/SwiftJava/Exceptions/Throwable+Error.swift similarity index 100% rename from Sources/JavaKit/Exceptions/Throwable+Error.swift rename to Sources/SwiftJava/Exceptions/Throwable+Error.swift diff --git a/Sources/SwiftJava/Helpers/_JNIMethodIDCache.swift b/Sources/SwiftJava/Helpers/_JNIMethodIDCache.swift new file mode 100644 index 000000000..a67d225f5 --- /dev/null +++ b/Sources/SwiftJava/Helpers/_JNIMethodIDCache.swift @@ -0,0 +1,59 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +/// A cache used to hold references for JNI method and classes. +/// +/// This type is used internally in by the outputted JExtract wrappers +/// to improve performance of any JNI lookups. +public final class _JNIMethodIDCache: Sendable { + public struct Method: Hashable { + public let name: String + public let signature: String + + public init(name: String, signature: String) { + self.name = name + self.signature = signature + } + } + + nonisolated(unsafe) let _class: jclass? + nonisolated(unsafe) let methods: [Method: jmethodID] + + public var javaClass: jclass { + self._class! + } + + public init(environment: UnsafeMutablePointer!, className: String, methods: [Method]) { + guard let clazz = environment.interface.FindClass(environment, className) else { + fatalError("Class \(className) could not be found!") + } + self._class = environment.interface.NewGlobalRef(environment, clazz)! + self.methods = methods.reduce(into: [:]) { (result, method) in + if let methodID = environment.interface.GetMethodID(environment, clazz, method.name, method.signature) { + result[method] = methodID + } else { + fatalError("Method \(method.signature) with signature \(method.signature) not found in class \(className)") + } + } + } + + + public subscript(_ method: Method) -> jmethodID? { + methods[method] + } + + public func cleanup(environment: UnsafeMutablePointer!) { + environment.interface.DeleteGlobalRef(environment, self._class) + } +} diff --git a/Sources/JavaKit/JavaClass+Initialization.swift b/Sources/SwiftJava/JavaClass+Initialization.swift similarity index 98% rename from Sources/JavaKit/JavaClass+Initialization.swift rename to Sources/SwiftJava/JavaClass+Initialization.swift index d443120f6..8101c37a8 100644 --- a/Sources/JavaKit/JavaClass+Initialization.swift +++ b/Sources/SwiftJava/JavaClass+Initialization.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaRuntime +import CJNI extension JavaClass { public typealias ObjectType = T diff --git a/Sources/JavaKit/JavaEnvironment.swift b/Sources/SwiftJava/JavaEnvironment.swift similarity index 97% rename from Sources/JavaKit/JavaEnvironment.swift rename to Sources/SwiftJava/JavaEnvironment.swift index 422262dad..fd6a626b6 100644 --- a/Sources/JavaKit/JavaEnvironment.swift +++ b/Sources/SwiftJava/JavaEnvironment.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaRuntime +import CJNI #if canImport(Android) typealias JNINativeInterface_ = JNINativeInterface diff --git a/Sources/JavaKit/JavaKitVM/JavaVirtualMachine.swift b/Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift similarity index 100% rename from Sources/JavaKit/JavaKitVM/JavaVirtualMachine.swift rename to Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift diff --git a/Sources/JavaKit/JavaKitVM/LockedState.swift b/Sources/SwiftJava/JavaKitVM/LockedState.swift similarity index 100% rename from Sources/JavaKit/JavaKitVM/LockedState.swift rename to Sources/SwiftJava/JavaKitVM/LockedState.swift diff --git a/Sources/JavaKit/JavaKitVM/ThreadLocalStorage.swift b/Sources/SwiftJava/JavaKitVM/ThreadLocalStorage.swift similarity index 100% rename from Sources/JavaKit/JavaKitVM/ThreadLocalStorage.swift rename to Sources/SwiftJava/JavaKitVM/ThreadLocalStorage.swift diff --git a/Sources/JavaKit/JavaObject+Inheritance.swift b/Sources/SwiftJava/JavaObject+Inheritance.swift similarity index 98% rename from Sources/JavaKit/JavaObject+Inheritance.swift rename to Sources/SwiftJava/JavaObject+Inheritance.swift index 43d86c2ae..aa80fa54f 100644 --- a/Sources/JavaKit/JavaObject+Inheritance.swift +++ b/Sources/SwiftJava/JavaObject+Inheritance.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaRuntime +import CJNI extension AnyJavaObject { /// Look up the other class type diff --git a/Sources/JavaKit/JavaObject+MethodCalls.swift b/Sources/SwiftJava/JavaObject+MethodCalls.swift similarity index 99% rename from Sources/JavaKit/JavaObject+MethodCalls.swift rename to Sources/SwiftJava/JavaObject+MethodCalls.swift index 9f02a58d2..1a62d0f47 100644 --- a/Sources/JavaKit/JavaObject+MethodCalls.swift +++ b/Sources/SwiftJava/JavaObject+MethodCalls.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaRuntime +import CJNI import JavaTypes /// Produce the mangling for a method with the given argument and result types. diff --git a/Sources/JavaKit/JavaObjectHolder.swift b/Sources/SwiftJava/JavaObjectHolder.swift similarity index 98% rename from Sources/JavaKit/JavaObjectHolder.swift rename to Sources/SwiftJava/JavaObjectHolder.swift index 173991c98..50f60a824 100644 --- a/Sources/JavaKit/JavaObjectHolder.swift +++ b/Sources/SwiftJava/JavaObjectHolder.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaRuntime +import CJNI /// Stores a reference to a Java object, managing it as a global reference so /// that the Java virtual machine will not move or deallocate the object diff --git a/Sources/JavaKit/JavaRuntime+Reexport.swift b/Sources/SwiftJava/JavaRuntime+Reexport.swift similarity index 94% rename from Sources/JavaKit/JavaRuntime+Reexport.swift rename to Sources/SwiftJava/JavaRuntime+Reexport.swift index e65d4e182..f1a19e1c5 100644 --- a/Sources/JavaKit/JavaRuntime+Reexport.swift +++ b/Sources/SwiftJava/JavaRuntime+Reexport.swift @@ -12,4 +12,4 @@ // //===----------------------------------------------------------------------===// -@_exported import JavaRuntime +@_exported import CJNI diff --git a/Sources/JavaKit/JavaValue.swift b/Sources/SwiftJava/JavaValue.swift similarity index 99% rename from Sources/JavaKit/JavaValue.swift rename to Sources/SwiftJava/JavaValue.swift index 0f2081447..a740eb503 100644 --- a/Sources/JavaKit/JavaValue.swift +++ b/Sources/SwiftJava/JavaValue.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaRuntime +import CJNI import JavaTypes /// Describes a type that can be bridged with Java. diff --git a/Sources/JavaKit/Macros.swift b/Sources/SwiftJava/Macros.swift similarity index 90% rename from Sources/JavaKit/Macros.swift rename to Sources/SwiftJava/Macros.swift index 029344d84..8e1bb86c5 100644 --- a/Sources/JavaKit/Macros.swift +++ b/Sources/SwiftJava/Macros.swift @@ -43,7 +43,7 @@ public macro JavaClass( _ fullClassName: String, extends: (any AnyJavaObject.Type)? = nil, implements: (any AnyJavaObject.Type)?... -) = #externalMacro(module: "JavaKitMacros", type: "JavaClassMacro") +) = #externalMacro(module: "SwiftJavaMacros", type: "JavaClassMacro") /// Attached macro that declares that a particular `struct` type is a wrapper around a Java interface. /// @@ -74,7 +74,7 @@ public macro JavaClass( ) @attached(extension, conformances: AnyJavaObject) public macro JavaInterface(_ fullClassName: String, extends: (any AnyJavaObject.Type)?...) = - #externalMacro(module: "JavaKitMacros", type: "JavaClassMacro") + #externalMacro(module: "SwiftJavaMacros", type: "JavaClassMacro") /// Attached macro that turns a Swift property into one that accesses a Java field on the underlying Java object. /// @@ -88,7 +88,7 @@ public macro JavaInterface(_ fullClassName: String, extends: (any AnyJavaObject. /// } /// ``` @attached(accessor) -public macro JavaField(_ javaFieldName: String? = nil, isFinal: Bool = false) = #externalMacro(module: "JavaKitMacros", type: "JavaFieldMacro") +public macro JavaField(_ javaFieldName: String? = nil, isFinal: Bool = false) = #externalMacro(module: "SwiftJavaMacros", type: "JavaFieldMacro") /// Attached macro that turns a Swift property into one that accesses a Java static field on the underlying Java object. @@ -102,7 +102,7 @@ public macro JavaField(_ javaFieldName: String? = nil, isFinal: Bool = false) = /// } /// ``` @attached(accessor) -public macro JavaStaticField(_ javaFieldName: String? = nil, isFinal: Bool = false) = #externalMacro(module: "JavaKitMacros", type: "JavaFieldMacro") +public macro JavaStaticField(_ javaFieldName: String? = nil, isFinal: Bool = false) = #externalMacro(module: "SwiftJavaMacros", type: "JavaFieldMacro") /// Attached macro that turns a Swift method into one that wraps a Java method on the underlying Java object. /// @@ -124,7 +124,7 @@ public macro JavaStaticField(_ javaFieldName: String? = nil, isFinal: Bool = fal /// /// corresponds to the Java constructor `HelloSwift(String name)`. @attached(body) -public macro JavaMethod() = #externalMacro(module: "JavaKitMacros", type: "JavaMethodMacro") +public macro JavaMethod() = #externalMacro(module: "SwiftJavaMacros", type: "JavaMethodMacro") /// Attached macro that turns a Swift method on JavaClass into one that wraps /// a Java static method on the underlying Java class object. @@ -136,7 +136,7 @@ public macro JavaMethod() = #externalMacro(module: "JavaKitMacros", type: "JavaM /// func sayHelloBack(_ i: Int32) -> Double /// ``` @attached(body) -public macro JavaStaticMethod() = #externalMacro(module: "JavaKitMacros", type: "JavaMethodMacro") +public macro JavaStaticMethod() = #externalMacro(module: "SwiftJavaMacros", type: "JavaMethodMacro") /// Macro that marks extensions to specify that all of the @JavaMethod /// methods are implementations of Java methods spelled as `native`. @@ -161,4 +161,4 @@ public macro JavaStaticMethod() = #externalMacro(module: "JavaKitMacros", type: /// } /// ``` @attached(peer) -public macro JavaImplementation(_ fullClassName: String) = #externalMacro(module: "JavaKitMacros", type: "JavaImplementationMacro") +public macro JavaImplementation(_ fullClassName: String) = #externalMacro(module: "SwiftJavaMacros", type: "JavaImplementationMacro") diff --git a/Sources/JavaKit/Optional+JavaObject.swift b/Sources/SwiftJava/Optional+JavaObject.swift similarity index 99% rename from Sources/JavaKit/Optional+JavaObject.swift rename to Sources/SwiftJava/Optional+JavaObject.swift index 7aff42942..f34ef2b31 100644 --- a/Sources/JavaKit/Optional+JavaObject.swift +++ b/Sources/SwiftJava/Optional+JavaObject.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaRuntime +import CJNI import JavaTypes extension Optional: JavaValue where Wrapped: AnyJavaObject { diff --git a/Sources/JavaKit/Optional+JavaOptional.swift b/Sources/SwiftJava/Optional+JavaOptional.swift similarity index 100% rename from Sources/JavaKit/Optional+JavaOptional.swift rename to Sources/SwiftJava/Optional+JavaOptional.swift diff --git a/Sources/JavaKit/generated/Appendable.swift b/Sources/SwiftJava/generated/Appendable.swift similarity index 95% rename from Sources/JavaKit/generated/Appendable.swift rename to Sources/SwiftJava/generated/Appendable.swift index 5c6663f27..7ff4f97ec 100644 --- a/Sources/JavaKit/generated/Appendable.swift +++ b/Sources/SwiftJava/generated/Appendable.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaInterface("java.lang.Appendable") public struct Appendable { diff --git a/Sources/JavaKit/generated/CharSequence.swift b/Sources/SwiftJava/generated/CharSequence.swift similarity index 96% rename from Sources/JavaKit/generated/CharSequence.swift rename to Sources/SwiftJava/generated/CharSequence.swift index cab17273c..c6e3d5be3 100644 --- a/Sources/JavaKit/generated/CharSequence.swift +++ b/Sources/SwiftJava/generated/CharSequence.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaInterface("java.lang.CharSequence") public struct CharSequence { diff --git a/Sources/JavaKit/generated/Exception.swift b/Sources/SwiftJava/generated/Exception.swift similarity index 96% rename from Sources/JavaKit/generated/Exception.swift rename to Sources/SwiftJava/generated/Exception.swift index 6fc9de8c0..8361f1b02 100644 --- a/Sources/JavaKit/generated/Exception.swift +++ b/Sources/SwiftJava/generated/Exception.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Exception") open class Exception: Throwable { diff --git a/Sources/JavaKit/generated/JavaArray.swift b/Sources/SwiftJava/generated/JavaArray.swift similarity index 99% rename from Sources/JavaKit/generated/JavaArray.swift rename to Sources/SwiftJava/generated/JavaArray.swift index 147f24df6..65d3f88f2 100644 --- a/Sources/JavaKit/generated/JavaArray.swift +++ b/Sources/SwiftJava/generated/JavaArray.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.reflect.Array") open class JavaArray: JavaObject { diff --git a/Sources/JavaKit/generated/JavaBoolean.swift b/Sources/SwiftJava/generated/JavaBoolean.swift similarity index 98% rename from Sources/JavaKit/generated/JavaBoolean.swift rename to Sources/SwiftJava/generated/JavaBoolean.swift index 0ab51f6d0..995afc571 100644 --- a/Sources/JavaKit/generated/JavaBoolean.swift +++ b/Sources/SwiftJava/generated/JavaBoolean.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Boolean") open class JavaBoolean: JavaObject { diff --git a/Sources/JavaKit/generated/JavaByte.swift b/Sources/SwiftJava/generated/JavaByte.swift similarity index 99% rename from Sources/JavaKit/generated/JavaByte.swift rename to Sources/SwiftJava/generated/JavaByte.swift index e75c5996c..a9ed639b4 100644 --- a/Sources/JavaKit/generated/JavaByte.swift +++ b/Sources/SwiftJava/generated/JavaByte.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Byte") open class JavaByte: JavaNumber { diff --git a/Sources/JavaKit/generated/JavaCharacter.swift b/Sources/SwiftJava/generated/JavaCharacter.swift similarity index 99% rename from Sources/JavaKit/generated/JavaCharacter.swift rename to Sources/SwiftJava/generated/JavaCharacter.swift index eead7dfd9..308a7fce5 100644 --- a/Sources/JavaKit/generated/JavaCharacter.swift +++ b/Sources/SwiftJava/generated/JavaCharacter.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Character") open class JavaCharacter: JavaObject { diff --git a/Sources/JavaKit/generated/JavaClass.swift b/Sources/SwiftJava/generated/JavaClass.swift similarity index 99% rename from Sources/JavaKit/generated/JavaClass.swift rename to Sources/SwiftJava/generated/JavaClass.swift index 03d7f5e6d..1144482fa 100644 --- a/Sources/JavaKit/generated/JavaClass.swift +++ b/Sources/SwiftJava/generated/JavaClass.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Class") open class JavaClass: JavaObject { diff --git a/Sources/JavaKit/generated/JavaClassLoader.swift b/Sources/SwiftJava/generated/JavaClassLoader.swift similarity index 99% rename from Sources/JavaKit/generated/JavaClassLoader.swift rename to Sources/SwiftJava/generated/JavaClassLoader.swift index 6c877cc30..b6fabea42 100644 --- a/Sources/JavaKit/generated/JavaClassLoader.swift +++ b/Sources/SwiftJava/generated/JavaClassLoader.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.ClassLoader") open class JavaClassLoader: JavaObject { diff --git a/Sources/JavaKit/generated/JavaDouble.swift b/Sources/SwiftJava/generated/JavaDouble.swift similarity index 99% rename from Sources/JavaKit/generated/JavaDouble.swift rename to Sources/SwiftJava/generated/JavaDouble.swift index efa77a955..9d979fb34 100644 --- a/Sources/JavaKit/generated/JavaDouble.swift +++ b/Sources/SwiftJava/generated/JavaDouble.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Double") open class JavaDouble: JavaNumber { diff --git a/Sources/JavaKit/generated/JavaError.swift b/Sources/SwiftJava/generated/JavaError.swift similarity index 96% rename from Sources/JavaKit/generated/JavaError.swift rename to Sources/SwiftJava/generated/JavaError.swift index 97c2e5559..da3c4218e 100644 --- a/Sources/JavaKit/generated/JavaError.swift +++ b/Sources/SwiftJava/generated/JavaError.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Error") open class JavaError: Throwable { diff --git a/Sources/JavaKit/generated/JavaFloat.swift b/Sources/SwiftJava/generated/JavaFloat.swift similarity index 99% rename from Sources/JavaKit/generated/JavaFloat.swift rename to Sources/SwiftJava/generated/JavaFloat.swift index 0c38d1aec..9b11957ee 100644 --- a/Sources/JavaKit/generated/JavaFloat.swift +++ b/Sources/SwiftJava/generated/JavaFloat.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Float") open class JavaFloat: JavaNumber { diff --git a/Sources/JavaKit/generated/JavaInteger.swift b/Sources/SwiftJava/generated/JavaInteger.swift similarity index 99% rename from Sources/JavaKit/generated/JavaInteger.swift rename to Sources/SwiftJava/generated/JavaInteger.swift index 646aac9ec..286bc5254 100644 --- a/Sources/JavaKit/generated/JavaInteger.swift +++ b/Sources/SwiftJava/generated/JavaInteger.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Integer") open class JavaInteger: JavaNumber { diff --git a/Sources/JavaKit/generated/JavaLong.swift b/Sources/SwiftJava/generated/JavaLong.swift similarity index 99% rename from Sources/JavaKit/generated/JavaLong.swift rename to Sources/SwiftJava/generated/JavaLong.swift index 7ff70efa5..9e4a183ee 100644 --- a/Sources/JavaKit/generated/JavaLong.swift +++ b/Sources/SwiftJava/generated/JavaLong.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Long") open class JavaLong: JavaNumber { diff --git a/Sources/JavaKit/generated/JavaNumber.swift b/Sources/SwiftJava/generated/JavaNumber.swift similarity index 96% rename from Sources/JavaKit/generated/JavaNumber.swift rename to Sources/SwiftJava/generated/JavaNumber.swift index 414cd89b1..81d0bacd6 100644 --- a/Sources/JavaKit/generated/JavaNumber.swift +++ b/Sources/SwiftJava/generated/JavaNumber.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Number") open class JavaNumber: JavaObject { diff --git a/Sources/JavaKit/generated/JavaObject.swift b/Sources/SwiftJava/generated/JavaObject.swift similarity index 97% rename from Sources/JavaKit/generated/JavaObject.swift rename to Sources/SwiftJava/generated/JavaObject.swift index 07a6eaff1..37e48b2dd 100644 --- a/Sources/JavaKit/generated/JavaObject.swift +++ b/Sources/SwiftJava/generated/JavaObject.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Object") open class JavaObject { diff --git a/Sources/JavaKit/generated/JavaOptional.swift b/Sources/SwiftJava/generated/JavaOptional.swift similarity index 98% rename from Sources/JavaKit/generated/JavaOptional.swift rename to Sources/SwiftJava/generated/JavaOptional.swift index bd77cfed2..7615c7384 100644 --- a/Sources/JavaKit/generated/JavaOptional.swift +++ b/Sources/SwiftJava/generated/JavaOptional.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.util.Optional") open class JavaOptional: JavaObject { diff --git a/Sources/JavaKit/generated/JavaOptionalDouble.swift b/Sources/SwiftJava/generated/JavaOptionalDouble.swift similarity index 98% rename from Sources/JavaKit/generated/JavaOptionalDouble.swift rename to Sources/SwiftJava/generated/JavaOptionalDouble.swift index 5926282ae..8f1514bef 100644 --- a/Sources/JavaKit/generated/JavaOptionalDouble.swift +++ b/Sources/SwiftJava/generated/JavaOptionalDouble.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.util.OptionalDouble") open class JavaOptionalDouble: JavaObject { diff --git a/Sources/JavaKit/generated/JavaOptionalInt.swift b/Sources/SwiftJava/generated/JavaOptionalInt.swift similarity index 97% rename from Sources/JavaKit/generated/JavaOptionalInt.swift rename to Sources/SwiftJava/generated/JavaOptionalInt.swift index 1237a085e..9543d0906 100644 --- a/Sources/JavaKit/generated/JavaOptionalInt.swift +++ b/Sources/SwiftJava/generated/JavaOptionalInt.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.util.OptionalInt") open class JavaOptionalInt: JavaObject { diff --git a/Sources/JavaKit/generated/JavaOptionalLong.swift b/Sources/SwiftJava/generated/JavaOptionalLong.swift similarity index 97% rename from Sources/JavaKit/generated/JavaOptionalLong.swift rename to Sources/SwiftJava/generated/JavaOptionalLong.swift index 79a9e06f7..ac9b44098 100644 --- a/Sources/JavaKit/generated/JavaOptionalLong.swift +++ b/Sources/SwiftJava/generated/JavaOptionalLong.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.util.OptionalLong") open class JavaOptionalLong: JavaObject { diff --git a/Sources/JavaKit/generated/JavaShort.swift b/Sources/SwiftJava/generated/JavaShort.swift similarity index 99% rename from Sources/JavaKit/generated/JavaShort.swift rename to Sources/SwiftJava/generated/JavaShort.swift index f425ae184..12ca1dd25 100644 --- a/Sources/JavaKit/generated/JavaShort.swift +++ b/Sources/SwiftJava/generated/JavaShort.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Short") open class JavaShort: JavaNumber { diff --git a/Sources/JavaKit/generated/JavaString.swift b/Sources/SwiftJava/generated/JavaString.swift similarity index 99% rename from Sources/JavaKit/generated/JavaString.swift rename to Sources/SwiftJava/generated/JavaString.swift index c5f627f21..393c1acf5 100644 --- a/Sources/JavaKit/generated/JavaString.swift +++ b/Sources/SwiftJava/generated/JavaString.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.String", implements: CharSequence.self) open class JavaString: JavaObject { diff --git a/Sources/JavaKit/generated/JavaVoid.swift b/Sources/SwiftJava/generated/JavaVoid.swift similarity index 92% rename from Sources/JavaKit/generated/JavaVoid.swift rename to Sources/SwiftJava/generated/JavaVoid.swift index 76a7334a0..b2976196b 100644 --- a/Sources/JavaKit/generated/JavaVoid.swift +++ b/Sources/SwiftJava/generated/JavaVoid.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Void") open class JavaVoid: JavaObject { diff --git a/Sources/JavaKit/generated/RuntimeException.swift b/Sources/SwiftJava/generated/RuntimeException.swift similarity index 96% rename from Sources/JavaKit/generated/RuntimeException.swift rename to Sources/SwiftJava/generated/RuntimeException.swift index c3e325062..aacd23625 100644 --- a/Sources/JavaKit/generated/RuntimeException.swift +++ b/Sources/SwiftJava/generated/RuntimeException.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.RuntimeException") open class RuntimeException: Exception { diff --git a/Sources/JavaKit/generated/Throwable.swift b/Sources/SwiftJava/generated/Throwable.swift similarity index 98% rename from Sources/JavaKit/generated/Throwable.swift rename to Sources/SwiftJava/generated/Throwable.swift index 574fedf56..823486d85 100644 --- a/Sources/JavaKit/generated/Throwable.swift +++ b/Sources/SwiftJava/generated/Throwable.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import JavaRuntime +import CJNI @JavaClass("java.lang.Throwable") open class Throwable: JavaObject { diff --git a/Sources/JavaKit/swift-java.config b/Sources/SwiftJava/swift-java.config similarity index 100% rename from Sources/JavaKit/swift-java.config rename to Sources/SwiftJava/swift-java.config diff --git a/Sources/JavaKitConfigurationShared/Configuration.swift b/Sources/SwiftJavaConfigurationShared/Configuration.swift similarity index 100% rename from Sources/JavaKitConfigurationShared/Configuration.swift rename to Sources/SwiftJavaConfigurationShared/Configuration.swift diff --git a/Sources/JavaKitConfigurationShared/GenerationMode.swift b/Sources/SwiftJavaConfigurationShared/GenerationMode.swift similarity index 100% rename from Sources/JavaKitConfigurationShared/GenerationMode.swift rename to Sources/SwiftJavaConfigurationShared/GenerationMode.swift diff --git a/Sources/JavaKitConfigurationShared/GradleDependencyParsing.swift b/Sources/SwiftJavaConfigurationShared/GradleDependencyParsing.swift similarity index 100% rename from Sources/JavaKitConfigurationShared/GradleDependencyParsing.swift rename to Sources/SwiftJavaConfigurationShared/GradleDependencyParsing.swift diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md index e8a3dfedb..32a82eb48 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md @@ -72,7 +72,7 @@ OPTIONS: For example, the `JavaKitJar` library is generated with this command line: ```swift -swift-java wrap-java --swift-module JavaKitJar --depends-on JavaKit=Sources/JavaKit/swift-java.config -o Sources/JavaKitJar/generated Sources/JavaKitJar/swift-java.config +swift-java wrap-java --swift-module JavaKitJar --depends-on SwiftJNI=Sources/SwiftJNI/swift-java.config -o Sources/JavaKitJar/generated Sources/JavaKitJar/swift-java.config ``` The `--swift-module JavaKitJar` parameter describes the name of the Swift module in which the code will be generated. @@ -149,7 +149,7 @@ with an associated target that depends on `JavaKit`: Now, in the `HelloSwift` Swift library, define a `struct` that provides the `main` method for the Java class we already defined: ```swift -import JavaKit +import SwiftJNI @JavaImplementation("org.swift.javakit.HelloSwiftMain") struct HelloSwiftMain { @@ -178,9 +178,9 @@ The easiest way to build a command-line program in Swift is with the [Swift argu ```swift import ArgumentParser -import JavaKit +import SwiftJNI -@JavaClass("org.swift.javakit.HelloSwiftMain") +@JavaClass("org.swift.jni.HelloSwiftMain") struct HelloSwiftMain: ParsableCommand { @Option(name: .shortAndLong, help: "Enable verbose output") var verbose: Bool = false diff --git a/Sources/JavaKitMacros/GenerationMode.swift b/Sources/SwiftJavaMacros/GenerationMode.swift similarity index 100% rename from Sources/JavaKitMacros/GenerationMode.swift rename to Sources/SwiftJavaMacros/GenerationMode.swift diff --git a/Sources/JavaKitMacros/ImplementsJavaMacro.swift b/Sources/SwiftJavaMacros/ImplementsJavaMacro.swift similarity index 100% rename from Sources/JavaKitMacros/ImplementsJavaMacro.swift rename to Sources/SwiftJavaMacros/ImplementsJavaMacro.swift diff --git a/Sources/JavaKitMacros/JavaClassMacro.swift b/Sources/SwiftJavaMacros/JavaClassMacro.swift similarity index 100% rename from Sources/JavaKitMacros/JavaClassMacro.swift rename to Sources/SwiftJavaMacros/JavaClassMacro.swift diff --git a/Sources/JavaKitMacros/JavaFieldMacro.swift b/Sources/SwiftJavaMacros/JavaFieldMacro.swift similarity index 100% rename from Sources/JavaKitMacros/JavaFieldMacro.swift rename to Sources/SwiftJavaMacros/JavaFieldMacro.swift diff --git a/Sources/JavaKitMacros/JavaMethodMacro.swift b/Sources/SwiftJavaMacros/JavaMethodMacro.swift similarity index 100% rename from Sources/JavaKitMacros/JavaMethodMacro.swift rename to Sources/SwiftJavaMacros/JavaMethodMacro.swift diff --git a/Sources/JavaKitMacros/MacroErrors.swift b/Sources/SwiftJavaMacros/MacroErrors.swift similarity index 100% rename from Sources/JavaKitMacros/MacroErrors.swift rename to Sources/SwiftJavaMacros/MacroErrors.swift diff --git a/Sources/JavaKitMacros/JavaKitMacrosPlugin.swift b/Sources/SwiftJavaMacros/SwiftJNIMacrosPlugin.swift similarity index 93% rename from Sources/JavaKitMacros/JavaKitMacrosPlugin.swift rename to Sources/SwiftJavaMacros/SwiftJNIMacrosPlugin.swift index 255e61f5d..637b2ea40 100644 --- a/Sources/JavaKitMacros/JavaKitMacrosPlugin.swift +++ b/Sources/SwiftJavaMacros/SwiftJNIMacrosPlugin.swift @@ -16,7 +16,7 @@ import SwiftCompilerPlugin import SwiftSyntaxMacros @main -struct JavaKitMacrosPlugin: CompilerPlugin { +struct SwiftJavaMacrosPlugin: CompilerPlugin { var providingMacros: [Macro.Type] = [ JavaImplementationMacro.self, JavaClassMacro.self, diff --git a/Sources/JavaKitMacros/SwiftSyntaxUtils.swift b/Sources/SwiftJavaMacros/SwiftSyntaxUtils.swift similarity index 100% rename from Sources/JavaKitMacros/SwiftSyntaxUtils.swift rename to Sources/SwiftJavaMacros/SwiftSyntaxUtils.swift diff --git a/Sources/JavaKitShared/TerminalColors.swift b/Sources/SwiftJavaShared/TerminalColors.swift similarity index 100% rename from Sources/JavaKitShared/TerminalColors.swift rename to Sources/SwiftJavaShared/TerminalColors.swift diff --git a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift index 2355ea81b..8bafd2573 100644 --- a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift +++ b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift @@ -14,16 +14,16 @@ import ArgumentParser import Foundation -import SwiftJavaLib +import SwiftJavaToolLib import JExtractSwiftLib -import JavaKit -import JavaKitJar -import JavaKitNetwork -import JavaKitReflection +import SwiftJava +import JavaUtilJar +import JavaNet +import JavaLangReflect import SwiftSyntax import SwiftSyntaxBuilder -import JavaKitConfigurationShared -import JavaKitShared +import SwiftJavaConfigurationShared +import SwiftJavaShared extension SwiftJava { struct ConfigureCommand: SwiftJavaBaseAsyncParsableCommand, HasCommonOptions, HasCommonJVMOptions { diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index b32f34a08..29db6081a 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -14,12 +14,12 @@ import Foundation import ArgumentParser -import SwiftJavaLib -import JavaKit -import JavaKitJar -import SwiftJavaLib +import SwiftJavaToolLib +import SwiftJava +import JavaUtilJar +import SwiftJavaToolLib import JExtractSwiftLib -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared /// Extract Java bindings from Swift sources or interface files. /// @@ -73,7 +73,7 @@ extension SwiftJava { @Option( help: """ A swift-java configuration file for a given Swift module name on which this module depends, - e.g., Sources/JavaKitJar/swift-java.config. There should be one of these options + e.g., Sources/JavaJar/swift-java.config. There should be one of these options for each Swift module that this module depends on (transitively) that contains wrapped Java sources. """ ) diff --git a/Sources/SwiftJavaTool/Commands/ResolveCommand.swift b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift index c492e7889..58c64690b 100644 --- a/Sources/SwiftJavaTool/Commands/ResolveCommand.swift +++ b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift @@ -14,13 +14,13 @@ import ArgumentParser import Foundation -import SwiftJavaLib -import JavaKit +import SwiftJavaToolLib +import SwiftJava import Foundation -import JavaKitJar -import SwiftJavaLib -import JavaKitConfigurationShared -import JavaKitShared +import JavaUtilJar +import SwiftJavaToolLib +import SwiftJavaConfigurationShared +import SwiftJavaShared import _Subprocess #if canImport(System) import System @@ -28,7 +28,7 @@ import System @preconcurrency import SystemPackage #endif -typealias Configuration = JavaKitConfigurationShared.Configuration +typealias Configuration = SwiftJavaConfigurationShared.Configuration extension SwiftJava { struct ResolveCommand: SwiftJavaBaseAsyncParsableCommand, HasCommonOptions, HasCommonJVMOptions { diff --git a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift index 3e4a62af8..69ad3ebe4 100644 --- a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift +++ b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift @@ -14,11 +14,11 @@ import Foundation import ArgumentParser -import SwiftJavaLib -import JavaKit -import JavaKitJar -import SwiftJavaLib -import JavaKitConfigurationShared +import SwiftJavaToolLib +import SwiftJava +import JavaUtilJar +import SwiftJavaToolLib +import SwiftJavaConfigurationShared extension SwiftJava { diff --git a/Sources/SwiftJavaTool/CommonOptions.swift b/Sources/SwiftJavaTool/CommonOptions.swift index e3ddfa44b..c88ecbc88 100644 --- a/Sources/SwiftJavaTool/CommonOptions.swift +++ b/Sources/SwiftJavaTool/CommonOptions.swift @@ -14,14 +14,14 @@ import ArgumentParser import Foundation -import SwiftJavaLib +import SwiftJavaToolLib import JExtractSwiftLib -import JavaKit -import JavaKitJar -import JavaKitNetwork +import SwiftJava +import JavaUtilJar +import JavaNet import SwiftSyntax -import JavaKitConfigurationShared -import JavaKitShared +import SwiftJavaConfigurationShared +import SwiftJavaShared // - MARK: Common Options diff --git a/Sources/SwiftJavaTool/Java/JavaClassLoader.swift b/Sources/SwiftJavaTool/Java/JavaClassLoader.swift index 41492d537..bd1ec3f2a 100644 --- a/Sources/SwiftJavaTool/Java/JavaClassLoader.swift +++ b/Sources/SwiftJavaTool/Java/JavaClassLoader.swift @@ -12,10 +12,10 @@ // //===----------------------------------------------------------------------===// -import SwiftJavaLib -import JavaKitShared -import JavaRuntime -import JavaKit +import SwiftJavaToolLib +import SwiftJavaShared +import CJNI +import SwiftJava @JavaClass("java.lang.ClassLoader") public struct ClassLoader { diff --git a/Sources/SwiftJavaTool/String+Extensions.swift b/Sources/SwiftJavaTool/String+Extensions.swift index f2bb9e729..304e217d5 100644 --- a/Sources/SwiftJavaTool/String+Extensions.swift +++ b/Sources/SwiftJavaTool/String+Extensions.swift @@ -14,11 +14,11 @@ import Foundation import ArgumentParser -import SwiftJavaLib -import JavaKit -import JavaKitJar -import SwiftJavaLib -import JavaKitConfigurationShared +import SwiftJavaToolLib +import SwiftJava +import JavaUtilJar +import SwiftJavaToolLib +import SwiftJavaConfigurationShared extension String { /// For a String that's of the form java.util.Vector, return the "Vector" diff --git a/Sources/SwiftJavaTool/SwiftJava.swift b/Sources/SwiftJavaTool/SwiftJava.swift index 01917d4b6..ac5182a07 100644 --- a/Sources/SwiftJavaTool/SwiftJava.swift +++ b/Sources/SwiftJavaTool/SwiftJava.swift @@ -14,16 +14,16 @@ import ArgumentParser import Foundation -import SwiftJavaLib +import SwiftJavaToolLib import JExtractSwiftLib -import JavaKit -import JavaKitJar -import JavaKitNetwork -import JavaKitReflection +import SwiftJava +import JavaUtilJar +import JavaNet +import JavaLangReflect import SwiftSyntax import SwiftSyntaxBuilder -import JavaKitConfigurationShared -import JavaKitShared +import SwiftJavaConfigurationShared +import SwiftJavaShared /// Command-line utility to drive the export of Java classes into Swift types. @main diff --git a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift index 20b177af6..25b162e1b 100644 --- a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift +++ b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift @@ -14,16 +14,16 @@ import ArgumentParser import Foundation -import SwiftJavaLib +import SwiftJavaToolLib import JExtractSwiftLib -import JavaKit -import JavaKitJar -import JavaKitNetwork -import JavaKitReflection +import SwiftJava +import JavaUtilJar +import JavaNet +import JavaLangReflect import SwiftSyntax import SwiftSyntaxBuilder -import JavaKitConfigurationShared -import JavaKitShared +import SwiftJavaConfigurationShared +import SwiftJavaShared protocol SwiftJavaBaseAsyncParsableCommand: AsyncParsableCommand { var logLevel: Logger.Level { get set } diff --git a/Sources/SwiftJavaLib/JavaClassTranslator.swift b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift similarity index 99% rename from Sources/SwiftJavaLib/JavaClassTranslator.swift rename to Sources/SwiftJavaToolLib/JavaClassTranslator.swift index ea6ca4810..a7c4d76f7 100644 --- a/Sources/SwiftJavaLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift @@ -12,8 +12,8 @@ // //===----------------------------------------------------------------------===// -import JavaKit -import JavaKitReflection +import SwiftJava +import JavaLangReflect import SwiftSyntax /// Utility type that translates a single Java class into its corresponding diff --git a/Sources/SwiftJavaLib/JavaTranslator+Configuration.swift b/Sources/SwiftJavaToolLib/JavaTranslator+Configuration.swift similarity index 97% rename from Sources/SwiftJavaLib/JavaTranslator+Configuration.swift rename to Sources/SwiftJavaToolLib/JavaTranslator+Configuration.swift index 0f764633d..4fc9feb04 100644 --- a/Sources/SwiftJavaLib/JavaTranslator+Configuration.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator+Configuration.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import Foundation -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared extension JavaTranslator { // /// Read a configuration file from the given URL. diff --git a/Sources/SwiftJavaLib/JavaTranslator+Validation.swift b/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift similarity index 100% rename from Sources/SwiftJavaLib/JavaTranslator+Validation.swift rename to Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift diff --git a/Sources/SwiftJavaLib/JavaTranslator.swift b/Sources/SwiftJavaToolLib/JavaTranslator.swift similarity index 98% rename from Sources/SwiftJavaLib/JavaTranslator.swift rename to Sources/SwiftJavaToolLib/JavaTranslator.swift index 225d3868a..2f6f280c3 100644 --- a/Sources/SwiftJavaLib/JavaTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator.swift @@ -12,12 +12,12 @@ // //===----------------------------------------------------------------------===// -import JavaKit -import JavaKitReflection +import SwiftJava +import JavaLangReflect import JavaTypes import SwiftBasicFormat import SwiftSyntax -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared import SwiftSyntaxBuilder /// Utility that translates Java classes into Swift source code to access @@ -45,7 +45,7 @@ package class JavaTranslator { /// an AnyJavaObject-conforming type) whereas the entry here should map to /// a value type. package let translatedToValueTypes: [String: (swiftType: String, swiftModule: String) ] = [ - "java.lang.String": ("String", "JavaKit"), + "java.lang.String": ("String", "SwiftJava"), ] /// The set of Swift modules that need to be imported to make the generated @@ -93,8 +93,8 @@ extension JavaTranslator { /// Default set of modules that will always be imported. private static let defaultImportedSwiftModules: Set = [ - "JavaKit", - "JavaRuntime", + "SwiftJava", + "CJNI", ] } diff --git a/Sources/SwiftJavaLib/MethodVariance.swift b/Sources/SwiftJavaToolLib/MethodVariance.swift similarity index 98% rename from Sources/SwiftJavaLib/MethodVariance.swift rename to Sources/SwiftJavaToolLib/MethodVariance.swift index 09bd64442..c130a75c9 100644 --- a/Sources/SwiftJavaLib/MethodVariance.swift +++ b/Sources/SwiftJavaToolLib/MethodVariance.swift @@ -11,8 +11,8 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// -import JavaKit -import JavaKitReflection +import SwiftJava +import JavaLangReflect /// Captures the relationship between two methods by comparing their parameter /// and result types. diff --git a/Sources/SwiftJavaLib/OptionalKind.swift b/Sources/SwiftJavaToolLib/OptionalKind.swift similarity index 100% rename from Sources/SwiftJavaLib/OptionalKind.swift rename to Sources/SwiftJavaToolLib/OptionalKind.swift diff --git a/Sources/SwiftJavaLib/StringExtras.swift b/Sources/SwiftJavaToolLib/StringExtras.swift similarity index 100% rename from Sources/SwiftJavaLib/StringExtras.swift rename to Sources/SwiftJavaToolLib/StringExtras.swift diff --git a/Sources/SwiftJavaLib/TranslationError.swift b/Sources/SwiftJavaToolLib/TranslationError.swift similarity index 97% rename from Sources/SwiftJavaLib/TranslationError.swift rename to Sources/SwiftJavaToolLib/TranslationError.swift index d44fd2d7d..1e01134b0 100644 --- a/Sources/SwiftJavaLib/TranslationError.swift +++ b/Sources/SwiftJavaToolLib/TranslationError.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaKitReflection +import JavaLangReflect /// Errors that can occur when translating Java types into Swift. enum TranslationError: Error { diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstanceCleanup.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstanceCleanup.java index c3a9beb6c..7b7bb1ec8 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstanceCleanup.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstanceCleanup.java @@ -16,6 +16,8 @@ import org.swift.swiftkit.core.SwiftInstanceCleanup; +import static org.swift.swiftkit.ffm.SwiftJavaLogGroup.LIFECYCLE; + import java.lang.foreign.MemorySegment; public class FFMSwiftInstanceCleanup implements SwiftInstanceCleanup { @@ -35,7 +37,7 @@ public void run() { // Allow null pointers just for AutoArena tests. if (type != null && memoryAddress != null) { - System.out.println("[debug] Destroy swift value [" + type.getSwiftName() + "]: " + memoryAddress); + SwiftRuntime.log(LIFECYCLE, "Destroy swift value [" + type.getSwiftName() + "]: " + memoryAddress); SwiftValueWitnessTable.destroy(type, memoryAddress); } } diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java index eca6be82d..e03b9bfec 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java @@ -17,6 +17,7 @@ import org.swift.swiftkit.core.SwiftInstance; import org.swift.swiftkit.core.CallTraces; import org.swift.swiftkit.core.util.PlatformUtils; +import org.swift.swiftkit.ffm.SwiftRuntime.swiftjava; import java.lang.foreign.*; import java.lang.invoke.MethodHandle; @@ -460,4 +461,30 @@ private static class swift_getTypeName { public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); } + public static void log(SwiftJavaLogGroup group, String message) { + if (group.isEnabled()) { + System.err.println(message); + } + } + + public static void log(SwiftJavaLogGroup group, String format, String ...args) { + if (group.isEnabled()) { + System.err.println(String.format(format, (Object[]) args)); + } + } + } + +enum SwiftJavaLogGroup { + LIFECYCLE; + + static boolean LOG_LIFECYCLE = + Boolean.getBoolean("swift-java.log.lifecycle"); + + boolean isEnabled() { + switch (this) { + case LIFECYCLE: return LOG_LIFECYCLE; + } + throw new IllegalArgumentException("Not handled log group: " + this); + } +} \ No newline at end of file diff --git a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift index 0b8ca1d33..06d1d5553 100644 --- a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// @_spi(Testing) import JExtractSwiftLib -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared import SwiftSyntax import Testing diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index 1f768fb2d..66563feef 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -14,7 +14,7 @@ import JExtractSwiftLib import Testing -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared import struct Foundation.CharacterSet enum RenderKind { diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index a87294b05..82747ec90 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import JExtractSwiftLib -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared import Testing final class FuncCallbackImportTests { diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift index a8d83a2a4..ff19f4a21 100644 --- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift +++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import JExtractSwiftLib -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared import Testing @Suite diff --git a/Tests/JExtractSwiftTests/InternalExtractTests.swift b/Tests/JExtractSwiftTests/InternalExtractTests.swift index 78e712363..54829bd77 100644 --- a/Tests/JExtractSwiftTests/InternalExtractTests.swift +++ b/Tests/JExtractSwiftTests/InternalExtractTests.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import JExtractSwiftLib -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared import Testing final class InternalExtractTests { diff --git a/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift b/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift index bfae447a9..61a94062b 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import JExtractSwiftLib -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared import Testing final class JNIUnsignedNumberTests { diff --git a/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift b/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift index 3ce839b9b..6017c9875 100644 --- a/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift +++ b/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import JExtractSwiftLib -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared import Testing @Suite diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift index 3aa7d394e..938b5e7fc 100644 --- a/Tests/JExtractSwiftTests/MethodImportTests.swift +++ b/Tests/JExtractSwiftTests/MethodImportTests.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import JExtractSwiftLib -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared import Testing final class MethodImportTests { diff --git a/Tests/JExtractSwiftTests/UnsignedNumberTests.swift b/Tests/JExtractSwiftTests/UnsignedNumberTests.swift index 11b91e53d..8109714cb 100644 --- a/Tests/JExtractSwiftTests/UnsignedNumberTests.swift +++ b/Tests/JExtractSwiftTests/UnsignedNumberTests.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import JExtractSwiftLib -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared import Testing final class UnsignedNumberTests { diff --git a/Tests/JavaKitConfigurationSharedTests/GradleDependencyParsingTests.swift b/Tests/SwiftJavaConfigurationSharedTests/GradleDependencyParsingTests.swift similarity index 97% rename from Tests/JavaKitConfigurationSharedTests/GradleDependencyParsingTests.swift rename to Tests/SwiftJavaConfigurationSharedTests/GradleDependencyParsingTests.swift index dbd05f66e..cb085fe22 100644 --- a/Tests/JavaKitConfigurationSharedTests/GradleDependencyParsingTests.swift +++ b/Tests/SwiftJavaConfigurationSharedTests/GradleDependencyParsingTests.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaKitConfigurationShared +import SwiftJavaConfigurationShared import Testing @Suite diff --git a/Tests/JavaKitMacroTests/JavaClassMacroTests.swift b/Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift similarity index 99% rename from Tests/JavaKitMacroTests/JavaClassMacroTests.swift rename to Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift index 1dc08ba86..7eead3d25 100644 --- a/Tests/JavaKitMacroTests/JavaClassMacroTests.swift +++ b/Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JavaKitMacros +import SwiftJavaMacros import SwiftSyntax import SwiftSyntaxBuilder import SwiftSyntaxMacros diff --git a/Tests/JavaKitTests/BasicRuntimeTests.swift b/Tests/SwiftJavaTests/BasicRuntimeTests.swift similarity index 98% rename from Tests/JavaKitTests/BasicRuntimeTests.swift rename to Tests/SwiftJavaTests/BasicRuntimeTests.swift index d42fa4b12..b6c18bee5 100644 --- a/Tests/JavaKitTests/BasicRuntimeTests.swift +++ b/Tests/SwiftJavaTests/BasicRuntimeTests.swift @@ -12,8 +12,8 @@ // //===----------------------------------------------------------------------===// -import JavaKit -import JavaKitNetwork +import SwiftJava +import JavaNet import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 /// Handy reference to the JVM abstraction. diff --git a/Tests/SwiftJavaTests/Java2SwiftTests.swift b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift similarity index 89% rename from Tests/SwiftJavaTests/Java2SwiftTests.swift rename to Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift index e2b68a344..f251766ac 100644 --- a/Tests/SwiftJavaTests/Java2SwiftTests.swift +++ b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift @@ -13,8 +13,8 @@ //===----------------------------------------------------------------------===// @_spi(Testing) -import JavaKit -import SwiftJavaLib +import SwiftJava +import SwiftJavaToolLib import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 /// Handy reference to the JVM abstraction. @@ -42,7 +42,7 @@ class Java2SwiftTests: XCTestCase { JavaObject.self, swiftTypeName: "MyJavaObject", expectedChunks: [ - "import JavaKit", + "import SwiftJava", """ @JavaClass("java.lang.Object") public struct MyJavaObject { @@ -67,7 +67,7 @@ class Java2SwiftTests: XCTestCase { "java.lang.Object": ("JavaObject", nil), ], expectedChunks: [ - "import JavaKit", + "import SwiftJava", """ @JavaClass("java.lang.Class", extends: JavaObject.self) public struct MyJavaClass { @@ -85,7 +85,7 @@ class Java2SwiftTests: XCTestCase { JavaMonth.self, swiftTypeName: "Month", expectedChunks: [ - "import JavaKit", + "import SwiftJava", "enum MonthCases: Equatable", "case APRIL", "public var enumValue: MonthCases!", @@ -164,7 +164,7 @@ class Java2SwiftTests: XCTestCase { "java.lang.ProcessBuilder$Redirect": [JavaClass().as(JavaClass.self)!], ], expectedChunks: [ - "import JavaKit", + "import SwiftJava", """ @JavaMethod public func redirectInput() -> ProcessBuilder.Redirect! @@ -204,7 +204,7 @@ class Java2SwiftTests: XCTestCase { "java.lang.ProcessBuilder$Redirect": [JavaClass().as(JavaClass.self)!], ], expectedChunks: [ - "import JavaKit", + "import SwiftJava", """ @JavaMethod public func redirectInput() -> ProcessBuilder.PBRedirect! @@ -248,13 +248,13 @@ class Java2SwiftTests: XCTestCase { MyObjects.self, swiftTypeName: "MyJavaObjects", translatedClasses: [ - "java.lang.Object" : ("JavaObject", "JavaKit"), - "java.util.function.Supplier" : ("MySupplier", "JavaKitFunction"), - "java.lang.String" : ("JavaString", "JavaKit"), + "java.lang.Object" : ("JavaObject", "SwiftJava"), + "java.util.function.Supplier" : ("MySupplier", "JavaUtilFunction"), + "java.lang.String" : ("JavaString", "SwiftJava"), ], expectedChunks: [ """ - import JavaKitFunction + import JavaUtilFunction """, """ @JavaClass("java.util.Objects", extends: JavaObject.self) @@ -274,7 +274,7 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "JavaObject", asClass: true, expectedChunks: [ - "import JavaKit", + "import SwiftJava", """ @JavaClass("java.lang.Object") open class JavaObject { @@ -305,10 +305,10 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "JavaString", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "JavaKit"), + "java.lang.Object" : ("JavaObject", "SwiftJava"), ], expectedChunks: [ - "import JavaKit", + "import SwiftJava", """ @JavaClass("java.lang.String") open class JavaString: JavaObject { @@ -343,7 +343,7 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "Month", asClass: true, expectedChunks: [ - "import JavaKit", + "import SwiftJava", "enum MonthCases: Equatable", "case APRIL", "public var enumValue: MonthCases!", @@ -380,12 +380,12 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "URLClassLoader", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "JavaKit"), - "java.lang.ClassLoader" : ("ClassLoader", "JavaKit"), - "java.net.URL" : ("URL", "JavaKitNetwork"), + "java.lang.Object" : ("JavaObject", "SwiftJava"), + "java.lang.ClassLoader" : ("ClassLoader", "SwiftJava"), + "java.net.URL" : ("URL", "JavaNet"), ], expectedChunks: [ - "import JavaKit", + "import SwiftJava", """ @JavaClass("java.net.URLClassLoader") open class URLClassLoader: ClassLoader { @@ -411,11 +411,11 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "URLClassLoader", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "JavaKit"), - "java.net.URL" : ("URL", "JavaKitNetwork"), + "java.lang.Object" : ("JavaObject", "SwiftJava"), + "java.net.URL" : ("URL", "JavaNet"), ], expectedChunks: [ - "import JavaKit", + "import SwiftJava", """ @JavaClass("java.net.URLClassLoader") open class URLClassLoader: JavaObject { @@ -440,12 +440,12 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "JavaByte", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "JavaKit"), - "java.lang.Number" : ("JavaNumber", "JavaKit"), - "java.lang.Byte" : ("JavaByte", "JavaKit"), + "java.lang.Object" : ("JavaObject", "SwiftJava"), + "java.lang.Number" : ("JavaNumber", "SwiftJava"), + "java.lang.Byte" : ("JavaByte", "SwiftJava"), ], expectedChunks: [ - "import JavaKit", + "import SwiftJava", """ @JavaClass("java.lang.Byte") open class JavaByte: JavaNumber { @@ -464,11 +464,11 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "MyJavaIntFunction", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "JavaKit"), + "java.lang.Object" : ("JavaObject", "SwiftJava"), "java.util.function.IntFunction": ("MyJavaIntFunction", nil), ], expectedChunks: [ - "import JavaKit", + "import SwiftJava", """ @JavaInterface("java.util.function.IntFunction") public struct MyJavaIntFunction { @@ -487,14 +487,14 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "Method", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "JavaKit"), - "java.lang.Class" : ("JavaClass", "JavaKit"), - "java.lang.reflect.Executable": ("Executable", "JavaKitReflection"), - "java.lang.reflect.Method": ("Method", "JavaKitReflection"), - "java.lang.reflect.TypeVariable" : ("TypeVariable", "JavaKitReflection"), + "java.lang.Object" : ("JavaObject", "SwiftJava"), + "java.lang.Class" : ("JavaClass", "SwiftJava"), + "java.lang.reflect.Executable": ("Executable", "JavaLangReflect"), + "java.lang.reflect.Method": ("Method", "JavaLangReflect"), + "java.lang.reflect.TypeVariable" : ("TypeVariable", "JavaLangReflect"), ], expectedChunks: [ - "import JavaKitReflection", + "import JavaLangReflect", """ @JavaClass("java.lang.reflect.Method") open class Method: Executable { @@ -521,14 +521,14 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "Constructor", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "JavaKit"), - "java.lang.Class" : ("JavaClass", "JavaKit"), - "java.lang.reflect.Executable": ("Executable", "JavaKitReflection"), - "java.lang.reflect.Method": ("Method", "JavaKitReflection"), - "java.lang.reflect.TypeVariable" : ("TypeVariable", "JavaKitReflection"), + "java.lang.Object" : ("JavaObject", "SwiftJava"), + "java.lang.Class" : ("JavaClass", "SwiftJava"), + "java.lang.reflect.Executable": ("Executable", "JavaLangReflect"), + "java.lang.reflect.Method": ("Method", "JavaLangReflect"), + "java.lang.reflect.TypeVariable" : ("TypeVariable", "JavaLangReflect"), ], expectedChunks: [ - "import JavaKitReflection", + "import JavaLangReflect", """ @JavaClass("java.lang.reflect.Constructor") open class Constructor: Executable { @@ -555,13 +555,13 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "NIOByteBuffer", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "JavaKit"), - "java.lang.Class" : ("JavaClass", "JavaKit"), - "java.nio.Buffer": ("NIOBuffer", "JavaKitNIO"), - "java.nio.ByteBuffer": ("NIOByteBuffer", "JavaKitNIO"), + "java.lang.Object" : ("JavaObject", "SwiftJava"), + "java.lang.Class" : ("JavaClass", "SwiftJava"), + "java.nio.Buffer": ("NIOBuffer", "JavaNio"), + "java.nio.ByteBuffer": ("NIOByteBuffer", "JavaNio"), ], expectedChunks: [ - "import JavaKitNIO", + "import JavaNio", """ @JavaClass("java.nio.ByteBuffer") open class NIOByteBuffer: NIOBuffer { diff --git a/Tests/SwiftJavaTests/JavaTranslatorValidationTests.swift b/Tests/SwiftJavaToolLibTests/JavaTranslatorValidationTests.swift similarity index 98% rename from Tests/SwiftJavaTests/JavaTranslatorValidationTests.swift rename to Tests/SwiftJavaToolLibTests/JavaTranslatorValidationTests.swift index a203486af..220f1c613 100644 --- a/Tests/SwiftJavaTests/JavaTranslatorValidationTests.swift +++ b/Tests/SwiftJavaToolLibTests/JavaTranslatorValidationTests.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import SwiftJavaLib +import SwiftJavaToolLib import XCTest final class JavaTranslatorValidationTests: XCTestCase { From 2268bb66cd7da3f2e5efbb52c8ca12d594692828 Mon Sep 17 00:00:00 2001 From: David Ko Date: Wed, 27 Aug 2025 09:12:26 -0400 Subject: [PATCH 071/141] Align Renaming Changes (#375) --- README.md | 12 +++--- .../{JavaKit.md => SwiftJava.md} | 38 +++++++++---------- .../Commands/ConfigureCommand.swift | 11 ++---- 3 files changed, 28 insertions(+), 33 deletions(-) rename Sources/SwiftJava/Documentation.docc/{JavaKit.md => SwiftJava.md} (90%) diff --git a/README.md b/README.md index 9f676905a..c15db5414 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ cd Samples/JavaKitSampleApp To run a simple example app showcasing the jextract (Java calling Swift) approach you can: ```bash -./gradlew Samples:SwiftKitSampleApp:run +./gradlew Samples:SwiftJavaExtractFFMSampleApp:run ``` This will also generate the necessary sources (by invoking jextract, extracting the `Sources/ExampleSwiftLibrary`) @@ -152,7 +152,7 @@ Please refer to the [Samples](Samples) directory for more sample apps which show You can run Swift [ordo-one/package-benchmark](https://github.com/ordo-one/package-benchmark) and OpenJDK [JMH](https://github.com/openjdk/jmh) benchmarks in this project. -Swift benchmarks are located under `Benchmarks/` and JMH benchmarks are currently part of the SwiftKit sample project: `Samples/SwiftKitSampleApp/src/jmh` because they depend on generated sources from the sample. +Swift benchmarks are located under `Benchmarks/` and JMH benchmarks are currently part of the SwiftKit sample project: `Samples/SwiftJavaExtractFFMSampleApp/src/jmh` because they depend on generated sources from the sample. ### Swift benchmarks @@ -168,8 +168,8 @@ swift package benchmark In order to run JMH benchmarks you can: ```bash -cd Samples/SwiftKitSampleApp -gradle jmh +cd Samples/SwiftJavaExtractFFMSampleApp +./gradlew jmh ``` Please read documentation of both performance testing tools and understand that results must be interpreted and not just taken at face value. Benchmarking is tricky and environment sensitive task, so please be careful when constructing and reading benchmarks and their results. If in doubt, please reach out on the forums. @@ -183,8 +183,8 @@ To view the rendered docc documentation you can use the docc preview command: ```bash xcrun docc preview Sources/SwiftJavaDocumentation/Documentation.docc -# OR JavaKit to view JavaKit documentation: -# xcrun docc preview Sources/SwiftJNI/Documentation.docc +# OR SwiftJava to view SwiftJava documentation: +# xcrun docc preview Sources/SwiftJava/Documentation.docc # ======================================== # Starting Local Preview Server diff --git a/Sources/SwiftJava/Documentation.docc/JavaKit.md b/Sources/SwiftJava/Documentation.docc/SwiftJava.md similarity index 90% rename from Sources/SwiftJava/Documentation.docc/JavaKit.md rename to Sources/SwiftJava/Documentation.docc/SwiftJava.md index a050e88be..d4b5dacd7 100644 --- a/Sources/SwiftJava/Documentation.docc/JavaKit.md +++ b/Sources/SwiftJava/Documentation.docc/SwiftJava.md @@ -1,8 +1,8 @@ -# JavaKit +# SwiftJava Library and tools to make it easy to use Java libraries from Swift using the Java Native Interface (JNI). -## JavaKit: Using Java libraries from Swift +## SwiftJava: Using Java libraries from Swift Existing Java libraries can be wrapped for use in Swift with the `swift-java` tool. In a Swift program, the most direct way to access a Java API is to use the SwiftPM plugin to provide Swift wrappers for the Java classes. To do so, add a configuration file `swift-java.config` into the source directory for the Swift target. This is a JSON file that specifies Java classes and the Swift type name that should be generated to wrap them. For example, the following file maps `java.math.BigInteger` to a Swift type named `BigInteger`: @@ -77,7 +77,7 @@ Swift ensures that the Java garbage collector will keep the object alive until ` ### Creating a Java Virtual Machine instance from Swift -When JavaKit requires a running Java Virtual Machine to use an operation (for example, to create an instance of `BigInteger`), it will query to determine if one is running and, if not, create one. To exercise more control over the creation and configuration of the Java virtual machine, use the `JavaVirtualMachine` class, which provides creation and query operations. One can create a shared instance by calling `JavaVirtualMachine.shared()`, optionally passing along extra options to the JVM (such as the class path): +When SwiftJava requires a running Java Virtual Machine to use an operation (for example, to create an instance of `BigInteger`), it will query to determine if one is running and, if not, create one. To exercise more control over the creation and configuration of the Java virtual machine, use the `JavaVirtualMachine` class, which provides creation and query operations. One can create a shared instance by calling `JavaVirtualMachine.shared()`, optionally passing along extra options to the JVM (such as the class path): ```swift let javaVirtualMachine = try JavaVirtualMachine.shared() @@ -100,7 +100,7 @@ let bigInt = BigInteger(veryBigNumber, environment: jniEnvironment) Java libraries are often distributed as Jar files. The `swift-java` tool can inspect a Jar file to create a `swift-java.config` file that will wrap all of the public classes for use in Swift. Following the example in `swift-java/Samples/JavaSieve`, we will wrap a small [Java library for computing prime numbers](https://github.com/gazman-sdk/quadratic-sieve-Java) for use in Swift. Assuming we have a Jar file `QuadraticSieve-1.0.jar` in the package directory, run the following command: ```swift -swift-java generate --module-name JavaSieve --jar QuadraticSieve-1.0.jar +swift-java configure --swift-module JavaSieve --jar QuadraticSieve-1.0.jar ``` The resulting configuration file will look something like this: @@ -138,7 +138,7 @@ The resulting configuration file will look something like this: } ``` -As with the previous `JavaProbablyPrime` sample, the `JavaSieve` target in `Package.swift` should depend on the `swift-java` package modules (`JavaKit`) and apply the `swift-java` plugin. This makes all of the Java classes found in the Jar file available to Swift within the `JavaSieve` target. +As with the previous `JavaProbablyPrime` sample, the `JavaSieve` target in `Package.swift` should depend on the `swift-java` package modules (`SwiftJava`) and apply the `swift-java` plugin. This makes all of the Java classes found in the Jar file available to Swift within the `JavaSieve` target. If you inspect the build output, there are a number of warnings that look like this: @@ -152,7 +152,7 @@ These warnings mean that some of the APIs in the Java library aren't available i .target( name: "JavaMath", dependencies: [ - .product(name: "JavaKit", package: "swift-java"), + .product(name: "SwiftJava", package: "swift-java"), ], plugins: [ .plugin(name: "SwiftJavaPlugin", package: "swift-java"), @@ -237,10 +237,10 @@ if let url = myObject.as(URL.self) { ### Implementing Java `native` methods in Swift -JavaKit supports implementing Java `native` methods in Swift using JNI with the `@JavaImplementation` macro. In Java, the method must be declared as `native`, e.g., +SwiftJava supports implementing Java `native` methods in Swift using JNI with the `@JavaImplementation` macro. In Java, the method must be declared as `native`, e.g., ```java -package org.swift.javakit.example; +package org.swift.swiftjava.example; public class HelloSwift { static { @@ -256,15 +256,15 @@ On the Swift side, the Java class needs to be exposed to Swift through `swift-ja ```swift { "classes" : { - "org.swift.javakit.example.HelloSwift" : "Hello", + "org.swift.swiftjava.example.HelloSwift" : "Hello", } } ``` -Implementations of `native` methods are written in an extension of the Swift type that has been marked with `@JavaImplementation`. The methods themselves must be marked with `@JavaMethod`, indicating that they are available to Java as well. To help ensure that the Swift code implements all of the `native` methods with the right signatures, JavaKit produces a protocol with the Swift type name suffixed by `NativeMethods`. Declare conformance to that protocol and implement its requirements, for example: +Implementations of `native` methods are written in an extension of the Swift type that has been marked with `@JavaImplementation`. The methods themselves must be marked with `@JavaMethod`, indicating that they are available to Java as well. To help ensure that the Swift code implements all of the `native` methods with the right signatures, SwiftJava produces a protocol with the Swift type name suffixed by `NativeMethods`. Declare conformance to that protocol and implement its requirements, for example: ```swift -@JavaImplementation("org.swift.javakit.HelloSwift") +@JavaImplementation("org.swift.swiftjava.HelloSwift") extension Hello: HelloNativeMethods { @JavaMethod func reportStatistics(_ meaning: String, _ numbers: [Double]) -> String { @@ -278,7 +278,7 @@ Java native methods that throw any checked exception should be marked as `throws The Swift implementations of Java `native` constructors and static methods require an additional Swift parameter `environment: JNIEnvironment? = nil`, which will receive the JNI environment in which the function is being executed. In case of nil, the `JavaVirtualMachine.shared().environment()` value will be used. -## JavaKit: Using Java libraries from Swift +## SwiftJava: Using Java libraries from Swift This section describes how Java libraries and mapped into Swift and their use from Swift. @@ -353,7 +353,7 @@ for entry in jarFile.entries()! { `JavaMethod` is a [function body macro](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0415-function-body-macros.md) that translates the argument and result types to/from Java and performs a call to the named method via JNI. -A Java method or constructor that throws a checked exception should be marked as `throws` in Swift. Swift's projection of Java throwable types (as `JavaKit.Throwable`) conforms to the Swift `Error` protocol, so Java exceptions will be rethrown as Swift errors. +A Java method or constructor that throws a checked exception should be marked as `throws` in Swift. Swift's projection of Java throwable types (as `SwiftJava.Throwable`) conforms to the Swift `Error` protocol, so Java exceptions will be rethrown as Swift errors. ### Java <-> Swift Type mapping @@ -380,14 +380,14 @@ For Swift projections of Java classes, the Swift type itself conforms to the `An Because Java has implicitly nullability of references, `AnyJavaObject` types do not directly conform to `JavaValue`: rather, optionals of `AnyJavaObject`-conforming type conform to `JavaValue`. This requires Swift code to deal with the optionality at interface boundaries rather than invite implicit NULL pointer dereferences. -A number of JavaKit modules provide Swift projections of Java classes and interfaces. Here are a few: +A number of SwiftJava modules provide Swift projections of Java classes and interfaces. Here are a few: | Java class | Swift class | Swift module | | --------------------- | -------------- | ---------------- | -| `java.lang.Object` | `JavaObject` | `JavaKit` | -| `java.lang.Class` | `JavaClass` | `JavaKit` | -| `java.lang.Throwable` | `Throwable` | `JavaKit` | -| `java.net.URL` | `URL` | `JavaKitNetwork` | +| `java.lang.Object` | `JavaObject` | `SwiftJava` | +| `java.lang.Class` | `JavaClass` | `SwiftJava` | +| `java.lang.Throwable` | `Throwable` | `SwiftJava` | +| `java.net.URL` | `URL` | `JavaNet` | The `swift-java` tool can translate any other Java classes into Swift projections. The easiest way to use `swift-java` is with the SwiftPM plugin described above. More information about using this tool directly are provided later in this document @@ -406,7 +406,7 @@ When building Java sources using the JavaCompilerPlugin this option is passed by ### Class objects and static methods -Every `AnyJavaObject` has a property `javaClass` that provides an instance of `JavaClass` specialized to the type. For example, `url.javaClass` will produce an instance of `JavaClass`. The `JavaClass` instance is a wrapper around a Java class object (`java.lang.Class`) that has two roles in Swift. First, it provides access to all of the APIs on the Java class object. The `JavaKitReflection` library, for example, exposes these APIs and the types they depend on (`Method`, +Every `AnyJavaObject` has a property `javaClass` that provides an instance of `JavaClass` specialized to the type. For example, `url.javaClass` will produce an instance of `JavaClass`. The `JavaClass` instance is a wrapper around a Java class object (`java.lang.Class`) that has two roles in Swift. First, it provides access to all of the APIs on the Java class object. The `JavaLangReflect` library, for example, exposes these APIs and the types they depend on (`Method`, `Constructor`, etc.) for dynamic reflection. Second, the `JavaClass` provides access to the `static` methods on the Java class. For example, [`java.net.URLConnection`](https://docs.oracle.com/javase/8/docs/api/java/net/URLConnection.html) has static methods to access default settings, such as the default for the `allowUserInteraction` field. These are exposed as instance methods on `JavaClass`, e.g., ```swift diff --git a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift index 8bafd2573..3bce6c18f 100644 --- a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift +++ b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift @@ -35,9 +35,7 @@ extension SwiftJava { @OptionGroup var commonJVMOptions: SwiftJava.CommonJVMOptions // TODO: This should be a "make wrappers" option that just detects when we give it a jar - @Flag( - help: "Specifies that the input is a *.jar file whose public classes will be loaded. The output of swift-java will be a configuration file (swift-java.config) that can be used as input to a subsequent swift-java invocation to generate wrappers for those public classes." - ) + @Flag(help: "Specifies that the input is a *.jar file whose public classes will be loaded. The output of swift-java will be a configuration file (swift-java.config) that can be used as input to a subsequent swift-java invocation to generate wrappers for those public classes.") var jar: Bool = false @Option( @@ -57,9 +55,7 @@ extension SwiftJava { swiftModule } - @Argument( - help: "The input file, which is either a swift-java configuration file or (if '-jar' was specified) a Jar file." - ) + @Argument(help: "The input file, which is either a swift-java configuration file or (if '-jar' was specified) a Jar file.") var input: String? } } @@ -199,10 +195,9 @@ extension SwiftJava.ConfigureCommand { javaCanonicalName.defaultSwiftNameForJavaClass } } - } package func fileOrDirectoryExists(at path: String) -> Bool { var isDirectory: ObjCBool = false return FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory) -} \ No newline at end of file +} From 3c36c767c6268d5b9d051e63ab669b5c91f1bb0d Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Thu, 28 Aug 2025 16:45:32 +0900 Subject: [PATCH 072/141] rename CJNI module to avoid conflicts; call it CSwiftJavaJNI (#377) --- Benchmarks/Package.swift | 2 +- Package.swift | 8 ++++---- Samples/JavaDependencySampleApp/Package.swift | 2 +- Samples/SwiftJavaExtractFFMSampleApp/Package.swift | 2 +- .../Sources/MySwiftLibrary/jni/JNIImplementations.swift | 2 +- Samples/SwiftJavaExtractJNISampleApp/Package.swift | 2 +- Sources/CJNI/include/module.modulemap | 4 ---- Sources/{CJNI => CSwiftJavaJNI}/dummy.c | 0 .../CJNI.h => CSwiftJavaJNI/include/CSwiftJavaJNI.h} | 6 +++--- Sources/CSwiftJavaJNI/include/module.modulemap | 4 ++++ .../JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift | 2 +- .../JavaKitDependencyResolver/DependencyResolver.swift | 2 +- .../JavaStdlib/JavaIO/generated/BufferedInputStream.swift | 2 +- Sources/JavaStdlib/JavaIO/generated/Charset.swift | 2 +- Sources/JavaStdlib/JavaIO/generated/Closeable.swift | 2 +- Sources/JavaStdlib/JavaIO/generated/File.swift | 2 +- Sources/JavaStdlib/JavaIO/generated/FileDescriptor.swift | 2 +- Sources/JavaStdlib/JavaIO/generated/FileReader.swift | 2 +- Sources/JavaStdlib/JavaIO/generated/Flushable.swift | 2 +- Sources/JavaStdlib/JavaIO/generated/InputStream.swift | 2 +- .../JavaStdlib/JavaIO/generated/InputStreamReader.swift | 2 +- Sources/JavaStdlib/JavaIO/generated/OutputStream.swift | 2 +- Sources/JavaStdlib/JavaIO/generated/Path.swift | 2 +- Sources/JavaStdlib/JavaIO/generated/Readable.swift | 2 +- Sources/JavaStdlib/JavaIO/generated/Reader.swift | 2 +- Sources/JavaStdlib/JavaIO/generated/StringReader.swift | 2 +- Sources/JavaStdlib/JavaIO/generated/WatchService.swift | 2 +- Sources/JavaStdlib/JavaIO/generated/Writer.swift | 2 +- .../JavaLangReflect/generated/AccessibleObject.swift | 2 +- .../JavaLangReflect/generated/AnnotatedType.swift | 2 +- .../JavaStdlib/JavaLangReflect/generated/Annotation.swift | 2 +- .../JavaLangReflect/generated/Constructor.swift | 2 +- .../JavaStdlib/JavaLangReflect/generated/Executable.swift | 2 +- Sources/JavaStdlib/JavaLangReflect/generated/Field.swift | 2 +- .../JavaLangReflect/generated/GenericArrayType.swift | 2 +- .../JavaLangReflect/generated/GenericDeclaration.swift | 2 +- Sources/JavaStdlib/JavaLangReflect/generated/Method.swift | 2 +- .../JavaStdlib/JavaLangReflect/generated/Parameter.swift | 2 +- .../JavaLangReflect/generated/ParameterizedType.swift | 2 +- Sources/JavaStdlib/JavaLangReflect/generated/Type.swift | 2 +- .../JavaLangReflect/generated/TypeVariable.swift | 2 +- .../JavaLangReflect/generated/WildcardType.swift | 2 +- Sources/JavaStdlib/JavaNet/generated/URI.swift | 2 +- Sources/JavaStdlib/JavaNet/generated/URL.swift | 2 +- Sources/JavaStdlib/JavaNet/generated/URLClassLoader.swift | 2 +- Sources/JavaStdlib/JavaNet/generated/URLConnection.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/ArrayDeque.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/ArrayList.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/BitSet.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/Enumeration.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/HashMap.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/HashSet.swift | 2 +- .../JavaStdlib/JavaUtil/generated/JavaCollection.swift | 2 +- .../JavaStdlib/JavaUtil/generated/JavaDictionary.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/JavaIterator.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/JavaSet.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/LinkedList.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/List.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/ListIterator.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/PriorityQueue.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/Queue.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/RandomAccess.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/Stack.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/TreeMap.swift | 2 +- Sources/JavaStdlib/JavaUtil/generated/TreeSet.swift | 2 +- .../JavaUtilFunction/generated/JavaBiConsumer.swift | 2 +- .../JavaUtilFunction/generated/JavaBiFunction.swift | 2 +- .../JavaUtilFunction/generated/JavaBiPredicate.swift | 2 +- .../JavaUtilFunction/generated/JavaBinaryOperator.swift | 2 +- .../JavaUtilFunction/generated/JavaBooleanSupplier.swift | 2 +- .../JavaUtilFunction/generated/JavaConsumer.swift | 2 +- .../generated/JavaDoubleBinaryOperator.swift | 2 +- .../JavaUtilFunction/generated/JavaDoubleConsumer.swift | 2 +- .../JavaUtilFunction/generated/JavaDoubleFunction.swift | 2 +- .../JavaUtilFunction/generated/JavaDoublePredicate.swift | 2 +- .../JavaUtilFunction/generated/JavaDoubleSupplier.swift | 2 +- .../generated/JavaDoubleToIntFunction.swift | 2 +- .../generated/JavaDoubleToLongFunction.swift | 2 +- .../generated/JavaDoubleUnaryOperator.swift | 2 +- .../JavaUtilFunction/generated/JavaFunction.swift | 2 +- .../generated/JavaIntBinaryOperator.swift | 2 +- .../JavaUtilFunction/generated/JavaIntConsumer.swift | 2 +- .../JavaUtilFunction/generated/JavaIntFunction.swift | 2 +- .../JavaUtilFunction/generated/JavaIntPredicate.swift | 2 +- .../JavaUtilFunction/generated/JavaIntSupplier.swift | 2 +- .../generated/JavaIntToDoubleFunction.swift | 2 +- .../generated/JavaIntToLongFunction.swift | 2 +- .../JavaUtilFunction/generated/JavaIntUnaryOperator.swift | 2 +- .../generated/JavaLongBinaryOperator.swift | 2 +- .../JavaUtilFunction/generated/JavaLongConsumer.swift | 2 +- .../JavaUtilFunction/generated/JavaLongFunction.swift | 2 +- .../JavaUtilFunction/generated/JavaLongPredicate.swift | 2 +- .../JavaUtilFunction/generated/JavaLongSupplier.swift | 2 +- .../generated/JavaLongToDoubleFunction.swift | 2 +- .../generated/JavaLongToIntFunction.swift | 2 +- .../generated/JavaLongUnaryOperator.swift | 2 +- .../generated/JavaObjDoubleConsumer.swift | 2 +- .../JavaUtilFunction/generated/JavaObjIntConsumer.swift | 2 +- .../JavaUtilFunction/generated/JavaObjLongConsumer.swift | 2 +- .../JavaUtilFunction/generated/JavaPredicate.swift | 2 +- .../JavaUtilFunction/generated/JavaSupplier.swift | 2 +- .../generated/JavaToDoubleBiFunction.swift | 2 +- .../JavaUtilFunction/generated/JavaToDoubleFunction.swift | 2 +- .../JavaUtilFunction/generated/JavaToIntBiFunction.swift | 2 +- .../JavaUtilFunction/generated/JavaToIntFunction.swift | 2 +- .../JavaUtilFunction/generated/JavaToLongBiFunction.swift | 2 +- .../JavaUtilFunction/generated/JavaToLongFunction.swift | 2 +- .../JavaUtilFunction/generated/JavaUnaryOperator.swift | 2 +- Sources/JavaStdlib/JavaUtilJar/generated/Attributes.swift | 2 +- Sources/JavaStdlib/JavaUtilJar/generated/JarEntry.swift | 2 +- Sources/JavaStdlib/JavaUtilJar/generated/JarFile.swift | 2 +- .../JavaStdlib/JavaUtilJar/generated/JarInputStream.swift | 2 +- .../JavaUtilJar/generated/JarOutputStream.swift | 2 +- Sources/JavaStdlib/JavaUtilJar/generated/Manifest.swift | 2 +- Sources/JavaStdlib/JavaUtilJar/generated/ZipEntry.swift | 2 +- Sources/SwiftJava/AnyJavaObject.swift | 2 +- Sources/SwiftJava/JavaClass+Initialization.swift | 2 +- Sources/SwiftJava/JavaEnvironment.swift | 2 +- Sources/SwiftJava/JavaObject+Inheritance.swift | 2 +- Sources/SwiftJava/JavaObject+MethodCalls.swift | 2 +- Sources/SwiftJava/JavaObjectHolder.swift | 2 +- Sources/SwiftJava/JavaRuntime+Reexport.swift | 2 +- Sources/SwiftJava/JavaValue.swift | 2 +- Sources/SwiftJava/Optional+JavaObject.swift | 2 +- Sources/SwiftJava/generated/Appendable.swift | 2 +- Sources/SwiftJava/generated/CharSequence.swift | 2 +- Sources/SwiftJava/generated/Exception.swift | 2 +- Sources/SwiftJava/generated/JavaArray.swift | 2 +- Sources/SwiftJava/generated/JavaBoolean.swift | 2 +- Sources/SwiftJava/generated/JavaByte.swift | 2 +- Sources/SwiftJava/generated/JavaCharacter.swift | 2 +- Sources/SwiftJava/generated/JavaClass.swift | 2 +- Sources/SwiftJava/generated/JavaClassLoader.swift | 2 +- Sources/SwiftJava/generated/JavaDouble.swift | 2 +- Sources/SwiftJava/generated/JavaError.swift | 2 +- Sources/SwiftJava/generated/JavaFloat.swift | 2 +- Sources/SwiftJava/generated/JavaInteger.swift | 2 +- Sources/SwiftJava/generated/JavaLong.swift | 2 +- Sources/SwiftJava/generated/JavaNumber.swift | 2 +- Sources/SwiftJava/generated/JavaObject.swift | 2 +- Sources/SwiftJava/generated/JavaOptional.swift | 2 +- Sources/SwiftJava/generated/JavaOptionalDouble.swift | 2 +- Sources/SwiftJava/generated/JavaOptionalInt.swift | 2 +- Sources/SwiftJava/generated/JavaOptionalLong.swift | 2 +- Sources/SwiftJava/generated/JavaShort.swift | 2 +- Sources/SwiftJava/generated/JavaString.swift | 2 +- Sources/SwiftJava/generated/JavaVoid.swift | 2 +- Sources/SwiftJava/generated/RuntimeException.swift | 2 +- Sources/SwiftJava/generated/Throwable.swift | 2 +- Sources/SwiftJavaTool/Java/JavaClassLoader.swift | 2 +- Sources/SwiftJavaToolLib/JavaTranslator.swift | 2 +- 151 files changed, 157 insertions(+), 157 deletions(-) delete mode 100644 Sources/CJNI/include/module.modulemap rename Sources/{CJNI => CSwiftJavaJNI}/dummy.c (100%) rename Sources/{CJNI/include/CJNI.h => CSwiftJavaJNI/include/CSwiftJavaJNI.h} (86%) create mode 100644 Sources/CSwiftJavaJNI/include/module.modulemap diff --git a/Benchmarks/Package.swift b/Benchmarks/Package.swift index 955378fc8..127e4d483 100644 --- a/Benchmarks/Package.swift +++ b/Benchmarks/Package.swift @@ -51,7 +51,7 @@ let package = Package( .executableTarget( name: "JavaApiCallBenchmarks", dependencies: [ - .product(name: "CJNI", package: "swift-java"), + .product(name: "CSwiftJavaJNI", package: "swift-java"), .product(name: "SwiftJava", package: "swift-java"), .product(name: "JavaNet", package: "swift-java"), .product(name: "Benchmark", package: "package-benchmark"), diff --git a/Package.swift b/Package.swift index a30b9510b..1c0e94810 100644 --- a/Package.swift +++ b/Package.swift @@ -101,8 +101,8 @@ let package = Package( ), .library( - name: "CJNI", - targets: ["CJNI"] + name: "CSwiftJavaJNI", + targets: ["CSwiftJavaJNI"] ), .library( @@ -237,7 +237,7 @@ let package = Package( .target( name: "SwiftJava", dependencies: [ - "CJNI", + "CSwiftJavaJNI", "SwiftJavaMacros", "JavaTypes", "SwiftJavaConfigurationShared", // for Configuration reading at runtime @@ -359,7 +359,7 @@ let package = Package( ), .target( - name: "CJNI", + name: "CSwiftJavaJNI", swiftSettings: [ .swiftLanguageMode(.v5), .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) diff --git a/Samples/JavaDependencySampleApp/Package.swift b/Samples/JavaDependencySampleApp/Package.swift index c5ae97c7d..573a60cc2 100644 --- a/Samples/JavaDependencySampleApp/Package.swift +++ b/Samples/JavaDependencySampleApp/Package.swift @@ -65,7 +65,7 @@ let package = Package( name: "JavaDependencySample", dependencies: [ .product(name: "SwiftJava", package: "swift-java"), - .product(name: "CJNI", package: "swift-java"), + .product(name: "CSwiftJavaJNI", package: "swift-java"), .product(name: "JavaUtilFunction", package: "swift-java"), "JavaCommonsCSV" ], diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Package.swift b/Samples/SwiftJavaExtractFFMSampleApp/Package.swift index 30630a01a..b9765c24a 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/Package.swift +++ b/Samples/SwiftJavaExtractFFMSampleApp/Package.swift @@ -64,7 +64,7 @@ let package = Package( name: "MySwiftLibrary", dependencies: [ .product(name: "SwiftJava", package: "swift-java"), - .product(name: "CJNI", package: "swift-java"), + .product(name: "CSwiftJavaJNI", package: "swift-java"), .product(name: "SwiftKitSwift", package: "swift-java"), ], exclude: [ diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/jni/JNIImplementations.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/jni/JNIImplementations.swift index 481265d50..50561d328 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/jni/JNIImplementations.swift +++ b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/jni/JNIImplementations.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("com.example.swift.HelloJava2Swift") open class HelloJava2Swift: JavaObject { diff --git a/Samples/SwiftJavaExtractJNISampleApp/Package.swift b/Samples/SwiftJavaExtractJNISampleApp/Package.swift index 0d2d2fa86..c7d5d7172 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Package.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Package.swift @@ -61,7 +61,7 @@ let package = Package( name: "MySwiftLibrary", dependencies: [ .product(name: "SwiftJava", package: "swift-java"), - .product(name: "CJNI", package: "swift-java"), + .product(name: "CSwiftJavaJNI", package: "swift-java"), .product(name: "SwiftKitSwift", package: "swift-java"), ], exclude: [ diff --git a/Sources/CJNI/include/module.modulemap b/Sources/CJNI/include/module.modulemap deleted file mode 100644 index c71f30c28..000000000 --- a/Sources/CJNI/include/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module CJNI { - umbrella header "CJNI.h" - export * -} diff --git a/Sources/CJNI/dummy.c b/Sources/CSwiftJavaJNI/dummy.c similarity index 100% rename from Sources/CJNI/dummy.c rename to Sources/CSwiftJavaJNI/dummy.c diff --git a/Sources/CJNI/include/CJNI.h b/Sources/CSwiftJavaJNI/include/CSwiftJavaJNI.h similarity index 86% rename from Sources/CJNI/include/CJNI.h rename to Sources/CSwiftJavaJNI/include/CSwiftJavaJNI.h index bef6e7ffd..fb5f71afd 100644 --- a/Sources/CJNI/include/CJNI.h +++ b/Sources/CSwiftJavaJNI/include/CSwiftJavaJNI.h @@ -12,9 +12,9 @@ // //===----------------------------------------------------------------------===// -#ifndef Swift_CJNI_h -#define Swift_CJNI_h +#ifndef CSwiftJavaJNI_h +#define CSwiftJavaJNI_h #include -#endif /* Swift_CJNI_h */ +#endif /* CSwiftJavaJNI_h */ diff --git a/Sources/CSwiftJavaJNI/include/module.modulemap b/Sources/CSwiftJavaJNI/include/module.modulemap new file mode 100644 index 000000000..2be3eda91 --- /dev/null +++ b/Sources/CSwiftJavaJNI/include/module.modulemap @@ -0,0 +1,4 @@ +module CSwiftJavaJNI { + umbrella header "CSwiftJavaJNI.h" + export * +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 536797838..329b7dfd4 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -409,7 +409,7 @@ extension JNISwift2JavaGenerator { // Generated by swift-java import SwiftJava - import CJNI + import CSwiftJavaJNI """ ) diff --git a/Sources/JavaKitDependencyResolver/DependencyResolver.swift b/Sources/JavaKitDependencyResolver/DependencyResolver.swift index 1ecc8dc58..2d339b151 100644 --- a/Sources/JavaKitDependencyResolver/DependencyResolver.swift +++ b/Sources/JavaKitDependencyResolver/DependencyResolver.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("org.swift.jni.dependencies.DependencyResolver") public struct DependencyResolver { diff --git a/Sources/JavaStdlib/JavaIO/generated/BufferedInputStream.swift b/Sources/JavaStdlib/JavaIO/generated/BufferedInputStream.swift index b4a134949..2a211eb9a 100644 --- a/Sources/JavaStdlib/JavaIO/generated/BufferedInputStream.swift +++ b/Sources/JavaStdlib/JavaIO/generated/BufferedInputStream.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.io.BufferedInputStream") open class BufferedInputStream: InputStream { diff --git a/Sources/JavaStdlib/JavaIO/generated/Charset.swift b/Sources/JavaStdlib/JavaIO/generated/Charset.swift index f3b9126c2..03e85a8c8 100644 --- a/Sources/JavaStdlib/JavaIO/generated/Charset.swift +++ b/Sources/JavaStdlib/JavaIO/generated/Charset.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.nio.charset.Charset") open class Charset: JavaObject { diff --git a/Sources/JavaStdlib/JavaIO/generated/Closeable.swift b/Sources/JavaStdlib/JavaIO/generated/Closeable.swift index 9e68c58bd..1df526419 100644 --- a/Sources/JavaStdlib/JavaIO/generated/Closeable.swift +++ b/Sources/JavaStdlib/JavaIO/generated/Closeable.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.io.Closeable") public struct Closeable { diff --git a/Sources/JavaStdlib/JavaIO/generated/File.swift b/Sources/JavaStdlib/JavaIO/generated/File.swift index 11831b5e8..68b04efb4 100644 --- a/Sources/JavaStdlib/JavaIO/generated/File.swift +++ b/Sources/JavaStdlib/JavaIO/generated/File.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.io.File") open class File: JavaObject { diff --git a/Sources/JavaStdlib/JavaIO/generated/FileDescriptor.swift b/Sources/JavaStdlib/JavaIO/generated/FileDescriptor.swift index 36f1686c4..4c95b2b3a 100644 --- a/Sources/JavaStdlib/JavaIO/generated/FileDescriptor.swift +++ b/Sources/JavaStdlib/JavaIO/generated/FileDescriptor.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.io.FileDescriptor") open class FileDescriptor: JavaObject { diff --git a/Sources/JavaStdlib/JavaIO/generated/FileReader.swift b/Sources/JavaStdlib/JavaIO/generated/FileReader.swift index ed0898c70..b793a3f6f 100644 --- a/Sources/JavaStdlib/JavaIO/generated/FileReader.swift +++ b/Sources/JavaStdlib/JavaIO/generated/FileReader.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.io.FileReader") open class FileReader: InputStreamReader { diff --git a/Sources/JavaStdlib/JavaIO/generated/Flushable.swift b/Sources/JavaStdlib/JavaIO/generated/Flushable.swift index 59854369f..95cfc4488 100644 --- a/Sources/JavaStdlib/JavaIO/generated/Flushable.swift +++ b/Sources/JavaStdlib/JavaIO/generated/Flushable.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.io.Flushable") public struct Flushable { diff --git a/Sources/JavaStdlib/JavaIO/generated/InputStream.swift b/Sources/JavaStdlib/JavaIO/generated/InputStream.swift index d521f2651..b42a8fd85 100644 --- a/Sources/JavaStdlib/JavaIO/generated/InputStream.swift +++ b/Sources/JavaStdlib/JavaIO/generated/InputStream.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.io.InputStream", implements: Closeable.self) open class InputStream: JavaObject { diff --git a/Sources/JavaStdlib/JavaIO/generated/InputStreamReader.swift b/Sources/JavaStdlib/JavaIO/generated/InputStreamReader.swift index faa04edae..7e55d6337 100644 --- a/Sources/JavaStdlib/JavaIO/generated/InputStreamReader.swift +++ b/Sources/JavaStdlib/JavaIO/generated/InputStreamReader.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.io.InputStreamReader") open class InputStreamReader: Reader { diff --git a/Sources/JavaStdlib/JavaIO/generated/OutputStream.swift b/Sources/JavaStdlib/JavaIO/generated/OutputStream.swift index 1d3ec3566..d499508cf 100644 --- a/Sources/JavaStdlib/JavaIO/generated/OutputStream.swift +++ b/Sources/JavaStdlib/JavaIO/generated/OutputStream.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.io.OutputStream", implements: Closeable.self, Flushable.self) open class OutputStream: JavaObject { diff --git a/Sources/JavaStdlib/JavaIO/generated/Path.swift b/Sources/JavaStdlib/JavaIO/generated/Path.swift index 87956f105..235d9cef1 100644 --- a/Sources/JavaStdlib/JavaIO/generated/Path.swift +++ b/Sources/JavaStdlib/JavaIO/generated/Path.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.nio.file.Path") public struct Path { diff --git a/Sources/JavaStdlib/JavaIO/generated/Readable.swift b/Sources/JavaStdlib/JavaIO/generated/Readable.swift index 25f482217..8961e18af 100644 --- a/Sources/JavaStdlib/JavaIO/generated/Readable.swift +++ b/Sources/JavaStdlib/JavaIO/generated/Readable.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.lang.Readable") public struct Readable { diff --git a/Sources/JavaStdlib/JavaIO/generated/Reader.swift b/Sources/JavaStdlib/JavaIO/generated/Reader.swift index e133f7419..5d8f77bf5 100644 --- a/Sources/JavaStdlib/JavaIO/generated/Reader.swift +++ b/Sources/JavaStdlib/JavaIO/generated/Reader.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.io.Reader", implements: Readable.self, Closeable.self) open class Reader: JavaObject { diff --git a/Sources/JavaStdlib/JavaIO/generated/StringReader.swift b/Sources/JavaStdlib/JavaIO/generated/StringReader.swift index aa3efd388..ae4464ed7 100644 --- a/Sources/JavaStdlib/JavaIO/generated/StringReader.swift +++ b/Sources/JavaStdlib/JavaIO/generated/StringReader.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.io.StringReader") open class StringReader: Reader { diff --git a/Sources/JavaStdlib/JavaIO/generated/WatchService.swift b/Sources/JavaStdlib/JavaIO/generated/WatchService.swift index 0b44c4fe2..e2c570a30 100644 --- a/Sources/JavaStdlib/JavaIO/generated/WatchService.swift +++ b/Sources/JavaStdlib/JavaIO/generated/WatchService.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.nio.file.WatchService", extends: Closeable.self) public struct WatchService { diff --git a/Sources/JavaStdlib/JavaIO/generated/Writer.swift b/Sources/JavaStdlib/JavaIO/generated/Writer.swift index 7712a1c0c..fe20d27d5 100644 --- a/Sources/JavaStdlib/JavaIO/generated/Writer.swift +++ b/Sources/JavaStdlib/JavaIO/generated/Writer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.io.Writer", implements: Appendable.self, Closeable.self, Flushable.self) open class Writer: JavaObject { diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/AccessibleObject.swift b/Sources/JavaStdlib/JavaLangReflect/generated/AccessibleObject.swift index c118f78b8..c6b0f4fcc 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/AccessibleObject.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/AccessibleObject.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.reflect.AccessibleObject") open class AccessibleObject: JavaObject { diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/AnnotatedType.swift b/Sources/JavaStdlib/JavaLangReflect/generated/AnnotatedType.swift index cc5b418c4..87480ef4b 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/AnnotatedType.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/AnnotatedType.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.lang.reflect.AnnotatedType") public struct AnnotatedType { diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/Annotation.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Annotation.swift index 66ab49cce..1449bf329 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/Annotation.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Annotation.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.lang.annotation.Annotation") public struct Annotation { diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/Constructor.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Constructor.swift index 76f49f118..202cba9bd 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/Constructor.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Constructor.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.reflect.Constructor") open class Constructor: Executable { diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift index 31799784e..3a6df8eab 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift @@ -1,7 +1,7 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava import JavaUtil -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.reflect.Executable", implements: GenericDeclaration.self) open class Executable: AccessibleObject { diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/Field.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Field.swift index 2c4a49e9a..ba4f45383 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/Field.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Field.swift @@ -1,7 +1,7 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava import JavaUtil -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.reflect.Field") open class Field: AccessibleObject { diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/GenericArrayType.swift b/Sources/JavaStdlib/JavaLangReflect/generated/GenericArrayType.swift index 75ad2c752..42375e086 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/GenericArrayType.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/GenericArrayType.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.lang.reflect.GenericArrayType", extends: Type.self) public struct GenericArrayType { diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/GenericDeclaration.swift b/Sources/JavaStdlib/JavaLangReflect/generated/GenericDeclaration.swift index bec84aa58..839fa7dba 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/GenericDeclaration.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/GenericDeclaration.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.lang.reflect.GenericDeclaration") public struct GenericDeclaration { diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/Method.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Method.swift index 4489cf24b..94371cd40 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/Method.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Method.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.reflect.Method") open class Method: Executable { diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/Parameter.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Parameter.swift index 6dea46fd0..35ea70984 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/Parameter.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Parameter.swift @@ -1,7 +1,7 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava import JavaUtil -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.reflect.Parameter") open class Parameter: JavaObject { diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift b/Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift index a427a2a5a..5e29ee052 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.lang.reflect.ParameterizedType", extends: Type.self) public struct ParameterizedType { diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/Type.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Type.swift index 05e8687ec..ff52b41a7 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/Type.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Type.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.lang.reflect.Type") public struct Type { diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift b/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift index e6a7899ce..736fcfde6 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.lang.reflect.TypeVariable", extends: Type.self) public struct TypeVariable { diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/WildcardType.swift b/Sources/JavaStdlib/JavaLangReflect/generated/WildcardType.swift index 2514495a1..a09b1b3b9 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/WildcardType.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/WildcardType.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.lang.reflect.WildcardType", extends: Type.self) public struct WildcardType { diff --git a/Sources/JavaStdlib/JavaNet/generated/URI.swift b/Sources/JavaStdlib/JavaNet/generated/URI.swift index f1a6c0788..d62424065 100644 --- a/Sources/JavaStdlib/JavaNet/generated/URI.swift +++ b/Sources/JavaStdlib/JavaNet/generated/URI.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.net.URI") open class URI: JavaObject { diff --git a/Sources/JavaStdlib/JavaNet/generated/URL.swift b/Sources/JavaStdlib/JavaNet/generated/URL.swift index 9140a1d9e..95ac8fb4c 100644 --- a/Sources/JavaStdlib/JavaNet/generated/URL.swift +++ b/Sources/JavaStdlib/JavaNet/generated/URL.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.net.URL") open class URL: JavaObject { diff --git a/Sources/JavaStdlib/JavaNet/generated/URLClassLoader.swift b/Sources/JavaStdlib/JavaNet/generated/URLClassLoader.swift index 1750f1970..d16e2eac6 100644 --- a/Sources/JavaStdlib/JavaNet/generated/URLClassLoader.swift +++ b/Sources/JavaStdlib/JavaNet/generated/URLClassLoader.swift @@ -1,7 +1,7 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava import JavaUtil -import CJNI +import CSwiftJavaJNI @JavaClass("java.net.URLClassLoader") open class URLClassLoader: JavaObject { diff --git a/Sources/JavaStdlib/JavaNet/generated/URLConnection.swift b/Sources/JavaStdlib/JavaNet/generated/URLConnection.swift index 6a93edd17..332e7425c 100644 --- a/Sources/JavaStdlib/JavaNet/generated/URLConnection.swift +++ b/Sources/JavaStdlib/JavaNet/generated/URLConnection.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.net.URLConnection") public struct URLConnection { diff --git a/Sources/JavaStdlib/JavaUtil/generated/ArrayDeque.swift b/Sources/JavaStdlib/JavaUtil/generated/ArrayDeque.swift index f0d257afb..b8a1fb189 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/ArrayDeque.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/ArrayDeque.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.ArrayDeque") open class ArrayDeque: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtil/generated/ArrayList.swift b/Sources/JavaStdlib/JavaUtil/generated/ArrayList.swift index b39ada568..9ead8f4c0 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/ArrayList.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/ArrayList.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.ArrayList", implements: List.self, RandomAccess.self) open class ArrayList: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtil/generated/BitSet.swift b/Sources/JavaStdlib/JavaUtil/generated/BitSet.swift index 2d89ba5a4..7356b4b89 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/BitSet.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/BitSet.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.BitSet") open class BitSet: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtil/generated/Enumeration.swift b/Sources/JavaStdlib/JavaUtil/generated/Enumeration.swift index 0229e16d4..9c039b166 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/Enumeration.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/Enumeration.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.Enumeration") public struct Enumeration { diff --git a/Sources/JavaStdlib/JavaUtil/generated/HashMap.swift b/Sources/JavaStdlib/JavaUtil/generated/HashMap.swift index 6fd38bc4a..a217d48fd 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/HashMap.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/HashMap.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.HashMap") open class HashMap: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtil/generated/HashSet.swift b/Sources/JavaStdlib/JavaUtil/generated/HashSet.swift index f7c37d21e..3b089c861 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/HashSet.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/HashSet.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.HashSet", implements: JavaSet.self) open class HashSet: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtil/generated/JavaCollection.swift b/Sources/JavaStdlib/JavaUtil/generated/JavaCollection.swift index 4d1c8a811..3a8db21b3 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/JavaCollection.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/JavaCollection.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.Collection") public struct JavaCollection { diff --git a/Sources/JavaStdlib/JavaUtil/generated/JavaDictionary.swift b/Sources/JavaStdlib/JavaUtil/generated/JavaDictionary.swift index 85ee9bebb..0a6508f5c 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/JavaDictionary.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/JavaDictionary.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.Dictionary") open class JavaDictionary: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtil/generated/JavaIterator.swift b/Sources/JavaStdlib/JavaUtil/generated/JavaIterator.swift index 1038a5e1b..06f09d7f0 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/JavaIterator.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/JavaIterator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.Iterator") public struct JavaIterator { diff --git a/Sources/JavaStdlib/JavaUtil/generated/JavaSet.swift b/Sources/JavaStdlib/JavaUtil/generated/JavaSet.swift index 7fec8e7ac..12ff00567 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/JavaSet.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/JavaSet.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.Set", extends: JavaCollection.self) public struct JavaSet { diff --git a/Sources/JavaStdlib/JavaUtil/generated/LinkedList.swift b/Sources/JavaStdlib/JavaUtil/generated/LinkedList.swift index 9f27a3e16..2464980f0 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/LinkedList.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/LinkedList.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.LinkedList") public struct LinkedList { diff --git a/Sources/JavaStdlib/JavaUtil/generated/List.swift b/Sources/JavaStdlib/JavaUtil/generated/List.swift index 1d745ec3b..2ebc19420 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/List.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/List.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.List") public struct List { diff --git a/Sources/JavaStdlib/JavaUtil/generated/ListIterator.swift b/Sources/JavaStdlib/JavaUtil/generated/ListIterator.swift index a643efa73..121330b13 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/ListIterator.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/ListIterator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.ListIterator", extends: JavaIterator.self) public struct ListIterator { diff --git a/Sources/JavaStdlib/JavaUtil/generated/PriorityQueue.swift b/Sources/JavaStdlib/JavaUtil/generated/PriorityQueue.swift index 13fc36292..d9d9a85a5 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/PriorityQueue.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/PriorityQueue.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.PriorityQueue") open class PriorityQueue: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtil/generated/Queue.swift b/Sources/JavaStdlib/JavaUtil/generated/Queue.swift index 1d51d7c40..b007e90a7 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/Queue.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/Queue.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.Queue", extends: JavaCollection.self) public struct Queue { diff --git a/Sources/JavaStdlib/JavaUtil/generated/RandomAccess.swift b/Sources/JavaStdlib/JavaUtil/generated/RandomAccess.swift index e0fcb3907..91b3fa31c 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/RandomAccess.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/RandomAccess.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.RandomAccess") public struct RandomAccess { diff --git a/Sources/JavaStdlib/JavaUtil/generated/Stack.swift b/Sources/JavaStdlib/JavaUtil/generated/Stack.swift index 636ed03be..be4330ebf 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/Stack.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/Stack.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.Stack") open class Stack: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtil/generated/TreeMap.swift b/Sources/JavaStdlib/JavaUtil/generated/TreeMap.swift index 4bf41ed37..c6dd3bb05 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/TreeMap.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/TreeMap.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.TreeMap") open class TreeMap: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtil/generated/TreeSet.swift b/Sources/JavaStdlib/JavaUtil/generated/TreeSet.swift index cbfe8979b..3ec9b1ea7 100644 --- a/Sources/JavaStdlib/JavaUtil/generated/TreeSet.swift +++ b/Sources/JavaStdlib/JavaUtil/generated/TreeSet.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.TreeSet") open class TreeSet: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiConsumer.swift index a8f331279..a01a0933e 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.BiConsumer") public struct JavaBiConsumer { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiFunction.swift index d83e74a83..ad54c58b1 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.BiFunction") public struct JavaBiFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiPredicate.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiPredicate.swift index f1870aa49..ee8aa0f50 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiPredicate.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBiPredicate.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.BiPredicate") public struct JavaBiPredicate { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBinaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBinaryOperator.swift index a03e9addf..1c7f54569 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBinaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBinaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface( "java.util.function.BinaryOperator", diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBooleanSupplier.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBooleanSupplier.swift index d6ce66de8..e6e4a3ced 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBooleanSupplier.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaBooleanSupplier.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.BooleanSupplier") public struct JavaBooleanSupplier { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaConsumer.swift index f8e64b2f7..7da9bea8d 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.Consumer") public struct JavaConsumer { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleBinaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleBinaryOperator.swift index ee82194d9..f6c1c671e 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleBinaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleBinaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.DoubleBinaryOperator") public struct JavaDoubleBinaryOperator { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleConsumer.swift index a4a5a48f2..baf53c4ac 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.DoubleConsumer") public struct JavaDoubleConsumer { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleFunction.swift index 96879af49..6f5d6752f 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.DoubleFunction") public struct JavaDoubleFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoublePredicate.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoublePredicate.swift index 27ac5861a..c594518d1 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoublePredicate.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoublePredicate.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.DoublePredicate") public struct JavaDoublePredicate { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleSupplier.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleSupplier.swift index 807668bcb..839ae7e78 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleSupplier.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleSupplier.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.DoubleSupplier") public struct JavaDoubleSupplier { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleToIntFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleToIntFunction.swift index 4de7ab7f1..438249ebf 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleToIntFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleToIntFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.DoubleToIntFunction") public struct JavaDoubleToIntFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleToLongFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleToLongFunction.swift index 492dbfa67..76b916db4 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleToLongFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleToLongFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.DoubleToLongFunction") public struct JavaDoubleToLongFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleUnaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleUnaryOperator.swift index 9db463ee6..cf1ff7e5c 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleUnaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaDoubleUnaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.DoubleUnaryOperator") public struct JavaDoubleUnaryOperator { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaFunction.swift index 6e2361d79..7a8165d87 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.Function") public struct JavaFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntBinaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntBinaryOperator.swift index f7a56f184..3df580c76 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntBinaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntBinaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.IntBinaryOperator") public struct JavaIntBinaryOperator { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntConsumer.swift index 62130b888..a8ac7c0bf 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.IntConsumer") public struct JavaIntConsumer { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntFunction.swift index c3fcdf1ec..6ebb7292d 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.IntFunction") public struct JavaIntFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntPredicate.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntPredicate.swift index 2ca8fcc0d..66c5e1335 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntPredicate.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntPredicate.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.IntPredicate") public struct JavaIntPredicate { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntSupplier.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntSupplier.swift index 05155e793..0976fd53d 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntSupplier.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntSupplier.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.IntSupplier") public struct JavaIntSupplier { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntToDoubleFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntToDoubleFunction.swift index f3efcc6d9..9891e8153 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntToDoubleFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntToDoubleFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.IntToDoubleFunction") public struct JavaIntToDoubleFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntToLongFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntToLongFunction.swift index 1bbe989c5..17a7b7fac 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntToLongFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntToLongFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.IntToLongFunction") public struct JavaIntToLongFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntUnaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntUnaryOperator.swift index 04ed0b2b8..89528a2b8 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntUnaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaIntUnaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.IntUnaryOperator") public struct JavaIntUnaryOperator { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongBinaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongBinaryOperator.swift index 1f3ca7915..1cd53b933 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongBinaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongBinaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.LongBinaryOperator") public struct JavaLongBinaryOperator { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongConsumer.swift index 82ae2a3bf..4c84754a7 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.LongConsumer") public struct JavaLongConsumer { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongFunction.swift index eeed2bd81..9ce4cef15 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.LongFunction") public struct JavaLongFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongPredicate.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongPredicate.swift index 77f1293e6..8f8f91fcf 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongPredicate.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongPredicate.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.LongPredicate") public struct JavaLongPredicate { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongSupplier.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongSupplier.swift index 60e5421d7..e0a203a18 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongSupplier.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongSupplier.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.LongSupplier") public struct JavaLongSupplier { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongToDoubleFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongToDoubleFunction.swift index a115477c0..7167d8fc9 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongToDoubleFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongToDoubleFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.LongToDoubleFunction") public struct JavaLongToDoubleFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongToIntFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongToIntFunction.swift index 984dbda31..006713043 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongToIntFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongToIntFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.LongToIntFunction") public struct JavaLongToIntFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongUnaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongUnaryOperator.swift index 7e379b0af..8cf8944be 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongUnaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaLongUnaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.LongUnaryOperator") public struct JavaLongUnaryOperator { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjDoubleConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjDoubleConsumer.swift index 5c19ff03c..aa7d5a48a 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjDoubleConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjDoubleConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.ObjDoubleConsumer") public struct JavaObjDoubleConsumer { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjIntConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjIntConsumer.swift index a511db83e..c53c3631c 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjIntConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjIntConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.ObjIntConsumer") public struct JavaObjIntConsumer { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjLongConsumer.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjLongConsumer.swift index a2c32cfa1..ff4f7798a 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjLongConsumer.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaObjLongConsumer.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.ObjLongConsumer") public struct JavaObjLongConsumer { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaPredicate.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaPredicate.swift index a2de359aa..b888e1786 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaPredicate.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaPredicate.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.Predicate") public struct JavaPredicate { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaSupplier.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaSupplier.swift index a751ae793..4d4c73cda 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaSupplier.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaSupplier.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.Supplier") public struct JavaSupplier { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToDoubleBiFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToDoubleBiFunction.swift index 739f1f178..35f77b785 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToDoubleBiFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToDoubleBiFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.ToDoubleBiFunction") public struct JavaToDoubleBiFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToDoubleFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToDoubleFunction.swift index acdf7adb0..56ee180d9 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToDoubleFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToDoubleFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.ToDoubleFunction") public struct JavaToDoubleFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToIntBiFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToIntBiFunction.swift index e1fdcc0af..dc17fa2b2 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToIntBiFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToIntBiFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.ToIntBiFunction") public struct JavaToIntBiFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToIntFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToIntFunction.swift index 4d963c57e..3663f4993 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToIntFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToIntFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.ToIntFunction") public struct JavaToIntFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToLongBiFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToLongBiFunction.swift index fb42c3e4e..1d5fc7391 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToLongBiFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToLongBiFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.ToLongBiFunction") public struct JavaToLongBiFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToLongFunction.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToLongFunction.swift index e34b5bd0d..66805be70 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToLongFunction.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaToLongFunction.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface("java.util.function.ToLongFunction") public struct JavaToLongFunction { diff --git a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaUnaryOperator.swift b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaUnaryOperator.swift index a31be86ea..11dc00ee1 100644 --- a/Sources/JavaStdlib/JavaUtilFunction/generated/JavaUnaryOperator.swift +++ b/Sources/JavaStdlib/JavaUtilFunction/generated/JavaUnaryOperator.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaInterface( "java.util.function.UnaryOperator", extends: JavaFunction.self) diff --git a/Sources/JavaStdlib/JavaUtilJar/generated/Attributes.swift b/Sources/JavaStdlib/JavaUtilJar/generated/Attributes.swift index 710ce6407..4897ebe3f 100644 --- a/Sources/JavaStdlib/JavaUtilJar/generated/Attributes.swift +++ b/Sources/JavaStdlib/JavaUtilJar/generated/Attributes.swift @@ -1,7 +1,7 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava import JavaUtil -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.jar.Attributes") open class Attributes: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtilJar/generated/JarEntry.swift b/Sources/JavaStdlib/JavaUtilJar/generated/JarEntry.swift index 7140cb974..adfb6d172 100644 --- a/Sources/JavaStdlib/JavaUtilJar/generated/JarEntry.swift +++ b/Sources/JavaStdlib/JavaUtilJar/generated/JarEntry.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.jar.JarEntry") open class JarEntry: ZipEntry { diff --git a/Sources/JavaStdlib/JavaUtilJar/generated/JarFile.swift b/Sources/JavaStdlib/JavaUtilJar/generated/JarFile.swift index db84e6264..e630afd2e 100644 --- a/Sources/JavaStdlib/JavaUtilJar/generated/JarFile.swift +++ b/Sources/JavaStdlib/JavaUtilJar/generated/JarFile.swift @@ -1,7 +1,7 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava import JavaUtil -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.jar.JarFile") open class JarFile: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtilJar/generated/JarInputStream.swift b/Sources/JavaStdlib/JavaUtilJar/generated/JarInputStream.swift index 7d2ce4233..60f007fe9 100644 --- a/Sources/JavaStdlib/JavaUtilJar/generated/JarInputStream.swift +++ b/Sources/JavaStdlib/JavaUtilJar/generated/JarInputStream.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.jar.JarInputStream") open class JarInputStream: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtilJar/generated/JarOutputStream.swift b/Sources/JavaStdlib/JavaUtilJar/generated/JarOutputStream.swift index 79a7c14ec..bd27471f6 100644 --- a/Sources/JavaStdlib/JavaUtilJar/generated/JarOutputStream.swift +++ b/Sources/JavaStdlib/JavaUtilJar/generated/JarOutputStream.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.jar.JarOutputStream") open class JarOutputStream: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtilJar/generated/Manifest.swift b/Sources/JavaStdlib/JavaUtilJar/generated/Manifest.swift index b43b9e85d..2033f41af 100644 --- a/Sources/JavaStdlib/JavaUtilJar/generated/Manifest.swift +++ b/Sources/JavaStdlib/JavaUtilJar/generated/Manifest.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.jar.Manifest") open class Manifest: JavaObject { diff --git a/Sources/JavaStdlib/JavaUtilJar/generated/ZipEntry.swift b/Sources/JavaStdlib/JavaUtilJar/generated/ZipEntry.swift index e276ae2a0..3066d54c7 100644 --- a/Sources/JavaStdlib/JavaUtilJar/generated/ZipEntry.swift +++ b/Sources/JavaStdlib/JavaUtilJar/generated/ZipEntry.swift @@ -1,6 +1,6 @@ // Auto-generated by Java-to-Swift wrapper generator. import SwiftJava -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.zip.ZipEntry") open class ZipEntry: JavaObject { diff --git a/Sources/SwiftJava/AnyJavaObject.swift b/Sources/SwiftJava/AnyJavaObject.swift index 02b9d70b2..e514d3e6a 100644 --- a/Sources/SwiftJava/AnyJavaObject.swift +++ b/Sources/SwiftJava/AnyJavaObject.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import CJNI +import CSwiftJavaJNI /// Protocol that describes Swift types that are bridged to a Java class type. /// diff --git a/Sources/SwiftJava/JavaClass+Initialization.swift b/Sources/SwiftJava/JavaClass+Initialization.swift index 8101c37a8..89bfa62b8 100644 --- a/Sources/SwiftJava/JavaClass+Initialization.swift +++ b/Sources/SwiftJava/JavaClass+Initialization.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import CJNI +import CSwiftJavaJNI extension JavaClass { public typealias ObjectType = T diff --git a/Sources/SwiftJava/JavaEnvironment.swift b/Sources/SwiftJava/JavaEnvironment.swift index fd6a626b6..a533a9322 100644 --- a/Sources/SwiftJava/JavaEnvironment.swift +++ b/Sources/SwiftJava/JavaEnvironment.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import CJNI +import CSwiftJavaJNI #if canImport(Android) typealias JNINativeInterface_ = JNINativeInterface diff --git a/Sources/SwiftJava/JavaObject+Inheritance.swift b/Sources/SwiftJava/JavaObject+Inheritance.swift index aa80fa54f..f306b9c6f 100644 --- a/Sources/SwiftJava/JavaObject+Inheritance.swift +++ b/Sources/SwiftJava/JavaObject+Inheritance.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import CJNI +import CSwiftJavaJNI extension AnyJavaObject { /// Look up the other class type diff --git a/Sources/SwiftJava/JavaObject+MethodCalls.swift b/Sources/SwiftJava/JavaObject+MethodCalls.swift index 1a62d0f47..4880f7503 100644 --- a/Sources/SwiftJava/JavaObject+MethodCalls.swift +++ b/Sources/SwiftJava/JavaObject+MethodCalls.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import CJNI +import CSwiftJavaJNI import JavaTypes /// Produce the mangling for a method with the given argument and result types. diff --git a/Sources/SwiftJava/JavaObjectHolder.swift b/Sources/SwiftJava/JavaObjectHolder.swift index 50f60a824..319a09e83 100644 --- a/Sources/SwiftJava/JavaObjectHolder.swift +++ b/Sources/SwiftJava/JavaObjectHolder.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import CJNI +import CSwiftJavaJNI /// Stores a reference to a Java object, managing it as a global reference so /// that the Java virtual machine will not move or deallocate the object diff --git a/Sources/SwiftJava/JavaRuntime+Reexport.swift b/Sources/SwiftJava/JavaRuntime+Reexport.swift index f1a19e1c5..9b2c02a82 100644 --- a/Sources/SwiftJava/JavaRuntime+Reexport.swift +++ b/Sources/SwiftJava/JavaRuntime+Reexport.swift @@ -12,4 +12,4 @@ // //===----------------------------------------------------------------------===// -@_exported import CJNI +@_exported import CSwiftJavaJNI diff --git a/Sources/SwiftJava/JavaValue.swift b/Sources/SwiftJava/JavaValue.swift index a740eb503..46efdb3fc 100644 --- a/Sources/SwiftJava/JavaValue.swift +++ b/Sources/SwiftJava/JavaValue.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import CJNI +import CSwiftJavaJNI import JavaTypes /// Describes a type that can be bridged with Java. diff --git a/Sources/SwiftJava/Optional+JavaObject.swift b/Sources/SwiftJava/Optional+JavaObject.swift index f34ef2b31..46fd99709 100644 --- a/Sources/SwiftJava/Optional+JavaObject.swift +++ b/Sources/SwiftJava/Optional+JavaObject.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import CJNI +import CSwiftJavaJNI import JavaTypes extension Optional: JavaValue where Wrapped: AnyJavaObject { diff --git a/Sources/SwiftJava/generated/Appendable.swift b/Sources/SwiftJava/generated/Appendable.swift index 7ff4f97ec..b0c67ec50 100644 --- a/Sources/SwiftJava/generated/Appendable.swift +++ b/Sources/SwiftJava/generated/Appendable.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaInterface("java.lang.Appendable") public struct Appendable { diff --git a/Sources/SwiftJava/generated/CharSequence.swift b/Sources/SwiftJava/generated/CharSequence.swift index c6e3d5be3..eadc509eb 100644 --- a/Sources/SwiftJava/generated/CharSequence.swift +++ b/Sources/SwiftJava/generated/CharSequence.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaInterface("java.lang.CharSequence") public struct CharSequence { diff --git a/Sources/SwiftJava/generated/Exception.swift b/Sources/SwiftJava/generated/Exception.swift index 8361f1b02..e87684cbd 100644 --- a/Sources/SwiftJava/generated/Exception.swift +++ b/Sources/SwiftJava/generated/Exception.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Exception") open class Exception: Throwable { diff --git a/Sources/SwiftJava/generated/JavaArray.swift b/Sources/SwiftJava/generated/JavaArray.swift index 65d3f88f2..ae1822088 100644 --- a/Sources/SwiftJava/generated/JavaArray.swift +++ b/Sources/SwiftJava/generated/JavaArray.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.reflect.Array") open class JavaArray: JavaObject { diff --git a/Sources/SwiftJava/generated/JavaBoolean.swift b/Sources/SwiftJava/generated/JavaBoolean.swift index 995afc571..bdf21df90 100644 --- a/Sources/SwiftJava/generated/JavaBoolean.swift +++ b/Sources/SwiftJava/generated/JavaBoolean.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Boolean") open class JavaBoolean: JavaObject { diff --git a/Sources/SwiftJava/generated/JavaByte.swift b/Sources/SwiftJava/generated/JavaByte.swift index a9ed639b4..e3f67c783 100644 --- a/Sources/SwiftJava/generated/JavaByte.swift +++ b/Sources/SwiftJava/generated/JavaByte.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Byte") open class JavaByte: JavaNumber { diff --git a/Sources/SwiftJava/generated/JavaCharacter.swift b/Sources/SwiftJava/generated/JavaCharacter.swift index 308a7fce5..406b45ee2 100644 --- a/Sources/SwiftJava/generated/JavaCharacter.swift +++ b/Sources/SwiftJava/generated/JavaCharacter.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Character") open class JavaCharacter: JavaObject { diff --git a/Sources/SwiftJava/generated/JavaClass.swift b/Sources/SwiftJava/generated/JavaClass.swift index 1144482fa..0f1af1cd0 100644 --- a/Sources/SwiftJava/generated/JavaClass.swift +++ b/Sources/SwiftJava/generated/JavaClass.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Class") open class JavaClass: JavaObject { diff --git a/Sources/SwiftJava/generated/JavaClassLoader.swift b/Sources/SwiftJava/generated/JavaClassLoader.swift index b6fabea42..349cba8db 100644 --- a/Sources/SwiftJava/generated/JavaClassLoader.swift +++ b/Sources/SwiftJava/generated/JavaClassLoader.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.ClassLoader") open class JavaClassLoader: JavaObject { diff --git a/Sources/SwiftJava/generated/JavaDouble.swift b/Sources/SwiftJava/generated/JavaDouble.swift index 9d979fb34..8d54f8de6 100644 --- a/Sources/SwiftJava/generated/JavaDouble.swift +++ b/Sources/SwiftJava/generated/JavaDouble.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Double") open class JavaDouble: JavaNumber { diff --git a/Sources/SwiftJava/generated/JavaError.swift b/Sources/SwiftJava/generated/JavaError.swift index da3c4218e..4ba9d2ca1 100644 --- a/Sources/SwiftJava/generated/JavaError.swift +++ b/Sources/SwiftJava/generated/JavaError.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Error") open class JavaError: Throwable { diff --git a/Sources/SwiftJava/generated/JavaFloat.swift b/Sources/SwiftJava/generated/JavaFloat.swift index 9b11957ee..ac989531d 100644 --- a/Sources/SwiftJava/generated/JavaFloat.swift +++ b/Sources/SwiftJava/generated/JavaFloat.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Float") open class JavaFloat: JavaNumber { diff --git a/Sources/SwiftJava/generated/JavaInteger.swift b/Sources/SwiftJava/generated/JavaInteger.swift index 286bc5254..948000373 100644 --- a/Sources/SwiftJava/generated/JavaInteger.swift +++ b/Sources/SwiftJava/generated/JavaInteger.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Integer") open class JavaInteger: JavaNumber { diff --git a/Sources/SwiftJava/generated/JavaLong.swift b/Sources/SwiftJava/generated/JavaLong.swift index 9e4a183ee..a986e9ef8 100644 --- a/Sources/SwiftJava/generated/JavaLong.swift +++ b/Sources/SwiftJava/generated/JavaLong.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Long") open class JavaLong: JavaNumber { diff --git a/Sources/SwiftJava/generated/JavaNumber.swift b/Sources/SwiftJava/generated/JavaNumber.swift index 81d0bacd6..78f988f10 100644 --- a/Sources/SwiftJava/generated/JavaNumber.swift +++ b/Sources/SwiftJava/generated/JavaNumber.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Number") open class JavaNumber: JavaObject { diff --git a/Sources/SwiftJava/generated/JavaObject.swift b/Sources/SwiftJava/generated/JavaObject.swift index 37e48b2dd..7db8a965c 100644 --- a/Sources/SwiftJava/generated/JavaObject.swift +++ b/Sources/SwiftJava/generated/JavaObject.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Object") open class JavaObject { diff --git a/Sources/SwiftJava/generated/JavaOptional.swift b/Sources/SwiftJava/generated/JavaOptional.swift index 7615c7384..08cc764a1 100644 --- a/Sources/SwiftJava/generated/JavaOptional.swift +++ b/Sources/SwiftJava/generated/JavaOptional.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.Optional") open class JavaOptional: JavaObject { diff --git a/Sources/SwiftJava/generated/JavaOptionalDouble.swift b/Sources/SwiftJava/generated/JavaOptionalDouble.swift index 8f1514bef..0d0e2eaeb 100644 --- a/Sources/SwiftJava/generated/JavaOptionalDouble.swift +++ b/Sources/SwiftJava/generated/JavaOptionalDouble.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.OptionalDouble") open class JavaOptionalDouble: JavaObject { diff --git a/Sources/SwiftJava/generated/JavaOptionalInt.swift b/Sources/SwiftJava/generated/JavaOptionalInt.swift index 9543d0906..2270e66e4 100644 --- a/Sources/SwiftJava/generated/JavaOptionalInt.swift +++ b/Sources/SwiftJava/generated/JavaOptionalInt.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.OptionalInt") open class JavaOptionalInt: JavaObject { diff --git a/Sources/SwiftJava/generated/JavaOptionalLong.swift b/Sources/SwiftJava/generated/JavaOptionalLong.swift index ac9b44098..10c3fbd0f 100644 --- a/Sources/SwiftJava/generated/JavaOptionalLong.swift +++ b/Sources/SwiftJava/generated/JavaOptionalLong.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.util.OptionalLong") open class JavaOptionalLong: JavaObject { diff --git a/Sources/SwiftJava/generated/JavaShort.swift b/Sources/SwiftJava/generated/JavaShort.swift index 12ca1dd25..4f387b36c 100644 --- a/Sources/SwiftJava/generated/JavaShort.swift +++ b/Sources/SwiftJava/generated/JavaShort.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Short") open class JavaShort: JavaNumber { diff --git a/Sources/SwiftJava/generated/JavaString.swift b/Sources/SwiftJava/generated/JavaString.swift index 393c1acf5..f3372f657 100644 --- a/Sources/SwiftJava/generated/JavaString.swift +++ b/Sources/SwiftJava/generated/JavaString.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.String", implements: CharSequence.self) open class JavaString: JavaObject { diff --git a/Sources/SwiftJava/generated/JavaVoid.swift b/Sources/SwiftJava/generated/JavaVoid.swift index b2976196b..54decbbc6 100644 --- a/Sources/SwiftJava/generated/JavaVoid.swift +++ b/Sources/SwiftJava/generated/JavaVoid.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Void") open class JavaVoid: JavaObject { diff --git a/Sources/SwiftJava/generated/RuntimeException.swift b/Sources/SwiftJava/generated/RuntimeException.swift index aacd23625..14516ed11 100644 --- a/Sources/SwiftJava/generated/RuntimeException.swift +++ b/Sources/SwiftJava/generated/RuntimeException.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.RuntimeException") open class RuntimeException: Exception { diff --git a/Sources/SwiftJava/generated/Throwable.swift b/Sources/SwiftJava/generated/Throwable.swift index 823486d85..7df74b7e8 100644 --- a/Sources/SwiftJava/generated/Throwable.swift +++ b/Sources/SwiftJava/generated/Throwable.swift @@ -1,5 +1,5 @@ // Auto-generated by Java-to-Swift wrapper generator. -import CJNI +import CSwiftJavaJNI @JavaClass("java.lang.Throwable") open class Throwable: JavaObject { diff --git a/Sources/SwiftJavaTool/Java/JavaClassLoader.swift b/Sources/SwiftJavaTool/Java/JavaClassLoader.swift index bd1ec3f2a..d465a2062 100644 --- a/Sources/SwiftJavaTool/Java/JavaClassLoader.swift +++ b/Sources/SwiftJavaTool/Java/JavaClassLoader.swift @@ -14,7 +14,7 @@ import SwiftJavaToolLib import SwiftJavaShared -import CJNI +import CSwiftJavaJNI import SwiftJava @JavaClass("java.lang.ClassLoader") diff --git a/Sources/SwiftJavaToolLib/JavaTranslator.swift b/Sources/SwiftJavaToolLib/JavaTranslator.swift index 2f6f280c3..7cb8a7eeb 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator.swift @@ -94,7 +94,7 @@ extension JavaTranslator { /// Default set of modules that will always be imported. private static let defaultImportedSwiftModules: Set = [ "SwiftJava", - "CJNI", + "CSwiftJavaJNI", ] } From 5744562b36b8c893448c5983742465c8e1a534f4 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Thu, 28 Aug 2025 16:57:44 +0900 Subject: [PATCH 073/141] Avoid jextracting +SwiftJava files (#378) --- Sources/JExtractSwiftLib/Swift2Java.swift | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Sources/JExtractSwiftLib/Swift2Java.swift b/Sources/JExtractSwiftLib/Swift2Java.swift index e62719937..192415922 100644 --- a/Sources/JExtractSwiftLib/Swift2Java.swift +++ b/Sources/JExtractSwiftLib/Swift2Java.swift @@ -124,8 +124,15 @@ public struct SwiftToJava { } func canExtract(from file: URL) -> Bool { - file.lastPathComponent.hasSuffix(".swift") || - file.lastPathComponent.hasSuffix(".swiftinterface") + guard file.lastPathComponent.hasSuffix(".swift") || + file.lastPathComponent.hasSuffix(".swiftinterface") else { + return false + } + if file.lastPathComponent.hasSuffix("+SwiftJava.swift") { + return false + } + + return true } } From 73d7882fc69ab0ef9d2067c1544983a0a286367a Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 29 Aug 2025 08:30:40 +0200 Subject: [PATCH 074/141] [JExtract/JNI] Add support for protocols as parameter types (#376) --- .../MySwiftLibrary/ConcreteProtocolAB.swift | 28 ++ .../Sources/MySwiftLibrary/ProtocolA.swift | 24 ++ .../Sources/MySwiftLibrary/ProtocolB.swift | 29 +++ .../java/com/example/swift/EnumBenchmark.java | 0 .../com/example/swift/AlignmentEnumTest.java | 1 + .../java/com/example/swift/ProtocolTest.java | 75 ++++++ .../Convenience/SwiftSyntax+Extensions.swift | 6 +- .../FFM/CDeclLowering/CRepresentation.swift | 2 +- ...Swift2JavaGenerator+FunctionLowering.swift | 9 +- ...t2JavaGenerator+JavaBindingsPrinting.swift | 7 +- ...MSwift2JavaGenerator+JavaTranslation.swift | 5 +- Sources/JExtractSwiftLib/ImportedDecls.swift | 6 +- ...t2JavaGenerator+JavaBindingsPrinting.swift | 138 ++++++++-- ...ISwift2JavaGenerator+JavaTranslation.swift | 157 +++++++++-- ...wift2JavaGenerator+NativeTranslation.swift | 160 ++++++++++-- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 33 +++ .../JNI/JNISwift2JavaGenerator.swift | 2 + Sources/JExtractSwiftLib/JavaParameter.swift | 45 +++- .../Swift2JavaTranslator.swift | 8 +- .../JExtractSwiftLib/Swift2JavaVisitor.swift | 10 +- .../SwiftNominalTypeDeclaration.swift | 4 + ...ype.swift => SwiftType+GenericTypes.swift} | 32 +++ .../SwiftTypes/SwiftType.swift | 20 +- .../Documentation.docc/SupportedFeatures.md | 56 +++- .../swift/swiftkit/core/JNISwiftInstance.java | 33 +-- .../swift/swiftkit/core/SwiftInstance.java | 20 +- .../org/swift/swiftkit/AutoArenaTest.java | 25 +- .../swift/swiftkit/ffm/FFMSwiftInstance.java | 18 +- .../org/swift/swiftkit/ffm/AutoArenaTest.java | 1 + .../JNI/JNIClassTests.swift | 25 +- .../JExtractSwiftTests/JNI/JNIEnumTests.swift | 21 +- .../JNI/JNIModuleTests.swift | 1 + .../JNI/JNIOptionalTests.swift | 10 +- .../JNI/JNIProtocolTests.swift | 246 ++++++++++++++++++ .../JNI/JNIStructTests.swift | 14 +- Tests/JExtractSwiftTests/SendableTests.swift | 4 +- 36 files changed, 1121 insertions(+), 154 deletions(-) create mode 100644 Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ConcreteProtocolAB.swift create mode 100644 Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolA.swift create mode 100644 Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolB.swift rename Samples/{JExtractJNISampleApp => SwiftJavaExtractJNISampleApp}/src/jmh/java/com/example/swift/EnumBenchmark.java (100%) create mode 100644 Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolTest.java rename Sources/JExtractSwiftLib/SwiftTypes/{SwiftType+RepresentativeConcreteeType.swift => SwiftType+GenericTypes.swift} (72%) create mode 100644 Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ConcreteProtocolAB.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ConcreteProtocolAB.swift new file mode 100644 index 000000000..ed55d0398 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ConcreteProtocolAB.swift @@ -0,0 +1,28 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +public class ConcreteProtocolAB: ProtocolA, ProtocolB { + public let constantA: Int64 + public let constantB: Int64 + public var mutable: Int64 = 0 + + public func name() -> String { + return "ConcreteProtocolAB" + } + + public init(constantA: Int64, constantB: Int64) { + self.constantA = constantA + self.constantB = constantB + } +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolA.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolA.swift new file mode 100644 index 000000000..d5281b81e --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolA.swift @@ -0,0 +1,24 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +public protocol ProtocolA { + var constantA: Int64 { get } + var mutable: Int64 { get set } + + func name() -> String +} + +public func takeProtocol(_ proto1: any ProtocolA, _ proto2: some ProtocolA) -> Int64 { + return proto1.constantA + proto2.constantA +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolB.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolB.swift new file mode 100644 index 000000000..70d075c25 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolB.swift @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +public protocol ProtocolB { + var constantB: Int64 { get } +} + +public func takeCombinedProtocol(_ proto: some ProtocolA & ProtocolB) -> Int64 { + return proto.constantA + proto.constantB +} + +public func takeGenericProtocol(_ proto1: First, _ proto2: Second) -> Int64 { + return proto1.constantA + proto2.constantB +} + +public func takeCombinedGenericProtocol(_ proto: T) -> Int64 { + return proto.constantA + proto.constantB +} diff --git a/Samples/JExtractJNISampleApp/src/jmh/java/com/example/swift/EnumBenchmark.java b/Samples/SwiftJavaExtractJNISampleApp/src/jmh/java/com/example/swift/EnumBenchmark.java similarity index 100% rename from Samples/JExtractJNISampleApp/src/jmh/java/com/example/swift/EnumBenchmark.java rename to Samples/SwiftJavaExtractJNISampleApp/src/jmh/java/com/example/swift/EnumBenchmark.java diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AlignmentEnumTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AlignmentEnumTest.java index 6be85c75d..5bf059515 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AlignmentEnumTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AlignmentEnumTest.java @@ -16,6 +16,7 @@ import org.junit.jupiter.api.Test; import org.swift.swiftkit.core.ConfinedSwiftMemorySession; +import org.swift.swiftkit.core.JNISwiftInstance; import org.swift.swiftkit.core.SwiftArena; import java.util.Optional; diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolTest.java new file mode 100644 index 000000000..c095a42a4 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolTest.java @@ -0,0 +1,75 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.SwiftArena; + +import static org.junit.jupiter.api.Assertions.*; + +public class ProtocolTest { + @Test + void takeProtocol() { + try (var arena = SwiftArena.ofConfined()) { + ConcreteProtocolAB proto1 = ConcreteProtocolAB.init(10, 5, arena); + ConcreteProtocolAB proto2 = ConcreteProtocolAB.init(20, 1, arena); + assertEquals(30, MySwiftLibrary.takeProtocol(proto1, proto2)); + } + } + + @Test + void takeCombinedProtocol() { + try (var arena = SwiftArena.ofConfined()) { + ConcreteProtocolAB proto1 = ConcreteProtocolAB.init(10, 5, arena); + assertEquals(15, MySwiftLibrary.takeCombinedProtocol(proto1)); + } + } + + @Test + void takeGenericProtocol() { + try (var arena = SwiftArena.ofConfined()) { + ConcreteProtocolAB proto1 = ConcreteProtocolAB.init(10, 5, arena); + ConcreteProtocolAB proto2 = ConcreteProtocolAB.init(20, 1, arena); + assertEquals(11, MySwiftLibrary.takeGenericProtocol(proto1, proto2)); + } + } + + @Test + void takeCombinedGenericProtocol() { + try (var arena = SwiftArena.ofConfined()) { + ConcreteProtocolAB proto1 = ConcreteProtocolAB.init(10, 5, arena); + assertEquals(15, MySwiftLibrary.takeCombinedGenericProtocol(proto1)); + } + } + + @Test + void protocolVariables() { + try (var arena = SwiftArena.ofConfined()) { + ProtocolA proto1 = ConcreteProtocolAB.init(10, 5, arena); + assertEquals(10, proto1.getConstantA()); + assertEquals(0, proto1.getMutable()); + proto1.setMutable(3); + assertEquals(3, proto1.getMutable()); + } + } + + @Test + void protocolMethod() { + try (var arena = SwiftArena.ofConfined()) { + ProtocolA proto1 = ConcreteProtocolAB.init(10, 5, arena); + assertEquals("ConcreteProtocolAB", proto1.name()); + } + } +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift index 3719d99d6..8a58f42ab 100644 --- a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift @@ -95,7 +95,11 @@ extension DeclModifierSyntax { } extension WithModifiersSyntax { - var isPublic: Bool { + func isPublic(in type: NominalTypeDeclSyntaxNode?) -> Bool { + if let type, case .protocolDecl(let protocolDecl) = Syntax(type).as(SyntaxEnum.self) { + return protocolDecl.isPublic(in: nil) + } + return self.modifiers.contains { modifier in modifier.isPublic } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift index d2a71defd..193b545f9 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift @@ -68,7 +68,7 @@ extension CType { case .optional(let wrapped) where wrapped.isPointer: try self.init(cdeclType: wrapped) - case .genericParameter, .metatype, .optional, .tuple, .opaque, .existential: + case .genericParameter, .metatype, .optional, .tuple, .opaque, .existential, .composite: throw CDeclToCLoweringError.invalidCDeclType(cdeclType) } } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index 0a61708e6..5dfa5b71e 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -344,6 +344,9 @@ struct CdeclLowering { case .optional(let wrapped): return try lowerOptionalParameter(wrapped, convention: convention, parameterName: parameterName, genericParameters: genericParameters, genericRequirements: genericRequirements) + + case .composite: + throw LoweringError.unhandledType(type) } } @@ -412,7 +415,7 @@ struct CdeclLowering { } throw LoweringError.unhandledType(.optional(wrappedType)) - case .function, .metatype, .optional: + case .function, .metatype, .optional, .composite: throw LoweringError.unhandledType(.optional(wrappedType)) } } @@ -513,7 +516,7 @@ struct CdeclLowering { // Custom types are not supported yet. throw LoweringError.unhandledType(type) - case .genericParameter, .function, .metatype, .optional, .tuple, .existential, .opaque: + case .genericParameter, .function, .metatype, .optional, .tuple, .existential, .opaque, .composite: // TODO: Implement throw LoweringError.unhandledType(type) } @@ -667,7 +670,7 @@ struct CdeclLowering { conversion: .tupleExplode(conversions, name: outParameterName) ) - case .genericParameter, .function, .optional, .existential, .opaque: + case .genericParameter, .function, .optional, .existential, .opaque, .composite: throw LoweringError.unhandledType(type) } } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index 84a902755..188f5e8bb 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -396,9 +396,12 @@ extension FFMSwift2JavaGenerator { // Indirect return receivers. for outParameter in translatedSignature.result.outParameters { - let memoryLayout = renderMemoryLayoutValue(for: outParameter.type) + guard case .concrete(let type) = outParameter.type else { + continue + } + let memoryLayout = renderMemoryLayoutValue(for: type) - let arena = if let className = outParameter.type.className, + let arena = if let className = type.className, analysis.importedTypes[className] != nil { // Use passed-in 'SwiftArena' for 'SwiftValue'. "swiftArena$" diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 00fa60e74..438393cb3 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -472,6 +472,9 @@ extension FFMSwift2JavaGenerator { genericParameters: genericParameters, genericRequirements: genericRequirements ) + + case .composite: + throw JavaTranslationError.unhandledType(swiftType) } } @@ -691,7 +694,7 @@ extension FFMSwift2JavaGenerator { // TODO: Implement. throw JavaTranslationError.unhandledType(swiftType) - case .genericParameter, .optional, .function, .existential, .opaque: + case .genericParameter, .optional, .function, .existential, .opaque, .composite: throw JavaTranslationError.unhandledType(swiftType) } diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 5655af006..7a071d9a5 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -34,9 +34,13 @@ package class ImportedNominalType: ImportedDecl { package var methods: [ImportedFunc] = [] package var variables: [ImportedFunc] = [] package var cases: [ImportedEnumCase] = [] + var inheritedTypes: [SwiftType] - init(swiftNominal: SwiftNominalTypeDeclaration) { + init(swiftNominal: SwiftNominalTypeDeclaration, lookupContext: SwiftTypeLookupContext) throws { self.swiftNominal = swiftNominal + self.inheritedTypes = swiftNominal.inheritanceTypes?.compactMap { + try? SwiftType($0.type, lookupContext: lookupContext) + } ?? [] } var swiftType: SwiftType { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 9351252e5..258b537e0 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -22,6 +22,7 @@ extension JNISwift2JavaGenerator { "org.swift.swiftkit.core.*", "org.swift.swiftkit.core.util.*", "java.util.*", + "java.util.concurrent.atomic.AtomicBoolean", // NonNull, Unsigned and friends "org.swift.swiftkit.core.annotations.*", @@ -101,6 +102,35 @@ extension JNISwift2JavaGenerator { printPackage(&printer) printImports(&printer) + switch decl.swiftNominal.kind { + case .actor, .class, .enum, .struct: + printConcreteType(&printer, decl) + case .protocol: + printProtocol(&printer, decl) + } + } + + private func printProtocol(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + let extends = ["JNISwiftInstance"] + printer.printBraceBlock("public interface \(decl.swiftNominal.name) extends \(extends.joined(separator: ", "))") { printer in + for initializer in decl.initializers { + printFunctionDowncallMethods(&printer, initializer, signaturesOnly: true) + printer.println() + } + + for method in decl.methods { + printFunctionDowncallMethods(&printer, method, signaturesOnly: true) + printer.println() + } + + for variable in decl.variables { + printFunctionDowncallMethods(&printer, variable, signaturesOnly: true) + printer.println() + } + } + } + + private func printConcreteType(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { printNominal(&printer, decl) { printer in printer.print( """ @@ -117,8 +147,18 @@ extension JNISwift2JavaGenerator { printer.print( """ + /** + * The designated constructor of any imported Swift types. + * + * @param selfPointer a pointer to the memory containing the value + * @param swiftArena the arena this object belongs to. When the arena goes out of scope, this value is destroyed. + */ private \(decl.swiftNominal.name)(long selfPointer, SwiftArena swiftArena) { - super(selfPointer, swiftArena); + SwiftObjects.requireNonZero(selfPointer, "selfPointer"); + this.selfPointer = selfPointer; + + // Only register once we have fully initialized the object since this will need the object pointer. + swiftArena.register(this); } /** @@ -136,6 +176,25 @@ extension JNISwift2JavaGenerator { """ ) + printer.print( + """ + /** Pointer to the "self". */ + private final long selfPointer; + + /** Used to track additional state of the underlying object, e.g. if it was explicitly destroyed. */ + private final AtomicBoolean $state$destroyed = new AtomicBoolean(false); + + public long $memoryAddress() { + return this.selfPointer; + } + + @Override + public AtomicBoolean $statusDestroyedFlag() { + return $state$destroyed; + } + """ + ) + printer.println() if decl.swiftNominal.kind == .enum { @@ -158,6 +217,8 @@ extension JNISwift2JavaGenerator { printer.println() } + printTypeMetadataAddressFunction(&printer, decl) + printer.println() printDestroyFunction(&printer, decl) } } @@ -194,7 +255,13 @@ extension JNISwift2JavaGenerator { if decl.swiftNominal.isSendable { printer.print("@ThreadSafe // Sendable") } - printer.printBraceBlock("public final class \(decl.swiftNominal.name) extends JNISwiftInstance") { printer in + var implements = ["JNISwiftInstance"] + implements += decl.inheritedTypes + .compactMap(\.asNominalTypeDeclaration) + .filter { $0.kind == .protocol } + .map(\.name) + let implementsClause = implements.joined(separator: ", ") + printer.printBraceBlock("public final class \(decl.swiftNominal.name) implements \(implementsClause)") { printer in body(&printer) } } @@ -279,14 +346,15 @@ extension JNISwift2JavaGenerator { printer.print("record $NativeParameters(\(nativeParameters.joined(separator: ", "))) {}") } - self.printJavaBindingWrapperMethod(&printer, translatedCase.getAsCaseFunction) + self.printJavaBindingWrapperMethod(&printer, translatedCase.getAsCaseFunction, signaturesOnly: false) printer.println() } } private func printFunctionDowncallMethods( _ printer: inout CodePrinter, - _ decl: ImportedFunc + _ decl: ImportedFunc, + signaturesOnly: Bool = false ) { guard translatedDecl(for: decl) != nil else { // Failed to translate. Skip. @@ -297,7 +365,7 @@ extension JNISwift2JavaGenerator { printJavaBindingWrapperHelperClass(&printer, decl) - printJavaBindingWrapperMethod(&printer, decl) + printJavaBindingWrapperMethod(&printer, decl, signaturesOnly: signaturesOnly) } /// Print the helper type container for a user-facing Java API. @@ -340,17 +408,18 @@ extension JNISwift2JavaGenerator { ) } - private func printJavaBindingWrapperMethod(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + private func printJavaBindingWrapperMethod(_ printer: inout CodePrinter, _ decl: ImportedFunc, signaturesOnly: Bool) { guard let translatedDecl = translatedDecl(for: decl) else { fatalError("Decl was not translated, \(decl)") } - printJavaBindingWrapperMethod(&printer, translatedDecl, importedFunc: decl) + printJavaBindingWrapperMethod(&printer, translatedDecl, importedFunc: decl, signaturesOnly: signaturesOnly) } private func printJavaBindingWrapperMethod( _ printer: inout CodePrinter, _ translatedDecl: TranslatedFunctionDecl, - importedFunc: ImportedFunc? = nil + importedFunc: ImportedFunc? = nil, + signaturesOnly: Bool ) { var modifiers = ["public"] if translatedDecl.isStatic { @@ -362,6 +431,19 @@ extension JNISwift2JavaGenerator { var parameters = translatedDecl.translatedFunctionSignature.parameters.map { $0.parameter.renderParameter() } let throwsClause = translatedDecl.isThrowing ? " throws Exception" : "" + let generics = translatedDecl.translatedFunctionSignature.parameters.reduce(into: [(String, [JavaType])]()) { generics, parameter in + guard case .generic(let name, let extends) = parameter.parameter.type else { + return + } + generics.append((name, extends)) + } + .map { "\($0) extends \($1.compactMap(\.className).joined(separator: " & "))" } + .joined(separator: ", ") + + if !generics.isEmpty { + modifiers.append("<" + generics + ">") + } + var annotationsStr = translatedSignature.annotations.map({ $0.render() }).joined(separator: "\n") if !annotationsStr.isEmpty { annotationsStr += "\n" } @@ -372,9 +454,7 @@ extension JNISwift2JavaGenerator { if let importedFunc { printDeclDocumentation(&printer, importedFunc) } - printer.printBraceBlock( - "\(annotationsStr)\(modifiers.joined(separator: " ")) \(resultType) \(translatedDecl.name)(\(parametersStr))\(throwsClause)" - ) { printer in + printer.printBraceBlock("\(annotationsStr)\(modifiers.joined(separator: " ")) \(resultType) \(translatedDecl.name)(\(parametersStr))\(throwsClause)") { printer in let globalArenaName = "SwiftMemoryManagement.GLOBAL_SWIFT_JAVA_ARENA" let arguments = translatedDecl.translatedFunctionSignature.parameters.map(\.parameter.name) + [globalArenaName] let call = "\(translatedDecl.name)(\(arguments.joined(separator: ", ")))" @@ -393,13 +473,17 @@ extension JNISwift2JavaGenerator { if let importedFunc { printDeclDocumentation(&printer, importedFunc) } - printer.printBraceBlock( - "\(annotationsStr)\(modifiers.joined(separator: " ")) \(resultType) \(translatedDecl.name)(\(parameters.joined(separator: ", ")))\(throwsClause)" - ) { printer in - printDowncall(&printer, translatedDecl) + let signature = "\(annotationsStr)\(modifiers.joined(separator: " ")) \(resultType) \(translatedDecl.name)(\(parameters.joined(separator: ", ")))\(throwsClause)" + if signaturesOnly { + printer.print("\(signature);") + } else { + printer.printBraceBlock(signature) { printer in + printDowncall(&printer, translatedDecl) + } + + printNativeFunction(&printer, translatedDecl) } - printNativeFunction(&printer, translatedDecl) } private func printNativeFunction(_ printer: inout CodePrinter, _ translatedDecl: TranslatedFunctionDecl) { @@ -470,13 +554,33 @@ extension JNISwift2JavaGenerator { ) } + private func printTypeMetadataAddressFunction(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + printer.print("private static native long $typeMetadataAddressDowncall();") + + let funcName = "$typeMetadataAddress" + printer.print("@Override") + printer.printBraceBlock("public long $typeMetadataAddress()") { printer in + printer.print( + """ + long self$ = this.$memoryAddress(); + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall("\(type.swiftNominal.name).\(funcName)", + "this", this, + "self", self$); + } + return \(type.swiftNominal.name).$typeMetadataAddressDowncall(); + """ + ) + } + } + /// Prints the destroy function for a `JNISwiftInstance` private func printDestroyFunction(_ printer: inout CodePrinter, _ type: ImportedNominalType) { printer.print("private static native void $destroy(long selfPointer);") let funcName = "$createDestroyFunction" printer.print("@Override") - printer.printBraceBlock("protected Runnable \(funcName)()") { printer in + printer.printBraceBlock("public Runnable \(funcName)()") { printer in printer.print( """ long self$ = this.$memoryAddress(); diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 073883a2c..a0178d3c7 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -29,7 +29,8 @@ extension JNISwift2JavaGenerator { config: config, swiftModuleName: swiftModuleName, javaPackage: self.javaPackage, - javaClassLookupTable: self.javaClassLookupTable + javaClassLookupTable: self.javaClassLookupTable, + knownTypes: SwiftKnownTypes(symbolTable: lookupContext.symbolTable) ) translated = try translation.translate(decl) } catch { @@ -54,7 +55,8 @@ extension JNISwift2JavaGenerator { config: config, swiftModuleName: swiftModuleName, javaPackage: self.javaPackage, - javaClassLookupTable: self.javaClassLookupTable + javaClassLookupTable: self.javaClassLookupTable, + knownTypes: SwiftKnownTypes(symbolTable: lookupContext.symbolTable) ) translated = try translation.translate(enumCase: decl) } catch { @@ -71,12 +73,14 @@ extension JNISwift2JavaGenerator { let swiftModuleName: String let javaPackage: String let javaClassLookupTable: JavaClassLookupTable + var knownTypes: SwiftKnownTypes func translate(enumCase: ImportedEnumCase) throws -> TranslatedEnumCase { let nativeTranslation = NativeJavaTranslation( config: self.config, javaPackage: self.javaPackage, - javaClassLookupTable: self.javaClassLookupTable + javaClassLookupTable: self.javaClassLookupTable, + knownTypes: self.knownTypes ) let methodName = "" // TODO: Used for closures, replace with better name? @@ -85,7 +89,9 @@ extension JNISwift2JavaGenerator { let translatedValues = try self.translateParameters( enumCase.parameters.map { ($0.name, $0.type) }, methodName: methodName, - parentName: parentName + parentName: parentName, + genericParameters: [], + genericRequirements: [] ) let conversions = try enumCase.parameters.enumerated().map { idx, parameter in @@ -160,7 +166,8 @@ extension JNISwift2JavaGenerator { let nativeTranslation = NativeJavaTranslation( config: self.config, javaPackage: self.javaPackage, - javaClassLookupTable: self.javaClassLookupTable + javaClassLookupTable: self.javaClassLookupTable, + knownTypes: self.knownTypes ) // Types with no parent will be outputted inside a "module" class. @@ -228,7 +235,15 @@ extension JNISwift2JavaGenerator { for (i, param) in swiftType.parameters.enumerated() { let paramName = param.parameterName ?? "_\(i)" translatedParams.append( - try translateParameter(swiftType: param.type, parameterName: paramName, methodName: name, parentName: parentName) + try translateParameter( + swiftType: param.type, + parameterName: paramName, + methodName: name, + parentName: parentName, + genericParameters: [], + genericRequirements: [], + parameterPosition: nil + ) ) } @@ -250,11 +265,19 @@ extension JNISwift2JavaGenerator { let parameters = try translateParameters( functionSignature.parameters.map { ($0.parameterName, $0.type )}, methodName: methodName, - parentName: parentName + parentName: parentName, + genericParameters: functionSignature.genericParameters, + genericRequirements: functionSignature.genericRequirements ) // 'self' - let selfParameter = try self.translateSelfParameter(functionSignature.selfParameter, methodName: methodName, parentName: parentName) + let selfParameter = try self.translateSelfParameter( + functionSignature.selfParameter, + methodName: methodName, + parentName: parentName, + genericParameters: functionSignature.genericParameters, + genericRequirements: functionSignature.genericRequirements + ) let resultType = try translate(swiftResult: functionSignature.result) @@ -268,22 +291,43 @@ extension JNISwift2JavaGenerator { func translateParameters( _ parameters: [(name: String?, type: SwiftType)], methodName: String, - parentName: String + parentName: String, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) throws -> [TranslatedParameter] { - try parameters.enumerated().map { idx, param in + try parameters.enumerated().map { + idx, + param in let parameterName = param.name ?? "arg\(idx)" - return try translateParameter(swiftType: param.type, parameterName: parameterName, methodName: methodName, parentName: parentName) + return try translateParameter( + swiftType: param.type, + parameterName: parameterName, + methodName: methodName, + parentName: parentName, + genericParameters: genericParameters, + genericRequirements: genericRequirements, + parameterPosition: idx + ) } } - func translateSelfParameter(_ selfParameter: SwiftSelfParameter?, methodName: String, parentName: String) throws -> TranslatedParameter? { + func translateSelfParameter( + _ selfParameter: SwiftSelfParameter?, + methodName: String, + parentName: String, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] + ) throws -> TranslatedParameter? { // 'self' if case .instance(let swiftSelf) = selfParameter { return try self.translateParameter( swiftType: swiftSelf.type, parameterName: swiftSelf.parameterName ?? "self", methodName: methodName, - parentName: parentName + parentName: parentName, + genericParameters: genericParameters, + genericRequirements: genericRequirements, + parameterPosition: nil ) } else { return nil @@ -294,7 +338,10 @@ extension JNISwift2JavaGenerator { swiftType: SwiftType, parameterName: String, methodName: String, - parentName: String + parentName: String, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement], + parameterPosition: Int? ) throws -> TranslatedParameter { // If the result type should cause any annotations on the method, include them here. @@ -354,7 +401,7 @@ extension JNISwift2JavaGenerator { return TranslatedParameter( parameter: JavaParameter( name: parameterName, - type: .class(package: nil, name: nominalTypeName), + type: .concrete(.class(package: nil, name: nominalTypeName)), annotations: parameterAnnotations ), conversion: .valueMemoryAddress(.placeholder) @@ -382,7 +429,29 @@ extension JNISwift2JavaGenerator { parameterName: parameterName ) - case .metatype, .tuple, .existential, .opaque, .genericParameter: + case .opaque(let proto), .existential(let proto): + guard let parameterPosition else { + fatalError("Cannot extract opaque or existential type that is not a parameter: \(proto)") + } + + return try translateProtocolParameter( + protocolType: proto, + parameterName: parameterName, + javaGenericName: "$T\(parameterPosition)" + ) + + case .genericParameter(let generic): + if let concreteTy = swiftType.typeIn(genericParameters: genericParameters, genericRequirements: genericRequirements) { + return try translateProtocolParameter( + protocolType: concreteTy, + parameterName: parameterName, + javaGenericName: generic.name + ) + } + + throw JavaTranslationError.unsupportedSwiftType(swiftType) + + case .metatype, .tuple, .composite: throw JavaTranslationError.unsupportedSwiftType(swiftType) } } @@ -401,6 +470,50 @@ extension JNISwift2JavaGenerator { } } + func translateProtocolParameter( + protocolType: SwiftType, + parameterName: String, + javaGenericName: String + ) throws -> TranslatedParameter { + switch protocolType { + case .nominal: + return try translateProtocolParameter(protocolTypes: [protocolType], parameterName: parameterName, javaGenericName: javaGenericName) + + case .composite(let types): + return try translateProtocolParameter(protocolTypes: types, parameterName: parameterName, javaGenericName: javaGenericName) + + default: + throw JavaTranslationError.unsupportedSwiftType(protocolType) + } + } + + private func translateProtocolParameter( + protocolTypes: [SwiftType], + parameterName: String, + javaGenericName: String + ) throws -> TranslatedParameter { + let javaProtocolTypes = try protocolTypes.map { + switch $0 { + case .nominal(let nominalType): + let nominalTypeName = nominalType.nominalTypeDecl.name + return JavaType.class(package: nil, name: nominalTypeName) + + default: + throw JavaTranslationError.unsupportedSwiftType($0) + } + } + + // We assume this is a JExtract class. + return TranslatedParameter( + parameter: JavaParameter( + name: parameterName, + type: .generic(name: javaGenericName, extends: javaProtocolTypes), + annotations: [] + ), + conversion: .commaSeparated([.valueMemoryAddress(.placeholder), .typeMetadataAddress(.placeholder)]) + ) + } + func translateOptionalParameter( wrappedType swiftType: SwiftType, parameterName: String @@ -519,7 +632,7 @@ extension JNISwift2JavaGenerator { case .optional(let wrapped): return try translateOptionalResult(wrappedType: wrapped, resultName: resultName) - case .metatype, .tuple, .function, .existential, .opaque, .genericParameter: + case .metatype, .tuple, .function, .existential, .opaque, .genericParameter, .composite: throw JavaTranslationError.unsupportedSwiftType(swiftType) } } @@ -743,6 +856,9 @@ extension JNISwift2JavaGenerator { /// `value.$memoryAddress()` indirect case valueMemoryAddress(JavaNativeConversionStep) + /// `value.$typeMetadataAddress()` + indirect case typeMetadataAddress(JavaNativeConversionStep) + /// Call `new \(Type)(\(placeholder), swiftArena$)` indirect case constructSwiftValue(JavaNativeConversionStep, JavaType) @@ -816,6 +932,10 @@ extension JNISwift2JavaGenerator { case .valueMemoryAddress: return "\(placeholder).$memoryAddress()" + case .typeMetadataAddress(let inner): + let inner = inner.render(&printer, placeholder) + return "\(inner).$typeMetadataAddress()" + case .constructSwiftValue(let inner, let javaType): let inner = inner.render(&printer, placeholder) return "new \(javaType.className!)(\(inner), swiftArena$)" @@ -922,6 +1042,9 @@ extension JNISwift2JavaGenerator { case .valueMemoryAddress(let inner): return inner.requiresSwiftArena + case .typeMetadataAddress(let inner): + return inner.requiresSwiftArena + case .commaSeparated(let list): return list.contains(where: { $0.requiresSwiftArena }) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 03273e684..bdbfe2f18 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -21,6 +21,7 @@ extension JNISwift2JavaGenerator { let config: Configuration let javaPackage: String let javaClassLookupTable: JavaClassLookupTable + var knownTypes: SwiftKnownTypes /// Translates a Swift function into the native JNI method signature. func translate( @@ -33,22 +34,26 @@ extension JNISwift2JavaGenerator { translatedParameter, swiftParameter in let parameterName = translatedParameter.parameter.name - return try translate( - swiftParameter: swiftParameter, + return try translateParameter( + type: swiftParameter.type, parameterName: parameterName, methodName: methodName, - parentName: parentName + parentName: parentName, + genericParameters: functionSignature.genericParameters, + genericRequirements: functionSignature.genericRequirements ) } // Lower the self parameter. let nativeSelf: NativeParameter? = switch functionSignature.selfParameter { case .instance(let selfParameter): - try translate( - swiftParameter: selfParameter, + try translateParameter( + type: selfParameter.type, parameterName: selfParameter.parameterName ?? "self", methodName: methodName, - parentName: parentName + parentName: parentName, + genericParameters: functionSignature.genericParameters, + genericRequirements: functionSignature.genericRequirements ) case nil, .initializer(_), .staticMethod(_): nil @@ -65,26 +70,32 @@ extension JNISwift2JavaGenerator { _ parameters: [SwiftParameter], translatedParameters: [TranslatedParameter], methodName: String, - parentName: String + parentName: String, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) throws -> [NativeParameter] { try zip(translatedParameters, parameters).map { translatedParameter, swiftParameter in let parameterName = translatedParameter.parameter.name - return try translate( - swiftParameter: swiftParameter, + return try translateParameter( + type: swiftParameter.type, parameterName: parameterName, methodName: methodName, - parentName: parentName + parentName: parentName, + genericParameters: genericParameters, + genericRequirements: genericRequirements ) } } - func translate( - swiftParameter: SwiftParameter, + func translateParameter( + type: SwiftType, parameterName: String, methodName: String, - parentName: String + parentName: String, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) throws -> NativeParameter { - switch swiftParameter.type { + switch type { case .nominal(let nominalType): let nominalTypeName = nominalType.nominalTypeDecl.name @@ -92,7 +103,7 @@ extension JNISwift2JavaGenerator { switch knownType { case .optional: guard let genericArgs = nominalType.genericArguments, genericArgs.count == 1 else { - throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) + throw JavaTranslationError.unsupportedSwiftType(type) } return try translateOptionalParameter( wrappedType: genericArgs[0], @@ -102,14 +113,14 @@ extension JNISwift2JavaGenerator { default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), javaType.implementsJavaValue else { - throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) + throw JavaTranslationError.unsupportedSwiftType(type) } return NativeParameter( parameters: [ JavaParameter(name: parameterName, type: javaType) ], - conversion: .initFromJNI(.placeholder, swiftType: swiftParameter.type) + conversion: .initFromJNI(.placeholder, swiftType: type) ) } @@ -117,7 +128,7 @@ extension JNISwift2JavaGenerator { if nominalType.isJavaKitWrapper { guard let javaType = nominalTypeName.parseJavaClassFromJavaKitName(in: self.javaClassLookupTable) else { - throw JavaTranslationError.wrappedJavaClassTranslationNotProvided(swiftParameter.type) + throw JavaTranslationError.wrappedJavaClassTranslationNotProvided(type) } return NativeParameter( @@ -140,7 +151,7 @@ extension JNISwift2JavaGenerator { parameters: [ JavaParameter(name: parameterName, type: .long) ], - conversion: .pointee(.extractSwiftValue(.placeholder, swiftType: swiftParameter.type)) + conversion: .pointee(.extractSwiftValue(.placeholder, swiftType: type)) ) case .tuple([]): @@ -180,11 +191,68 @@ extension JNISwift2JavaGenerator { parameterName: parameterName ) - case .metatype, .tuple, .existential, .opaque, .genericParameter: - throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) + case .opaque(let proto), .existential(let proto): + return try translateProtocolParameter( + protocolType: proto, + parameterName: parameterName + ) + + case .genericParameter: + if let concreteTy = type.typeIn(genericParameters: genericParameters, genericRequirements: genericRequirements) { + return try translateProtocolParameter( + protocolType: concreteTy, + parameterName: parameterName + ) + } + + throw JavaTranslationError.unsupportedSwiftType(type) + + case .metatype, .tuple, .composite: + throw JavaTranslationError.unsupportedSwiftType(type) + } + } + + func translateProtocolParameter( + protocolType: SwiftType, + parameterName: String + ) throws -> NativeParameter { + switch protocolType { + case .nominal(let nominalType): + let protocolName = nominalType.nominalTypeDecl.qualifiedName + return try translateProtocolParameter(protocolNames: [protocolName], parameterName: parameterName) + + case .composite(let types): + let protocolNames = try types.map { + guard let nominalTypeName = $0.asNominalType?.nominalTypeDecl.qualifiedName else { + throw JavaTranslationError.unsupportedSwiftType($0) + } + return nominalTypeName + } + + return try translateProtocolParameter(protocolNames: protocolNames, parameterName: parameterName) + + default: + throw JavaTranslationError.unsupportedSwiftType(protocolType) } } + private func translateProtocolParameter( + protocolNames: [String], + parameterName: String + ) throws -> NativeParameter { + return NativeParameter( + parameters: [ + JavaParameter(name: parameterName, type: .long), + JavaParameter(name: "\(parameterName)_typeMetadataAddress", type: .long) + ], + conversion: .extractSwiftProtocolValue( + .placeholder, + typeMetadataVariableName: .combinedName(component: "typeMetadataAddress"), + protocolNames: protocolNames + ) + ) + } + func translateOptionalParameter( wrappedType swiftType: SwiftType, parameterName: String @@ -351,7 +419,7 @@ extension JNISwift2JavaGenerator { outParameters: [] ) - case .function, .metatype, .optional, .tuple, .existential, .opaque, .genericParameter: + case .function, .metatype, .optional, .tuple, .existential, .opaque, .genericParameter, .composite: throw JavaTranslationError.unsupportedSwiftType(type) } } @@ -380,7 +448,7 @@ extension JNISwift2JavaGenerator { // Custom types are not supported yet. throw JavaTranslationError.unsupportedSwiftType(type) - case .function, .metatype, .optional, .tuple, .existential, .opaque, .genericParameter: + case .function, .metatype, .optional, .tuple, .existential, .opaque, .genericParameter, .composite: throw JavaTranslationError.unsupportedSwiftType(type) } } @@ -432,7 +500,7 @@ extension JNISwift2JavaGenerator { case .optional(let wrapped): return try translateOptionalResult(wrappedType: wrapped, resultName: resultName) - case .metatype, .tuple, .function, .existential, .opaque, .genericParameter: + case .metatype, .tuple, .function, .existential, .opaque, .genericParameter, .composite: throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) } } @@ -480,6 +548,12 @@ extension JNISwift2JavaGenerator { /// `SwiftType(from: value, in: environment!)` indirect case initFromJNI(NativeSwiftConversionStep, swiftType: SwiftType) + indirect case extractSwiftProtocolValue( + NativeSwiftConversionStep, + typeMetadataVariableName: NativeSwiftConversionStep, + protocolNames: [String] + ) + /// Extracts a swift type at a pointer given by a long. indirect case extractSwiftValue( NativeSwiftConversionStep, @@ -540,6 +614,35 @@ extension JNISwift2JavaGenerator { let inner = inner.render(&printer, placeholder) return "\(swiftType)(fromJNI: \(inner), in: environment!)" + case .extractSwiftProtocolValue(let inner, let typeMetadataVariableName, let protocolNames): + let inner = inner.render(&printer, placeholder) + let typeMetadataVariableName = typeMetadataVariableName.render(&printer, placeholder) + let existentialName = "\(inner)Existential$" + + let compositeProtocolName = "(\(protocolNames.joined(separator: " & ")))" + + // TODO: Remove the _openExistential when we decide to only support language mode v6+ + printer.print( + """ + guard let \(inner)TypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: \(typeMetadataVariableName), in: environment!))) else { + fatalError("\(typeMetadataVariableName) memory address was null") + } + let \(inner)DynamicType$: Any.Type = unsafeBitCast(\(inner)TypeMetadataPointer$, to: Any.Type.self) + guard let \(inner)RawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: \(inner), in: environment!))) else { + fatalError("\(inner) memory address was null") + } + #if hasFeature(ImplicitOpenExistentials) + let \(existentialName) = \(inner)RawPointer$.load(as: \(inner)DynamicType$) as! any \(compositeProtocolName) + #else + func \(inner)DoLoad(_ ty: Ty.Type) -> any \(compositeProtocolName) { + \(inner)RawPointer$.load(as: ty) as! any \(compositeProtocolName) + } + let \(existentialName) = _openExistential(\(inner)DynamicType$, do: \(inner)DoLoad) + #endif + """ + ) + return existentialName + case .extractSwiftValue(let inner, let swiftType, let allowNil): let inner = inner.render(&printer, placeholder) let pointerName = "\(inner)$" @@ -584,7 +687,14 @@ extension JNISwift2JavaGenerator { let methodSignature = MethodSignature( resultType: nativeResult.javaType, - parameterTypes: parameters.flatMap { $0.parameters.map(\.type) } + parameterTypes: parameters.flatMap { + $0.parameters.map { parameter in + guard case .concrete(let type) = parameter.type else { + fatalError("Closures do not support Java generics") + } + return type + } + } ) let names = parameters.flatMap { $0.parameters.map(\.name) } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 329b7dfd4..3848ceac9 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -109,6 +109,15 @@ extension JNISwift2JavaGenerator { printJNICache(&printer, type) printer.println() + switch type.swiftNominal.kind { + case .actor, .class, .enum, .struct: + printConcreteTypeThunks(&printer, type) + case .protocol: + printProtocolThunks(&printer, type) + } + } + + private func printConcreteTypeThunks(_ printer: inout CodePrinter, _ type: ImportedNominalType) { for initializer in type.initializers { printSwiftFunctionThunk(&printer, initializer) printer.println() @@ -134,9 +143,16 @@ extension JNISwift2JavaGenerator { printer.println() } + printTypeMetadataAddressThunk(&printer, type) + printer.println() printDestroyFunctionThunk(&printer, type) } + private func printProtocolThunks(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + let protocolName = type.swiftNominal.name + } + + private func printEnumDiscriminator(_ printer: inout CodePrinter, _ type: ImportedNominalType) { let selfPointerParam = JavaParameter(name: "selfPointer", type: .long) printCDecl( @@ -415,6 +431,23 @@ extension JNISwift2JavaGenerator { ) } + private func printTypeMetadataAddressThunk(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + printCDecl( + &printer, + javaMethodName: "$typeMetadataAddressDowncall", + parentName: type.swiftNominal.name, + parameters: [], + resultType: .long + ) { printer in + printer.print( + """ + let metadataPointer = unsafeBitCast(\(type.swiftNominal.qualifiedName).self, to: UnsafeRawPointer.self) + return Int64(Int(bitPattern: metadataPointer)).getJNIValue(in: environment) + """ + ) + } + } + /// Prints the implementation of the destroy function. private func printDestroyFunctionThunk(_ printer: inout CodePrinter, _ type: ImportedNominalType) { let selfPointerParam = JavaParameter(name: "selfPointer", type: .long) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index f9b99f6b5..a677bcde8 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -28,6 +28,7 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { let javaPackage: String let swiftOutputDirectory: String let javaOutputDirectory: String + let lookupContext: SwiftTypeLookupContext let javaClassLookupTable: JavaClassLookupTable @@ -61,6 +62,7 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { self.swiftOutputDirectory = swiftOutputDirectory self.javaOutputDirectory = javaOutputDirectory self.javaClassLookupTable = javaClassLookupTable + self.lookupContext = translator.lookupContext // If we are forced to write empty files, construct the expected outputs if translator.config.writeEmptyFiles ?? false { diff --git a/Sources/JExtractSwiftLib/JavaParameter.swift b/Sources/JExtractSwiftLib/JavaParameter.swift index a12b13b28..43f5a2b49 100644 --- a/Sources/JExtractSwiftLib/JavaParameter.swift +++ b/Sources/JExtractSwiftLib/JavaParameter.swift @@ -16,17 +16,56 @@ import JavaTypes /// Represent a parameter in Java code. struct JavaParameter { - let name: String - let type: JavaType + enum ParameterType: CustomStringConvertible { + case concrete(JavaType) + case generic(name: String, extends: [JavaType]) + + var jniTypeSignature: String { + switch self { + case .concrete(let type): + return type.jniTypeSignature + case .generic(_, let extends): + guard !extends.isEmpty else { + return "Ljava/lang/Object;" + } + + // Generics only use the first type for JNI + return extends.first!.jniTypeSignature + } + } + + var jniTypeName: String { + switch self { + case .concrete(let type): type.jniTypeName + case .generic: "jobject?" + } + } + + var description: String { + switch self { + case .concrete(let type): type.description + case .generic(let name, _): name + } + } + } + var name: String + var type: ParameterType + /// Parameter annotations are used in parameter declarations like this: `@Annotation int example` let annotations: [JavaAnnotation] - init(name: String, type: JavaType, annotations: [JavaAnnotation] = []) { + init(name: String, type: ParameterType, annotations: [JavaAnnotation] = []) { self.name = name self.type = type self.annotations = annotations } + init(name: String, type: JavaType, annotations: [JavaAnnotation] = []) { + self.name = name + self.type = .concrete(type) + self.annotations = annotations + } + func renderParameter() -> String { if annotations.isEmpty { return "\(type) \(name)" diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index 02049ed4b..ccbbd2745 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -146,6 +146,8 @@ extension Swift2JavaTranslator { return check(ty) case .existential(let ty), .opaque(let ty): return check(ty) + case .composite(let types): + return types.contains(where: check) case .genericParameter: return false } @@ -200,7 +202,7 @@ extension Swift2JavaTranslator { _ nominalNode: some DeclGroupSyntax & NamedDeclSyntax & WithModifiersSyntax & WithAttributesSyntax, parent: ImportedNominalType? ) -> ImportedNominalType? { - if !nominalNode.shouldExtract(config: config, log: log) { + if !nominalNode.shouldExtract(config: config, log: log, in: parent) { return nil } @@ -225,7 +227,7 @@ extension Swift2JavaTranslator { guard swiftNominalDecl.moduleName == self.swiftModuleName else { return nil } - guard swiftNominalDecl.syntax!.shouldExtract(config: config, log: log) else { + guard swiftNominalDecl.syntax!.shouldExtract(config: config, log: log, in: nil) else { return nil } @@ -239,7 +241,7 @@ extension Swift2JavaTranslator { return alreadyImported } - let importedNominal = ImportedNominalType(swiftNominal: nominal) + let importedNominal = try? ImportedNominalType(swiftNominal: nominal, lookupContext: lookupContext) importedTypes[fullName] = importedNominal return importedNominal diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index 9b183cb6b..13185a5c6 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift @@ -105,7 +105,7 @@ final class Swift2JavaVisitor { } func visit(functionDecl node: FunctionDeclSyntax, in typeContext: ImportedNominalType?) { - guard node.shouldExtract(config: config, log: log) else { + guard node.shouldExtract(config: config, log: log, in: typeContext) else { return } @@ -183,7 +183,7 @@ final class Swift2JavaVisitor { } func visit(variableDecl node: VariableDeclSyntax, in typeContext: ImportedNominalType?) { - guard node.shouldExtract(config: config, log: log) else { + guard node.shouldExtract(config: config, log: log, in: typeContext) else { return } @@ -237,7 +237,7 @@ final class Swift2JavaVisitor { self.log.info("Initializer must be within a current type; \(node)") return } - guard node.shouldExtract(config: config, log: log) else { + guard node.shouldExtract(config: config, log: log, in: typeContext) else { return } @@ -293,10 +293,10 @@ final class Swift2JavaVisitor { } extension DeclSyntaxProtocol where Self: WithModifiersSyntax & WithAttributesSyntax { - func shouldExtract(config: Configuration, log: Logger) -> Bool { + func shouldExtract(config: Configuration, log: Logger, in parent: ImportedNominalType?) -> Bool { let meetsRequiredAccessLevel: Bool = switch config.effectiveMinimumInputAccessLevelMode { - case .public: self.isPublic + case .public: self.isPublic(in: parent?.swiftNominal.syntax) case .package: self.isAtLeastPackage case .internal: self.isAtLeastInternal } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift index 335979a44..763a5da23 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -93,6 +93,10 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { return firstInheritanceType.type }() + var inheritanceTypes: InheritedTypeListSyntax? { + self.syntax?.inheritanceClause?.inheritedTypes + } + /// Returns true if this type conforms to `Sendable` and therefore is "threadsafe". lazy var isSendable: Bool = { // Check if Sendable is in the inheritance list diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType+RepresentativeConcreteeType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType+GenericTypes.swift similarity index 72% rename from Sources/JExtractSwiftLib/SwiftTypes/SwiftType+RepresentativeConcreteeType.swift rename to Sources/JExtractSwiftLib/SwiftTypes/SwiftType+GenericTypes.swift index ce668485d..1a658513d 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType+RepresentativeConcreteeType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType+GenericTypes.swift @@ -27,6 +27,38 @@ extension SwiftType { genericRequirements: genericRequirements ) } + + /// Returns the protocol type if this is a generic parameter in the list + func typeIn( + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] + ) -> SwiftType? { + switch self { + case .genericParameter(let genericParam): + if genericParameters.contains(genericParam) { + let types: [SwiftType] = genericRequirements.compactMap { + guard case .inherits(let left, let right) = $0, left == self else { + return nil + } + return right + } + + if types.isEmpty { + // TODO: Any?? + return nil + } else if types.count == 1 { + return types.first! + } else { + return .composite(types) + } + } + + return nil + + default: + return nil + } + } } private func representativeConcreteType( diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index 58bb65c39..81afe6370 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -37,6 +37,9 @@ enum SwiftType: Equatable { /// `some ` indirect case opaque(SwiftType) + /// `type1` & `type2` + indirect case composite([SwiftType]) + static var void: Self { return .tuple([]) } @@ -45,7 +48,7 @@ enum SwiftType: Equatable { switch self { case .nominal(let nominal): nominal case .tuple(let elements): elements.count == 1 ? elements[0].asNominalType : nil - case .genericParameter, .function, .metatype, .optional, .existential, .opaque: nil + case .genericParameter, .function, .metatype, .optional, .existential, .opaque, .composite: nil } } @@ -88,7 +91,7 @@ enum SwiftType: Equatable { return nominal.nominalTypeDecl.isReferenceType case .metatype, .function: return true - case .genericParameter, .optional, .tuple, .existential, .opaque: + case .genericParameter, .optional, .tuple, .existential, .opaque, .composite: return false } } @@ -123,7 +126,7 @@ extension SwiftType: CustomStringConvertible { /// requires parentheses. private var postfixRequiresParentheses: Bool { switch self { - case .function, .existential, .opaque: true + case .function, .existential, .opaque, .composite: true case .genericParameter, .metatype, .nominal, .optional, .tuple: false } } @@ -147,6 +150,8 @@ extension SwiftType: CustomStringConvertible { return "any \(constraintType)" case .opaque(let constraintType): return "some \(constraintType)" + case .composite(let types): + return types.map(\.description).joined(separator: " & ") } } } @@ -208,7 +213,7 @@ extension SwiftNominalType { extension SwiftType { init(_ type: TypeSyntax, lookupContext: SwiftTypeLookupContext) throws { switch type.as(TypeSyntaxEnum.self) { - case .arrayType, .classRestrictionType, .compositionType, + case .arrayType, .classRestrictionType, .dictionaryType, .missingType, .namedOpaqueReturnType, .packElementType, .packExpansionType, .suppressedType: throw TypeTranslationError.unimplementedType(type) @@ -311,6 +316,13 @@ extension SwiftType { } else { self = .opaque(try SwiftType(someOrAntType.constraint, lookupContext: lookupContext)) } + + case .compositionType(let compositeType): + let types = try compositeType.elements.map { + try SwiftType($0.type, lookupContext: lookupContext) + } + + self = .composite(types) } } diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 8d1931496..5884270a7 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -60,10 +60,17 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Async functions `func async` and properties: `var { get async {} }` | ❌ | ❌ | | Arrays: `[UInt8]`, `[T]` | ❌ | ❌ | | Dictionaries: `[String: Int]`, `[K:V]` | ❌ | ❌ | -| Generic functions | ❌ | ❌ | -| `Foundation.Data`, `any Foundation.DataProtocol` | ✅ | ❌ | +| Generic parameters in functions: `func f(x: T)` | ❌ | ✅ | +| Generic return values in functions: `func f() -> T` | ❌ | ❌ | | Tuples: `(Int, String)`, `(A, B, C)` | ❌ | ❌ | -| Protocols: `protocol`, existential parameters `any Collection` | ❌ | ❌ | +| Protocols: `protocol` | ❌ | ✅ | +| Protocols: `protocol` with associated types | ❌ | ❌ | +| Existential parameters `f(x: any SomeProtocol) ` | ❌ | ✅ | +| Existential parameters `f(x: any (A & B)) ` | ❌ | ✅ | +| Existential return types `f() -> any Collection ` | ❌ | ❌ | +| Foundation Data and DataProtocol: `f(x: any DataProtocol) -> Data` | ✅ | ❌ | +| Opaque parameters: `func take(worker: some Builder) -> some Builder` | ❌ | ✅ | +| Opaque return types: `func get() -> some Builder` | ❌ | ❌ | | Optional parameters: `func f(i: Int?, class: MyClass?)` | ✅ | ✅ | | Optional return types: `func f() -> Int?`, `func g() -> MyClass?` | ❌ | ✅ | | Primitive types: `Bool`, `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `Float`, `Double` | ✅ | ✅ | @@ -90,7 +97,6 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Result builders | ❌ | ❌ | | Automatic Reference Counting of class types / lifetime safety | ✅ | ✅ | | Value semantic types (e.g. struct copying) | ❌ | ❌ | -| Opaque types: `func get() -> some Builder`, func take(worker: some Worker) | ❌ | ❌ | | Swift concurrency: `func async`, `actor`, `distribued actor` | ❌ | ❌ | | | | | | | | | @@ -266,5 +272,47 @@ try (var arena = SwiftArena.ofConfined()) { } ``` +### Protocols +> Note: Protocols are currently only supported in JNI mode. +> +> With the exception of `any DataProtocol` which is handled as `Foundation.Data` in the FFM mode. +Swift `protocol` types are imported as Java `interface`s. For now, we require that all +concrete types of an interface wrap a Swift instance. In the future, we will add support +for providing Java-based implementations of interfaces, that you can pass to Java functions. + +Consider the following Swift protocol: +```swift +protocol Named { + var name: String { get } + + func describe() -> String +} +``` +will be exported as +```java +interface Named extends JNISwiftInstance { + public String getName(); + + public String describe(); +} +``` + +#### Parameters +Any opaque, existential or generic parameters are imported as Java generics. +This means that the following function: +```swift +func f(x: S, y: any C, z: some D) +``` +will be exported as +```java + void f(S x, T1 y, T2 z) +``` +On the Java side, only SwiftInstance implementing types may be passed; +so this isn't a way for compatibility with just any arbitrary Java interfaces, +but specifically, for allowing passing concrete binding types generated by jextract from Swift types +which conform a to a given Swift protocol. + +#### Returning protocol types +Protocols are not yet supported as return types. diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java index 95f1e5a0f..4e3b8378f 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java @@ -14,32 +14,7 @@ package org.swift.swiftkit.core; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicBoolean; - -public abstract class JNISwiftInstance extends SwiftInstance { - // Pointer to the "self". - protected final long selfPointer; - - /** - * The designated constructor of any imported Swift types. - * - * @param selfPointer a pointer to the memory containing the value - * @param arena the arena this object belongs to. When the arena goes out of scope, this value is destroyed. - */ - protected JNISwiftInstance(long selfPointer, SwiftArena arena) { - SwiftObjects.requireNonZero(selfPointer, "selfPointer"); - this.selfPointer = selfPointer; - - // Only register once we have fully initialized the object since this will need the object pointer. - arena.register(this); - } - - @Override - public long $memoryAddress() { - return this.selfPointer; - } - +public interface JNISwiftInstance extends SwiftInstance { /** * Creates a function that will be called when the value should be destroyed. * This will be code-generated to call a native method to do deinitialization and deallocation. @@ -52,10 +27,12 @@ protected JNISwiftInstance(long selfPointer, SwiftArena arena) { * * @return a function that is called when the value should be destroyed. */ - protected abstract Runnable $createDestroyFunction(); + Runnable $createDestroyFunction(); + + long $typeMetadataAddress(); @Override - public SwiftInstanceCleanup $createCleanup() { + default SwiftInstanceCleanup $createCleanup() { var statusDestroyedFlag = $statusDestroyedFlag(); Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true); diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstance.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstance.java index 44955cc6a..3ab07ffb4 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstance.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftInstance.java @@ -16,12 +16,7 @@ import java.util.concurrent.atomic.AtomicBoolean; -public abstract class SwiftInstance { - - // TODO: make this a flagset integer and/or use a field updater - /** Used to track additional state of the underlying object, e.g. if it was explicitly destroyed. */ - private final AtomicBoolean $state$destroyed = new AtomicBoolean(false); - +public interface SwiftInstance { /** * Pointer to the {@code self} of the underlying Swift object or value. * @@ -29,14 +24,14 @@ public abstract class SwiftInstance { * is kept alive using some means (e.g. a class remains retained), as * this function does not ensure safety of the address in any way. */ - public abstract long $memoryAddress(); + long $memoryAddress(); /** * Called when the arena has decided the value should be destroyed. *

* Warning: The cleanup action must not capture {@code this}. */ - public abstract SwiftInstanceCleanup $createCleanup(); + SwiftInstanceCleanup $createCleanup(); /** * Exposes a boolean value which can be used to indicate if the object was destroyed. @@ -45,10 +40,7 @@ public abstract class SwiftInstance { * form a strong reference to the {@code SwiftInstance} which could prevent the cleanup from running, * if using an GC managed instance (e.g. using an {@code AutoSwiftMemorySession}. */ - public final AtomicBoolean $statusDestroyedFlag() { - return this.$state$destroyed; - } - + AtomicBoolean $statusDestroyedFlag(); /** * Ensures that this instance has not been destroyed. *

@@ -56,8 +48,8 @@ public abstract class SwiftInstance { * to be thrown. This check should be performed before accessing {@code $memorySegment} to prevent * use-after-free errors. */ - protected final void $ensureAlive() { - if (this.$state$destroyed.get()) { + default void $ensureAlive() { + if (this.$statusDestroyedFlag().get()) { throw new IllegalStateException("Attempted to call method on already destroyed instance of " + getClass().getSimpleName() + "!"); } } diff --git a/SwiftKitCore/src/test/java/org/swift/swiftkit/AutoArenaTest.java b/SwiftKitCore/src/test/java/org/swift/swiftkit/AutoArenaTest.java index b414962f5..120934215 100644 --- a/SwiftKitCore/src/test/java/org/swift/swiftkit/AutoArenaTest.java +++ b/SwiftKitCore/src/test/java/org/swift/swiftkit/AutoArenaTest.java @@ -18,6 +18,8 @@ import org.swift.swiftkit.core.JNISwiftInstance; import org.swift.swiftkit.core.SwiftArena; +import java.util.concurrent.atomic.AtomicBoolean; + public class AutoArenaTest { @Test @@ -45,13 +47,30 @@ public void cleaner_releases_native_resource() { } } - private static class FakeSwiftInstance extends JNISwiftInstance { + private static class FakeSwiftInstance implements JNISwiftInstance { + AtomicBoolean $state$destroyed = new AtomicBoolean(false); + public FakeSwiftInstance(SwiftArena arena) { - super(1, arena); + arena.register(this); } - protected Runnable $createDestroyFunction() { + public Runnable $createDestroyFunction() { return () -> {}; } + + @Override + public long $typeMetadataAddress() { + return 0; + } + + @Override + public long $memoryAddress() { + return 0; + } + + @Override + public AtomicBoolean $statusDestroyedFlag() { + return $state$destroyed; + } } } diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstance.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstance.java index 1236bad2a..70324c942 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstance.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMSwiftInstance.java @@ -18,10 +18,15 @@ import org.swift.swiftkit.core.SwiftInstanceCleanup; import java.lang.foreign.MemorySegment; +import java.util.concurrent.atomic.AtomicBoolean; -public abstract class FFMSwiftInstance extends SwiftInstance { +public abstract class FFMSwiftInstance implements SwiftInstance { private final MemorySegment memorySegment; + // TODO: make this a flagset integer and/or use a field updater + /** Used to track additional state of the underlying object, e.g. if it was explicitly destroyed. */ + private final AtomicBoolean $state$destroyed = new AtomicBoolean(false); + /** * The designated constructor of any imported Swift types. * @@ -52,6 +57,16 @@ protected FFMSwiftInstance(MemorySegment segment, AllocatingSwiftArena arena) { */ public abstract SwiftAnyType $swiftType(); + /** + * Exposes a boolean value which can be used to indicate if the object was destroyed. + *

+ * This is exposing the object, rather than performing the action because we don't want to accidentally + * form a strong reference to the {@code SwiftInstance} which could prevent the cleanup from running, + * if using an GC managed instance (e.g. using an {@code AutoSwiftMemorySession}. + */ + public final AtomicBoolean $statusDestroyedFlag() { + return this.$state$destroyed; + } @Override public SwiftInstanceCleanup $createCleanup() { @@ -65,7 +80,6 @@ protected FFMSwiftInstance(MemorySegment segment, AllocatingSwiftArena arena) { ); } - /** * Returns `true` if this swift instance is a reference type, i.e. a `class` or (`distributed`) `actor`. * diff --git a/SwiftKitFFM/src/test/java/org/swift/swiftkit/ffm/AutoArenaTest.java b/SwiftKitFFM/src/test/java/org/swift/swiftkit/ffm/AutoArenaTest.java index b95f6c458..18c0a5af0 100644 --- a/SwiftKitFFM/src/test/java/org/swift/swiftkit/ffm/AutoArenaTest.java +++ b/SwiftKitFFM/src/test/java/org/swift/swiftkit/ffm/AutoArenaTest.java @@ -18,6 +18,7 @@ import java.lang.foreign.GroupLayout; import java.lang.foreign.MemorySegment; +import java.util.concurrent.atomic.AtomicBoolean; public class AutoArenaTest { diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift index 30bb7433d..a7527aa8d 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -55,9 +55,12 @@ struct JNIClassTests { import org.swift.swiftkit.core.*; import org.swift.swiftkit.core.util.*; + import java.util.*; + import java.util.concurrent.atomic.AtomicBoolean; + import org.swift.swiftkit.core.annotations.*; """, """ - public final class MyClass extends JNISwiftInstance { + public final class MyClass implements JNISwiftInstance { static final String LIB_NAME = "SwiftModule"; @SuppressWarnings("unused") @@ -68,9 +71,21 @@ struct JNIClassTests { } """, """ - private MyClass(long selfPointer, SwiftArena swiftArena) { - super(selfPointer, swiftArena); - } + private final long selfPointer; + """, + """ + public long $memoryAddress() { + return this.selfPointer; + } + """, + """ + private MyClass(long selfPointer, SwiftArena swiftArena) { + SwiftObjects.requireNonZero(selfPointer, "selfPointer"); + this.selfPointer = selfPointer; + + // Only register once we have fully initialized the object since this will need the object pointer. + swiftArena.register(this); + } """, """ public static MyClass wrapMemoryAddressUnsafe(long selfPointer, SwiftArena swiftArena) { @@ -92,7 +107,7 @@ struct JNIClassTests { expectedChunks: [ """ @Override - protected Runnable $createDestroyFunction() { + public Runnable $createDestroyFunction() { long self$ = this.$memoryAddress(); if (CallTraces.TRACE_DOWNCALLS) { CallTraces.traceDowncall("MyClass.$createDestroyFunction", diff --git a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift index 477650437..c6aaf923d 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift @@ -39,9 +39,12 @@ struct JNIEnumTests { import org.swift.swiftkit.core.*; import org.swift.swiftkit.core.util.*; + import java.util.*; + import java.util.concurrent.atomic.AtomicBoolean; + import org.swift.swiftkit.core.annotations.*; """, """ - public final class MyEnum extends JNISwiftInstance { + public final class MyEnum implements JNISwiftInstance { static final String LIB_NAME = "SwiftModule"; @SuppressWarnings("unused") @@ -53,7 +56,19 @@ struct JNIEnumTests { """, """ private MyEnum(long selfPointer, SwiftArena swiftArena) { - super(selfPointer, swiftArena); + SwiftObjects.requireNonZero(selfPointer, "selfPointer"); + this.selfPointer = selfPointer; + + // Only register once we have fully initialized the object since this will need the object pointer. + swiftArena.register(this); + } + """, + """ + private final long selfPointer; + """, + """ + public long $memoryAddress() { + return this.selfPointer; } """, """ @@ -66,7 +81,7 @@ struct JNIEnumTests { """, """ @Override - protected Runnable $createDestroyFunction() { + public Runnable $createDestroyFunction() { long self$ = this.$memoryAddress(); if (CallTraces.TRACE_DOWNCALLS) { CallTraces.traceDowncall("MyEnum.$createDestroyFunction", diff --git a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift index ad55f491e..27b0cdea7 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift @@ -46,6 +46,7 @@ struct JNIModuleTests { import org.swift.swiftkit.core.*; import org.swift.swiftkit.core.util.*; import java.util.*; + import java.util.concurrent.atomic.AtomicBoolean; import org.swift.swiftkit.core.annotations.*; public final class SwiftModule { diff --git a/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift index 548a2eacc..be2e0f6a9 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift @@ -127,12 +127,12 @@ struct JNIOptionalTests { result$ = innerResult$.getJNIValue(in: environment!) var flag$ = Int8(1) environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) - } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:649 + } else { result$ = String.jniPlaceholderValue var flag$ = Int8(0) environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) - } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:659 + } return result$ } """ @@ -190,12 +190,12 @@ struct JNIOptionalTests { result$ = _resultBits$.getJNIValue(in: environment!) var flag$ = Int8(1) environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) - } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:649 + } else { result$ = 0 var flag$ = Int8(0) environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) - } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:659 + } return result$ } """ @@ -243,7 +243,7 @@ struct JNIOptionalTests { func Java_com_example_swift_SwiftModule__00024optionalJavaKitClass__Ljava_lang_Long_2(environment: UnsafeMutablePointer!, thisClass: jclass, arg: jobject?) { SwiftModule.optionalJavaKitClass(arg.map { return JavaLong(javaThis: $0, environment: environment!) - } // render(_:_:) @ JExtractSwiftLib/JNISwift2JavaGenerator+NativeTranslation.swift:691 + } ) } """ diff --git a/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift b/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift new file mode 100644 index 000000000..b5a0fcdbe --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift @@ -0,0 +1,246 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import Testing + +@Suite +struct JNIProtocolTests { + let source = """ + public protocol SomeProtocol { + var x: Int64 { get set } + + public func method() {} + } + + public protocol B {} + + public class SomeClass: SomeProtocol {} + + public func takeProtocol(x: some SomeProtocol, y: any SomeProtocol) + public func takeGeneric(s: S) + public func takeComposite(x: any SomeProtocol & B) + """ + + @Test + func generatesJavaInterface() throws { + try assertOutput( + input: source, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + // Generated by jextract-swift + // Swift module: SwiftModule + + package com.example.swift; + + import org.swift.swiftkit.core.*; + import org.swift.swiftkit.core.util.*; + """, + """ + public interface SomeProtocol extends JNISwiftInstance { + ... + } + """, + """ + public long getX(); + """, + """ + public void setX(long newValue); + """, + """ + public void method(); + """ + ]) + } + + @Test + func generatesJavaClassWithExtends() throws { + try assertOutput( + input: source, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public final class SomeClass implements JNISwiftInstance, SomeProtocol { + """ + ]) + } + + @Test + func takeProtocol_java() throws { + try assertOutput( + input: source, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public static <$T0 extends SomeProtocol, $T1 extends SomeProtocol> void takeProtocol($T0 x, $T1 y) { + SwiftModule.$takeProtocol(x.$memoryAddress(), x.$typeMetadataAddress(), y.$memoryAddress(), y.$typeMetadataAddress()); + } + """, + """ + private static native void $takeProtocol(long x, long x_typeMetadataAddress, long y, long y_typeMetadataAddress); + """ + ]) + } + + @Test + func takeProtocol_swift() throws { + try assertOutput( + input: source, + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024takeProtocol__JJJJ") + func Java_com_example_swift_SwiftModule__00024takeProtocol__JJJJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, x_typeMetadataAddress: jlong, y: jlong, y_typeMetadataAddress: jlong) { + guard let xTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: x_typeMetadataAddress, in: environment!))) else { + fatalError("x_typeMetadataAddress memory address was null") + } + let xDynamicType$: Any.Type = unsafeBitCast(xTypeMetadataPointer$, to: Any.Type.self) + guard let xRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: x, in: environment!))) else { + fatalError("x memory address was null") + } + #if hasFeature(ImplicitOpenExistentials) + let xExistential$ = xRawPointer$.load(as: xDynamicType$) as! any (SomeProtocol) + #else + func xDoLoad(_ ty: Ty.Type) -> any (SomeProtocol) { + xRawPointer$.load(as: ty) as! any (SomeProtocol) + } + let xExistential$ = _openExistential(xDynamicType$, do: xDoLoad) + #endif + guard let yTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: y_typeMetadataAddress, in: environment!))) else { + fatalError("y_typeMetadataAddress memory address was null") + } + let yDynamicType$: Any.Type = unsafeBitCast(yTypeMetadataPointer$, to: Any.Type.self) + guard let yRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: y, in: environment!))) else { + fatalError("y memory address was null") + } + #if hasFeature(ImplicitOpenExistentials) + let yExistential$ = yRawPointer$.load(as: yDynamicType$) as! any (SomeProtocol) + #else + func yDoLoad(_ ty: Ty.Type) -> any (SomeProtocol) { + yRawPointer$.load(as: ty) as! any (SomeProtocol) + } + let yExistential$ = _openExistential(yDynamicType$, do: yDoLoad) + #endif + SwiftModule.takeProtocol(x: xExistential$, y: yExistential$) + } + """ + ] + ) + } + + @Test + func takeGeneric_java() throws { + try assertOutput( + input: source, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public static void takeGeneric(S s) { + SwiftModule.$takeGeneric(s.$memoryAddress(), s.$typeMetadataAddress()); + } + """, + """ + private static native void $takeGeneric(long s, long s_typeMetadataAddress); + """ + ]) + } + + @Test + func takeGeneric_swift() throws { + try assertOutput( + input: source, + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024takeGeneric__JJ") + func Java_com_example_swift_SwiftModule__00024takeGeneric__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, s: jlong, s_typeMetadataAddress: jlong) { + guard let sTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: s_typeMetadataAddress, in: environment!))) else { + fatalError("s_typeMetadataAddress memory address was null") + } + let sDynamicType$: Any.Type = unsafeBitCast(sTypeMetadataPointer$, to: Any.Type.self) + guard let sRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: s, in: environment!))) else { + fatalError("s memory address was null") + } + #if hasFeature(ImplicitOpenExistentials) + let sExistential$ = sRawPointer$.load(as: sDynamicType$) as! any (SomeProtocol) + #else + func sDoLoad(_ ty: Ty.Type) -> any (SomeProtocol) { + sRawPointer$.load(as: ty) as! any (SomeProtocol) + } + let sExistential$ = _openExistential(sDynamicType$, do: sDoLoad) + #endif + SwiftModule.takeGeneric(s: sExistential$) + } + """ + ] + ) + } + + @Test + func takeComposite_java() throws { + try assertOutput( + input: source, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public static <$T0 extends SomeProtocol & B> void takeComposite($T0 x) { + SwiftModule.$takeComposite(x.$memoryAddress(), x.$typeMetadataAddress()); + } + """, + """ + private static native void $takeComposite(long x, long x_typeMetadataAddress); + """ + ]) + } + + @Test + func takeComposite_swift() throws { + try assertOutput( + input: source, + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024takeComposite__JJ") + func Java_com_example_swift_SwiftModule__00024takeComposite__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, x_typeMetadataAddress: jlong) { + guard let xTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: x_typeMetadataAddress, in: environment!))) else { + fatalError("x_typeMetadataAddress memory address was null") + } + let xDynamicType$: Any.Type = unsafeBitCast(xTypeMetadataPointer$, to: Any.Type.self) + guard let xRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: x, in: environment!))) else { + fatalError("x memory address was null") + } + #if hasFeature(ImplicitOpenExistentials) + let xExistential$ = xRawPointer$.load(as: xDynamicType$) as! any (SomeProtocol & B) + #else + func xDoLoad(_ ty: Ty.Type) -> any (SomeProtocol & B) { + xRawPointer$.load(as: ty) as! any (SomeProtocol & B) + } + let xExistential$ = _openExistential(xDynamicType$, do: xDoLoad) + #endif + SwiftModule.takeComposite(x: xExistential$) + } + """ + ] + ) + } +} diff --git a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift index 7f54fa7e1..a7c689aaf 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift @@ -47,7 +47,7 @@ struct JNIStructTests { """,]) try assertOutput(input: source, .jni, .java, expectedChunks: [ """ - public final class MyStruct extends JNISwiftInstance { + public final class MyStruct implements JNISwiftInstance { static final String LIB_NAME = "SwiftModule"; @SuppressWarnings("unused") @@ -58,9 +58,13 @@ struct JNIStructTests { } """, """ - private MyStruct(long selfPointer, SwiftArena swiftArena) { - super(selfPointer, swiftArena); - } + private MyStruct(long selfPointer, SwiftArena swiftArena) { + SwiftObjects.requireNonZero(selfPointer, "selfPointer"); + this.selfPointer = selfPointer; + + // Only register once we have fully initialized the object since this will need the object pointer. + swiftArena.register(this); + } """, """ public static MyStruct wrapMemoryAddressUnsafe(long selfPointer, SwiftArena swiftArena) { @@ -80,7 +84,7 @@ struct JNIStructTests { expectedChunks: [ """ @Override - protected Runnable $createDestroyFunction() { + public Runnable $createDestroyFunction() { long self$ = this.$memoryAddress(); if (CallTraces.TRACE_DOWNCALLS) { CallTraces.traceDowncall("MyStruct.$createDestroyFunction", diff --git a/Tests/JExtractSwiftTests/SendableTests.swift b/Tests/JExtractSwiftTests/SendableTests.swift index d6d0d2d6e..5116fc030 100644 --- a/Tests/JExtractSwiftTests/SendableTests.swift +++ b/Tests/JExtractSwiftTests/SendableTests.swift @@ -46,11 +46,11 @@ final class SendableTests { expectedChunks: [ """ @ThreadSafe // Sendable - public final class SendableStruct extends JNISwiftInstance { + public final class SendableStruct implements JNISwiftInstance { static final String LIB_NAME = "SwiftModule"; """, ] ) } -} \ No newline at end of file +} From 67c8153984abbcdbfa258006f9e109b10c500264 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 29 Aug 2025 16:55:17 +0900 Subject: [PATCH 075/141] Add destructuring pattern match example to enum docs (#381) --- .../Documentation.docc/SupportedFeatures.md | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 5884270a7..d4c120832 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -209,10 +209,10 @@ Vehicle vehicle = ...; switch (vehicle.getDiscriminator()) { case BICYCLE: System.out.println("I am a bicycle!"); - break + break; case CAR: System.out.println("I am a car!"); - break + break; } ``` If you also want access to the associated values, you have various options @@ -223,12 +223,24 @@ Vehicle vehicle = ...; switch (vehicle.getCase()) { case Vehicle.Bicycle b: System.out.println("Bicycle maker: " + b.maker()); - break + break; case Vehicle.Car c: System.out.println("Car: " + c.arg0()); - break + break; } ``` +or even, destructuring the records in the switch statement's pattern match directly: +```java +Vehicle vehicle = ...; +switch (vehicle.getCase()) { + case Vehicle.Car(var name, var unused): + System.out.println("Car: " + name); + break; + default: + break; +} +``` + For Java 16+ you can use [pattern matching for instanceof](https://openjdk.org/jeps/394) ```java Vehicle vehicle = ...; From 899dc23a992eb0265215c8b4bc3fb50b8d012983 Mon Sep 17 00:00:00 2001 From: David Ko Date: Fri, 29 Aug 2025 21:51:27 -0400 Subject: [PATCH 076/141] Update README and SupportedFeatures (#383) * update gitgnore * align with README JExtract from Java to Swift * add watch to 'you may want to this' * add intro to SwiftJavaDocumentation index.md * add intro to SwiftJavaDocumentation index.md * add intro to SwiftJavaDocumentation index.md * align documentation with module rename * SwiftJavaDocumentation correction * Revert "SwiftJavaDocumentation correction" This reverts commit f65061cf242697f10cbd738fbe76a21875c57a39. * revert ConfigureCommand formatting * revert SupportFeatures.md * update benchmark sample project name * renaming JavaKit to SwiftJava * renaming JavaKit to SwiftJava in SupportedFeatures --- README.md | 12 ++++++------ .../Documentation.docc/SupportedFeatures.md | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index c15db5414..52c61bb45 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This repository contains two approaches to Swift/Java interoperability. -- Swift library (`JavaKit`) and bindings generator that allows a Swift program to make use of Java libraries by wrapping Java classes in corresponding Swift types, allowing Swift to directly call any wrapped Java API. +- Swift library (`SwiftJava`) and bindings generator that allows a Swift program to make use of Java libraries by wrapping Java classes in corresponding Swift types, allowing Swift to directly call any wrapped Java API. - The `swift-java` tool which which offers automated ways to import or "extract" bindings to sources or libraries in either language. The results are bindings for Swift or Java. ## :construction: :construction: :construction: Early Development :construction: :construction: :construction: @@ -19,11 +19,11 @@ The primary purpose of this repository is to create an environment for collabora This project consists of different modules which have different Swift and Java runtime requirements. -## JavaKit macros +## SwiftJava macros -JavaKit is a Swift library offering macros which simplify writing JNI code "by hand" but also calling Java code from Swift. +SwiftJava is a Swift library offering macros which simplify writing JNI code "by hand" but also calling Java code from Swift. -It is possible to generate Swift bindings to Java libraries using JavaKit by using the `swift-java wrap-java` command. +It is possible to generate Swift bindings to Java libraries using SwiftJava by using the `swift-java wrap-java` command. Required language/runtime versions: - **JDK 17+**, any recent JDK installation should be sufficient, as only general reflection and JNI APIs are used by this integration @@ -124,12 +124,12 @@ Please always use the gradle wrapper (`./gradlew`) to make sure to use the appro Sample apps are located in the `Samples/` directory, and they showcase full "roundtrip" usage of the library and/or tools. -#### JavaKit (Swift -> Java) +#### SwiftJava (Swift -> Java) To run a simple app showcasing a Swift process calling into a Java library you can run: ```bash -cd Samples/JavaKitSampleApp +cd Samples/SwiftJavaExtractFFMSampleApp ./ci-validate.sh # which is just `swift build` and a `java -cp ...` invocation of the compiled program ``` diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index d4c120832..997e680eb 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -4,13 +4,13 @@ Summary of features supported by the swift-java interoperability libraries and t ## Overview -JavaKit supports both directions of interoperability, using Swift macros and source generation +SwiftJava supports both directions of interoperability, using Swift macros and source generation (via the `swift-java wrap-java` command). ### Java -> Swift -It is possible to use JavaKit macros and the `wrap-java` command to simplify implementing -Java `native` functions. JavaKit simplifies the type conversions +It is possible to use SwiftJava macros and the `wrap-java` command to simplify implementing +Java `native` functions. SwiftJava simplifies the type conversions > tip: This direction of interoperability is covered in the WWDC2025 session 'Explore Swift and Java interoperability' > around the [7-minute mark](https://youtu.be/QSHO-GUGidA?si=vUXxphTeO-CHVZ3L&t=448). @@ -74,8 +74,8 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Optional parameters: `func f(i: Int?, class: MyClass?)` | ✅ | ✅ | | Optional return types: `func f() -> Int?`, `func g() -> MyClass?` | ❌ | ✅ | | Primitive types: `Bool`, `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `Float`, `Double` | ✅ | ✅ | -| Parameters: JavaKit wrapped types `JavaLong`, `JavaInteger` | ❌ | ✅ | -| Return values: JavaKit wrapped types `JavaLong`, `JavaInteger` | ❌ | ❌ | +| Parameters: SwiftJava wrapped types `JavaLong`, `JavaInteger` | ❌ | ✅ | +| Return values: SwiftJava wrapped types `JavaLong`, `JavaInteger` | ❌ | ❌ | | Unsigned primitive types: `UInt`, `UInt8`, `UInt16`, `UInt32`, `UInt64` | ✅ * | ✅ * | | String (with copying data) | ✅ | ✅ | | Variadic parameters: `T...` | ❌ | ❌ | From e187ad6f6ef72122218778b7b8af8bbd4c871a3e Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Mon, 1 Sep 2025 15:34:49 +0900 Subject: [PATCH 077/141] Rename reexport file to match reexported module (#385) --- .../{JavaRuntime+Reexport.swift => CSwiftJavaJNI+Reexport.swift} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Sources/SwiftJava/{JavaRuntime+Reexport.swift => CSwiftJavaJNI+Reexport.swift} (100%) diff --git a/Sources/SwiftJava/JavaRuntime+Reexport.swift b/Sources/SwiftJava/CSwiftJavaJNI+Reexport.swift similarity index 100% rename from Sources/SwiftJava/JavaRuntime+Reexport.swift rename to Sources/SwiftJava/CSwiftJavaJNI+Reexport.swift From 748961d69abdde19ebe5463800fbe4cf6883c204 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 3 Sep 2025 20:03:06 +0900 Subject: [PATCH 078/141] Correct "running in ... mode" message This takes into account the settings file, so we should use the configured mode. Noticed when we printed "ffm mode" while we were actually in JNI mode --- Sources/SwiftJavaTool/Commands/JExtractCommand.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index 29db6081a..6fc5c2fde 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -103,7 +103,7 @@ extension SwiftJava.JExtractCommand { config.inputSwiftDirectory = "\(FileManager.default.currentDirectoryPath)/Sources/\(swiftModule)" } - print("[debug][swift-java] Running 'swift-java jextract' in mode: " + "\(self.mode)".bold) + print("[debug][swift-java] Running 'swift-java jextract' in mode: " + "\(config.mode ?? .ffm)".bold) // Load all of the dependent configurations and associate them with Swift modules. let dependentConfigs = try loadDependentConfigs(dependsOn: self.dependsOn) From 4b392128fca13aa014accda44543d21b71fcd65d Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 10 Sep 2025 09:20:26 +0900 Subject: [PATCH 079/141] Start using Swift 6.1.3 in CI (#388) It contains a patch for swiftpm issues we've been facing during build https://forums.swift.org/t/announcing-swift-6-1-3/82022 --- .github/workflows/pull_request.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 0a2b6949b..e0a3ee231 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -27,7 +27,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['6.1.2'] + swift_version: ['6.1.3'] os_version: ['jammy'] jdk_vendor: ['corretto'] steps: @@ -45,7 +45,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['6.1.2', 'nightly'] + swift_version: ['6.1.3', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -71,7 +71,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['6.1.2'] + swift_version: ['6.1.3'] os_version: ['macos'] jdk_vendor: ['corretto'] env: @@ -95,7 +95,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['6.1.2'] + swift_version: ['6.1.3'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -115,7 +115,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.1.2', 'nightly'] + swift_version: ['6.1.3', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -137,7 +137,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.1.2'] + swift_version: ['6.1.3'] os_version: ['macos'] jdk_vendor: ['corretto'] env: @@ -157,7 +157,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.1.2', 'nightly'] + swift_version: ['6.1.3', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] sample_app: [ # TODO: use a reusable-workflow to generate those names @@ -185,7 +185,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.1.2'] # no nightly testing on macOS + swift_version: ['6.1.3'] # no nightly testing on macOS os_version: ['macos'] jdk_vendor: ['corretto'] sample_app: [ # TODO: use a reusable-workflow to generate those names From 3d9433f95f6323b2707590b29474f12247ae842c Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Wed, 10 Sep 2025 11:43:49 +0900 Subject: [PATCH 080/141] [SwiftJava] Fix Android build by making JNINativeInterface_ public (#387) --- Sources/SwiftJava/JavaEnvironment.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftJava/JavaEnvironment.swift b/Sources/SwiftJava/JavaEnvironment.swift index a533a9322..9506dbead 100644 --- a/Sources/SwiftJava/JavaEnvironment.swift +++ b/Sources/SwiftJava/JavaEnvironment.swift @@ -15,7 +15,7 @@ import CSwiftJavaJNI #if canImport(Android) -typealias JNINativeInterface_ = JNINativeInterface +public typealias JNINativeInterface_ = JNINativeInterface #endif extension UnsafeMutablePointer { From 94e87becdf38118760dd25e3b6cd6febf8d133f3 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Mon, 22 Sep 2025 16:05:44 +0900 Subject: [PATCH 081/141] Fix error handling for JNI mode unsigned numbers Flip the assertion, the failure was in the wrong branch in JNI mode --- Sources/SwiftJavaTool/Commands/JExtractCommand.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index 6fc5c2fde..ec7c0aeb7 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -117,9 +117,9 @@ extension SwiftJava.JExtractCommand { if self.mode == .jni { switch self.unsignedNumbers { case .annotate: - throw IllegalModeCombinationError("JNI mode does not support '\(JExtractUnsignedIntegerMode.wrapGuava)' Unsigned integer mode! \(Self.helpMessage)") - case .wrapGuava: () // OK + case .wrapGuava: + throw IllegalModeCombinationError("JNI mode does not support '\(JExtractUnsignedIntegerMode.wrapGuava)' Unsigned integer mode! \(Self.helpMessage)") } } else if self.mode == .ffm { guard self.memoryManagementMode == .explicit else { From ebe04bbddd822179df2f179645c5cc132353d6d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=C3=B3rio=20Gevartosky=20Torrezan?= Date: Sun, 28 Sep 2025 03:42:27 -0300 Subject: [PATCH 082/141] Fixing export JAVA_HOME command missing quote (#390) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 52c61bb45..a1a18d79f 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ The use of JDK 24 is required to build the project, even though the libraries be When using sdkman the easiest way to export JAVA_HOME is to export the "current" used JDK's home, like this: ```bash -export JAVA_HOME="$(sdk home java current) +export JAVA_HOME="$(sdk home java current)" ``` ### Testing your changes From a1dc8484f0f89dd08210ada9362f6b294a091e31 Mon Sep 17 00:00:00 2001 From: Thomas Krajacic Date: Tue, 7 Oct 2025 14:48:10 +0200 Subject: [PATCH 083/141] Add note about installing JDK (#394) Co-authored-by: Konrad `ktoso` Malawski --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index a1a18d79f..2cdeec9c6 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,23 @@ The primary purpose of this repository is to create an environment for collabora This project consists of different modules which have different Swift and Java runtime requirements. +On macOS for example, you can install the JDK with [homebrew](https://brew.sh) using + +```bash +$ brew install openjdk +# and create a symlink into /Library/Java/JavaVirtualMachines +$ sudo ln -sfn /opt/homebrew/opt/openjdk/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk.jdk + +# or if you have a distribution as cask it will be installed into /Library/Java/JavaVirtualMachines +$ brew install --cask corretto +``` + +or you can use a JDK manager like [sdkman](https://sdkman.io/install/) and set your `JAVA_HOME` environment variable + +```bash +$ export JAVA_HOME="$(sdk home java current)" +``` + ## SwiftJava macros SwiftJava is a Swift library offering macros which simplify writing JNI code "by hand" but also calling Java code from Swift. From a9d733236136d3756aca9a236e80c09b4a9df09a Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Tue, 7 Oct 2025 21:48:38 +0900 Subject: [PATCH 084/141] Explain self-publishing swiftkit (#395) --- README.md | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2cdeec9c6..ff643c49c 100644 --- a/README.md +++ b/README.md @@ -5,14 +5,37 @@ This repository contains two approaches to Swift/Java interoperability. - Swift library (`SwiftJava`) and bindings generator that allows a Swift program to make use of Java libraries by wrapping Java classes in corresponding Swift types, allowing Swift to directly call any wrapped Java API. - The `swift-java` tool which which offers automated ways to import or "extract" bindings to sources or libraries in either language. The results are bindings for Swift or Java. -## :construction: :construction: :construction: Early Development :construction: :construction: :construction: +## :construction: Early Development :construction: -**:construction: :construction: :construction: This is a *very early* prototype and everything is subject to change. :construction: :construction: :construction:** +**:construction: This is a *very early* prototype and everything is subject to change. :construction:** Parts of this project are incomplete, not fleshed out, and subject to change without any notice. The primary purpose of this repository is to create an environment for collaboration and joint exploration of the Swift/Java interoperability story. The project will transition to a more structured approach once key goals have been outlined. +### :construction: Self-publish support Java libraries (SwiftKit) + +While we work out how to provide the necessary support libraries for the Java side of Java code generated by `swift-java jextract`, +you will currently need to publish them locally and depend on them this way; + +To publish the libraries to your local maven repository (`$HOME/.m2`), you can run: + +``` +// in swift-java/ +./gradlew publishToMavenLocal +``` + +To consume these libraries in your Java project built using Gradle, you can then include the local repository in the repositories to resolve dependencies from: + +``` +repositories { + mavenLocal() + mavenCentral() +} +``` + +We anticipate simplifying this in the future. + ## Dependencies ### Required JDK versions From 92361949bf5809e48e6e22d053ef611cccfa09ee Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Tue, 7 Oct 2025 22:15:41 +0900 Subject: [PATCH 085/141] CI: Update macOS Swift from 6.1.3 to 6.2.0 (#396) * CI: Update macOS Swift from 6.1.3 to 6.2.0 * only change macOS in this PR --- .github/workflows/pull_request.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index e0a3ee231..ffc42e3c0 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -71,7 +71,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['6.1.3'] + swift_version: ['6.2.0'] os_version: ['macos'] jdk_vendor: ['corretto'] env: @@ -95,7 +95,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['6.1.3'] + swift_version: ['6.2.0'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -137,7 +137,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.1.3'] + swift_version: ['6.2.0'] os_version: ['macos'] jdk_vendor: ['corretto'] env: @@ -185,7 +185,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.1.3'] # no nightly testing on macOS + swift_version: ['6.2.0'] # no nightly testing on macOS os_version: ['macos'] jdk_vendor: ['corretto'] sample_app: [ # TODO: use a reusable-workflow to generate those names From 5cd11e5788a7677f339132a1dbd1fbf7f4e446e8 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Mon, 13 Oct 2025 06:19:14 +0200 Subject: [PATCH 086/141] Minimum changes for Android support (#401) --- Package.swift | 2 +- Sources/CSwiftJavaJNI/AndroidSupport.cpp | 98 ++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 Sources/CSwiftJavaJNI/AndroidSupport.cpp diff --git a/Package.swift b/Package.swift index 1c0e94810..52ec7ae7f 100644 --- a/Package.swift +++ b/Package.swift @@ -362,7 +362,7 @@ let package = Package( name: "CSwiftJavaJNI", swiftSettings: [ .swiftLanguageMode(.v5), - .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"], .when(platforms: [.macOS, .linux, .windows])) ] ), diff --git a/Sources/CSwiftJavaJNI/AndroidSupport.cpp b/Sources/CSwiftJavaJNI/AndroidSupport.cpp new file mode 100644 index 000000000..a223530a1 --- /dev/null +++ b/Sources/CSwiftJavaJNI/AndroidSupport.cpp @@ -0,0 +1,98 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +#ifdef __ANDROID__ + +#include +#include +#include + +// these are not exported by the Android SDK + +extern "C" { + using JavaRuntime_GetDefaultJavaVMInitArgs_fn = jint (*)(void *vm_args); + using JavaRuntime_CreateJavaVM_fn = jint (*)(JavaVM **, JNIEnv **, void *); + using JavaRuntime_GetCreatedJavaVMs_fn = jint (*)(JavaVM **, jsize, jsize *); +} + +static JavaRuntime_GetDefaultJavaVMInitArgs_fn + JavaRuntime_GetDefaultJavaVMInitArgs; +static JavaRuntime_CreateJavaVM_fn JavaRuntime_CreateJavaVM; +static JavaRuntime_GetCreatedJavaVMs_fn JavaRuntime_GetCreatedJavaVMs; + +static void *JavaRuntime_dlhandle; + +__attribute__((constructor)) static void JavaRuntime_init(void) { + JavaRuntime_dlhandle = dlopen("libnativehelper.so", RTLD_NOW | RTLD_LOCAL); + if (JavaRuntime_dlhandle == nullptr) { + __android_log_print(ANDROID_LOG_FATAL, "JavaRuntime", + "failed to open libnativehelper.so: %s", dlerror()); + return; + } + + JavaRuntime_GetDefaultJavaVMInitArgs = + reinterpret_cast( + dlsym(JavaRuntime_dlhandle, "JNI_GetDefaultJavaVMInitArgs")); + if (JavaRuntime_GetDefaultJavaVMInitArgs == nullptr) + __android_log_print(ANDROID_LOG_FATAL, "JavaRuntime", + "JNI_GetDefaultJavaVMInitArgs not found: %s", + dlerror()); + + JavaRuntime_CreateJavaVM = reinterpret_cast( + dlsym(JavaRuntime_dlhandle, "JNI_CreateJavaVM")); + if (JavaRuntime_CreateJavaVM == nullptr) + __android_log_print(ANDROID_LOG_FATAL, "JavaRuntime", + "JNI_CreateJavaVM not found: %s", dlerror()); + + JavaRuntime_GetCreatedJavaVMs = + reinterpret_cast( + dlsym(JavaRuntime_dlhandle, "JNI_GetCreatedJavaVMs")); + if (JavaRuntime_GetCreatedJavaVMs == nullptr) + __android_log_print(ANDROID_LOG_FATAL, "JavaRuntime", + "JNI_GetCreatedJavaVMs not found: %s", dlerror()); +} + +__attribute__((destructor)) static void JavaRuntime_deinit(void) { + if (JavaRuntime_dlhandle) { + dlclose(JavaRuntime_dlhandle); + JavaRuntime_dlhandle = nullptr; + } + + JavaRuntime_GetDefaultJavaVMInitArgs = nullptr; + JavaRuntime_CreateJavaVM = nullptr; + JavaRuntime_GetCreatedJavaVMs = nullptr; +} + +jint JNI_GetDefaultJavaVMInitArgs(void *vm_args) { + if (JavaRuntime_GetDefaultJavaVMInitArgs == nullptr) + return JNI_ERR; + + return (*JavaRuntime_GetDefaultJavaVMInitArgs)(vm_args); +} + +jint JNI_CreateJavaVM(JavaVM **vm, JNIEnv **env, void *vm_args) { + if (JavaRuntime_CreateJavaVM == nullptr) + return JNI_ERR; + + return (*JavaRuntime_CreateJavaVM)(vm, env, vm_args); +} + +jint JNI_GetCreatedJavaVMs(JavaVM **vmBuf, jsize bufLen, jsize *nVMs) { + if (JavaRuntime_GetCreatedJavaVMs == nullptr) + return JNI_ERR; + + return (*JavaRuntime_GetCreatedJavaVMs)(vmBuf, bufLen, nVMs); +} + +#endif From 791e734856e7155e9c9da7b5e9983b0ca12b4e3d Mon Sep 17 00:00:00 2001 From: Melissa Kilby Date: Wed, 15 Oct 2025 02:00:00 -0700 Subject: [PATCH 087/141] chore: restrict GitHub workflow permissions - future-proof (#399) --- .github/workflows/pull_request.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index ffc42e3c0..35cdd4488 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -1,5 +1,8 @@ name: pull_request +permissions: + contents: read + on: pull_request: types: [opened, reopened, synchronize] From c77273e035a2358cbd6a5fd951b4af579bbb4c17 Mon Sep 17 00:00:00 2001 From: rayman-zhao <128336994+rayman-zhao@users.noreply.github.com> Date: Wed, 15 Oct 2025 19:29:56 +0800 Subject: [PATCH 088/141] Fixed problems when build JavaKitSampleApp on Windows (#400) --- .../JavaCompilerPlugin.swift | 20 ++++++++++++------- Samples/JavaKitSampleApp/Package.swift | 5 ++--- .../JavaKitVM/JavaVirtualMachine.swift | 14 ++++++++++--- .../JavaKitVM/ThreadLocalStorage.swift | 2 +- .../Platforms/Subprocess+Windows.swift | 3 +-- 5 files changed, 28 insertions(+), 16 deletions(-) diff --git a/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift b/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift index e12e13e81..f90150fd0 100644 --- a/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift +++ b/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift @@ -30,11 +30,11 @@ struct JavaCompilerBuildToolPlugin: BuildToolPlugin { // Note: Target doesn't have a directoryURL counterpart to directory, // so we cannot eliminate this deprecation warning. - let sourceDir = target.directory.string + let sourceDir = URL(filePath: target.directory.string) // The name of the configuration file SwiftJava.config from the target for // which we are generating Swift wrappers for Java classes. - let configFile = URL(filePath: sourceDir).appending(path: "swift-java.config") + let configFile = sourceDir.appending(path: "swift-java.config") let config: Configuration? if let configData = try? Data(contentsOf: configFile) { @@ -51,13 +51,14 @@ struct JavaCompilerBuildToolPlugin: BuildToolPlugin { } let sourceFilePath = sourceFileURL.path - guard sourceFilePath.starts(with: sourceDir) else { + let sourceDirPath = sourceDir.path + guard sourceFilePath.starts(with: sourceDirPath) else { fatalError("Could not get relative path for source file \(sourceFilePath)") } return URL(filePath: context.pluginWorkDirectoryURL.path) .appending(path: "Java") - .appending(path: String(sourceFilePath.dropFirst(sourceDir.count))) + .appending(path: String(sourceFilePath.dropFirst(sourceDirPath.count))) .deletingPathExtension() .appendingPathExtension("class") } @@ -65,14 +66,19 @@ struct JavaCompilerBuildToolPlugin: BuildToolPlugin { let javaHome = URL(filePath: findJavaHome()) let javaClassFileURL = context.pluginWorkDirectoryURL .appending(path: "Java") + #if os(Windows) + let javac = "javac.exe" + #else + let javac = "javac" + #endif return [ .buildCommand( displayName: "Compiling \(javaFiles.count) Java files for target \(sourceModule.name) to \(javaClassFileURL)", executable: javaHome .appending(path: "bin") - .appending(path: "javac"), - arguments: javaFiles.map { $0.path(percentEncoded: false) } + [ - "-d", javaClassFileURL.path(), + .appending(path: javac), + arguments: javaFiles.map { $0.path } + [ + "-d", javaClassFileURL.path, "-parameters", // keep parameter names, which allows us to emit them in generated Swift decls ] + (config?.compilerVersionArgs ?? []), inputFiles: javaFiles, diff --git a/Samples/JavaKitSampleApp/Package.swift b/Samples/JavaKitSampleApp/Package.swift index 32451e925..881b8d0ba 100644 --- a/Samples/JavaKitSampleApp/Package.swift +++ b/Samples/JavaKitSampleApp/Package.swift @@ -35,9 +35,8 @@ let javaIncludePath = "\(javaHome)/include" let javaPlatformIncludePath = "\(javaIncludePath)/linux" #elseif os(macOS) let javaPlatformIncludePath = "\(javaIncludePath)/darwin" -#else - // TODO: Handle windows as well - #error("Currently only macOS and Linux platforms are supported, this may change in the future.") +#elseif os(Windows) + let javaPlatformIncludePath = "\(javaIncludePath)/win32)" #endif let package = Package( diff --git a/Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift b/Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift index edbddbb78..7443039a8 100644 --- a/Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift +++ b/Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift @@ -25,6 +25,14 @@ typealias JNIEnvPointer = UnsafeMutablePointer typealias JNIEnvPointer = UnsafeMutableRawPointer #endif +extension FileManager { +#if os(Windows) + static let pathSeparator = ";" +#else + static let pathSeparator = ":" +#endif +} + public final class JavaVirtualMachine: @unchecked Sendable { /// The JNI version that we depend on. static let jniVersion = JNI_VERSION_1_6 @@ -81,8 +89,8 @@ public final class JavaVirtualMachine: @unchecked Sendable { print("[warning][swift-java][JavaVirtualMachine] Missing classpath element: \(URL(fileURLWithPath: path).absoluteString)") // TODO: stderr } } - let colonSeparatedClassPath = classpath.joined(separator: ":") - allVMOptions.append("-Djava.class.path=\(colonSeparatedClassPath)") + let pathSeparatedClassPath = classpath.joined(separator: FileManager.pathSeparator) + allVMOptions.append("-Djava.class.path=\(pathSeparatedClassPath)") } allVMOptions.append(contentsOf: vmOptions) @@ -237,7 +245,7 @@ extension JavaVirtualMachine { ignoreUnrecognized: Bool = false, replace: Bool = false ) throws -> JavaVirtualMachine { - precondition(!classpath.contains(where: { $0.contains(":") }), "Classpath element must not contain `:`! Split the path into elements! Was: \(classpath)") + precondition(!classpath.contains(where: { $0.contains(FileManager.pathSeparator) }), "Classpath element must not contain `\(FileManager.pathSeparator)`! Split the path into elements! Was: \(classpath)") return try sharedJVM.withLock { (sharedJVMPointer: inout JavaVirtualMachine?) in // If we already have a JavaVirtualMachine instance, return it. diff --git a/Sources/SwiftJava/JavaKitVM/ThreadLocalStorage.swift b/Sources/SwiftJava/JavaKitVM/ThreadLocalStorage.swift index 7ea0b50a8..037e328b7 100644 --- a/Sources/SwiftJava/JavaKitVM/ThreadLocalStorage.swift +++ b/Sources/SwiftJava/JavaKitVM/ThreadLocalStorage.swift @@ -56,7 +56,7 @@ package struct ThreadLocalStorage: ~Copyable { _key = 0 pthread_key_create(&_key, onThreadExit) #elseif canImport(WinSDK) - key = FlsAlloc(onThreadExit) + _key = FlsAlloc(onThreadExit) #endif } diff --git a/Sources/_Subprocess/Platforms/Subprocess+Windows.swift b/Sources/_Subprocess/Platforms/Subprocess+Windows.swift index aaf53ea01..e84d14148 100644 --- a/Sources/_Subprocess/Platforms/Subprocess+Windows.swift +++ b/Sources/_Subprocess/Platforms/Subprocess+Windows.swift @@ -1213,8 +1213,7 @@ extension UInt8 { extension OutputProtocol { internal func output(from data: [UInt8]) throws -> OutputType { return try data.withUnsafeBytes { - let span = RawSpan(_unsafeBytes: $0) - return try self.output(from: span) + return try self.output(from: $0) } } } From 3173f82db004ad6068ffc4779ddc7a7936a12559 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 21 Oct 2025 11:46:45 +0200 Subject: [PATCH 089/141] Fix release build and enable benchmarks (#406) --- .github/workflows/pull_request.yml | 22 ++++++++++++++++++++++ Package.swift | 3 ++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 35cdd4488..f2a1db716 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -112,6 +112,28 @@ jobs: - name: Gradle compile JMH benchmarks run: ./gradlew compileJmh --info + benchmark-swift: + name: Benchmark (Swift) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + swift_version: ['6.2.0'] + os_version: ['jammy'] + jdk_vendor: ['corretto'] + container: + image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} + env: + SWIFT_JAVA_VERBOSE: true + steps: + - uses: actions/checkout@v4 + - name: Prepare CI Environment + uses: ./.github/actions/prepare_env + - name: Install jemalloc + run: apt-get update && apt-get install -y libjemalloc-dev + - name: Swift Benchmarks + run: swift package --package-path Benchmarks/ benchmark + test-swift: name: Test (Swift) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) runs-on: ubuntu-latest diff --git a/Package.swift b/Package.swift index 52ec7ae7f..f6cc83e7b 100644 --- a/Package.swift +++ b/Package.swift @@ -245,7 +245,8 @@ let package = Package( exclude: ["swift-java.config"], swiftSettings: [ .swiftLanguageMode(.v5), - .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"], .when(platforms: [.macOS, .linux, .windows])) + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"], .when(platforms: [.macOS, .linux, .windows])), + .unsafeFlags(["-Xfrontend", "-sil-verify-none"], .when(configuration: .release)), // Workaround for https://github.com/swiftlang/swift/issues/84899 ], linkerSettings: [ .unsafeFlags( From 782af4738b2fb79755bb9c797a26d818d3e87040 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Wed, 22 Oct 2025 23:13:15 +0200 Subject: [PATCH 090/141] Change `SwiftKitCore` to Java 11 instead of 17 (#407) --- SwiftKitCore/build.gradle | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/SwiftKitCore/build.gradle b/SwiftKitCore/build.gradle index 82874e20c..b61074a6a 100644 --- a/SwiftKitCore/build.gradle +++ b/SwiftKitCore/build.gradle @@ -43,11 +43,13 @@ publishing { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(17)) + languageVersion.set(JavaLanguageVersion.of(24)) } - // Support Android 6+ (Java 7) - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 +} + +tasks.withType(JavaCompile).configureEach { + // Support Java 11 + options.release.set(11) } dependencies { From d006f9a785738a1287fd7f6e7a67e9732e635cad Mon Sep 17 00:00:00 2001 From: Nakaoka Rei Date: Fri, 24 Oct 2025 12:13:04 +1300 Subject: [PATCH 091/141] docs: fix typo in README.md (#408) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ff643c49c..486587477 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ This repository contains two approaches to Swift/Java interoperability. - Swift library (`SwiftJava`) and bindings generator that allows a Swift program to make use of Java libraries by wrapping Java classes in corresponding Swift types, allowing Swift to directly call any wrapped Java API. -- The `swift-java` tool which which offers automated ways to import or "extract" bindings to sources or libraries in either language. The results are bindings for Swift or Java. +- The `swift-java` tool which offers automated ways to import or "extract" bindings to sources or libraries in either language. The results are bindings for Swift or Java. ## :construction: Early Development :construction: From f7485cc6e7864276061bc5b748a13fa8ca721c57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Bukowiecki?= Date: Fri, 24 Oct 2025 01:13:26 +0200 Subject: [PATCH 092/141] Fix passed JExtractGenerationMode not being used by JExtractCommand. (#404) Co-authored-by: pelekon <13712101+pelekon@users.noreply.github.com> --- Sources/SwiftJavaTool/Commands/JExtractCommand.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index ec7c0aeb7..c730cbf6e 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -40,7 +40,7 @@ extension SwiftJava { @OptionGroup var commonOptions: SwiftJava.CommonOptions @Option(help: "The mode of generation to use for the output files. Used with jextract mode.") - var mode: JExtractGenerationMode = .ffm + var mode: JExtractGenerationMode? @Option(help: "The name of the Swift module into which the resulting Swift types will be generated.") var swiftModule: String @@ -86,6 +86,11 @@ extension SwiftJava.JExtractCommand { if let javaPackage { config.javaPackage = javaPackage } + if let mode { + config.mode = mode + } else if config.mode == nil { + config.mode = .ffm + } config.swiftModule = self.effectiveSwiftModule config.outputJavaDirectory = outputJava config.outputSwiftDirectory = outputSwift From cfbcf6c62dd8a81b6f240cc771464beeec5138d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Bukowiecki?= Date: Fri, 24 Oct 2025 04:10:09 +0200 Subject: [PATCH 093/141] Properly extract usage of Data type from FoundationEssentials. (#405) Co-authored-by: pelekon <13712101+pelekon@users.noreply.github.com> --- .../FFM/CDeclLowering/CRepresentation.swift | 4 +- ...Swift2JavaGenerator+FunctionLowering.swift | 10 +-- ...MSwift2JavaGenerator+JavaTranslation.swift | 6 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 46 +++++++++- .../JNI/JNIJavaTypeTranslator.swift | 2 +- .../Swift2JavaTranslator.swift | 8 +- .../SwiftTypes/DependencyScanner.swift | 65 ++++++++++++++- .../SwiftTypes/ImportedSwiftModule.swift | 36 ++++++++ .../SwiftTypes/SwiftKnownModules.swift | 28 ++++++- .../SwiftTypes/SwiftKnownTypeDecls.swift | 8 +- .../SwiftTypes/SwiftKnownTypes.swift | 9 +- .../SwiftTypes/SwiftModuleSymbolTable.swift | 20 +++++ .../SwiftParsedModuleSymbolTableBuilder.swift | 13 ++- .../SwiftTypes/SwiftSymbolTable.swift | 21 +++-- .../JExtractSwiftTests/DataImportTests.swift | 83 ++++++++++++++----- 15 files changed, 303 insertions(+), 56 deletions(-) create mode 100644 Sources/JExtractSwiftLib/SwiftTypes/ImportedSwiftModule.swift diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift index 193b545f9..2621dd8bc 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift @@ -125,7 +125,9 @@ extension SwiftKnownTypeDeclKind { .qualified(const: true, volatile: false, type: .void) ) case .void: .void - case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string, .data, .dataProtocol, .optional: + case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, + .unsafeBufferPointer, .unsafeMutableBufferPointer, .string, .foundationData, .foundationDataProtocol, + .essentialsData, .essentialsDataProtocol, .optional: nil } } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index 5dfa5b71e..7c8d7ab21 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -282,7 +282,7 @@ struct CdeclLowering { ) } - case .data: + case .foundationData, .essentialsData: break default: @@ -375,7 +375,7 @@ struct CdeclLowering { case .nominal(let nominal): if let knownType = nominal.nominalTypeDecl.knownTypeKind { switch knownType { - case .data: + case .foundationData, .essentialsData: break case .unsafeRawPointer, .unsafeMutableRawPointer: throw LoweringError.unhandledType(.optional(wrappedType)) @@ -387,7 +387,7 @@ struct CdeclLowering { throw LoweringError.unhandledType(.optional(wrappedType)) case .void, .string: throw LoweringError.unhandledType(.optional(wrappedType)) - case .dataProtocol: + case .foundationDataProtocol, .essentialsDataProtocol: throw LoweringError.unhandledType(.optional(wrappedType)) default: // Unreachable? Should be handled by `CType(cdeclType:)` lowering above. @@ -505,7 +505,7 @@ struct CdeclLowering { ]) ) - case .data: + case .foundationData, .essentialsData: break default: @@ -606,7 +606,7 @@ struct CdeclLowering { case .void: return LoweredResult(cdeclResultType: .void, cdeclOutParameters: [], conversion: .placeholder) - case .data: + case .foundationData, .essentialsData: break case .string, .optional: diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 438393cb3..5d38a0f95 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -411,7 +411,7 @@ extension FFMSwift2JavaGenerator { conversion: .call(.placeholder, function: "SwiftRuntime.toCString", withArena: true) ) - case .data: + case .foundationData, .essentialsData: break default: @@ -515,7 +515,7 @@ extension FFMSwift2JavaGenerator { case .nominal(let nominal): if let knownType = nominal.nominalTypeDecl.knownTypeKind { switch knownType { - case .data, .dataProtocol: + case .foundationData, .foundationDataProtocol, .essentialsData, .essentialsDataProtocol: break default: throw JavaTranslationError.unhandledType(.optional(swiftType)) @@ -658,7 +658,7 @@ extension FFMSwift2JavaGenerator { ) ) - case .data: + case .foundationData, .essentialsData: break case .unsafePointer, .unsafeMutablePointer: diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift index f35973eb9..e43ea4c51 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -125,11 +125,55 @@ extension FFMSwift2JavaGenerator { } func printSwiftThunkImports(_ printer: inout CodePrinter) { + let mainSymbolSourceModules = Set( + self.lookupContext.symbolTable.importedModules.values.filter { $0.alternativeModules?.isMainSourceOfSymbols ?? false }.map(\.moduleName) + ) + for module in self.lookupContext.symbolTable.importedModules.keys.sorted() { guard module != "Swift" else { continue } - printer.print("import \(module)") + + guard let alternativeModules = self.lookupContext.symbolTable.importedModules[module]?.alternativeModules else { + printer.print("import \(module)") + continue + } + + // Try to print only on main module from relation chain as it has every other module. + guard !mainSymbolSourceModules.isDisjoint(with: alternativeModules.moduleNames) || alternativeModules.isMainSourceOfSymbols else { + if !alternativeModules.isMainSourceOfSymbols { + printer.print("import \(module)") + } + continue + } + + var importGroups: [String: [String]] = [:] + for name in alternativeModules.moduleNames { + guard let otherModule = self.lookupContext.symbolTable.importedModules[name] else { continue } + + let groupKey = otherModule.requiredAvailablityOfModuleWithName ?? otherModule.moduleName + importGroups[groupKey, default: []].append(otherModule.moduleName) + } + + for (index, group) in importGroups.keys.sorted().enumerated() { + if index > 0 && importGroups.keys.count > 1 { + printer.print("#elseif canImport(\(group))") + } else { + printer.print("#if canImport(\(group))") + } + + for groupModule in importGroups[group] ?? [] { + printer.print("import \(groupModule)") + } + } + + if (importGroups.keys.isEmpty) { + printer.print("import \(module)") + } else { + printer.print("#else") + printer.print("import \(module)") + printer.print("#endif") + } } printer.println() } diff --git a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift index 983396ac9..0d1fc675a 100644 --- a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift @@ -50,7 +50,7 @@ enum JNIJavaTypeTranslator { .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, - .optional, .data, .dataProtocol: + .optional, .foundationData, .foundationDataProtocol, .essentialsData, .essentialsDataProtocol: return nil } } diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index ccbbd2745..9dcba3407 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -108,10 +108,10 @@ extension Swift2JavaTranslator { visitor.visit(sourceFile: input.syntax) } - // If any API uses 'Foundation.Data', import 'Data' as if it's declared in - // this module. - if let dataDecl = self.symbolTable[.data] { - let dataProtocolDecl = self.symbolTable[.dataProtocol]! + // If any API uses 'Foundation.Data' or 'FoundationEssentials.Data', + // import 'Data' as if it's declared in this module. + if let dataDecl = self.symbolTable[.foundationData] ?? self.symbolTable[.essentialsData] { + let dataProtocolDecl = (self.symbolTable[.foundationDataProtocol] ?? self.symbolTable[.essentialsDataProtocol])! if self.isUsing(where: { $0 == dataDecl || $0 == dataProtocolDecl }) { visitor.visit(nominalDecl: dataDecl.syntax!.asNominal!, in: nil) } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/DependencyScanner.swift b/Sources/JExtractSwiftLib/SwiftTypes/DependencyScanner.swift index 409c81a76..79abe9812 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/DependencyScanner.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/DependencyScanner.swift @@ -15,15 +15,74 @@ import SwiftSyntax /// Scan importing modules. -func importingModuleNames(sourceFile: SourceFileSyntax) -> [String] { - var importingModuleNames: [String] = [] +func importingModules(sourceFile: SourceFileSyntax) -> [ImportedSwiftModule] { + var importingModuleNames: [ImportedSwiftModule] = [] for item in sourceFile.statements { if let importDecl = item.item.as(ImportDeclSyntax.self) { guard let moduleName = importDecl.path.first?.name.text else { continue } - importingModuleNames.append(moduleName) + importingModuleNames.append(ImportedSwiftModule(name: moduleName, availableWithModuleName: nil, alternativeModuleNames: [])) + } else if let ifConfigDecl = item.item.as(IfConfigDeclSyntax.self) { + importingModuleNames.append(contentsOf: modules(from: ifConfigDecl)) } } return importingModuleNames } + +private func modules(from ifConfigDecl: IfConfigDeclSyntax) -> [ImportedSwiftModule] { + guard + let firstClause = ifConfigDecl.clauses.first, + let calledExpression = firstClause.condition?.as(FunctionCallExprSyntax.self)?.calledExpression.as(DeclReferenceExprSyntax.self), + calledExpression.baseName.text == "canImport" else { + return [] + } + + var modules: [ImportedSwiftModule] = [] + modules.reserveCapacity(ifConfigDecl.clauses.count) + + for (index, clause) in ifConfigDecl.clauses.enumerated() { + let importedModuleNames = clause.elements?.as(CodeBlockItemListSyntax.self)? + .compactMap { CodeBlockItemSyntax($0) } + .compactMap { $0.item.as(ImportDeclSyntax.self) } + .compactMap { $0.path.first?.name.text } ?? [] + + let importModuleName: String? = if + let funcCallExpr = clause.condition?.as(FunctionCallExprSyntax.self), + let calledDeclReference = funcCallExpr.calledExpression.as(DeclReferenceExprSyntax.self), + calledDeclReference.baseName.text == "canImport", + let moduleNameSyntax = funcCallExpr.arguments.first?.expression.as(DeclReferenceExprSyntax.self) { + moduleNameSyntax.baseName.text + } else { + nil + } + + let clauseModules = importedModuleNames.map { + ImportedSwiftModule(name: $0, + availableWithModuleName: importModuleName, + alternativeModuleNames: []) + } + + // Assume single import from #else statement is fallback and use it as main source of symbols + if + clauseModules.count == 1 && + index == (ifConfigDecl.clauses.count - 1) && + clause.poundKeyword.tokenKind == .poundElse { + var fallbackModule: ImportedSwiftModule = clauseModules[0] + var moduleNames: [String] = [] + moduleNames.reserveCapacity(modules.count) + + for i in 0.. + var isMainSourceOfSymbols: Bool + + init(name: String, availableWithModuleName: String? = nil, alternativeModuleNames: Set = [], isMainSourceOfSymbols: Bool = false) { + self.name = name + self.availableWithModuleName = availableWithModuleName + self.alternativeModuleNames = alternativeModuleNames + self.isMainSourceOfSymbols = isMainSourceOfSymbols + } + + static func ==(lhs: Self, rhs: Self) -> Bool { + lhs.name == rhs.name + } + + func hash(into hasher: inout Hasher) { + hasher.combine(name) + } +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift index 938d0c4aa..45d5df5aa 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift @@ -18,6 +18,7 @@ import SwiftSyntaxBuilder enum SwiftKnownModule: String { case swift = "Swift" case foundation = "Foundation" + case foundationEssentials = "FoundationEssentials" var name: String { return self.rawValue @@ -27,13 +28,15 @@ enum SwiftKnownModule: String { return switch self { case .swift: swiftSymbolTable case .foundation: foundationSymbolTable + case .foundationEssentials: foundationEssentialsSymbolTable } } var sourceFile: SourceFileSyntax { return switch self { case .swift: swiftSourceFile - case .foundation: foundationSourceFile + case .foundation: foundationEssentialsSourceFile + case .foundationEssentials: foundationEssentialsSourceFile } } } @@ -44,8 +47,21 @@ private var swiftSymbolTable: SwiftModuleSymbolTable { return builder.finalize() } +private var foundationEssentialsSymbolTable: SwiftModuleSymbolTable { + var builder = SwiftParsedModuleSymbolTableBuilder( + moduleName: "FoundationEssentials", + requiredAvailablityOfModuleWithName: "FoundationEssentials", + alternativeModules: .init(isMainSourceOfSymbols: false, moduleNames: ["Foundation"]), + importedModules: ["Swift": swiftSymbolTable]) + builder.handle(sourceFile: foundationEssentialsSourceFile) + return builder.finalize() +} + private var foundationSymbolTable: SwiftModuleSymbolTable { - var builder = SwiftParsedModuleSymbolTableBuilder(moduleName: "Foundation", importedModules: ["Swift": swiftSymbolTable]) + var builder = SwiftParsedModuleSymbolTableBuilder( + moduleName: "Foundation", + alternativeModules: .init(isMainSourceOfSymbols: true, moduleNames: ["FoundationEssentials"]), + importedModules: ["Swift": swiftSymbolTable]) builder.handle(sourceFile: foundationSourceFile) return builder.finalize() } @@ -87,7 +103,7 @@ private let swiftSourceFile: SourceFileSyntax = """ } """ -private let foundationSourceFile: SourceFileSyntax = """ +private let foundationEssentialsSourceFile: SourceFileSyntax = """ public protocol DataProtocol {} public struct Data: DataProtocol { @@ -96,3 +112,9 @@ private let foundationSourceFile: SourceFileSyntax = """ public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) -> Void) } """ + +private var foundationSourceFile: SourceFileSyntax { + // On platforms other than Darwin, imports such as FoundationEssentials, FoundationNetworking, etc. are used, + // so this file should be created by combining the files of the aforementioned modules. + foundationEssentialsSourceFile +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift index 11ff25c4d..809f2aa16 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift @@ -15,6 +15,7 @@ import SwiftSyntax enum SwiftKnownTypeDeclKind: String, Hashable { + // Swift case bool = "Swift.Bool" case int = "Swift.Int" case uint = "Swift.UInt" @@ -40,8 +41,11 @@ enum SwiftKnownTypeDeclKind: String, Hashable { case void = "Swift.Void" case string = "Swift.String" - case dataProtocol = "Foundation.DataProtocol" - case data = "Foundation.Data" + // Foundation + case foundationDataProtocol = "Foundation.DataProtocol" + case essentialsDataProtocol = "FoundationEssentials.DataProtocol" + case foundationData = "Foundation.Data" + case essentialsData = "FoundationEssentials.Data" var moduleAndName: (module: String, name: String) { let qualified = self.rawValue diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift index 8c70f7e28..25b1135ae 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift @@ -35,8 +35,10 @@ struct SwiftKnownTypes { var unsafeRawPointer: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.unsafeRawPointer])) } var unsafeMutableRawPointer: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.unsafeMutableRawPointer])) } - var dataProtocol: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.dataProtocol])) } - var data: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.data])) } + var foundationDataProtocol: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.foundationDataProtocol])) } + var foundationData: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.foundationData])) } + var essentialsDataProtocol: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.essentialsDataProtocol])) } + var essentialsData: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.essentialsData])) } func unsafePointer(_ pointeeType: SwiftType) -> SwiftType { .nominal( @@ -78,7 +80,8 @@ struct SwiftKnownTypes { /// given protocol kind. E.g. `String` for `StringProtocol` func representativeType(of knownProtocol: SwiftKnownTypeDeclKind) -> SwiftType? { switch knownProtocol { - case .dataProtocol: return self.data + case .foundationDataProtocol: return self.foundationData + case .essentialsDataProtocol: return self.essentialsData default: return nil } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftModuleSymbolTable.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftModuleSymbolTable.swift index f1bbaa12a..6328045f5 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftModuleSymbolTable.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftModuleSymbolTable.swift @@ -19,6 +19,12 @@ struct SwiftModuleSymbolTable: SwiftSymbolTableProtocol { /// The name of this module. let moduleName: String + /// The name of module required to be imported and checked via canImport statement. + let requiredAvailablityOfModuleWithName: String? + + /// Data about alternative modules which provides desired symbos e.g. FoundationEssentials is non-Darwin platform alternative for Foundation + let alternativeModules: AlternativeModuleNamesData? + /// The top-level nominal types, found by name. var topLevelTypes: [String: SwiftNominalTypeDeclaration] = [:] @@ -36,4 +42,18 @@ struct SwiftModuleSymbolTable: SwiftSymbolTableProtocol { func lookupNestedType(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftNominalTypeDeclaration? { nestedTypes[parent]?[name] } + + func isAlternative(for moduleName: String) -> Bool { + alternativeModules.flatMap { $0.moduleNames.contains(moduleName) } ?? false + } } + +extension SwiftModuleSymbolTable { + struct AlternativeModuleNamesData { + /// Flag indicating module should be used as source of symbols to avoid duplication of symbols. + let isMainSourceOfSymbols: Bool + + /// Names of modules which are alternative for currently checked module. + let moduleNames: Set + } +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift index 9b8ef2366..8abb21f5f 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift @@ -26,9 +26,18 @@ struct SwiftParsedModuleSymbolTableBuilder { /// Extension decls their extended type hasn't been resolved. var unresolvedExtensions: [ExtensionDeclSyntax] - init(moduleName: String, importedModules: [String: SwiftModuleSymbolTable], log: Logger? = nil) { + init( + moduleName: String, + requiredAvailablityOfModuleWithName: String? = nil, + alternativeModules: SwiftModuleSymbolTable.AlternativeModuleNamesData? = nil, + importedModules: [String: SwiftModuleSymbolTable], + log: Logger? = nil + ) { self.log = log - self.symbolTable = .init(moduleName: moduleName) + self.symbolTable = .init( + moduleName: moduleName, + requiredAvailablityOfModuleWithName: requiredAvailablityOfModuleWithName, + alternativeModules: alternativeModules) self.importedModules = importedModules self.unresolvedExtensions = [] } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift index 239d38c4d..4271e2975 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift @@ -42,6 +42,9 @@ package class SwiftSymbolTable { let parsedModule:SwiftModuleSymbolTable private var knownTypeToNominal: [SwiftKnownTypeDeclKind: SwiftNominalTypeDeclaration] = [:] + private var prioritySortedImportedModules: [SwiftModuleSymbolTable] { + importedModules.values.sorted(by: { ($0.alternativeModules?.isMainSourceOfSymbols ?? false) && $0.moduleName < $1.moduleName }) + } init(parsedModule: SwiftModuleSymbolTable, importedModules: [String: SwiftModuleSymbolTable]) { self.parsedModule = parsedModule @@ -58,18 +61,22 @@ extension SwiftSymbolTable { // Prepare imported modules. // FIXME: Support arbitrary dependencies. - var moduleNames: Set = [] + var modules: Set = [] for sourceFile in sourceFiles { - moduleNames.formUnion(importingModuleNames(sourceFile: sourceFile)) + modules.formUnion(importingModules(sourceFile: sourceFile)) } var importedModules: [String: SwiftModuleSymbolTable] = [:] importedModules[SwiftKnownModule.swift.name] = SwiftKnownModule.swift.symbolTable - for moduleName in moduleNames.sorted() { + for module in modules { + // We don't need duplicates of symbols, first known definition is enough to parse module + // e.g Data from FoundationEssentials and Foundation collide and lead to different results due to random order of keys in Swift's Dictionary + // guard module.isMainSourceOfSymbols || !importedModules.contains(where: { $0.value.isAlternative(for: String)}) else { continue } + if - importedModules[moduleName] == nil, - let knownModule = SwiftKnownModule(rawValue: moduleName) + importedModules[module.name] == nil, + let knownModule = SwiftKnownModule(rawValue: module.name) { - importedModules[moduleName] = knownModule.symbolTable + importedModules[module.name] = knownModule.symbolTable } } @@ -95,7 +102,7 @@ extension SwiftSymbolTable: SwiftSymbolTableProtocol { return parsedResult } - for importedModule in importedModules.values { + for importedModule in prioritySortedImportedModules { if let result = importedModule.lookupTopLevelNominalType(name) { return result } diff --git a/Tests/JExtractSwiftTests/DataImportTests.swift b/Tests/JExtractSwiftTests/DataImportTests.swift index cf4ed3872..bad4174db 100644 --- a/Tests/JExtractSwiftTests/DataImportTests.swift +++ b/Tests/JExtractSwiftTests/DataImportTests.swift @@ -16,7 +16,14 @@ import JExtractSwiftLib import Testing final class DataImportTests { - let data_interfaceFile = + private static let ifConfigImport = """ + #if canImport(FoundationEssentials) + import FoundationEssentials + #else + import Foundation + #endif + """ + private static let foundationData_interfaceFile = """ import Foundation @@ -24,23 +31,53 @@ final class DataImportTests { public func returnData() -> Data """ - let dataProtocol_interfaceFile = + private static let foundationDataProtocol_interfaceFile = """ import Foundation public func receiveDataProtocol(dat: some DataProtocol, dat2: T?) """ + private static let essentialsData_interfaceFile = + """ + import FoundationEssentials + + public func receiveData(dat: Data) + public func returnData() -> Data + """ - @Test("Import Data: Swift thunks") - func data_swiftThunk() throws { + private static let essentialsDataProtocol_interfaceFile = + """ + import FoundationEssentials + + public func receiveDataProtocol(dat: some DataProtocol, dat2: T?) + """ + private static let ifConfigData_interfaceFile = + """ + \(ifConfigImport) + + public func receiveData(dat: Data) + public func returnData() -> Data + """ + + private static let ifConfigDataProtocol_interfaceFile = + """ + \(ifConfigImport) + + public func receiveDataProtocol(dat: some DataProtocol, dat2: T?) + """ + + @Test("Import Data: Swift thunks", arguments: zip( + [Self.foundationData_interfaceFile, Self.essentialsData_interfaceFile, Self.ifConfigData_interfaceFile], + ["import Foundation", "import FoundationEssentials", Self.ifConfigImport] + )) + func data_swiftThunk(fileContent: String, expectedImportChunk: String) throws { try assertOutput( - input: data_interfaceFile, .ffm, .swift, + input: fileContent, .ffm, .swift, + detectChunkByInitialLines: 10, expectedChunks: [ - """ - import Foundation - """, + expectedImportChunk, """ @_cdecl("swiftjava_SwiftModule_receiveData_dat") public func swiftjava_SwiftModule_receiveData_dat(_ dat: UnsafeRawPointer) { @@ -87,11 +124,12 @@ final class DataImportTests { ) } - @Test("Import Data: JavaBindings") - func data_javaBindings() throws { - + @Test("Import Data: JavaBindings", arguments: [ + Self.foundationData_interfaceFile, Self.essentialsData_interfaceFile, Self.ifConfigData_interfaceFile + ]) + func data_javaBindings(fileContent: String) throws { try assertOutput( - input: data_interfaceFile, .ffm, .java, + input: fileContent, .ffm, .java, expectedChunks: [ """ /** @@ -333,14 +371,15 @@ final class DataImportTests { ) } - @Test("Import DataProtocol: Swift thunks") - func dataProtocol_swiftThunk() throws { + @Test("Import DataProtocol: Swift thunks", arguments: zip( + [Self.foundationDataProtocol_interfaceFile, Self.essentialsDataProtocol_interfaceFile, Self.ifConfigDataProtocol_interfaceFile], + ["import Foundation", "import FoundationEssentials", Self.ifConfigImport] + )) + func dataProtocol_swiftThunk(fileContent: String, expectedImportChunk: String) throws { try assertOutput( - input: dataProtocol_interfaceFile, .ffm, .swift, + input: fileContent, .ffm, .swift, expectedChunks: [ - """ - import Foundation - """, + expectedImportChunk, """ @_cdecl("swiftjava_SwiftModule_receiveDataProtocol_dat_dat2") public func swiftjava_SwiftModule_receiveDataProtocol_dat_dat2(_ dat: UnsafeRawPointer, _ dat2: UnsafeRawPointer?) { @@ -356,11 +395,13 @@ final class DataImportTests { ) } - @Test("Import DataProtocol: JavaBindings") - func dataProtocol_javaBindings() throws { + @Test("Import DataProtocol: JavaBindings", arguments: [ + Self.foundationDataProtocol_interfaceFile, Self.essentialsDataProtocol_interfaceFile, Self.ifConfigDataProtocol_interfaceFile + ]) + func dataProtocol_javaBindings(fileContent: String) throws { try assertOutput( - input: dataProtocol_interfaceFile, .ffm, .java, + input: fileContent, .ffm, .java, expectedChunks: [ """ /** From e072b2e35528fe364bdd6ab5527f4c7516f2fec6 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 24 Oct 2025 11:38:42 +0900 Subject: [PATCH 094/141] Revise project status and API stability notes in README Updated README to clarify project status and API stability. --- README.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 486587477..7f25914d7 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,9 @@ This repository contains two approaches to Swift/Java interoperability. ## :construction: Early Development :construction: -**:construction: This is a *very early* prototype and everything is subject to change. :construction:** +**:construction: This project is in early development, please keep this in mind as you try out the project and do provide feedback about any issues you encounter. :construction:** -Parts of this project are incomplete, not fleshed out, and subject to change without any notice. - -The primary purpose of this repository is to create an environment for collaboration and joint exploration of the Swift/Java interoperability story. The project will transition to a more structured approach once key goals have been outlined. +There is no guarantee about API stability of this package, neither in the Java or Swift parts, until the project releases a stable 1.0 release; APIs may change without prior notice. ### :construction: Self-publish support Java libraries (SwiftKit) @@ -66,7 +64,7 @@ SwiftJava is a Swift library offering macros which simplify writing JNI code "by It is possible to generate Swift bindings to Java libraries using SwiftJava by using the `swift-java wrap-java` command. Required language/runtime versions: -- **JDK 17+**, any recent JDK installation should be sufficient, as only general reflection and JNI APIs are used by this integration +- **JDK 17+**, any recent JDK installation should be sufficient, as only general reflection and JNI APIs are used by this integratio - **Swift 6.0.x**, because the library uses modern Swift macros **swift-java jextract** @@ -83,8 +81,7 @@ This is the primary way we envision calling Swift code from server-side Java lib Required language/runtime versions: - **Swift 6.1**, because of dependence on rich swift interface files -- **JDK 24+** - - We are validating the implementation using the currently supported non-LTE release, which at present means JDK-24. +- **JDK 25+**, the most recent LTS release which includes the stable Foreign Function and Memory APIs. ## swift-java jextract --mode=jni From d7e195949c3bb625c6c6e2944dc7ce3f88d96214 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 24 Oct 2025 15:17:57 +0900 Subject: [PATCH 095/141] Update JDK from 24 to 25 (#409) --- .github/actions/prepare_env/action.yml | 10 +++++----- ...d-logic.java-common-conventions.gradle.kts | 2 +- README.md | 13 ++++++------ Samples/SwiftAndJavaJarSampleLib/build.gradle | 17 ++++++++-------- .../SwiftAndJavaJarSampleLib/ci-validate.sh | 14 ++++++------- .../SwiftJavaExtractFFMSampleApp/build.gradle | 17 ++++++++-------- .../SwiftJavaExtractJNISampleApp/build.gradle | 17 ++++++++-------- SwiftKitCore/build.gradle | 3 ++- SwiftKitFFM/build.gradle | 3 ++- docker/Dockerfile | 2 +- docker/install_jdk.sh | 20 +++++++++---------- gradle.properties | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 13 files changed, 64 insertions(+), 58 deletions(-) diff --git a/.github/actions/prepare_env/action.yml b/.github/actions/prepare_env/action.yml index 3fdd5d4b1..90854db0b 100644 --- a/.github/actions/prepare_env/action.yml +++ b/.github/actions/prepare_env/action.yml @@ -12,7 +12,7 @@ runs: with: distribution: ${{ matrix.jdk_vendor }} java-version: | - 24 + 25 17 cache: 'gradle' - name: Set JAVA_HOME_{N} @@ -23,10 +23,10 @@ runs: elif [[ -n "$JAVA_HOME_21_ARM64" ]]; then echo "JAVA_HOME_21=$JAVA_HOME_21_ARM64" >> $GITHUB_ENV fi - if [[ -n "$JAVA_HOME_24_X64" ]]; then - echo "JAVA_HOME_24=$JAVA_HOME_24_X64" >> $GITHUB_ENV - elif [[ -n "$JAVA_HOME_24_ARM64" ]]; then - echo "JAVA_HOME_24=$JAVA_HOME_24_ARM64" >> $GITHUB_ENV + if [[ -n "$JAVA_HOME_25_X64" ]]; then + echo "JAVA_HOME_25=$JAVA_HOME_25_X64" >> $GITHUB_ENV + elif [[ -n "$JAVA_HOME_25_ARM64" ]]; then + echo "JAVA_HOME_25=$JAVA_HOME_25_ARM64" >> $GITHUB_ENV fi # - name: Check Java environment # shell: bash diff --git a/BuildLogic/src/main/kotlin/build-logic.java-common-conventions.gradle.kts b/BuildLogic/src/main/kotlin/build-logic.java-common-conventions.gradle.kts index df0a6633a..1f2df6e59 100644 --- a/BuildLogic/src/main/kotlin/build-logic.java-common-conventions.gradle.kts +++ b/BuildLogic/src/main/kotlin/build-logic.java-common-conventions.gradle.kts @@ -23,7 +23,7 @@ plugins { java { toolchain { - languageVersion = JavaLanguageVersion.of(24) + languageVersion = JavaLanguageVersion.of(25) } } diff --git a/README.md b/README.md index 7f25914d7..da56781c3 100644 --- a/README.md +++ b/README.md @@ -80,8 +80,9 @@ This does require the use of the relatively recent [JEP-454: Foreign Function & This is the primary way we envision calling Swift code from server-side Java libraries and applications. Required language/runtime versions: -- **Swift 6.1**, because of dependence on rich swift interface files -- **JDK 25+**, the most recent LTS release which includes the stable Foreign Function and Memory APIs. +- **Swift 6.1**, because of dependence on rich swift interface files +- **JDK 25+** + - We are validating the implementation using the currently supported non-LTE release, which at present means JDK-25. ## swift-java jextract --mode=jni @@ -101,7 +102,7 @@ This project contains multiple builds, living side by side together. You will need to have: - Swift (6.1.x+) -- Java (24+ for FFM, even though we support lower JDK targets) +- Java (25+ for FFM, even though we support lower JDK targets) - Gradle (installed by "Gradle wrapper" automatically when you run gradle through `./gradlew`) ### Preparing your environment @@ -120,12 +121,12 @@ however any recent enough Java distribution should work correctly. You can use s # Install sdkman from: https://sdkman.io curl -s "https://get.sdkman.io" | bash sdk install java 17.0.15-amzn -sdk install java 24.0.1-amzn +sdk install java 25.0.1-amzn -sdk use java 24.0.1-amzn +sdk use java 25.0.1-amzn ``` -The use of JDK 24 is required to build the project, even though the libraries being published may target lower Java versions. +The use of JDK 25 is required to build the project, even though the libraries being published may target lower Java versions. ❗️ Please make sure to `export JAVA_HOME` such that swift-java can find the necessary java libraries! When using sdkman the easiest way to export JAVA_HOME is to export the "current" used JDK's home, like this: diff --git a/Samples/SwiftAndJavaJarSampleLib/build.gradle b/Samples/SwiftAndJavaJarSampleLib/build.gradle index 96b25e224..3ad59fe5c 100644 --- a/Samples/SwiftAndJavaJarSampleLib/build.gradle +++ b/Samples/SwiftAndJavaJarSampleLib/build.gradle @@ -38,7 +38,7 @@ repositories { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(24)) + languageVersion.set(JavaLanguageVersion.of(25)) } } @@ -46,6 +46,7 @@ dependencies { implementation(project(':SwiftKitCore')) implementation(project(':SwiftKitFFM')) + testRuntimeOnly("org.junit.platform:junit-platform-launcher") // TODO: workaround for not finding junit: https://github.com/gradle/gradle/issues/34512 testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") } @@ -54,16 +55,16 @@ def swiftProductsWithJExtractPlugin() { def stdout = new ByteArrayOutputStream() def stderr = new ByteArrayOutputStream() - def result = exec { - commandLine 'swift', 'package', 'describe', '--type', 'json' - standardOutput = stdout - errorOutput = stderr - ignoreExitValue = true - } + def processBuilder = new ProcessBuilder('swift', 'package', 'describe', '--type', 'json') + def process = processBuilder.start() + + process.consumeProcessOutput(stdout, stderr) + process.waitFor() + def exitValue = process.exitValue() def jsonOutput = stdout.toString() - if (result.exitValue == 0) { + if (exitValue == 0) { def json = new JsonSlurper().parseText(jsonOutput) def products = json.targets .findAll { target -> diff --git a/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh b/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh index 4fde0ef05..2daddc615 100755 --- a/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh +++ b/Samples/SwiftAndJavaJarSampleLib/ci-validate.sh @@ -10,19 +10,19 @@ SWIFT_VERSION="$(swift -version | awk '/Swift version/ { print $3 }')" # This is how env variables are set by setup-java if [ "$(uname -m)" = 'arm64' ]; then ARCH=ARM64 - JAVAC="${JAVA_HOME_24_ARM64}/bin/javac" - JAVA="${JAVA_HOME_24_ARM64}/bin/java" + JAVAC="${JAVA_HOME_25_ARM64}/bin/javac" + JAVA="${JAVA_HOME_25_ARM64}/bin/java" else ARCH=X64 - JAVAC="${JAVA_HOME_24_X64}/bin/javac" - JAVA="${JAVA_HOME_24_X64}/bin/java" + JAVAC="${JAVA_HOME_25_X64}/bin/javac" + JAVA="${JAVA_HOME_25_X64}/bin/java" fi -if [ -n "$JAVA_HOME_24_$ARCH" ]; then - export JAVA_HOME="$JAVA_HOME_24_$ARCH" +if [ -n "$JAVA_HOME_25_$ARCH" ]; then + export JAVA_HOME="$JAVA_HOME_25_$ARCH" elif [ "$(uname -s)" = 'Linux' ] then - export PATH="${PATH}:/usr/lib/jvm/jdk-24/bin" # we need to make sure to use the latest JDK to actually compile/run the executable + export PATH="${PATH}:/usr/lib/jvm/jdk-25/bin" # we need to make sure to use the latest JDK to actually compile/run the executable fi # check if we can compile a plain Example file that uses the generated Java bindings that should be in the generated jar diff --git a/Samples/SwiftJavaExtractFFMSampleApp/build.gradle b/Samples/SwiftJavaExtractFFMSampleApp/build.gradle index 048c984de..0200f2341 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/build.gradle +++ b/Samples/SwiftJavaExtractFFMSampleApp/build.gradle @@ -31,7 +31,7 @@ repositories { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(24)) + languageVersion.set(JavaLanguageVersion.of(25)) } } @@ -39,16 +39,16 @@ def swiftProductsWithJExtractPlugin() { def stdout = new ByteArrayOutputStream() def stderr = new ByteArrayOutputStream() - def result = exec { - commandLine 'swift', 'package', 'describe', '--type', 'json' - standardOutput = stdout - errorOutput = stderr - ignoreExitValue = true - } + def processBuilder = new ProcessBuilder('swift', 'package', 'describe', '--type', 'json') + def process = processBuilder.start() + + process.consumeProcessOutput(stdout, stderr) + process.waitFor() + def exitValue = process.exitValue() def jsonOutput = stdout.toString() - if (result.exitValue == 0) { + if (exitValue == 0) { def json = new JsonSlurper().parseText(jsonOutput) def products = json.targets .findAll { target -> @@ -150,6 +150,7 @@ dependencies { implementation(project(':SwiftKitCore')) implementation(project(':SwiftKitFFM')) + testRuntimeOnly("org.junit.platform:junit-platform-launcher") // TODO: workaround for not finding junit: https://github.com/gradle/gradle/issues/34512 // TODO: workaround for not finding junit: https://github.com/gradle/gradle/issues/34512 testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") } diff --git a/Samples/SwiftJavaExtractJNISampleApp/build.gradle b/Samples/SwiftJavaExtractJNISampleApp/build.gradle index 54bd725af..b1aa84906 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/build.gradle +++ b/Samples/SwiftJavaExtractJNISampleApp/build.gradle @@ -32,7 +32,7 @@ repositories { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(24)) + languageVersion.set(JavaLanguageVersion.of(25)) } } @@ -40,16 +40,16 @@ def swiftProductsWithJExtractPlugin() { def stdout = new ByteArrayOutputStream() def stderr = new ByteArrayOutputStream() - def result = exec { - commandLine 'swift', 'package', 'describe', '--type', 'json' - standardOutput = stdout - errorOutput = stderr - ignoreExitValue = true - } + def processBuilder = new ProcessBuilder('swift', 'package', 'describe', '--type', 'json') + def process = processBuilder.start() + + process.consumeProcessOutput(stdout, stderr) + process.waitFor() + def exitValue = process.exitValue() def jsonOutput = stdout.toString() - if (result.exitValue == 0) { + if (exitValue == 0) { def json = new JsonSlurper().parseText(jsonOutput) def products = json.targets .findAll { target -> @@ -150,6 +150,7 @@ tasks.clean { dependencies { implementation(project(':SwiftKitCore')) + testRuntimeOnly("org.junit.platform:junit-platform-launcher") // TODO: workaround for not finding junit: https://github.com/gradle/gradle/issues/34512 testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") } diff --git a/SwiftKitCore/build.gradle b/SwiftKitCore/build.gradle index b61074a6a..9e4891dcd 100644 --- a/SwiftKitCore/build.gradle +++ b/SwiftKitCore/build.gradle @@ -43,7 +43,7 @@ publishing { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(24)) + languageVersion.set(JavaLanguageVersion.of(25)) } } @@ -53,6 +53,7 @@ tasks.withType(JavaCompile).configureEach { } dependencies { + testRuntimeOnly("org.junit.platform:junit-platform-launcher") // TODO: workaround for not finding junit: https://github.com/gradle/gradle/issues/34512 testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") } diff --git a/SwiftKitFFM/build.gradle b/SwiftKitFFM/build.gradle index d818586d5..c04b1017c 100644 --- a/SwiftKitFFM/build.gradle +++ b/SwiftKitFFM/build.gradle @@ -42,13 +42,14 @@ publishing { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(24)) + languageVersion.set(JavaLanguageVersion.of(25)) } } dependencies { implementation(project(':SwiftKitCore')) + testRuntimeOnly("org.junit.platform:junit-platform-launcher") // TODO: workaround for not finding junit: https://github.com/gradle/gradle/issues/34512 testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") } diff --git a/docker/Dockerfile b/docker/Dockerfile index c3568b54b..1109dab30 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -20,7 +20,7 @@ ENV LANGUAGE=en_US.UTF-8 # JDK dependency RUN curl -s "https://get.sdkman.io" | bash -RUN bash -c "source /root/.sdkman/bin/sdkman-init.sh && sdk install java 24.0.1-amzn" +RUN bash -c "source /root/.sdkman/bin/sdkman-init.sh && sdk install java 25.0.1-amzn" RUN curl -O https://download.swift.org/swiftly/linux/swiftly-$(uname -m).tar.gz && \ tar zxf swiftly-$(uname -m).tar.gz && \ diff --git a/docker/install_jdk.sh b/docker/install_jdk.sh index 8746e3abc..b69545f9c 100755 --- a/docker/install_jdk.sh +++ b/docker/install_jdk.sh @@ -14,7 +14,7 @@ ##===----------------------------------------------------------------------===## set -euo pipefail -# We need JDK 24 because that's the supported version with latest FFM +# We need JDK 25 because that's the supported version with latest FFM # However, we also need JDK 23 at most because Gradle does not support 24. # Supported JDKs: corretto @@ -36,9 +36,9 @@ download_and_install_jdk() { if [ "$JDK_VENDOR" = 'corretto' ]; then if [ "$(uname -m)" = 'aarch64' ]; then case "$jdk_version" in - "24") - jdk_url="https://corretto.aws/downloads/latest/amazon-corretto-24-aarch64-linux-jdk.tar.gz" - expected_md5="3b543f4e971350b73d0ab6d8174cc030" + "25") + jdk_url="https://corretto.aws/downloads/latest/amazon-corretto-25-aarch64-linux-jdk.tar.gz" + expected_md5="37588d5d2a24b26525b9c563ad65cc77" ;; *) echo "Unsupported JDK version: '$jdk_version'" @@ -47,9 +47,9 @@ download_and_install_jdk() { esac else case "$jdk_version" in - "24") - jdk_url="https://corretto.aws/downloads/latest/amazon-corretto-24-x64-linux-jdk.tar.gz" - expected_md5="130885ded3cbfc712fbe9f7dace45a52" + "25") + jdk_url="https://corretto.aws/downloads/latest/amazon-corretto-25-x64-linux-jdk.tar.gz" + expected_md5="7e56b1a9d71637ce4dc4047b23d0453e" ;; *) echo "Unsupported JDK version: '$jdk_version'" @@ -94,12 +94,12 @@ download_and_install_jdk() { cd "$HOME" } -# Usage: Install JDK 24 -download_and_install_jdk "24" +# Usage: Install JDK 25 +download_and_install_jdk "25" ls -la /usr/lib/jvm/ cd /usr/lib/jvm/ -ln -s jdk-24 default-jdk +ln -s jdk-25 default-jdk find . | grep java | grep bin echo "JAVA_HOME = /usr/lib/jvm/default-jdk" /usr/lib/jvm/default-jdk/bin/java -version \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 679a1a1fa..182db452b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -org.gradle.java.installations.fromEnv=JAVA_HOME_24,JAVA_HOME_24_X64,JAVA_HOME_24_ARM64 \ No newline at end of file +org.gradle.java.installations.fromEnv=JAVA_HOME_25,JAVA_HOME_25_X64,JAVA_HOME_25_ARM64 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ff23a68d7..2e1113280 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From 6168c628759ca85088d84fa0ae2c1e98d0ca8985 Mon Sep 17 00:00:00 2001 From: Dave Lester <18080+davelester@users.noreply.github.com> Date: Fri, 24 Oct 2025 13:55:21 -0700 Subject: [PATCH 096/141] Updates swift-java project README (#411) * Updates project README.md to clarify its current status, make the language around self-publication of supporting libraries more accessible, and remove the construction emoji that previously appeared. * Update README.md Co-authored-by: Honza Dvorsky --------- Co-authored-by: Dave Lester Co-authored-by: Konrad `ktoso` Malawski Co-authored-by: Honza Dvorsky --- README.md | 57 +++++++++++++++++++++++++++---------------------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index da56781c3..2688f288b 100644 --- a/README.md +++ b/README.md @@ -5,16 +5,32 @@ This repository contains two approaches to Swift/Java interoperability. - Swift library (`SwiftJava`) and bindings generator that allows a Swift program to make use of Java libraries by wrapping Java classes in corresponding Swift types, allowing Swift to directly call any wrapped Java API. - The `swift-java` tool which offers automated ways to import or "extract" bindings to sources or libraries in either language. The results are bindings for Swift or Java. -## :construction: Early Development :construction: +## Dependencies + +### Required JDK versions + +Note that this project consists of multiple modules which currently have different Swift and Java runtime requirements. + +You'll need to install the necessary JDK version locally. On macOS for example, you can install the JDK with [homebrew](https://brew.sh) using: + +```bash +$ brew install openjdk +# and create a symlink into /Library/Java/JavaVirtualMachines +$ sudo ln -sfn /opt/homebrew/opt/openjdk/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk.jdk -**:construction: This project is in early development, please keep this in mind as you try out the project and do provide feedback about any issues you encounter. :construction:** +# or if you have a distribution as cask it will be installed into /Library/Java/JavaVirtualMachines +$ brew install --cask corretto +``` -There is no guarantee about API stability of this package, neither in the Java or Swift parts, until the project releases a stable 1.0 release; APIs may change without prior notice. +Alternatively, you can use a JDK manager like [sdkman](https://sdkman.io/install/) and set your `JAVA_HOME` environment variable: + +```bash +$ export JAVA_HOME="$(sdk home java current)" +``` -### :construction: Self-publish support Java libraries (SwiftKit) +## Self-publish supporting Java libraries -While we work out how to provide the necessary support libraries for the Java side of Java code generated by `swift-java jextract`, -you will currently need to publish them locally and depend on them this way; +Swift-java relies on supporting libraries that are under active development and not yet published to Maven Central. To use the project, you'll need to self-publish these libraries locally so your Java project can depend on them. To publish the libraries to your local maven repository (`$HOME/.m2`), you can run: @@ -34,29 +50,6 @@ repositories { We anticipate simplifying this in the future. -## Dependencies - -### Required JDK versions - -This project consists of different modules which have different Swift and Java runtime requirements. - -On macOS for example, you can install the JDK with [homebrew](https://brew.sh) using - -```bash -$ brew install openjdk -# and create a symlink into /Library/Java/JavaVirtualMachines -$ sudo ln -sfn /opt/homebrew/opt/openjdk/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk.jdk - -# or if you have a distribution as cask it will be installed into /Library/Java/JavaVirtualMachines -$ brew install --cask corretto -``` - -or you can use a JDK manager like [sdkman](https://sdkman.io/install/) and set your `JAVA_HOME` environment variable - -```bash -$ export JAVA_HOME="$(sdk home java current)" -``` - ## SwiftJava macros SwiftJava is a Swift library offering macros which simplify writing JNI code "by hand" but also calling Java code from Swift. @@ -231,3 +224,9 @@ xcrun docc preview Sources/SwiftJavaDocumentation/Documentation.docc # Monitoring /Users/ktoso/code/swift-java/Sources/SwiftJavaDocumentation/Documentation.docc for changes... ``` + +## Project Status + +**This project is under active development. We welcome feedback about any issues you encounter.** + +There is no guarantee about API stability until the project reaches a 1.0 release. \ No newline at end of file From 89b2bcc8b66a17fd94779f0b9d6ceb2df32e120b Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Sat, 25 Oct 2025 07:19:45 +0900 Subject: [PATCH 097/141] jextract: generate one output swift file per input file (#403) --- .../JExtractSwiftPlugin.swift | 8 +- .../MySwiftLibrary/MultiplePublicTypes.swift | 26 ++++++ .../ci-validate.sh | 2 + .../MultipleTypesFromSingleFileTest.java | 36 +++++++++ .../MySwiftLibrary/MultiplePublicTypes.swift | 26 ++++++ .../SwiftJavaExtractJNISampleApp/build.gradle | 3 +- .../ci-validate.sh | 2 + .../MultipleTypesFromSingleFileTest.java | 36 +++++++++ Sources/JExtractSwiftLib/CodePrinter.swift | 34 ++++++-- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 47 ++++++++--- .../FFM/FFMSwift2JavaGenerator.swift | 20 ++--- Sources/JExtractSwiftLib/ImportedDecls.swift | 8 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 47 ++++++++--- .../JNI/JNISwift2JavaGenerator.swift | 6 +- .../Swift2JavaTranslator.swift | 29 +++---- .../JExtractSwiftLib/Swift2JavaVisitor.swift | 80 +++++++++++++------ .../SwiftTypes/SwiftFunctionSignature.swift | 2 +- .../SwiftTypes/SwiftKnownModules.swift | 6 +- .../SwiftNominalTypeDeclaration.swift | 27 ++++++- .../SwiftParsedModuleSymbolTableBuilder.swift | 26 +++--- .../SwiftTypes/SwiftSymbolTable.swift | 13 +-- .../SwiftTypes/SwiftTypeLookupContext.swift | 25 +++--- .../JavaKitVM/JavaVirtualMachine.swift | 2 +- .../Asserts/TextAssertions.swift | 2 +- .../FuncCallbackImportTests.swift | 6 +- .../FunctionDescriptorImportTests.swift | 4 +- .../MethodImportTests.swift | 18 ++--- .../SwiftSymbolTableTests.swift | 5 +- 28 files changed, 399 insertions(+), 147 deletions(-) create mode 100644 Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MultiplePublicTypes.swift create mode 100644 Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MultipleTypesFromSingleFileTest.java create mode 100644 Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MultiplePublicTypes.swift create mode 100644 Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MultipleTypesFromSingleFileTest.java diff --git a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift index 49ed3c4da..8d0be4559 100644 --- a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift +++ b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift @@ -93,6 +93,7 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { $0.pathExtension == "swift" } + // Output Swift files are just Java filename based converted to Swift files one-to-one var outputSwiftFiles: [URL] = swiftFiles.compactMap { sourceFileURL in guard sourceFileURL.isFileURL else { return nil as URL? @@ -102,7 +103,7 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { guard sourceFilePath.starts(with: sourceDir) else { fatalError("Could not get relative path for source file \(sourceFilePath)") } - var outputURL = outputSwiftDirectory + let outputURL = outputSwiftDirectory .appending(path: String(sourceFilePath.dropFirst(sourceDir.count).dropLast(sourceFileURL.lastPathComponent.count + 1))) let inputFileName = sourceFileURL.deletingPathExtension().lastPathComponent @@ -116,11 +117,12 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { // If the module uses 'Data' type, the thunk file is emitted as if 'Data' is declared // in that module. Declare the thunk file as the output. - // FIXME: Make this conditional. outputSwiftFiles += [ - outputSwiftDirectory.appending(path: "Data+SwiftJava.swift") + outputSwiftDirectory.appending(path: "Foundation+SwiftJava.swift") ] + print("[swift-java-plugin] Output swift files:\n - \(outputSwiftFiles.map({$0.absoluteString}).joined(separator: "\n - "))") + return [ .buildCommand( displayName: "Generate Java wrappers for Swift types", diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MultiplePublicTypes.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MultiplePublicTypes.swift new file mode 100644 index 000000000..234cb357e --- /dev/null +++ b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MultiplePublicTypes.swift @@ -0,0 +1,26 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// This file exists to exercise the swiftpm plugin generating separate output Java files +// for the public types; because Java public types must be in a file with the same name as the type. + +public struct PublicTypeOne { + public init() {} + public func test() {} +} + +public struct PublicTypeTwo { + public init() {} + public func test() {} +} \ No newline at end of file diff --git a/Samples/SwiftJavaExtractFFMSampleApp/ci-validate.sh b/Samples/SwiftJavaExtractFFMSampleApp/ci-validate.sh index c7a68d228..8758bbee0 100755 --- a/Samples/SwiftJavaExtractFFMSampleApp/ci-validate.sh +++ b/Samples/SwiftJavaExtractFFMSampleApp/ci-validate.sh @@ -3,5 +3,7 @@ set -x set -e +swift build # as a workaround for building swift build from within gradle having issues on CI sometimes + ./gradlew run ./gradlew test \ No newline at end of file diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MultipleTypesFromSingleFileTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MultipleTypesFromSingleFileTest.java new file mode 100644 index 000000000..d3cd791c0 --- /dev/null +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MultipleTypesFromSingleFileTest.java @@ -0,0 +1,36 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.*; +import org.swift.swiftkit.ffm.*; + +import java.lang.foreign.Arena; +import java.util.Optional; +import java.util.OptionalInt; + +import static org.junit.jupiter.api.Assertions.*; + +public class MultipleTypesFromSingleFileTest { + + @Test + void bothTypesMustHaveBeenGenerated() { + try (var arena = AllocatingSwiftArena.ofConfined()) { + PublicTypeOne.init(arena); + PublicTypeTwo.init(arena); + } + } +} \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MultiplePublicTypes.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MultiplePublicTypes.swift new file mode 100644 index 000000000..b7a02d787 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MultiplePublicTypes.swift @@ -0,0 +1,26 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// This file exists to exercise the swiftpm plugin generating separate output Java files +// for the public types; because Java public types must be in a file with the same name as the type. + +public struct PublicTypeOne { + public init() {} + public func test() {} +} + +public struct PublicTypeTwo { + public init() {} + public func test() {} +} \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/build.gradle b/Samples/SwiftJavaExtractJNISampleApp/build.gradle index b1aa84906..d92c96fbc 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/build.gradle +++ b/Samples/SwiftJavaExtractJNISampleApp/build.gradle @@ -102,7 +102,8 @@ def jextract = tasks.register("jextract", Exec) { workingDir = layout.projectDirectory commandLine "swift" - args("build") // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build + // TODO: -v for debugging build issues... + args("build", "-v") // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build // If we wanted to execute a specific subcommand, we can like this: // args("run",/* // "swift-java", "jextract", diff --git a/Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh b/Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh index c7a68d228..8758bbee0 100755 --- a/Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh +++ b/Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh @@ -3,5 +3,7 @@ set -x set -e +swift build # as a workaround for building swift build from within gradle having issues on CI sometimes + ./gradlew run ./gradlew test \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MultipleTypesFromSingleFileTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MultipleTypesFromSingleFileTest.java new file mode 100644 index 000000000..ae02b8727 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MultipleTypesFromSingleFileTest.java @@ -0,0 +1,36 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.ConfinedSwiftMemorySession; +import org.swift.swiftkit.core.SwiftArena; + +import java.lang.foreign.Arena; +import java.util.Optional; +import java.util.OptionalInt; + +import static org.junit.jupiter.api.Assertions.*; + +public class MultipleTypesFromSingleFileTest { + + @Test + void bothTypesMustHaveBeenGenerated() { + try (var arena = SwiftArena.ofConfined()) { + PublicTypeOne.init(arena); + PublicTypeTwo.init(arena); + } + } +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/CodePrinter.swift b/Sources/JExtractSwiftLib/CodePrinter.swift index 8c22fbee1..c5f04d51e 100644 --- a/Sources/JExtractSwiftLib/CodePrinter.swift +++ b/Sources/JExtractSwiftLib/CodePrinter.swift @@ -216,14 +216,28 @@ extension CodePrinter { /// - Returns: the output path of the generated file, if any (i.e. not in accumulate in memory mode) package mutating func writeContents( - outputDirectory: String, + outputDirectory _outputDirectory: String, javaPackagePath: String?, - filename: String + filename _filename: String ) throws -> URL? { + + // We handle 'filename' that has a path, since that simplifies passing paths from root output directory enourmously. + // This just moves the directory parts into the output directory part in order for us to create the sub-directories. + let outputDirectory: String + let filename: String + if _filename.contains(PATH_SEPARATOR) { + let parts = _filename.split(separator: PATH_SEPARATOR) + outputDirectory = _outputDirectory.appending(PATH_SEPARATOR).appending(parts.dropLast().joined(separator: PATH_SEPARATOR)) + filename = "\(parts.last!)" + } else { + outputDirectory = _outputDirectory + filename = _filename + } + guard self.mode != .accumulateAll else { // if we're accumulating everything, we don't want to finalize/flush any contents // let's mark that this is where a write would have happened though: - print("// ^^^^ Contents of: \(outputDirectory)/\(filename)") + print("// ^^^^ Contents of: \(outputDirectory)\(PATH_SEPARATOR)\(filename)") return nil } @@ -233,7 +247,7 @@ extension CodePrinter { "// ==== ---------------------------------------------------------------------------------------------------" ) if let javaPackagePath { - print("// \(javaPackagePath)/\(filename)") + print("// \(javaPackagePath)\(PATH_SEPARATOR)\(filename)") } else { print("// \(filename)") } @@ -242,9 +256,15 @@ extension CodePrinter { } let targetDirectory = [outputDirectory, javaPackagePath].compactMap { $0 }.joined(separator: PATH_SEPARATOR) - log.trace("Prepare target directory: \(targetDirectory)") - try FileManager.default.createDirectory( - atPath: targetDirectory, withIntermediateDirectories: true) + log.debug("Prepare target directory: '\(targetDirectory)' for file \(filename.bold)") + do { + try FileManager.default.createDirectory( + atPath: targetDirectory, withIntermediateDirectories: true) + } catch { + // log and throw since it can be confusing what the reason for failing the write was otherwise + log.warning("Failed to create directory: \(targetDirectory)") + throw error + } let outputPath = Foundation.URL(fileURLWithPath: targetDirectory).appendingPathComponent(filename) try contents.write( diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift index e43ea4c51..5ef266c88 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -22,8 +22,16 @@ extension FFMSwift2JavaGenerator { } package func writeSwiftExpectedEmptySources() throws { + let pendingFileCount = self.expectedOutputSwiftFiles.count + guard pendingFileCount > 0 else { + return // no need to write any empty files, yay + } + + print("[swift-java] Write empty [\(self.expectedOutputSwiftFiles.count)] 'expected' files in: \(swiftOutputDirectory)/") + for expectedFileName in self.expectedOutputSwiftFiles { - log.trace("Write empty file: \(expectedFileName) ...") + log.debug("Write SwiftPM-'expected' empty file: \(expectedFileName.bold)") + var printer = CodePrinter() printer.print("// Empty file generated on purpose") @@ -46,7 +54,7 @@ extension FFMSwift2JavaGenerator { outputDirectory: self.swiftOutputDirectory, javaPackagePath: nil, filename: moduleFilename) { - print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile))") + log.info("Generated: \(moduleFilenameBase.bold).swift (at \(outputFile.absoluteString))") self.expectedOutputSwiftFiles.remove(moduleFilename) } } catch { @@ -54,24 +62,41 @@ extension FFMSwift2JavaGenerator { } // === All types - // FIXME: write them all into the same file they were declared from +SwiftJava - for (_, ty) in self.analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) { - let fileNameBase = "\(ty.swiftNominal.qualifiedName)+SwiftJava" - let filename = "\(fileNameBase).swift" - log.debug("Printing contents: \(filename)") + // We have to write all types to their corresponding output file that matches the file they were declared in, + // because otherwise SwiftPM plugins will not pick up files apropriately -- we expect 1 output +SwiftJava.swift file for every input. + for group: (key: String, value: [Dictionary.Element]) in Dictionary(grouping: self.analysis.importedTypes, by: { $0.value.sourceFilePath }) { + log.warning("Writing types in file group: \(group.key): \(group.value.map(\.key))") + + let importedTypesForThisFile = group.value + .map(\.value) + .sorted(by: { $0.qualifiedName < $1.qualifiedName }) + + let inputFileName = "\(group.key)".split(separator: "/").last ?? "__Unknown.swift" + let filename = "\(inputFileName)".replacing(".swift", with: "+SwiftJava.swift") + + for ty in importedTypesForThisFile { + log.info("Printing Swift thunks for type: \(ty.qualifiedName.bold)") + printer.printSeparator("Thunks for \(ty.qualifiedName)") + + do { + try printSwiftThunkSources(&printer, ty: ty) + } catch { + log.warning("Failed to print to Swift thunks for type'\(ty.qualifiedName)' to '\(filename)', error: \(error)") + } + + } + log.warning("Write Swift thunks file: \(filename.bold)") do { - try printSwiftThunkSources(&printer, ty: ty) - if let outputFile = try printer.writeContents( outputDirectory: self.swiftOutputDirectory, javaPackagePath: nil, filename: filename) { - print("[swift-java] Generated: \(fileNameBase.bold).swift (at \(outputFile))") + log.info("Done writing Swift thunks to: \(outputFile.absoluteString)") self.expectedOutputSwiftFiles.remove(filename) } } catch { - log.warning("Failed to write to Swift thunks: \(filename)") + log.warning("Failed to write to Swift thunks: \(filename), error: \(error)") } } } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index c9a5028b8..f9ba88b92 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -60,16 +60,14 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { // If we are forced to write empty files, construct the expected outputs if translator.config.writeEmptyFiles ?? false { self.expectedOutputSwiftFiles = Set(translator.inputs.compactMap { (input) -> String? in - guard let filePathPart = input.filePath.split(separator: "/\(translator.swiftModuleName)/").last else { + guard let filePathPart = input.path.split(separator: "/\(translator.swiftModuleName)/").last else { return nil } return String(filePathPart.replacing(".swift", with: "+SwiftJava.swift")) }) self.expectedOutputSwiftFiles.insert("\(translator.swiftModuleName)Module+SwiftJava.swift") - - // FIXME: Can we avoid this? - self.expectedOutputSwiftFiles.insert("Data+SwiftJava.swift") + self.expectedOutputSwiftFiles.insert("Foundation+SwiftJava.swift") } else { self.expectedOutputSwiftFiles = [] } @@ -77,16 +75,12 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { func generate() throws { try writeSwiftThunkSources() - print("[swift-java] Generated Swift sources (module: '\(self.swiftModuleName)') in: \(swiftOutputDirectory)/") + log.info("Generated Swift sources (module: '\(self.swiftModuleName)') in: \(swiftOutputDirectory)/") try writeExportedJavaSources() - print("[swift-java] Generated Java sources (package: '\(javaPackage)') in: \(javaOutputDirectory)/") + log.info("Generated Java sources (package: '\(javaPackage)') in: \(javaOutputDirectory)/") - let pendingFileCount = self.expectedOutputSwiftFiles.count - if pendingFileCount > 0 { - print("[swift-java] Write empty [\(pendingFileCount)] 'expected' files in: \(swiftOutputDirectory)/") - try writeSwiftExpectedEmptySources() - } + try writeSwiftExpectedEmptySources() } } @@ -134,7 +128,7 @@ extension FFMSwift2JavaGenerator { javaPackagePath: javaPackagePath, filename: filename ) { - print("[swift-java] Generated: \(ty.swiftNominal.name.bold).java (at \(outputFile))") + log.info("Generated: \((ty.swiftNominal.name.bold + ".java").bold) (at \(outputFile.absoluteString))") } } @@ -148,7 +142,7 @@ extension FFMSwift2JavaGenerator { javaPackagePath: javaPackagePath, filename: filename) { - print("[swift-java] Generated: \(self.swiftModuleName).java (at \(outputFile))") + log.info("Generated: \((self.swiftModuleName + ".java").bold) (at \(outputFile.absoluteString))") } } } diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 7a071d9a5..1f0d3efc9 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -27,9 +27,15 @@ package enum SwiftAPIKind { /// Describes a Swift nominal type (e.g., a class, struct, enum) that has been /// imported and is being translated into Java. -package class ImportedNominalType: ImportedDecl { +package final class ImportedNominalType: ImportedDecl { let swiftNominal: SwiftNominalTypeDeclaration + // The short path from module root to the file in which this nominal was originally declared. + // E.g. for `Sources/Example/My/Types.swift` it would be `My/Types.swift`. + package var sourceFilePath: String { + self.swiftNominal.sourceFilePath + } + package var initializers: [ImportedFunc] = [] package var methods: [ImportedFunc] = [] package var variables: [ImportedFunc] = [] diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 3848ceac9..1d4f81dc1 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -26,8 +26,16 @@ extension JNISwift2JavaGenerator { } package func writeSwiftExpectedEmptySources() throws { + let pendingFileCount = self.expectedOutputSwiftFiles.count + guard pendingFileCount > 0 else { + return // no need to write any empty files, yay + } + + print("[swift-java] Write empty [\(self.expectedOutputSwiftFiles.count)] 'expected' files in: \(swiftOutputDirectory)/") + for expectedFileName in self.expectedOutputSwiftFiles { - logger.trace("Write empty file: \(expectedFileName) ...") + logger.debug("Write SwiftPM-'expected' empty file: \(expectedFileName.bold)") + var printer = CodePrinter() printer.print("// Empty file generated on purpose") @@ -52,27 +60,46 @@ extension JNISwift2JavaGenerator { javaPackagePath: nil, filename: moduleFilename ) { - print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile))") + logger.info("Generated: \(moduleFilenameBase.bold).swift (at \(outputFile.absoluteString))") self.expectedOutputSwiftFiles.remove(moduleFilename) } - for (_, ty) in self.analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) { - let fileNameBase = "\(ty.swiftNominal.qualifiedName)+SwiftJava" - let filename = "\(fileNameBase).swift" - logger.debug("Printing contents: \(filename)") + // === All types + // We have to write all types to their corresponding output file that matches the file they were declared in, + // because otherwise SwiftPM plugins will not pick up files apropriately -- we expect 1 output +SwiftJava.swift file for every input. + for group: (key: String, value: [Dictionary.Element]) in Dictionary(grouping: self.analysis.importedTypes, by: { $0.value.sourceFilePath }) { + logger.warning("Writing types in file group: \(group.key): \(group.value.map(\.key))") - do { - try printNominalTypeThunks(&printer, ty) + let importedTypesForThisFile = group.value + .map(\.value) + .sorted(by: { $0.qualifiedName < $1.qualifiedName }) + let inputFileName = "\(group.key)".split(separator: "/").last ?? "__Unknown.swift" + let filename = "\(inputFileName)".replacing(".swift", with: "+SwiftJava.swift") + + for ty in importedTypesForThisFile { + logger.info("Printing Swift thunks for type: \(ty.qualifiedName.bold)") + printer.printSeparator("Thunks for \(ty.qualifiedName)") + + do { + try printNominalTypeThunks(&printer, ty) + } catch { + logger.warning("Failed to print to Swift thunks for type'\(ty.qualifiedName)' to '\(filename)', error: \(error)") + } + + } + + logger.warning("Write Swift thunks file: \(filename.bold)") + do { if let outputFile = try printer.writeContents( outputDirectory: self.swiftOutputDirectory, javaPackagePath: nil, filename: filename) { - print("[swift-java] Generated: \(fileNameBase.bold).swift (at \(outputFile))") + logger.info("Done writing Swift thunks to: \(outputFile.absoluteString)") self.expectedOutputSwiftFiles.remove(filename) } } catch { - logger.warning("Failed to write to Swift thunks: \(filename)") + logger.warning("Failed to write to Swift thunks: \(filename), error: \(error)") } } } catch { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index a677bcde8..3b84cfb97 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -67,16 +67,14 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { // If we are forced to write empty files, construct the expected outputs if translator.config.writeEmptyFiles ?? false { self.expectedOutputSwiftFiles = Set(translator.inputs.compactMap { (input) -> String? in - guard let filePathPart = input.filePath.split(separator: "/\(translator.swiftModuleName)/").last else { + guard let filePathPart = input.path.split(separator: "/\(translator.swiftModuleName)/").last else { return nil } return String(filePathPart.replacing(".swift", with: "+SwiftJava.swift")) }) self.expectedOutputSwiftFiles.insert("\(translator.swiftModuleName)Module+SwiftJava.swift") - - // FIXME: Can we avoid this? - self.expectedOutputSwiftFiles.insert("Data+SwiftJava.swift") + self.expectedOutputSwiftFiles.insert("Foundation+SwiftJava.swift") } else { self.expectedOutputSwiftFiles = [] } diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index 9dcba3407..7de792c0f 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -32,12 +32,7 @@ public final class Swift2JavaTranslator { // ==== Input - struct Input { - let filePath: String - let syntax: SourceFileSyntax - } - - var inputs: [Input] = [] + var inputs: [SwiftJavaInputFile] = [] /// A list of used Swift class names that live in dependencies, e.g. `JavaInteger` package var dependenciesClasses: [String] = [] @@ -85,15 +80,12 @@ extension Swift2JavaTranslator { package func add(filePath: String, text: String) { log.trace("Adding: \(filePath)") let sourceFileSyntax = Parser.parse(source: text) - self.inputs.append(Input(filePath: filePath, syntax: sourceFileSyntax)) + self.inputs.append(SwiftJavaInputFile(syntax: sourceFileSyntax, path: filePath)) } /// Convenient method for analyzing single file. - package func analyze( - file: String, - text: String - ) throws { - self.add(filePath: file, text: text) + package func analyze(path: String, text: String) throws { + self.add(filePath: path, text: text) try self.analyze() } @@ -104,8 +96,8 @@ extension Swift2JavaTranslator { let visitor = Swift2JavaVisitor(translator: self) for input in self.inputs { - log.trace("Analyzing \(input.filePath)") - visitor.visit(sourceFile: input.syntax) + log.trace("Analyzing \(input.path)") + visitor.visit(inputFile: input) } // If any API uses 'Foundation.Data' or 'FoundationEssentials.Data', @@ -113,7 +105,7 @@ extension Swift2JavaTranslator { if let dataDecl = self.symbolTable[.foundationData] ?? self.symbolTable[.essentialsData] { let dataProtocolDecl = (self.symbolTable[.foundationDataProtocol] ?? self.symbolTable[.essentialsDataProtocol])! if self.isUsing(where: { $0 == dataDecl || $0 == dataProtocolDecl }) { - visitor.visit(nominalDecl: dataDecl.syntax!.asNominal!, in: nil) + visitor.visit(nominalDecl: dataDecl.syntax!.asNominal!, in: nil, sourceFilePath: "Foundation/FAKE_FOUNDATION_DATA.swift") } } } @@ -123,7 +115,7 @@ extension Swift2JavaTranslator { let symbolTable = SwiftSymbolTable.setup( moduleName: self.swiftModuleName, - inputs.map({ $0.syntax }) + [dependenciesSource], + inputs + [dependenciesSource], log: self.log ) self.lookupContext = SwiftTypeLookupContext(symbolTable: symbolTable) @@ -184,13 +176,14 @@ extension Swift2JavaTranslator { } /// Returns a source file that contains all the available dependency classes. - private func buildDependencyClassesSourceFile() -> SourceFileSyntax { + private func buildDependencyClassesSourceFile() -> SwiftJavaInputFile { let contents = self.dependenciesClasses.map { "public class \($0) {}" } .joined(separator: "\n") - return SourceFileSyntax(stringLiteral: contents) + let syntax = SourceFileSyntax(stringLiteral: contents) + return SwiftJavaInputFile(syntax: syntax, path: "FakeDependencyClassesSourceFile.swift") } } diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index 13185a5c6..247b26626 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift @@ -29,41 +29,42 @@ final class Swift2JavaVisitor { var log: Logger { translator.log } - func visit(sourceFile node: SourceFileSyntax) { + func visit(inputFile: SwiftJavaInputFile) { + let node = inputFile.syntax for codeItem in node.statements { if let declNode = codeItem.item.as(DeclSyntax.self) { - self.visit(decl: declNode, in: nil) + self.visit(decl: declNode, in: nil, sourceFilePath: inputFile.path) } } } - func visit(decl node: DeclSyntax, in parent: ImportedNominalType?) { + func visit(decl node: DeclSyntax, in parent: ImportedNominalType?, sourceFilePath: String) { switch node.as(DeclSyntaxEnum.self) { case .actorDecl(let node): - self.visit(nominalDecl: node, in: parent) + self.visit(nominalDecl: node, in: parent, sourceFilePath: sourceFilePath) case .classDecl(let node): - self.visit(nominalDecl: node, in: parent) + self.visit(nominalDecl: node, in: parent, sourceFilePath: sourceFilePath) case .structDecl(let node): - self.visit(nominalDecl: node, in: parent) + self.visit(nominalDecl: node, in: parent, sourceFilePath: sourceFilePath) case .enumDecl(let node): - self.visit(enumDecl: node, in: parent) + self.visit(enumDecl: node, in: parent, sourceFilePath: sourceFilePath) case .protocolDecl(let node): - self.visit(nominalDecl: node, in: parent) + self.visit(nominalDecl: node, in: parent, sourceFilePath: sourceFilePath) case .extensionDecl(let node): - self.visit(extensionDecl: node, in: parent) + self.visit(extensionDecl: node, in: parent, sourceFilePath: sourceFilePath) case .typeAliasDecl: break // TODO: Implement; https://github.com/swiftlang/swift-java/issues/338 case .associatedTypeDecl: - break // TODO: Implement + break // TODO: Implement associated types case .initializerDecl(let node): self.visit(initializerDecl: node, in: parent) case .functionDecl(let node): - self.visit(functionDecl: node, in: parent) + self.visit(functionDecl: node, in: parent, sourceFilePath: sourceFilePath) case .variableDecl(let node): - self.visit(variableDecl: node, in: parent) + self.visit(variableDecl: node, in: parent, sourceFilePath: sourceFilePath) case .subscriptDecl: - // TODO: Implement + // TODO: Implement subscripts break case .enumCaseDecl(let node): self.visit(enumCaseDecl: node, in: parent) @@ -75,23 +76,32 @@ final class Swift2JavaVisitor { func visit( nominalDecl node: some DeclSyntaxProtocol & DeclGroupSyntax & NamedDeclSyntax & WithAttributesSyntax & WithModifiersSyntax, - in parent: ImportedNominalType? + in parent: ImportedNominalType?, + sourceFilePath: String ) { guard let importedNominalType = translator.importedNominalType(node, parent: parent) else { return } for memberItem in node.memberBlock.members { - self.visit(decl: memberItem.decl, in: importedNominalType) + self.visit(decl: memberItem.decl, in: importedNominalType, sourceFilePath: sourceFilePath) } } - func visit(enumDecl node: EnumDeclSyntax, in parent: ImportedNominalType?) { - self.visit(nominalDecl: node, in: parent) + func visit( + enumDecl node: EnumDeclSyntax, + in parent: ImportedNominalType?, + sourceFilePath: String + ) { + self.visit(nominalDecl: node, in: parent, sourceFilePath: sourceFilePath) self.synthesizeRawRepresentableConformance(enumDecl: node, in: parent) } - func visit(extensionDecl node: ExtensionDeclSyntax, in parent: ImportedNominalType?) { + func visit( + extensionDecl node: ExtensionDeclSyntax, + in parent: ImportedNominalType?, + sourceFilePath: String + ) { guard parent == nil else { // 'extension' in a nominal type is invalid. Ignore return @@ -100,11 +110,15 @@ final class Swift2JavaVisitor { return } for memberItem in node.memberBlock.members { - self.visit(decl: memberItem.decl, in: importedNominalType) + self.visit(decl: memberItem.decl, in: importedNominalType, sourceFilePath: sourceFilePath) } } - func visit(functionDecl node: FunctionDeclSyntax, in typeContext: ImportedNominalType?) { + func visit( + functionDecl node: FunctionDeclSyntax, + in typeContext: ImportedNominalType?, + sourceFilePath: String + ) { guard node.shouldExtract(config: config, log: log, in: typeContext) else { return } @@ -139,7 +153,10 @@ final class Swift2JavaVisitor { } } - func visit(enumCaseDecl node: EnumCaseDeclSyntax, in typeContext: ImportedNominalType?) { + func visit( + enumCaseDecl node: EnumCaseDeclSyntax, + in typeContext: ImportedNominalType? + ) { guard let typeContext else { self.log.info("Enum case must be within a current type; \(node)") return @@ -182,7 +199,11 @@ final class Swift2JavaVisitor { } } - func visit(variableDecl node: VariableDeclSyntax, in typeContext: ImportedNominalType?) { + func visit( + variableDecl node: VariableDeclSyntax, + in typeContext: ImportedNominalType?, + sourceFilePath: String + ) { guard node.shouldExtract(config: config, log: log, in: typeContext) else { return } @@ -232,7 +253,10 @@ final class Swift2JavaVisitor { } } - func visit(initializerDecl node: InitializerDeclSyntax, in typeContext: ImportedNominalType?) { + func visit( + initializerDecl node: InitializerDeclSyntax, + in typeContext: ImportedNominalType?, + ) { guard let typeContext else { self.log.info("Initializer must be within a current type; \(node)") return @@ -265,7 +289,10 @@ final class Swift2JavaVisitor { typeContext.initializers.append(imported) } - private func synthesizeRawRepresentableConformance(enumDecl node: EnumDeclSyntax, in parent: ImportedNominalType?) { + private func synthesizeRawRepresentableConformance( + enumDecl node: EnumDeclSyntax, + in parent: ImportedNominalType? + ) { guard let imported = translator.importedNominalType(node, parent: parent) else { return } @@ -279,14 +306,15 @@ final class Swift2JavaVisitor { { if !imported.variables.contains(where: { $0.name == "rawValue" && $0.functionSignature.result.type != inheritanceType }) { let decl: DeclSyntax = "public var rawValue: \(raw: inheritanceType.description) { get }" - self.visit(decl: decl, in: imported) + self.visit(decl: decl, in: imported, sourceFilePath: imported.sourceFilePath) } + // FIXME: why is this un-used imported.variables.first?.signatureString if !imported.initializers.contains(where: { $0.functionSignature.parameters.count == 1 && $0.functionSignature.parameters.first?.parameterName == "rawValue" && $0.functionSignature.parameters.first?.type == inheritanceType }) { let decl: DeclSyntax = "public init?(rawValue: \(raw: inheritanceType))" - self.visit(decl: decl, in: imported) + self.visit(decl: decl, in: imported, sourceFilePath: imported.sourceFilePath) } } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift index a5b01bee3..7e0f4885f 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift @@ -195,7 +195,7 @@ extension SwiftFunctionSignature { guard parameterNode.specifier == nil else { throw SwiftFunctionTranslationError.genericParameterSpecifier(parameterNode) } - let param = try lookupContext.typeDeclaration(for: parameterNode) as! SwiftGenericParameterDeclaration + let param = try lookupContext.typeDeclaration(for: parameterNode, sourceFilePath: "FIXME_HAS_NO_PATH.swift") as! SwiftGenericParameterDeclaration params.append(param) if let inheritedNode = parameterNode.inheritedType { let inherited = try SwiftType(inheritedNode, lookupContext: lookupContext) diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift index 45d5df5aa..7acb11997 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift @@ -43,7 +43,7 @@ enum SwiftKnownModule: String { private var swiftSymbolTable: SwiftModuleSymbolTable { var builder = SwiftParsedModuleSymbolTableBuilder(moduleName: "Swift", importedModules: [:]) - builder.handle(sourceFile: swiftSourceFile) + builder.handle(sourceFile: swiftSourceFile, sourceFilePath: "SwiftStdlib.swift") // FIXME: missing path here return builder.finalize() } @@ -53,7 +53,7 @@ private var foundationEssentialsSymbolTable: SwiftModuleSymbolTable { requiredAvailablityOfModuleWithName: "FoundationEssentials", alternativeModules: .init(isMainSourceOfSymbols: false, moduleNames: ["Foundation"]), importedModules: ["Swift": swiftSymbolTable]) - builder.handle(sourceFile: foundationEssentialsSourceFile) + builder.handle(sourceFile: foundationEssentialsSourceFile, sourceFilePath: "FakeFoundation.swift") return builder.finalize() } @@ -62,7 +62,7 @@ private var foundationSymbolTable: SwiftModuleSymbolTable { moduleName: "Foundation", alternativeModules: .init(isMainSourceOfSymbols: true, moduleNames: ["FoundationEssentials"]), importedModules: ["Swift": swiftSymbolTable]) - builder.handle(sourceFile: foundationSourceFile) + builder.handle(sourceFile: foundationSourceFile, sourceFilePath: "Foundation.swift") return builder.finalize() } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift index 763a5da23..0b8b5651a 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -19,20 +19,37 @@ import SwiftSyntax public typealias NominalTypeDeclSyntaxNode = any DeclGroupSyntax & NamedDeclSyntax & WithAttributesSyntax & WithModifiersSyntax package class SwiftTypeDeclaration { + + // The short path from module root to the file in which this nominal was originally declared. + // E.g. for `Sources/Example/My/Types.swift` it would be `My/Types.swift`. + let sourceFilePath: String + /// The module in which this nominal type is defined. If this is a nested type, the /// module might be different from that of the parent type, if this nominal type /// is defined in an extension within another module. let moduleName: String - /// The name of this nominal type, e.g., 'MyCollection'. + /// The name of this nominal type, e.g. 'MyCollection'. let name: String - init(moduleName: String, name: String) { + init(sourceFilePath: String, moduleName: String, name: String) { + self.sourceFilePath = sourceFilePath self.moduleName = moduleName self.name = name } } +/// A syntax node paired with a simple file path +package struct SwiftJavaInputFile { + let syntax: SourceFileSyntax + /// Simple file path of the file from which the syntax node was parsed. + let path: String + package init(syntax: SourceFileSyntax, path: String) { + self.syntax = syntax + self.path = path + } +} + /// Describes a nominal type declaration, which can be of any kind (class, struct, etc.) /// and has a name, parent type (if nested), and owning module. package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { @@ -66,6 +83,7 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { /// Create a nominal type declaration from the syntax node for a nominal type /// declaration. init( + sourceFilePath: String, moduleName: String, parent: SwiftNominalTypeDeclaration?, node: NominalTypeDeclSyntaxNode @@ -82,7 +100,7 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { case .structDecl: self.kind = .struct default: fatalError("Not a nominal type declaration") } - super.init(moduleName: moduleName, name: node.name.text) + super.init(sourceFilePath: sourceFilePath, moduleName: moduleName, name: node.name.text) } lazy var firstInheritanceType: TypeSyntax? = { @@ -145,11 +163,12 @@ package class SwiftGenericParameterDeclaration: SwiftTypeDeclaration { let syntax: GenericParameterSyntax init( + sourceFilePath: String, moduleName: String, node: GenericParameterSyntax ) { self.syntax = node - super.init(moduleName: moduleName, name: node.name.text) + super.init(sourceFilePath: sourceFilePath, moduleName: moduleName, name: node.name.text) } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift index 8abb21f5f..c55864102 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift @@ -50,7 +50,8 @@ struct SwiftParsedModuleSymbolTableBuilder { extension SwiftParsedModuleSymbolTableBuilder { mutating func handle( - sourceFile: SourceFileSyntax + sourceFile: SourceFileSyntax, + sourceFilePath: String ) { // Find top-level type declarations. for statement in sourceFile.statements { @@ -60,10 +61,10 @@ extension SwiftParsedModuleSymbolTableBuilder { } if let nominalTypeNode = decl.asNominal { - self.handle(nominalTypeDecl: nominalTypeNode, parent: nil) + self.handle(sourceFilePath: sourceFilePath, nominalTypeDecl: nominalTypeNode, parent: nil) } if let extensionNode = decl.as(ExtensionDeclSyntax.self) { - self.handle(extensionDecl: extensionNode) + self.handle(extensionDecl: extensionNode, sourceFilePath: sourceFilePath) } } } @@ -71,6 +72,7 @@ extension SwiftParsedModuleSymbolTableBuilder { /// Add a nominal type declaration and all of the nested types within it to the symbol /// table. mutating func handle( + sourceFilePath: String, nominalTypeDecl node: NominalTypeDeclSyntaxNode, parent: SwiftNominalTypeDeclaration? ) { @@ -83,6 +85,7 @@ extension SwiftParsedModuleSymbolTableBuilder { // Otherwise, create the nominal type declaration. let nominalTypeDecl = SwiftNominalTypeDeclaration( + sourceFilePath: sourceFilePath, moduleName: moduleName, parent: parent, node: node @@ -96,26 +99,28 @@ extension SwiftParsedModuleSymbolTableBuilder { symbolTable.topLevelTypes[nominalTypeDecl.name] = nominalTypeDecl } - self.handle(memberBlock: node.memberBlock, parent: nominalTypeDecl) + self.handle(sourceFilePath: sourceFilePath, memberBlock: node.memberBlock, parent: nominalTypeDecl) } mutating func handle( + sourceFilePath: String, memberBlock node: MemberBlockSyntax, parent: SwiftNominalTypeDeclaration ) { for member in node.members { // Find any nested types within this nominal type and add them. if let nominalMember = member.decl.asNominal { - self.handle(nominalTypeDecl: nominalMember, parent: parent) + self.handle(sourceFilePath: sourceFilePath, nominalTypeDecl: nominalMember, parent: parent) } } } mutating func handle( - extensionDecl node: ExtensionDeclSyntax + extensionDecl node: ExtensionDeclSyntax, + sourceFilePath: String ) { - if !self.tryHandle(extension: node) { + if !self.tryHandle(extension: node, sourceFilePath: sourceFilePath) { self.unresolvedExtensions.append(node) } } @@ -123,7 +128,8 @@ extension SwiftParsedModuleSymbolTableBuilder { /// Add any nested types within the given extension to the symbol table. /// If the extended nominal type can't be resolved, returns false. mutating func tryHandle( - extension node: ExtensionDeclSyntax + extension node: ExtensionDeclSyntax, + sourceFilePath: String ) -> Bool { // Try to resolve the type referenced by this extension declaration. // If it fails, we'll try again later. @@ -141,7 +147,7 @@ extension SwiftParsedModuleSymbolTableBuilder { } // Find any nested types within this extension and add them. - self.handle(memberBlock: node.memberBlock, parent: extendedNominal) + self.handle(sourceFilePath: sourceFilePath, memberBlock: node.memberBlock, parent: extendedNominal) return true } @@ -158,7 +164,7 @@ extension SwiftParsedModuleSymbolTableBuilder { while !unresolvedExtensions.isEmpty { var extensions = self.unresolvedExtensions extensions.removeAll(where: { - self.tryHandle(extension: $0) + self.tryHandle(extension: $0, sourceFilePath: "FIXME_MISSING_FILEPATH.swift") // FIXME: missing filepath here in finalize }) // If we didn't resolve anything, we're done. diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift index 4271e2975..ef299bab3 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift @@ -39,7 +39,7 @@ extension SwiftSymbolTableProtocol { package class SwiftSymbolTable { let importedModules: [String: SwiftModuleSymbolTable] - let parsedModule:SwiftModuleSymbolTable + let parsedModule: SwiftModuleSymbolTable private var knownTypeToNominal: [SwiftKnownTypeDeclKind: SwiftNominalTypeDeclaration] = [:] private var prioritySortedImportedModules: [SwiftModuleSymbolTable] { @@ -55,15 +55,16 @@ package class SwiftSymbolTable { extension SwiftSymbolTable { package static func setup( moduleName: String, - _ sourceFiles: some Collection, + _ inputFiles: some Collection, log: Logger ) -> SwiftSymbolTable { // Prepare imported modules. // FIXME: Support arbitrary dependencies. var modules: Set = [] - for sourceFile in sourceFiles { - modules.formUnion(importingModules(sourceFile: sourceFile)) + for inputFile in inputFiles { + let importedModules = importingModules(sourceFile: inputFile.syntax) + modules.formUnion(importedModules) } var importedModules: [String: SwiftModuleSymbolTable] = [:] importedModules[SwiftKnownModule.swift.name] = SwiftKnownModule.swift.symbolTable @@ -84,8 +85,8 @@ extension SwiftSymbolTable { var builder = SwiftParsedModuleSymbolTableBuilder(moduleName: moduleName, importedModules: importedModules, log: log) // First, register top-level and nested nominal types to the symbol table. - for sourceFile in sourceFiles { - builder.handle(sourceFile: sourceFile) + for sourceFile in inputFiles { + builder.handle(sourceFile: sourceFile.syntax, sourceFilePath: sourceFile.path) } let parsedModule = builder.finalize() return SwiftSymbolTable(parsedModule: parsedModule, importedModules: importedModules) diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift index 9ede2b1b9..f47669b57 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift @@ -48,7 +48,7 @@ class SwiftTypeLookupContext { } case .lookInMembers(let scopeNode): - if let nominalDecl = try typeDeclaration(for: scopeNode) { + if let nominalDecl = try typeDeclaration(for: scopeNode, sourceFilePath: "FIXME.swift") { // FIXME: no path here // implement some node -> file if let found = symbolTable.lookupNestedType(name.name, parent: nominalDecl as! SwiftNominalTypeDeclaration) { return found } @@ -74,9 +74,9 @@ class SwiftTypeLookupContext { for name in names { switch name { case .identifier(let identifiableSyntax, _): - return try? typeDeclaration(for: identifiableSyntax) + return try? typeDeclaration(for: identifiableSyntax, sourceFilePath: "FIXME_NO_PATH.swift") // FIXME: how to get path here? case .declaration(let namedDeclSyntax): - return try? typeDeclaration(for: namedDeclSyntax) + return try? typeDeclaration(for: namedDeclSyntax, sourceFilePath: "FIXME_NO_PATH.swift") // FIXME: how to get path here? case .implicit(let implicitDecl): // TODO: Implement _ = implicitDecl @@ -90,7 +90,7 @@ class SwiftTypeLookupContext { /// Returns the type declaration object associated with the `Syntax` node. /// If there's no declaration created, create an instance on demand, and cache it. - func typeDeclaration(for node: some SyntaxProtocol) throws -> SwiftTypeDeclaration? { + func typeDeclaration(for node: some SyntaxProtocol, sourceFilePath: String) throws -> SwiftTypeDeclaration? { if let found = typeDecls[node.id] { return found } @@ -98,17 +98,17 @@ class SwiftTypeLookupContext { let typeDecl: SwiftTypeDeclaration switch Syntax(node).as(SyntaxEnum.self) { case .genericParameter(let node): - typeDecl = SwiftGenericParameterDeclaration(moduleName: symbolTable.moduleName, node: node) + typeDecl = SwiftGenericParameterDeclaration(sourceFilePath: sourceFilePath, moduleName: symbolTable.moduleName, node: node) case .classDecl(let node): - typeDecl = try nominalTypeDeclaration(for: node) + typeDecl = try nominalTypeDeclaration(for: node, sourceFilePath: sourceFilePath) case .actorDecl(let node): - typeDecl = try nominalTypeDeclaration(for: node) + typeDecl = try nominalTypeDeclaration(for: node, sourceFilePath: sourceFilePath) case .structDecl(let node): - typeDecl = try nominalTypeDeclaration(for: node) + typeDecl = try nominalTypeDeclaration(for: node, sourceFilePath: sourceFilePath) case .enumDecl(let node): - typeDecl = try nominalTypeDeclaration(for: node) + typeDecl = try nominalTypeDeclaration(for: node, sourceFilePath: sourceFilePath) case .protocolDecl(let node): - typeDecl = try nominalTypeDeclaration(for: node) + typeDecl = try nominalTypeDeclaration(for: node, sourceFilePath: sourceFilePath) case .typeAliasDecl: fatalError("typealias not implemented") case .associatedTypeDecl: @@ -122,8 +122,9 @@ class SwiftTypeLookupContext { } /// Create a nominal type declaration instance for the specified syntax node. - private func nominalTypeDeclaration(for node: NominalTypeDeclSyntaxNode) throws -> SwiftNominalTypeDeclaration { + private func nominalTypeDeclaration(for node: NominalTypeDeclSyntaxNode, sourceFilePath: String) throws -> SwiftNominalTypeDeclaration { SwiftNominalTypeDeclaration( + sourceFilePath: sourceFilePath, moduleName: self.symbolTable.moduleName, parent: try parentTypeDecl(for: node), node: node @@ -136,7 +137,7 @@ class SwiftTypeLookupContext { while let parentDecl = node.ancestorDecl { switch parentDecl.as(DeclSyntaxEnum.self) { case .structDecl, .classDecl, .actorDecl, .enumDecl, .protocolDecl: - return (try typeDeclaration(for: parentDecl) as! SwiftNominalTypeDeclaration) + return (try typeDeclaration(for: parentDecl, sourceFilePath: "FIXME_NO_SOURCE_FILE.swift") as! SwiftNominalTypeDeclaration) // FIXME: need to get the source file of the parent default: node = parentDecl continue diff --git a/Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift b/Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift index 7443039a8..bb574c8ad 100644 --- a/Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift +++ b/Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift @@ -190,8 +190,8 @@ extension JavaVirtualMachine { // If we failed to attach, report that. if let attachError = VMError(fromJNIError: attachResult) { + // throw attachError fatalError("JVM Error: \(attachError)") - throw attachError } JavaVirtualMachine.destroyTLS.set(jniEnv!) diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index 66563feef..001d34faf 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -42,7 +42,7 @@ func assertOutput( let translator = Swift2JavaTranslator(config: config) translator.dependenciesClasses = Array(javaClassLookupTable.keys) - try! translator.analyze(file: "/fake/Fake.swiftinterface", text: input) + try! translator.analyze(path: "/fake/Fake.swiftinterface", text: input) let output: String var printer: CodePrinter = CodePrinter(mode: .accumulateAll) diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index 82747ec90..79b51c197 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -42,7 +42,7 @@ final class FuncCallbackImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error - try st.analyze(file: "Fake.swift", text: Self.class_interfaceFile) + try st.analyze(path: "Fake.swift", text: Self.class_interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.name == "callMe" }! @@ -131,7 +131,7 @@ final class FuncCallbackImportTests { config.swiftModule = "__FakeModule" let st = Swift2JavaTranslator(config: config) - try st.analyze(file: "Fake.swift", text: Self.class_interfaceFile) + try st.analyze(path: "Fake.swift", text: Self.class_interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.name == "callMeMore" }! @@ -247,7 +247,7 @@ final class FuncCallbackImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error - try st.analyze(file: "Fake.swift", text: Self.class_interfaceFile) + try st.analyze(path: "Fake.swift", text: Self.class_interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.name == "withBuffer" }! diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift index ff19f4a21..b6ae6f6c6 100644 --- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift +++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift @@ -239,7 +239,7 @@ extension FunctionDescriptorTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = logLevel - try st.analyze(file: "/fake/Sample.swiftinterface", text: interfaceFile) + try st.analyze(path: "/fake/Sample.swiftinterface", text: interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.name == methodIdentifier @@ -273,7 +273,7 @@ extension FunctionDescriptorTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = logLevel - try st.analyze(file: "/fake/Sample.swiftinterface", text: interfaceFile) + try st.analyze(path: "/fake/Sample.swiftinterface", text: interfaceFile) let generator = FFMSwift2JavaGenerator( config: config, diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift index 938b5e7fc..6ba930162 100644 --- a/Tests/JExtractSwiftTests/MethodImportTests.swift +++ b/Tests/JExtractSwiftTests/MethodImportTests.swift @@ -70,7 +70,7 @@ final class MethodImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error - try st.analyze(file: "Fake.swift", text: class_interfaceFile) + try st.analyze(path: "Fake.swift", text: class_interfaceFile) let generator = FFMSwift2JavaGenerator( config: config, @@ -110,7 +110,7 @@ final class MethodImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error - try st.analyze(file: "Fake.swift", text: class_interfaceFile) + try st.analyze(path: "Fake.swift", text: class_interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.name == "globalTakeInt" @@ -152,7 +152,7 @@ final class MethodImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error - try st.analyze(file: "Fake.swift", text: class_interfaceFile) + try st.analyze(path: "Fake.swift", text: class_interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.name == "globalTakeIntLongString" @@ -196,7 +196,7 @@ final class MethodImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error - try st.analyze(file: "Fake.swift", text: class_interfaceFile) + try st.analyze(path: "Fake.swift", text: class_interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.name == "globalReturnClass" @@ -240,7 +240,7 @@ final class MethodImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error - try st.analyze(file: "Fake.swift", text: class_interfaceFile) + try st.analyze(path: "Fake.swift", text: class_interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.name == "swapRawBufferPointer" @@ -287,7 +287,7 @@ final class MethodImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .error - try st.analyze(file: "Fake.swift", text: class_interfaceFile) + try st.analyze(path: "Fake.swift", text: class_interfaceFile) let funcDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.methods.first { $0.name == "helloMemberFunction" @@ -330,7 +330,7 @@ final class MethodImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .info - try st.analyze(file: "Fake.swift", text: class_interfaceFile) + try st.analyze(path: "Fake.swift", text: class_interfaceFile) let funcDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.methods.first { $0.name == "makeInt" @@ -373,7 +373,7 @@ final class MethodImportTests { let st = Swift2JavaTranslator(config: config) st.log.logLevel = .info - try st.analyze(file: "Fake.swift", text: class_interfaceFile) + try st.analyze(path: "Fake.swift", text: class_interfaceFile) let initDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.initializers.first { $0.name == "init" @@ -418,7 +418,7 @@ final class MethodImportTests { st.log.logLevel = .info - try st.analyze(file: "Fake.swift", text: class_interfaceFile) + try st.analyze(path: "Fake.swift", text: class_interfaceFile) let initDecl: ImportedFunc = st.importedTypes["MySwiftStruct"]!.initializers.first { $0.name == "init" diff --git a/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift b/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift index fdbf2d5fa..b437454c5 100644 --- a/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift +++ b/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift @@ -34,7 +34,10 @@ struct SwiftSymbolTableSuite { """ let symbolTable = SwiftSymbolTable.setup( moduleName: "MyModule", - [sourceFile1, sourceFile2], + [ + .init(syntax: sourceFile1, path: "Fake.swift"), + .init(syntax: sourceFile2, path: "Fake2.swift") + ], log: Logger(label: "swift-java", logLevel: .critical) ) From 8560a5b0048ba09f4b2d974daafd9c8c2617fa5a Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 31 Oct 2025 16:07:32 +0100 Subject: [PATCH 098/141] [JExtract/JNI] Add support for `async` Swift methods (#413) --- .github/workflows/pull_request.yml | 20 +- Package.swift | 31 +- README.md | 12 +- .../SwiftAndJavaJarSampleLib/Package.swift | 2 +- .../Package.swift | 2 +- .../Package.swift | 4 +- .../Sources/MySwiftLibrary/Async.swift | 38 ++ .../Sources/MySwiftLibrary/MySwiftError.swift | 17 + .../Sources/MySwiftLibrary/Optionals.swift | 4 + .../com/example/swift/AsyncBenchmark.java | 60 +++ .../java/com/example/swift/AsyncTest.java | 77 ++++ .../com/example/swift/MySwiftClassTest.java | 8 + .../java/com/example/swift/OptionalsTest.java | 13 +- .../Convenience/JavaType+Extensions.swift | 28 +- .../Convenience/SwiftSyntax+Extensions.swift | 4 +- ...t2JavaGenerator+JavaBindingsPrinting.swift | 2 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 4 +- .../FFM/FFMSwift2JavaGenerator.swift | 4 +- Sources/JExtractSwiftLib/ImportedDecls.swift | 4 + ...t2JavaGenerator+JavaBindingsPrinting.swift | 5 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 97 ++++- ...wift2JavaGenerator+NativeTranslation.swift | 141 +++++-- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 48 ++- Sources/JExtractSwiftLib/JavaParameter.swift | 1 + .../JavaTypes/JavaType+JDK.swift | 5 + .../SwiftTypes/SwiftEffectSpecifier.swift | 1 + .../SwiftTypes/SwiftFunctionSignature.swift | 14 +- .../SwiftTypes/SwiftType.swift | 2 +- .../SwiftTypes/SwiftTypeLookupContext.swift | 16 +- Sources/JavaTypes/JavaType+JNI.swift | 6 +- Sources/JavaTypes/JavaType+JavaSource.swift | 14 +- Sources/JavaTypes/JavaType+SwiftNames.swift | 6 +- Sources/JavaTypes/JavaType.swift | 2 +- Sources/JavaTypes/Mangling.swift | 2 +- .../Configuration.swift | 6 + .../JExtract/JExtractAsyncFuncMode.swift | 33 ++ .../JExtract/JExtractGenerationMode.swift | 22 ++ .../JExtractMemoryManagementMode.swift | 34 ++ .../JExtractMinimumAccessLevelMode.swift | 26 ++ .../JExtractUnsignedIntegerMode.swift} | 45 +-- .../Documentation.docc/SupportedFeatures.md | 21 +- .../DefaultCaches.swift | 67 ++++ .../_JNIBoxedConversions.swift | 105 ++++++ .../_JNIMethodIDCache.swift | 21 +- .../Commands/JExtractCommand.swift | 5 + .../SwiftRuntimeFunctions.swift} | 0 SwiftKitCore/build.gradle | 6 +- .../core/ConfinedSwiftMemorySession.java | 13 +- .../org/swift/swiftkit/core/SwiftArena.java | 2 +- .../swift/swiftkit/core/SwiftLibraries.java | 12 +- SwiftKitFFM/build.gradle | 6 +- .../swiftkit/ffm/AllocatingSwiftArena.java | 2 +- .../ffm/FFMConfinedSwiftMemorySession.java | 4 +- .../org/swift/swiftkit/ffm/SwiftRuntime.java | 10 +- .../JNI/JNIAsyncTests.swift | 348 ++++++++++++++++++ .../JNI/JNIClassTests.swift | 20 +- .../JNI/JNIClosureTests.swift | 14 +- .../JExtractSwiftTests/JNI/JNIEnumTests.swift | 14 +- .../JNI/JNIJavaKitTests.swift | 2 +- .../JNI/JNIModuleTests.swift | 8 +- .../JNI/JNIOptionalTests.swift | 14 +- .../JNI/JNIProtocolTests.swift | 16 +- .../JNI/JNIStructTests.swift | 8 +- .../JNI/JNIVariablesTests.swift | 22 +- 64 files changed, 1338 insertions(+), 262 deletions(-) create mode 100644 Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift create mode 100644 Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftError.swift create mode 100644 Samples/SwiftJavaExtractJNISampleApp/src/jmh/java/com/example/swift/AsyncBenchmark.java create mode 100644 Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java create mode 100644 Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncFuncMode.swift create mode 100644 Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift create mode 100644 Sources/SwiftJavaConfigurationShared/JExtract/JExtractMemoryManagementMode.swift create mode 100644 Sources/SwiftJavaConfigurationShared/JExtract/JExtractMinimumAccessLevelMode.swift rename Sources/SwiftJavaConfigurationShared/{GenerationMode.swift => JExtract/JExtractUnsignedIntegerMode.swift} (68%) create mode 100644 Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift create mode 100644 Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift rename Sources/{SwiftJava/Helpers => SwiftJavaRuntimeSupport}/_JNIMethodIDCache.swift (67%) rename Sources/{SwiftKitSwift/SwiftKit.swift => SwiftRuntimeFunctions/SwiftRuntimeFunctions.swift} (100%) create mode 100644 Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index f2a1db716..5e5b767a6 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -26,11 +26,11 @@ jobs: name: Documentation check runs-on: ubuntu-latest container: - image: 'swift:6.1-noble' + image: 'swift:6.2-noble' strategy: fail-fast: true matrix: - swift_version: ['6.1.3'] + swift_version: ['6.2'] os_version: ['jammy'] jdk_vendor: ['corretto'] steps: @@ -48,7 +48,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['6.1.3', 'nightly'] + swift_version: ['6.2', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -74,7 +74,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['6.2.0'] + swift_version: ['6.2'] os_version: ['macos'] jdk_vendor: ['corretto'] env: @@ -98,7 +98,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['6.2.0'] + swift_version: ['6.2'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -118,7 +118,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['6.2.0'] + swift_version: ['6.2'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -140,7 +140,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.1.3', 'nightly'] + swift_version: ['6.2', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -162,7 +162,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.2.0'] + swift_version: ['6.2'] os_version: ['macos'] jdk_vendor: ['corretto'] env: @@ -182,7 +182,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.1.3', 'nightly'] + swift_version: ['6.2', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] sample_app: [ # TODO: use a reusable-workflow to generate those names @@ -210,7 +210,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.2.0'] # no nightly testing on macOS + swift_version: ['6.2'] # no nightly testing on macOS os_version: ['macos'] jdk_vendor: ['corretto'] sample_app: [ # TODO: use a reusable-workflow to generate those names diff --git a/Package.swift b/Package.swift index f6cc83e7b..09743135c 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 6.0 +// swift-tools-version: 6.2 // The swift-tools-version declares the minimum version of Swift required to build this package. import CompilerPluginSupport @@ -169,9 +169,14 @@ let package = Package( // Support library written in Swift for SwiftKit "Java" .library( - name: "SwiftKitSwift", + name: "SwiftJavaRuntimeSupport", + targets: ["SwiftJavaRuntimeSupport"] + ), + + .library( + name: "SwiftRuntimeFunctions", type: .dynamic, - targets: ["SwiftKitSwift"] + targets: ["SwiftRuntimeFunctions"] ), .library( @@ -197,7 +202,7 @@ let package = Package( ], dependencies: [ - .package(url: "https://github.com/swiftlang/swift-syntax", from: "601.0.1"), + .package(url: "https://github.com/swiftlang/swift-syntax", from: "602.0.0"), .package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"), .package(url: "https://github.com/apple/swift-system", from: "1.4.0"), @@ -213,7 +218,8 @@ let package = Package( name: "SwiftJavaDocumentation", dependencies: [ "SwiftJava", - "SwiftKitSwift", + "SwiftJavaRuntimeSupport", + "SwiftRuntimeFunctions", ] ), @@ -351,8 +357,19 @@ let package = Package( ] ), .target( - name: "SwiftKitSwift", - dependencies: [], + name: "SwiftJavaRuntimeSupport", + dependencies: [ + "CSwiftJavaJNI", + "SwiftJava" + ], + swiftSettings: [ + .swiftLanguageMode(.v5), + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) + ] + ), + + .target( + name: "SwiftRuntimeFunctions", swiftSettings: [ .swiftLanguageMode(.v5), .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) diff --git a/README.md b/README.md index 2688f288b..6f9a5fde8 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ It is possible to generate Swift bindings to Java libraries using SwiftJava by u Required language/runtime versions: - **JDK 17+**, any recent JDK installation should be sufficient, as only general reflection and JNI APIs are used by this integratio -- **Swift 6.0.x**, because the library uses modern Swift macros +- **Swift 6.2.x**, because the library uses modern Swift macros **swift-java jextract** @@ -73,7 +73,7 @@ This does require the use of the relatively recent [JEP-454: Foreign Function & This is the primary way we envision calling Swift code from server-side Java libraries and applications. Required language/runtime versions: -- **Swift 6.1**, because of dependence on rich swift interface files +- **Swift 6.2**, because of dependence on rich swift interface files - **JDK 25+** - We are validating the implementation using the currently supported non-LTE release, which at present means JDK-25. @@ -85,7 +85,7 @@ This mode is more limited in some performance and flexibility that it can offer, We recommend this mode when FFM is not available, or wide ranging deployment compatibility is your priority. When performance is paramaunt, we recommend the FFM mode instead. Required language/runtime versions: -- **Swift 6.1**, because of dependence on rich swift interface files +- **Swift 6.2**, because of dependence on rich swift interface files - **Java 7+**, including @@ -94,7 +94,7 @@ Required language/runtime versions: This project contains multiple builds, living side by side together. You will need to have: -- Swift (6.1.x+) +- Swift (6.2.x+) - Java (25+ for FFM, even though we support lower JDK targets) - Gradle (installed by "Gradle wrapper" automatically when you run gradle through `./gradlew`) @@ -104,7 +104,7 @@ Install **Swift**, the easiest way to do this is to use **Swiftly**: [swift.org/ This should automatically install a recent Swift, but you can always make sure by running: ```bash -swiftly install 6.1.2 --use +swiftly install 6.2 --use ``` Install a recent enough Java distribution. We validate this project using Corretto so you can choose to use that as well, @@ -229,4 +229,4 @@ xcrun docc preview Sources/SwiftJavaDocumentation/Documentation.docc **This project is under active development. We welcome feedback about any issues you encounter.** -There is no guarantee about API stability until the project reaches a 1.0 release. \ No newline at end of file +There is no guarantee about API stability until the project reaches a 1.0 release. diff --git a/Samples/SwiftAndJavaJarSampleLib/Package.swift b/Samples/SwiftAndJavaJarSampleLib/Package.swift index c350a0e7f..32ffbb287 100644 --- a/Samples/SwiftAndJavaJarSampleLib/Package.swift +++ b/Samples/SwiftAndJavaJarSampleLib/Package.swift @@ -63,7 +63,7 @@ let package = Package( .target( name: "MySwiftLibrary", dependencies: [ - .product(name: "SwiftKitSwift", package: "swift-java"), + .product(name: "SwiftRuntimeFunctions", package: "swift-java"), ], exclude: [ "swift-java.config", diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Package.swift b/Samples/SwiftJavaExtractFFMSampleApp/Package.swift index b9765c24a..98d1bd33f 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/Package.swift +++ b/Samples/SwiftJavaExtractFFMSampleApp/Package.swift @@ -65,7 +65,7 @@ let package = Package( dependencies: [ .product(name: "SwiftJava", package: "swift-java"), .product(name: "CSwiftJavaJNI", package: "swift-java"), - .product(name: "SwiftKitSwift", package: "swift-java"), + .product(name: "SwiftRuntimeFunctions", package: "swift-java"), ], exclude: [ "swift-java.config", diff --git a/Samples/SwiftJavaExtractJNISampleApp/Package.swift b/Samples/SwiftJavaExtractJNISampleApp/Package.swift index c7d5d7172..33a35c49e 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Package.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 6.0 +// swift-tools-version: 6.2 // The swift-tools-version declares the minimum version of Swift required to build this package. import CompilerPluginSupport @@ -62,7 +62,7 @@ let package = Package( dependencies: [ .product(name: "SwiftJava", package: "swift-java"), .product(name: "CSwiftJavaJNI", package: "swift-java"), - .product(name: "SwiftKitSwift", package: "swift-java"), + .product(name: "SwiftJavaRuntimeSupport", package: "swift-java"), ], exclude: [ "swift-java.config" diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift new file mode 100644 index 000000000..ebef18924 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftJava + +public func asyncSum(i1: Int64, i2: Int64) async -> Int64 { + return i1 + i2 +} + +public func asyncSleep() async throws { + try await Task.sleep(for: .milliseconds(500)) +} + +public func asyncCopy(myClass: MySwiftClass) async throws -> MySwiftClass { + let new = MySwiftClass(x: myClass.x, y: myClass.y) + try await Task.sleep(for: .milliseconds(500)) + return new +} + +public func asyncOptional(i: Int64) async throws -> Int64? { + try await Task.sleep(for: .milliseconds(100)) + return i +} + +public func asyncThrows() async throws { + throw MySwiftError.swiftError +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftError.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftError.swift new file mode 100644 index 000000000..d9d77d38b --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftError.swift @@ -0,0 +1,17 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +enum MySwiftError: Error { + case swiftError +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift index 3e1221021..ca1d74586 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Optionals.swift @@ -62,6 +62,10 @@ public func optionalJavaKitLong(input: Optional) -> Int64? { } } +public func optionalThrowing() throws -> Int64? { + throw MySwiftError.swiftError +} + public func multipleOptionals( input1: Optional, input2: Optional, diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/jmh/java/com/example/swift/AsyncBenchmark.java b/Samples/SwiftJavaExtractJNISampleApp/src/jmh/java/com/example/swift/AsyncBenchmark.java new file mode 100644 index 000000000..9cc74246f --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/jmh/java/com/example/swift/AsyncBenchmark.java @@ -0,0 +1,60 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; +import org.swift.swiftkit.core.ClosableSwiftArena; +import org.swift.swiftkit.core.ConfinedSwiftMemorySession; +import org.swift.swiftkit.core.SwiftArena; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.AverageTime) +@Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS) +@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@State(Scope.Benchmark) +@Fork(value = 1, jvmArgsAppend = { "--enable-native-access=ALL-UNNAMED" }) +public class AsyncBenchmark { + /** + * Parameter for the number of parallel tasks to launch. + */ + @Param({"100", "500", "1000"}) + public int taskCount; + + @Setup(Level.Trial) + public void beforeAll() {} + + @TearDown(Level.Trial) + public void afterAll() {} + + @Benchmark + public void asyncSum(Blackhole bh) { + CompletableFuture[] futures = new CompletableFuture[taskCount]; + + // Launch all tasks in parallel using supplyAsync on our custom executor + for (int i = 0; i < taskCount; i++) { + futures[i] = MySwiftLibrary.asyncSum(10, 5).thenAccept(bh::consume); + } + + // Wait for all futures to complete. + CompletableFuture.allOf(futures).join(); + } +} \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java new file mode 100644 index 000000000..ae6e7cc7a --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java @@ -0,0 +1,77 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import com.example.swift.MySwiftClass; +import com.example.swift.MySwiftLibrary; +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.SwiftArena; + +import java.time.Duration; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import static org.junit.jupiter.api.Assertions.*; + +public class AsyncTest { + @Test + void asyncSum() { + CompletableFuture future = MySwiftLibrary.asyncSum(10, 12); + + Long result = future.join(); + assertEquals(22, result); + } + + @Test + void asyncSleep() { + CompletableFuture future = MySwiftLibrary.asyncSleep(); + future.join(); + } + + @Test + void asyncCopy() { + try (var arena = SwiftArena.ofConfined()) { + MySwiftClass obj = MySwiftClass.init(10, 5, arena); + CompletableFuture future = MySwiftLibrary.asyncCopy(obj, arena); + + MySwiftClass result = future.join(); + + assertEquals(10, result.getX()); + assertEquals(5, result.getY()); + } + } + + @Test + void asyncThrows() { + CompletableFuture future = MySwiftLibrary.asyncThrows(); + + ExecutionException ex = assertThrows(ExecutionException.class, future::get); + + Throwable cause = ex.getCause(); + assertNotNull(cause); + assertEquals(Exception.class, cause.getClass()); + assertEquals("swiftError", cause.getMessage()); + } + + @Test + void asyncOptional() { + CompletableFuture future = MySwiftLibrary.asyncOptional(42); + assertEquals(OptionalLong.of(42), future.join()); + } +} \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java index 2e9a7e623..fba8f13f3 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -152,4 +152,12 @@ void addXWithJavaLong() { assertEquals(70, c1.addXWithJavaLong(javaLong)); } } + + @Test + void getAsyncVariable() { + try (var arena = SwiftArena.ofConfined()) { + MySwiftClass c1 = MySwiftClass.init(20, 10, arena); + assertEquals(42, c1.getGetAsync().join()); + } + } } \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java index d60ff6d5b..d26de1d1f 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/OptionalsTest.java @@ -14,6 +14,7 @@ package com.example.swift; +import com.example.swift.MySwiftLibrary; import org.junit.jupiter.api.Test; import org.swift.swiftkit.core.SwiftArena; @@ -22,8 +23,7 @@ import java.util.OptionalInt; import java.util.OptionalLong; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; public class OptionalsTest { @Test @@ -113,4 +113,13 @@ void multipleOptionals() { assertEquals(result, OptionalLong.of(1L)); } } + + @Test + void optionalThrows() { + try (var arena = SwiftArena.ofConfined()) { + Exception exception = assertThrows(Exception.class, () -> MySwiftLibrary.optionalThrowing()); + + assertEquals("swiftError", exception.getMessage()); + } + } } \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift index cb849e795..3b29fcd30 100644 --- a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift @@ -25,7 +25,7 @@ extension JavaType { case .long: return "J" case .float: return "F" case .double: return "D" - case .class(let package, let name): + case .class(let package, let name, _): let nameWithInnerClasses = name.replacingOccurrences(of: ".", with: "$") if let package { return "L\(package.replacingOccurrences(of: ".", with: "/"))/\(nameWithInnerClasses);" @@ -111,4 +111,30 @@ extension JavaType { false } } + + /// Returns the boxed type, or self if the type is already a Java class. + var boxedType: JavaType { + switch self { + case .boolean: + return .class(package: "java.lang", name: "Boolean") + case .byte: + return .class(package: "java.lang", name: "Byte") + case .char: + return .class(package: "java.lang", name: "Character") + case .short: + return .class(package: "java.lang", name: "Short") + case .int: + return .class(package: "java.lang", name: "Integer") + case .long: + return .class(package: "java.lang", name: "Long") + case .float: + return .class(package: "java.lang", name: "Float") + case .double: + return .class(package: "java.lang", name: "Double") + case .void: + return .class(package: "java.lang", name: "Void") + default: + return self + } + } } diff --git a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift index 8a58f42ab..d3902aa4d 100644 --- a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import SwiftDiagnostics -import SwiftSyntax +@_spi(ExperimentalLanguageFeatures) import SwiftSyntax extension WithModifiersSyntax { var accessControlModifiers: DeclModifierListSyntax { @@ -218,6 +218,8 @@ extension DeclSyntaxProtocol { } else { "var" } + case .usingDecl(let node): + node.nameForDebug } } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index 188f5e8bb..a4485fff0 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -453,7 +453,7 @@ extension FFMSwift2JavaGenerator { func renderMemoryLayoutValue(for javaType: JavaType) -> String { if let layout = ForeignValueLayout(javaType: javaType) { return layout.description - } else if case .class(package: _, name: let customClass) = javaType { + } else if case .class(package: _, name: let customClass, _) = javaType { return ForeignValueLayout(customType: customClass).description } else { fatalError("renderMemoryLayoutValue not supported for \(javaType)") diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift index 5ef266c88..f233ef007 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -108,7 +108,7 @@ extension FFMSwift2JavaGenerator { """ // Generated by swift-java - import SwiftKitSwift + import SwiftRuntimeFunctions """) @@ -136,7 +136,7 @@ extension FFMSwift2JavaGenerator { """ // Generated by swift-java - import SwiftKitSwift + import SwiftRuntimeFunctions """ ) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index f9ba88b92..c445d5c99 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -189,7 +189,7 @@ extension FFMSwift2JavaGenerator { private static final boolean INITIALIZED_LIBS = initializeLibs(); static boolean initializeLibs() { System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFT_CORE); - System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFTKITSWIFT); + System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFT_RUNTIME_FUNCTIONS); System.loadLibrary(LIB_NAME); return true; } @@ -339,7 +339,7 @@ extension FFMSwift2JavaGenerator { private static SymbolLookup getSymbolLookup() { if (SwiftLibraries.AUTO_LOAD_LIBS) { System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFT_CORE); - System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFTKITSWIFT); + System.loadLibrary(SwiftLibraries.LIB_NAME_SWIFT_RUNTIME_FUNCTIONS); System.loadLibrary(LIB_NAME); } diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 1f0d3efc9..84cc43c07 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -192,6 +192,10 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible { self.functionSignature.effectSpecifiers.contains(.throws) } + var isAsync: Bool { + self.functionSignature.isAsync + } + init( module: String, swiftDecl: any DeclSyntaxProtocol, diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 258b537e0..ce6ec18ed 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -429,7 +429,7 @@ extension JNISwift2JavaGenerator { let translatedSignature = translatedDecl.translatedFunctionSignature let resultType = translatedSignature.resultType.javaType var parameters = translatedDecl.translatedFunctionSignature.parameters.map { $0.parameter.renderParameter() } - let throwsClause = translatedDecl.isThrowing ? " throws Exception" : "" + let throwsClause = translatedDecl.isThrowing && !translatedDecl.isAsync ? " throws Exception" : "" let generics = translatedDecl.translatedFunctionSignature.parameters.reduce(into: [(String, [JavaType])]()) { generics, parameter in guard case .generic(let name, let extends) = parameter.parameter.type else { @@ -483,7 +483,6 @@ extension JNISwift2JavaGenerator { printNativeFunction(&printer, translatedDecl) } - } private func printNativeFunction(_ printer: inout CodePrinter, _ translatedDecl: TranslatedFunctionDecl) { @@ -523,7 +522,7 @@ extension JNISwift2JavaGenerator { // Indirect return receivers for outParameter in translatedFunctionSignature.resultType.outParameters { - printer.print("\(outParameter.type) \(outParameter.name) = \(outParameter.allocation.render());") + printer.print("\(outParameter.type) \(outParameter.name) = \(outParameter.allocation.render(type: outParameter.type));") arguments.append(outParameter.name) } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index a0178d3c7..56174e3ed 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -118,6 +118,7 @@ extension JNISwift2JavaGenerator { name: getAsCaseName, isStatic: false, isThrowing: false, + isAsync: false, nativeFunctionName: "$\(getAsCaseName)", parentName: enumName, functionTypes: [], @@ -181,13 +182,13 @@ extension JNISwift2JavaGenerator { } // Swift -> Java - let translatedFunctionSignature = try translate( + var translatedFunctionSignature = try translate( functionSignature: decl.functionSignature, methodName: javaName, parentName: parentName ) // Java -> Java (native) - let nativeFunctionSignature = try nativeTranslation.translate( + var nativeFunctionSignature = try nativeTranslation.translate( functionSignature: decl.functionSignature, translatedFunctionSignature: translatedFunctionSignature, methodName: javaName, @@ -212,10 +213,21 @@ extension JNISwift2JavaGenerator { } } + // Handle async methods + if decl.functionSignature.isAsync { + self.convertToAsync( + translatedFunctionSignature: &translatedFunctionSignature, + nativeFunctionSignature: &nativeFunctionSignature, + originalFunctionSignature: decl.functionSignature, + mode: config.effectiveAsyncFuncMode + ) + } + return TranslatedFunctionDecl( name: javaName, isStatic: decl.isStatic || !decl.hasParent || decl.isInitializer, isThrowing: decl.isThrowing, + isAsync: decl.isAsync, nativeFunctionName: "$\(javaName)", parentName: parentName, functionTypes: funcTypes, @@ -470,6 +482,50 @@ extension JNISwift2JavaGenerator { } } + func convertToAsync( + translatedFunctionSignature: inout TranslatedFunctionSignature, + nativeFunctionSignature: inout NativeFunctionSignature, + originalFunctionSignature: SwiftFunctionSignature, + mode: JExtractAsyncFuncMode + ) { + switch mode { + case .completableFuture: + // Update translated function + + let nativeFutureType = JavaType.completableFuture(nativeFunctionSignature.result.javaType) + + let futureOutParameter = OutParameter( + name: "$future", + type: nativeFutureType, + allocation: .new + ) + + let result = translatedFunctionSignature.resultType + translatedFunctionSignature.resultType = TranslatedResult( + javaType: .completableFuture(translatedFunctionSignature.resultType.javaType), + annotations: result.annotations, + outParameters: result.outParameters + [futureOutParameter], + conversion: .aggregate(variable: nil, [ + .print(.placeholder), // Make the downcall + .method(.constant("$future"), function: "thenApply", arguments: [ + .lambda(args: ["futureResult$"], body: .replacingPlaceholder(result.conversion, placeholder: "futureResult$")) + ]) + ]) + ) + + // Update native function + nativeFunctionSignature.result.javaType = .void + nativeFunctionSignature.result.conversion = .asyncCompleteFuture( + nativeFunctionSignature.result.conversion, + swiftFunctionResultType: originalFunctionSignature.result.type, + nativeReturnType: nativeFunctionSignature.result.javaType, + outParameters: nativeFunctionSignature.result.outParameters, + isThrowing: originalFunctionSignature.isThrowing + ) + nativeFunctionSignature.result.outParameters.append(.init(name: "result_future", type: nativeFutureType)) + } + } + func translateProtocolParameter( protocolType: SwiftType, parameterName: String, @@ -753,6 +809,8 @@ extension JNISwift2JavaGenerator { let isThrowing: Bool + let isAsync: Bool + /// The name of the native function let nativeFunctionName: String @@ -812,11 +870,15 @@ extension JNISwift2JavaGenerator { struct OutParameter { enum Allocation { case newArray(JavaType, size: Int) + case new - func render() -> String { + func render(type: JavaType) -> String { switch self { case .newArray(let javaType, let size): "new \(javaType)[\(size)]" + + case .new: + "new \(type)()" } } } @@ -912,6 +974,12 @@ extension JNISwift2JavaGenerator { /// Access a member of the value indirect case replacingPlaceholder(JavaNativeConversionStep, placeholder: String) + /// `(args) -> { return body; }` + indirect case lambda(args: [String] = [], body: JavaNativeConversionStep) + + /// Prints the conversion step, ignoring the output. + indirect case print(JavaNativeConversionStep) + /// Returns the conversion string applied to the placeholder. func render(_ printer: inout CodePrinter, _ placeholder: String) -> String { // NOTE: 'printer' is used if the conversion wants to cause side-effects. @@ -1024,6 +1092,23 @@ extension JNISwift2JavaGenerator { case .replacingPlaceholder(let inner, let placeholder): return inner.render(&printer, placeholder) + + case .lambda(let args, let body): + var printer = CodePrinter() + printer.printBraceBlock("(\(args.joined(separator: ", "))) ->") { printer in + let body = body.render(&printer, placeholder) + if !body.isEmpty { + printer.print("return \(body);") + } else { + printer.print("return;") + } + } + return printer.finalize() + + case .print(let inner): + let inner = inner.render(&printer, placeholder) + printer.print("\(inner);") + return "" } } @@ -1074,6 +1159,12 @@ extension JNISwift2JavaGenerator { case .replacingPlaceholder(let inner, _): return inner.requiresSwiftArena + + case .lambda(_, let body): + return body.requiresSwiftArena + + case .print(let inner): + return inner.requiresSwiftArena } } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index bdbfe2f18..45208d7b9 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -59,34 +59,15 @@ extension JNISwift2JavaGenerator { nil } - return try NativeFunctionSignature( + let result = try translate(swiftResult: functionSignature.result) + + return NativeFunctionSignature( selfParameter: nativeSelf, parameters: parameters, - result: translate(swiftResult: functionSignature.result) + result: result ) } - func translateParameters( - _ parameters: [SwiftParameter], - translatedParameters: [TranslatedParameter], - methodName: String, - parentName: String, - genericParameters: [SwiftGenericParameterDeclaration], - genericRequirements: [SwiftGenericRequirement] - ) throws -> [NativeParameter] { - try zip(translatedParameters, parameters).map { translatedParameter, swiftParameter in - let parameterName = translatedParameter.parameter.name - return try translateParameter( - type: swiftParameter.type, - parameterName: parameterName, - methodName: methodName, - parentName: parentName, - genericParameters: genericParameters, - genericRequirements: genericRequirements - ) - } - } - func translateParameter( type: SwiftType, parameterName: String, @@ -508,8 +489,8 @@ extension JNISwift2JavaGenerator { struct NativeFunctionSignature { let selfParameter: NativeParameter? - let parameters: [NativeParameter] - let result: NativeResult + var parameters: [NativeParameter] + var result: NativeResult } struct NativeParameter { @@ -522,8 +503,8 @@ extension JNISwift2JavaGenerator { } struct NativeResult { - let javaType: JavaType - let conversion: NativeSwiftConversionStep + var javaType: JavaType + var conversion: NativeSwiftConversionStep /// Out parameters for populating the indirect return values. var outParameters: [JavaParameter] @@ -545,7 +526,7 @@ extension JNISwift2JavaGenerator { /// `value.getJValue(in:)` indirect case getJValue(NativeSwiftConversionStep) - /// `SwiftType(from: value, in: environment!)` + /// `SwiftType(from: value, in: environment)` indirect case initFromJNI(NativeSwiftConversionStep, swiftType: SwiftType) indirect case extractSwiftProtocolValue( @@ -588,6 +569,14 @@ extension JNISwift2JavaGenerator { indirect case unwrapOptional(NativeSwiftConversionStep, name: String, fatalErrorMessage: String) + indirect case asyncCompleteFuture( + NativeSwiftConversionStep, + swiftFunctionResultType: SwiftType, + nativeReturnType: JavaType, + outParameters: [JavaParameter], + isThrowing: Bool + ) + /// Returns the conversion string applied to the placeholder. func render(_ printer: inout CodePrinter, _ placeholder: String) -> String { // NOTE: 'printer' is used if the conversion wants to cause side-effects. @@ -604,15 +593,15 @@ extension JNISwift2JavaGenerator { case .getJNIValue(let inner): let inner = inner.render(&printer, placeholder) - return "\(inner).getJNIValue(in: environment!)" + return "\(inner).getJNIValue(in: environment)" case .getJValue(let inner): let inner = inner.render(&printer, placeholder) - return "\(inner).getJValue(in: environment!)" + return "\(inner).getJValue(in: environment)" case .initFromJNI(let inner, let swiftType): let inner = inner.render(&printer, placeholder) - return "\(swiftType)(fromJNI: \(inner), in: environment!)" + return "\(swiftType)(fromJNI: \(inner), in: environment)" case .extractSwiftProtocolValue(let inner, let typeMetadataVariableName, let protocolNames): let inner = inner.render(&printer, placeholder) @@ -624,11 +613,11 @@ extension JNISwift2JavaGenerator { // TODO: Remove the _openExistential when we decide to only support language mode v6+ printer.print( """ - guard let \(inner)TypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: \(typeMetadataVariableName), in: environment!))) else { + guard let \(inner)TypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: \(typeMetadataVariableName), in: environment))) else { fatalError("\(typeMetadataVariableName) memory address was null") } let \(inner)DynamicType$: Any.Type = unsafeBitCast(\(inner)TypeMetadataPointer$, to: Any.Type.self) - guard let \(inner)RawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: \(inner), in: environment!))) else { + guard let \(inner)RawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: \(inner), in: environment))) else { fatalError("\(inner) memory address was null") } #if hasFeature(ImplicitOpenExistentials) @@ -651,7 +640,7 @@ extension JNISwift2JavaGenerator { } printer.print( """ - let \(inner)Bits$ = Int(Int64(fromJNI: \(inner), in: environment!)) + let \(inner)Bits$ = Int(Int64(fromJNI: \(inner), in: environment)) let \(pointerName) = UnsafeMutablePointer<\(swiftType)>(bitPattern: \(inner)Bits$) """ ) @@ -709,13 +698,13 @@ extension JNISwift2JavaGenerator { printer.print( """ - let class$ = environment!.interface.GetObjectClass(environment, \(placeholder)) - let methodID$ = environment!.interface.GetMethodID(environment, class$, "apply", "\(methodSignature.mangledName)")! + let class$ = environment.interface.GetObjectClass(environment, \(placeholder)) + let methodID$ = environment.interface.GetMethodID(environment, class$, "apply", "\(methodSignature.mangledName)")! let arguments$: [jvalue] = [\(arguments.joined(separator: ", "))] """ ) - let upcall = "environment!.interface.\(nativeResult.javaType.jniCallMethodAName)(environment, \(placeholder), methodID$, arguments$)" + let upcall = "environment.interface.\(nativeResult.javaType.jniCallMethodAName)(environment, \(placeholder), methodID$, arguments$)" let result = nativeResult.conversion.render(&printer, upcall) if nativeResult.javaType.isVoid { @@ -731,7 +720,7 @@ extension JNISwift2JavaGenerator { case .initializeJavaKitWrapper(let inner, let wrapperName): let inner = inner.render(&printer, placeholder) - return "\(wrapperName)(javaThis: \(inner), environment: environment!)" + return "\(wrapperName)(javaThis: \(inner), environment: environment)" case .optionalLowering(let valueConversion, let discriminatorName, let valueName): let value = valueConversion.render(&printer, valueName) @@ -815,6 +804,82 @@ extension JNISwift2JavaGenerator { """ ) return unwrappedName + + case .asyncCompleteFuture( + let inner, + let swiftFunctionResultType, + let nativeReturnType, + let outParameters, + let isThrowing + ): + // Global ref all indirect returns + for outParameter in outParameters { + printer.print("let \(outParameter.name) = environment.interface.NewGlobalRef(environment, \(outParameter.name))") + } + + printer.print( + """ + let globalFuture = environment.interface.NewGlobalRef(environment, result_future) + """ + ) + + func printDo(printer: inout CodePrinter) { + printer.print("let swiftResult$ = await \(placeholder)") + printer.print("environment = try JavaVirtualMachine.shared().environment()") + let inner = inner.render(&printer, "swiftResult$") + if swiftFunctionResultType.isVoid { + printer.print("environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)])") + } else { + printer.printBraceBlock("withVaList([SwiftJavaRuntimeSupport._JNIBoxedConversions.box(\(inner), in: environment)])") { printer in + printer.print("environment.interface.CallBooleanMethodV(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, $0)") + } + } + } + + func printTask(printer: inout CodePrinter) { + printer.printBraceBlock("defer") { printer in + // Defer might on any thread, so we need to attach environment. + printer.print("let deferEnvironment = try! JavaVirtualMachine.shared().environment()") + printer.print("environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture)") + for outParameter in outParameters { + printer.print("environment.interface.DeleteGlobalRef(deferEnvironment, \(outParameter.name))") + } + } + if isThrowing { + printer.printBraceBlock("do") { printer in + printDo(printer: &printer) + } + printer.printBraceBlock("catch") { printer in + // We might not be on the same thread after the suspension, so we need to attach the thread again. + printer.print( + """ + let catchEnvironment = try! JavaVirtualMachine.shared().environment() + let exception = catchEnvironment.interface.NewObjectA(catchEnvironment, _JNIMethodIDCache.Exception.class, _JNIMethodIDCache.Exception.constructWithMessage, [String(describing: error).getJValue(in: catchEnvironment)]) + catchEnvironment.interface.CallBooleanMethodA(catchEnvironment, globalFuture, _JNIMethodIDCache.CompletableFuture.completeExceptionally, [jvalue(l: exception)]) + """ + ) + } + } else { + printDo(printer: &printer) + } + } + + printer.printBraceBlock("if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *)") { printer in + printer.printBraceBlock("Task.immediate") { printer in + // Immediate runs on the caller thread, so we don't need to attach the environment again. + printer.print("var environment = environment!") // this is to ensure we always use the same environment name, even though we are rebinding it. + printTask(printer: &printer) + } + } + printer.printBraceBlock("else") { printer in + printer.printBraceBlock("Task") { printer in + // We can be on any thread, so we need to attach the thread. + printer.print("var environment = try! JavaVirtualMachine.shared().environment()") + printTask(printer: &printer) + } + } + + return "" } } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 1d4f81dc1..5403ae400 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -353,29 +353,42 @@ extension JNISwift2JavaGenerator { } // Lower the result. - let innerBody: String - if !decl.functionSignature.result.type.isVoid { + func innerBody(in printer: inout CodePrinter) -> String { let loweredResult = nativeSignature.result.conversion.render(&printer, result) - innerBody = "return \(loweredResult)" - } else { - innerBody = result + + if !decl.functionSignature.result.type.isVoid { + return "return \(loweredResult)" + } else { + return loweredResult + } } - if decl.isThrowing { - // TODO: Handle classes for dummy value - let dummyReturn = !nativeSignature.result.javaType.isVoid ? "return \(decl.functionSignature.result.type).jniPlaceholderValue" : "" + if decl.isThrowing, !decl.isAsync { + let dummyReturn: String + + if nativeSignature.result.javaType.isVoid { + dummyReturn = "" + } else { + // We assume it is something that implements JavaValue + dummyReturn = "return \(nativeSignature.result.javaType.swiftTypeName(resolver: { _ in "" })).jniPlaceholderValue" + } + + printer.print("do {") + printer.indent() + printer.print(innerBody(in: &printer)) + printer.outdent() + printer.print("} catch {") + printer.indent() printer.print( - """ - do { - \(innerBody) - } catch { - environment.throwAsException(error) - \(dummyReturn) - } - """ + """ + environment.throwAsException(error) + \(dummyReturn) + """ ) + printer.outdent() + printer.print("}") } else { - printer.print(innerBody) + printer.print(innerBody(in: &printer)) } } @@ -453,6 +466,7 @@ extension JNISwift2JavaGenerator { import SwiftJava import CSwiftJavaJNI + import SwiftJavaRuntimeSupport """ ) diff --git a/Sources/JExtractSwiftLib/JavaParameter.swift b/Sources/JExtractSwiftLib/JavaParameter.swift index 43f5a2b49..2670e6ee2 100644 --- a/Sources/JExtractSwiftLib/JavaParameter.swift +++ b/Sources/JExtractSwiftLib/JavaParameter.swift @@ -24,6 +24,7 @@ struct JavaParameter { switch self { case .concrete(let type): return type.jniTypeSignature + case .generic(_, let extends): guard !extends.isEmpty else { return "Ljava/lang/Object;" diff --git a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift index 028508014..5e5a72688 100644 --- a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift +++ b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift @@ -39,4 +39,9 @@ extension JavaType { static var javaLangThrowable: JavaType { .class(package: "java.lang", name: "Throwable") } + + /// The description of the type java.util.concurrent.CompletableFuture + static func completableFuture(_ T: JavaType) -> JavaType { + .class(package: "java.util.concurrent", name: "CompletableFuture", typeParameters: [T.boxedType]) + } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift index 9ba4d7ad0..fb28586b1 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift @@ -14,4 +14,5 @@ enum SwiftEffectSpecifier: Equatable { case `throws` + case `async` } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift index 7e0f4885f..1f2fbd365 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift @@ -30,6 +30,14 @@ public struct SwiftFunctionSignature: Equatable { var genericParameters: [SwiftGenericParameterDeclaration] var genericRequirements: [SwiftGenericRequirement] + var isAsync: Bool { + effectSpecifiers.contains(.async) + } + + var isThrowing: Bool { + effectSpecifiers.contains(.throws) + } + init( selfParameter: SwiftSelfParameter? = nil, parameters: [SwiftParameter], @@ -245,8 +253,8 @@ extension SwiftFunctionSignature { if signature.effectSpecifiers?.throwsClause != nil { effectSpecifiers.append(.throws) } - if let asyncSpecifier = signature.effectSpecifiers?.asyncSpecifier { - throw SwiftFunctionTranslationError.async(asyncSpecifier) + if signature.effectSpecifiers?.asyncSpecifier != nil { + effectSpecifiers.append(.async) } let parameters = try signature.parameterClause.parameters.map { param in @@ -331,7 +339,7 @@ extension SwiftFunctionSignature { effectSpecifiers.append(.throws) } if let asyncSpecifier = decl.effectSpecifiers?.asyncSpecifier { - throw SwiftFunctionTranslationError.async(asyncSpecifier) + effectSpecifiers.append(.async) } return effectSpecifiers } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index 81afe6370..3840b3e13 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -215,7 +215,7 @@ extension SwiftType { switch type.as(TypeSyntaxEnum.self) { case .arrayType, .classRestrictionType, .dictionaryType, .missingType, .namedOpaqueReturnType, - .packElementType, .packExpansionType, .suppressedType: + .packElementType, .packExpansionType, .suppressedType, .inlineArrayType: throw TypeTranslationError.unimplementedType(type) case .attributedType(let attributedType): diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift index f47669b57..13f42cfca 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift @@ -42,24 +42,19 @@ class SwiftTypeLookupContext { return typeDeclaration(for: names) } - case .fromFileScope(_, let names): - if !names.isEmpty { - return typeDeclaration(for: names) - } - - case .lookInMembers(let scopeNode): + case .lookForMembers(let scopeNode): if let nominalDecl = try typeDeclaration(for: scopeNode, sourceFilePath: "FIXME.swift") { // FIXME: no path here // implement some node -> file if let found = symbolTable.lookupNestedType(name.name, parent: nominalDecl as! SwiftNominalTypeDeclaration) { return found } } - case .lookInGenericParametersOfExtendedType(let extensionNode): + case .lookForGenericParameters(let extensionNode): // TODO: Implement _ = extensionNode break - case .mightIntroduceDollarIdentifiers: + case .lookForImplicitClosureParameters: // Dollar identifier can't be a type, ignore. break } @@ -81,8 +76,9 @@ class SwiftTypeLookupContext { // TODO: Implement _ = implicitDecl break - case .dollarIdentifier: - break + case .equivalentNames(let equivalentNames): + // TODO: Implement + _ = equivalentNames } } return nil diff --git a/Sources/JavaTypes/JavaType+JNI.swift b/Sources/JavaTypes/JavaType+JNI.swift index 08361bac7..b9b00ccb8 100644 --- a/Sources/JavaTypes/JavaType+JNI.swift +++ b/Sources/JavaTypes/JavaType+JNI.swift @@ -34,9 +34,9 @@ extension JavaType { case .array(.float): "jfloatArray?" case .array(.double): "jdoubleArray?" case .array: "jobjectArray?" - case .class(package: "java.lang", name: "String"): "jstring?" - case .class(package: "java.lang", name: "Class"): "jclass?" - case .class(package: "java.lang", name: "Throwable"): "jthrowable?" + case .class(package: "java.lang", name: "String", _): "jstring?" + case .class(package: "java.lang", name: "Class", _): "jclass?" + case .class(package: "java.lang", name: "Throwable", _): "jthrowable?" case .class: "jobject?" } } diff --git a/Sources/JavaTypes/JavaType+JavaSource.swift b/Sources/JavaTypes/JavaType+JavaSource.swift index f9e2e0cdc..f81f0c6d8 100644 --- a/Sources/JavaTypes/JavaType+JavaSource.swift +++ b/Sources/JavaTypes/JavaType+JavaSource.swift @@ -51,9 +51,13 @@ extension JavaType: CustomStringConvertible { case .double: "double" case .void: "void" case .array(let elementType): "\(elementType.description)[]" - case .class(package: let package, name: let name): + case .class(package: let package, name: let name, let typeParameters): if let package { - "\(package).\(name)" + if !typeParameters.isEmpty { + "\(package).\(name)<\(typeParameters.map(\.description).joined(separator: ", "))>" + } else { + "\(package).\(name)" + } } else { name } @@ -64,7 +68,7 @@ extension JavaType: CustomStringConvertible { /// and nil otherwise. public var className: String? { switch self { - case .class(_, let name): + case .class(_, let name, _): return name default: return nil @@ -75,9 +79,9 @@ extension JavaType: CustomStringConvertible { /// and nil otherwise. public var fullyQualifiedClassName: String? { switch self { - case .class(.some(let package), let name): + case .class(.some(let package), let name, _): return "\(package).\(name)" - case .class(nil, let name): + case .class(nil, let name, _): return name default: return nil diff --git a/Sources/JavaTypes/JavaType+SwiftNames.swift b/Sources/JavaTypes/JavaType+SwiftNames.swift index 20de73fc8..7015ef918 100644 --- a/Sources/JavaTypes/JavaType+SwiftNames.swift +++ b/Sources/JavaTypes/JavaType+SwiftNames.swift @@ -25,7 +25,7 @@ extension JavaType { .array: return false - case .class(package: "java.lang", name: "String"): + case .class(package: "java.lang", name: "String", _): return !stringIsValueType case .class: @@ -38,7 +38,7 @@ extension JavaType { case .boolean, .byte, .char, .short, .int, .long, .float, .double, .void, .array: return false - case .class(package: "java.lang", name: "Runnable"): + case .class(package: "java.lang", name: "Runnable", _): return true case .class: return false @@ -57,7 +57,7 @@ extension JavaType { case .boolean, .byte, .char, .short, .int, .long, .float, .double, .void, .array: return false - case .class(package: "java.lang", name: "String"): + case .class(package: "java.lang", name: "String", _): return true case .class: return false diff --git a/Sources/JavaTypes/JavaType.swift b/Sources/JavaTypes/JavaType.swift index 2a2d901fe..ce7191bac 100644 --- a/Sources/JavaTypes/JavaType.swift +++ b/Sources/JavaTypes/JavaType.swift @@ -28,7 +28,7 @@ public enum JavaType: Equatable, Hashable { /// A Java class separated into its package (e.g., "java.lang") and class name /// (e.g., "Object") - case `class`(package: String?, name: String) + case `class`(package: String?, name: String, typeParameters: [JavaType] = []) /// A Java array. indirect case array(JavaType) diff --git a/Sources/JavaTypes/Mangling.swift b/Sources/JavaTypes/Mangling.swift index f0dbd4840..1311b7179 100644 --- a/Sources/JavaTypes/Mangling.swift +++ b/Sources/JavaTypes/Mangling.swift @@ -35,7 +35,7 @@ extension JavaType { case .short: "S" case .void: "V" case .array(let elementType): "[" + elementType.mangledName - case .class(package: let package, name: let name): + case .class(package: let package, name: let name, _): "L\(package!).\(name.replacingPeriodsWithDollars());".replacingPeriodsWithSlashes() } } diff --git a/Sources/SwiftJavaConfigurationShared/Configuration.swift b/Sources/SwiftJavaConfigurationShared/Configuration.swift index 2d9b43118..6d8d20e51 100644 --- a/Sources/SwiftJavaConfigurationShared/Configuration.swift +++ b/Sources/SwiftJavaConfigurationShared/Configuration.swift @@ -46,6 +46,7 @@ public struct Configuration: Codable { public var effectiveUnsignedNumbersMode: JExtractUnsignedIntegerMode { unsignedNumbersMode ?? .default } + public var minimumInputAccessLevelMode: JExtractMinimumAccessLevelMode? public var effectiveMinimumInputAccessLevelMode: JExtractMinimumAccessLevelMode { minimumInputAccessLevelMode ?? .default @@ -56,6 +57,11 @@ public struct Configuration: Codable { memoryManagementMode ?? .default } + public var asyncFuncMode: JExtractAsyncFuncMode? + public var effectiveAsyncFuncMode: JExtractAsyncFuncMode { + asyncFuncMode ?? .default + } + // ==== java 2 swift --------------------------------------------------------- /// The Java class path that should be passed along to the swift-java tool. diff --git a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncFuncMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncFuncMode.swift new file mode 100644 index 000000000..221649c52 --- /dev/null +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncFuncMode.swift @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +/// Configures how Swift `async` functions should be extracted by jextract. +public enum JExtractAsyncFuncMode: String, Codable { + /// Extract Swift `async` APIs as Java functions that return `CompletableFuture`s. + case completableFuture + + /// Extract Swift `async` APIs as Java functions that return `Future`s. + /// + /// This mode is useful for platforms that do not have `CompletableFuture` support, such as + /// Android 23 and below. + /// + /// - Note: Prefer using the `completableFuture` mode instead, if possible. +// case future +} + +extension JExtractAsyncFuncMode { + public static var `default`: Self { + .completableFuture + } +} diff --git a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift new file mode 100644 index 000000000..8df00cf02 --- /dev/null +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift @@ -0,0 +1,22 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +/// Determines which source generation mode JExtract should be using: JNI or Foreign Function and Memory. +public enum JExtractGenerationMode: String, Codable { + /// Foreign Value and Memory API + case ffm + + /// Java Native Interface + case jni +} diff --git a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractMemoryManagementMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractMemoryManagementMode.swift new file mode 100644 index 000000000..be77d27d3 --- /dev/null +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractMemoryManagementMode.swift @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +/// Configures how memory should be managed by the user +public enum JExtractMemoryManagementMode: String, Codable { + /// Force users to provide an explicit `SwiftArena` to all calls that require them. + case explicit + + /// Provide both explicit `SwiftArena` support + /// and a default global automatic `SwiftArena` that will deallocate memory when the GC decides to. + case allowGlobalAutomatic + + public static var `default`: Self { + .explicit + } + + public var requiresGlobalArena: Bool { + switch self { + case .explicit: false + case .allowGlobalAutomatic: true + } + } +} diff --git a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractMinimumAccessLevelMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractMinimumAccessLevelMode.swift new file mode 100644 index 000000000..22fead577 --- /dev/null +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractMinimumAccessLevelMode.swift @@ -0,0 +1,26 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +/// The minimum access level which +public enum JExtractMinimumAccessLevelMode: String, Codable { + case `public` + case `package` + case `internal` +} + +extension JExtractMinimumAccessLevelMode { + public static var `default`: Self { + .public + } +} diff --git a/Sources/SwiftJavaConfigurationShared/GenerationMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractUnsignedIntegerMode.swift similarity index 68% rename from Sources/SwiftJavaConfigurationShared/GenerationMode.swift rename to Sources/SwiftJavaConfigurationShared/JExtract/JExtractUnsignedIntegerMode.swift index 22fdd5f5a..d7a94070e 100644 --- a/Sources/SwiftJavaConfigurationShared/GenerationMode.swift +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractUnsignedIntegerMode.swift @@ -12,15 +12,6 @@ // //===----------------------------------------------------------------------===// -/// Determines which source generation mode JExtract should be using: JNI or Foreign Function and Memory. -public enum JExtractGenerationMode: String, Codable { - /// Foreign Value and Memory API - case ffm - - /// Java Native Interface - case jni -} - /// Configures how Swift unsigned integers should be extracted by jextract. public enum JExtractUnsignedIntegerMode: String, Codable { /// Treat unsigned Swift integers as their signed equivalents in Java signatures, @@ -51,6 +42,7 @@ public enum JExtractUnsignedIntegerMode: String, Codable { // case widenOrAnnotate } + extension JExtractUnsignedIntegerMode { public var needsConversion: Bool { switch self { @@ -63,38 +55,3 @@ extension JExtractUnsignedIntegerMode { .annotate } } - -/// The minimum access level which -public enum JExtractMinimumAccessLevelMode: String, Codable { - case `public` - case `package` - case `internal` -} - -extension JExtractMinimumAccessLevelMode { - public static var `default`: Self { - .public - } -} - - -/// Configures how memory should be managed by the user -public enum JExtractMemoryManagementMode: String, Codable { - /// Force users to provide an explicit `SwiftArena` to all calls that require them. - case explicit - - /// Provide both explicit `SwiftArena` support - /// and a default global automatic `SwiftArena` that will deallocate memory when the GC decides to. - case allowGlobalAutomatic - - public static var `default`: Self { - .explicit - } - - public var requiresGlobalArena: Bool { - switch self { - case .explicit: false - case .allowGlobalAutomatic: true - } - } -} diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 997e680eb..e97ed03ed 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -57,7 +57,7 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Typed throws: `func x() throws(E)` | ❌ | ❌ | | Stored properties: `var`, `let` (with `willSet`, `didSet`) | ✅ | ✅ | | Computed properties: `var` (incl. `throws`) | ✅ / TODO | ✅ | -| Async functions `func async` and properties: `var { get async {} }` | ❌ | ❌ | +| Async functions `func async` and properties: `var { get async {} }` | ❌ | ✅ | | Arrays: `[UInt8]`, `[T]` | ❌ | ❌ | | Dictionaries: `[String: Int]`, `[K:V]` | ❌ | ❌ | | Generic parameters in functions: `func f(x: T)` | ❌ | ✅ | @@ -97,7 +97,6 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Result builders | ❌ | ❌ | | Automatic Reference Counting of class types / lifetime safety | ✅ | ✅ | | Value semantic types (e.g. struct copying) | ❌ | ❌ | -| Swift concurrency: `func async`, `actor`, `distribued actor` | ❌ | ❌ | | | | | | | | | @@ -328,3 +327,21 @@ which conform a to a given Swift protocol. #### Returning protocol types Protocols are not yet supported as return types. + +### `async` functions + +> Note: Importing `async` functions is currently only available in the JNI mode of jextract. + +Asynchronous functions in Swift can be extraced using different modes, which are explained below. + +#### Async function mode: completable-future (default) + +In this mode `async` functions in Swift are extracted as Java methods returning a `java.util.concurrent.CompletableFuture`. +This mode gives the most flexibility and should be prefered if your platform supports `CompletableFuture`. + +#### Async mode: future + +This is a mode for legacy platforms, where `CompletableFuture` is not available, such as Android 23 and below. +In this mode `async` functions in Swift are extracted as Java methods returning a `java.util.concurrent.Future`. +To enable this mode pass the `--async-func-mode future` command line option, +or set the `asyncFuncMode` configuration value in `swift-java.config` diff --git a/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift b/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift new file mode 100644 index 000000000..1c3079bc3 --- /dev/null +++ b/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift @@ -0,0 +1,67 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftJava + +extension _JNIMethodIDCache { + public enum CompletableFuture { + private static let completeMethod = Method( + name: "complete", + signature: "(Ljava/lang/Object;)Z" + ) + + private static let completeExceptionallyMethod = Method( + name: "completeExceptionally", + signature: "(Ljava/lang/Throwable;)Z" + ) + + private static let cache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/util/concurrent/CompletableFuture", + methods: [completeMethod, completeExceptionallyMethod] + ) + + public static var `class`: jclass { + cache.javaClass + } + + /// CompletableFuture.complete(T) + public static var complete: jmethodID { + cache.methods[completeMethod]! + } + + /// CompletableFuture.completeExceptionally(Throwable) + public static var completeExceptionally: jmethodID { + cache.methods[completeExceptionallyMethod]! + } + } + + public enum Exception { + private static let messageConstructor = Method(name: "", signature: "(Ljava/lang/String;)V") + + private static let cache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Exception", + methods: [messageConstructor] + ) + + public static var `class`: jclass { + cache.javaClass + } + + public static var constructWithMessage: jmethodID { + cache.methods[messageConstructor]! + } + } +} diff --git a/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift b/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift new file mode 100644 index 000000000..68d98ffcd --- /dev/null +++ b/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift @@ -0,0 +1,105 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import CSwiftJavaJNI +import SwiftJava + +public enum _JNIBoxedConversions { + private static let booleanMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(Z)Ljava/lang/Boolean;", isStatic: true) + private static let byteMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(B)Ljava/lang/Byte;", isStatic: true) + private static let charMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(C)Ljava/lang/Character;", isStatic: true) + private static let shortMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(S)Ljava/lang/Short;", isStatic: true) + private static let intMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(I)Ljava/lang/Integer;", isStatic: true) + private static let longMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(J)Ljava/lang/Long;", isStatic: true) + private static let floatMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(F)Ljava/lang/Float;", isStatic: true) + private static let doubleMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(D)Ljava/lang/Double;", isStatic: true) + + private static let booleanCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Boolean", + methods: [booleanMethod] + ) + + private static let byteCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Byte", + methods: [byteMethod] + ) + private static let charCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Character", + methods: [charMethod] + ) + + private static let shortCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Short", + methods: [shortMethod] + ) + private static let intCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Integer", + methods: [intMethod] + ) + + private static let longCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Long", + methods: [longMethod] + ) + + private static let floatCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Float", + methods: [floatMethod] + ) + + private static let doubleCache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "java/lang/Double", + methods: [doubleMethod] + ) + + public static func box(_ value: jboolean, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, booleanCache.javaClass, booleanCache.methods[booleanMethod]!, [jvalue(z: value)])! + } + + public static func box(_ value: jbyte, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, byteCache.javaClass, byteCache.methods[byteMethod]!, [jvalue(b: value)])! + } + + public static func box(_ value: jchar, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, charCache.javaClass, charCache.methods[charMethod]!, [jvalue(c: value)])! + } + + public static func box(_ value: jshort, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, shortCache.javaClass, shortCache.methods[shortMethod]!, [jvalue(s: value)])! + } + + public static func box(_ value: jint, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, intCache.javaClass, intCache.methods[intMethod]!, [jvalue(i: value)])! + } + + public static func box(_ value: jlong, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, longCache.javaClass, longCache.methods[longMethod]!, [jvalue(j: value)])! + } + + public static func box(_ value: jfloat, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, floatCache.javaClass, floatCache.methods[floatMethod]!, [jvalue(f: value)])! + } + + public static func box(_ value: jdouble, in env: JNIEnvironment) -> jobject { + env.interface.CallStaticObjectMethodA(env, doubleCache.javaClass, doubleCache.methods[doubleMethod]!, [jvalue(d: value)])! + } +} diff --git a/Sources/SwiftJava/Helpers/_JNIMethodIDCache.swift b/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift similarity index 67% rename from Sources/SwiftJava/Helpers/_JNIMethodIDCache.swift rename to Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift index a67d225f5..dd7eb5d13 100644 --- a/Sources/SwiftJava/Helpers/_JNIMethodIDCache.swift +++ b/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift @@ -12,6 +12,9 @@ // //===----------------------------------------------------------------------===// +import CSwiftJavaJNI +import SwiftJava + /// A cache used to hold references for JNI method and classes. /// /// This type is used internally in by the outputted JExtract wrappers @@ -20,10 +23,12 @@ public final class _JNIMethodIDCache: Sendable { public struct Method: Hashable { public let name: String public let signature: String + public let isStatic: Bool - public init(name: String, signature: String) { + public init(name: String, signature: String, isStatic: Bool = false) { self.name = name self.signature = signature + self.isStatic = isStatic } } @@ -40,10 +45,18 @@ public final class _JNIMethodIDCache: Sendable { } self._class = environment.interface.NewGlobalRef(environment, clazz)! self.methods = methods.reduce(into: [:]) { (result, method) in - if let methodID = environment.interface.GetMethodID(environment, clazz, method.name, method.signature) { - result[method] = methodID + if method.isStatic { + if let methodID = environment.interface.GetStaticMethodID(environment, clazz, method.name, method.signature) { + result[method] = methodID + } else { + fatalError("Static method \(method.signature) with signature \(method.signature) not found in class \(className)") + } } else { - fatalError("Method \(method.signature) with signature \(method.signature) not found in class \(className)") + if let methodID = environment.interface.GetMethodID(environment, clazz, method.name, method.signature) { + result[method] = methodID + } else { + fatalError("Method \(method.signature) with signature \(method.signature) not found in class \(className)") + } } } } diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index c730cbf6e..aba79983d 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -78,6 +78,9 @@ extension SwiftJava { """ ) var dependsOn: [String] = [] + + @Option(help: "The mode to use for extracting asynchronous Swift functions. By default async methods are extracted as Java functions returning CompletableFuture.") + var asyncFuncMode: JExtractAsyncFuncMode = .default } } @@ -98,6 +101,7 @@ extension SwiftJava.JExtractCommand { config.unsignedNumbersMode = unsignedNumbers config.minimumInputAccessLevelMode = minimumInputAccessLevel config.memoryManagementMode = memoryManagementMode + config.asyncFuncMode = asyncFuncMode try checkModeCompatibility() @@ -162,3 +166,4 @@ extension JExtractGenerationMode: ExpressibleByArgument {} extension JExtractUnsignedIntegerMode: ExpressibleByArgument {} extension JExtractMinimumAccessLevelMode: ExpressibleByArgument {} extension JExtractMemoryManagementMode: ExpressibleByArgument {} +extension JExtractAsyncFuncMode: ExpressibleByArgument {} diff --git a/Sources/SwiftKitSwift/SwiftKit.swift b/Sources/SwiftRuntimeFunctions/SwiftRuntimeFunctions.swift similarity index 100% rename from Sources/SwiftKitSwift/SwiftKit.swift rename to Sources/SwiftRuntimeFunctions/SwiftRuntimeFunctions.swift diff --git a/SwiftKitCore/build.gradle b/SwiftKitCore/build.gradle index 9e4891dcd..f4376e94a 100644 --- a/SwiftKitCore/build.gradle +++ b/SwiftKitCore/build.gradle @@ -73,10 +73,10 @@ tasks.test { } } -// SwiftKit depends on SwiftKitSwift (Swift library that this Java library calls into) +// SwiftKit depends on SwiftRuntimeFunctions (Swift library that this Java library calls into) def compileSwift = tasks.register("compileSwift", Exec) { - description "Compile the swift-java SwiftKitSwift dynamic library that SwiftKit (Java) calls into" + description "Compile the swift-java SwiftRuntimeFunctions dynamic library that SwiftKit (Java) calls into" inputs.file(new File(rootDir, "Package.swift")) inputs.dir(new File(rootDir, "Sources/")) // a bit generous, but better safe than sorry, and include all Swift source changes @@ -84,7 +84,7 @@ def compileSwift = tasks.register("compileSwift", Exec) { workingDir = rootDir commandLine "swift" - args("build", "--target", "SwiftKitSwift") + args("build", "--target", "SwiftRuntimeFunctions") } tasks.build { dependsOn("compileSwift") diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java index 4383a6fe2..3514d9c16 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java @@ -16,6 +16,8 @@ import java.util.LinkedList; import java.util.List; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicInteger; public class ConfinedSwiftMemorySession implements ClosableSwiftArena { @@ -23,21 +25,17 @@ public class ConfinedSwiftMemorySession implements ClosableSwiftArena { final static int CLOSED = 0; final static int ACTIVE = 1; - final Thread owner; final AtomicInteger state; final ConfinedResourceList resources; - public ConfinedSwiftMemorySession(Thread owner) { - this.owner = owner; + public ConfinedSwiftMemorySession() { this.state = new AtomicInteger(ACTIVE); this.resources = new ConfinedResourceList(); } void checkValid() throws RuntimeException { - if (this.owner != null && this.owner != Thread.currentThread()) { - throw new WrongThreadException(String.format("ConfinedSwift arena is confined to %s but was closed from %s!", this.owner, Thread.currentThread())); - } else if (this.state.get() < ACTIVE) { + if (this.state.get() < ACTIVE) { throw new RuntimeException("SwiftArena is already closed!"); } } @@ -61,8 +59,7 @@ public void register(SwiftInstance instance) { } static final class ConfinedResourceList implements SwiftResourceList { - // TODO: Could use intrusive linked list to avoid one indirection here - final List resourceCleanups = new LinkedList<>(); + final Queue resourceCleanups = new ConcurrentLinkedQueue<>(); void add(SwiftInstanceCleanup cleanup) { resourceCleanups.add(cleanup); diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java index 3b6c46269..96353c375 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftArena.java @@ -31,7 +31,7 @@ public interface SwiftArena { void register(SwiftInstance instance); static ClosableSwiftArena ofConfined() { - return new ConfinedSwiftMemorySession(Thread.currentThread()); + return new ConfinedSwiftMemorySession(); } static SwiftArena ofAuto() { diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java index a093cc504..7eccde749 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftLibraries.java @@ -24,10 +24,10 @@ public final class SwiftLibraries { - // Library names of core Swift and SwiftKit + // Library names of core Swift and SwiftRuntimeFunctions public static final String LIB_NAME_SWIFT_CORE = "swiftCore"; public static final String LIB_NAME_SWIFT_CONCURRENCY = "swift_Concurrency"; - public static final String LIB_NAME_SWIFTKITSWIFT = "SwiftKitSwift"; + public static final String LIB_NAME_SWIFT_RUNTIME_FUNCTIONS = "SwiftRuntimeFunctions"; /** * Allows for configuration if jextracted types should automatically attempt to load swiftCore and the library type is from. @@ -42,10 +42,10 @@ public final class SwiftLibraries { @SuppressWarnings("unused") private static final boolean INITIALIZED_LIBS = loadLibraries(false); - public static boolean loadLibraries(boolean loadSwiftKit) { - System.loadLibrary(LIB_NAME_SWIFTKITSWIFT); - if (loadSwiftKit) { - System.loadLibrary(LIB_NAME_SWIFTKITSWIFT); + public static boolean loadLibraries(boolean loadSwiftRuntimeFunctions) { + System.loadLibrary(LIB_NAME_SWIFT_CORE); + if (loadSwiftRuntimeFunctions) { + System.loadLibrary(LIB_NAME_SWIFT_RUNTIME_FUNCTIONS); } return true; } diff --git a/SwiftKitFFM/build.gradle b/SwiftKitFFM/build.gradle index c04b1017c..dcbbe8dfc 100644 --- a/SwiftKitFFM/build.gradle +++ b/SwiftKitFFM/build.gradle @@ -75,10 +75,10 @@ tasks.withType(JavaCompile).configureEach { options.compilerArgs.add("-Xlint:preview") } -// SwiftKit depends on SwiftKitSwift (Swift library that this Java library calls into) +// SwiftKit depends on SwiftRuntimeFunctions (Swift library that this Java library calls into) def compileSwift = tasks.register("compileSwift", Exec) { - description "Compile the swift-java SwiftKitSwift dynamic library that SwiftKit (Java) calls into" + description "Compile the swift-java SwiftRuntimeFunctions dynamic library that SwiftKit (Java) calls into" inputs.file(new File(rootDir, "Package.swift")) inputs.dir(new File(rootDir, "Sources/")) // a bit generous, but better safe than sorry, and include all Swift source changes @@ -86,7 +86,7 @@ def compileSwift = tasks.register("compileSwift", Exec) { workingDir = rootDir commandLine "swift" - args("build", "--target", "SwiftKitSwift") + args("build", "--target", "SwiftRuntimeFunctions") } tasks.build { dependsOn("compileSwift") diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingSwiftArena.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingSwiftArena.java index 08fad15bc..b72818a3e 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingSwiftArena.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/AllocatingSwiftArena.java @@ -24,7 +24,7 @@ public interface AllocatingSwiftArena extends SwiftArena, SegmentAllocator { MemorySegment allocate(long byteSize, long byteAlignment); static ClosableAllocatingSwiftArena ofConfined() { - return new FFMConfinedSwiftMemorySession(Thread.currentThread()); + return new FFMConfinedSwiftMemorySession(); } static AllocatingSwiftArena ofAuto() { diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMConfinedSwiftMemorySession.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMConfinedSwiftMemorySession.java index 424e54b68..05daebd7a 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMConfinedSwiftMemorySession.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/FFMConfinedSwiftMemorySession.java @@ -22,8 +22,8 @@ final class FFMConfinedSwiftMemorySession extends ConfinedSwiftMemorySession implements AllocatingSwiftArena, ClosableAllocatingSwiftArena { final Arena arena; - public FFMConfinedSwiftMemorySession(Thread owner) { - super(owner); + public FFMConfinedSwiftMemorySession() { + super(); this.arena = Arena.ofConfined(); } diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java index e03b9bfec..74a270c0a 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftRuntime.java @@ -33,7 +33,7 @@ public class SwiftRuntime { public static final String STDLIB_DYLIB_NAME = "swiftCore"; - public static final String SWIFTKITSWIFT_DYLIB_NAME = "SwiftKitSwift"; + public static final String SWIFT_RUNTIME_FUNCTIONS_DYLIB_NAME = "SwiftRuntimeFunctions"; private static final String STDLIB_MACOS_DYLIB_PATH = "/usr/lib/swift/libswiftCore.dylib"; @@ -42,10 +42,10 @@ public class SwiftRuntime { @SuppressWarnings("unused") private static final boolean INITIALIZED_LIBS = loadLibraries(false); - public static boolean loadLibraries(boolean loadSwiftKit) { + public static boolean loadLibraries(boolean loadSwiftRuntimeFunctions) { System.loadLibrary(STDLIB_DYLIB_NAME); - if (loadSwiftKit) { - System.loadLibrary(SWIFTKITSWIFT_DYLIB_NAME); + if (loadSwiftRuntimeFunctions) { + System.loadLibrary(SWIFT_RUNTIME_FUNCTIONS_DYLIB_NAME); } return true; } @@ -487,4 +487,4 @@ boolean isEnabled() { } throw new IllegalArgumentException("Not handled log group: " + this); } -} \ No newline at end of file +} diff --git a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift new file mode 100644 index 000000000..5f4881924 --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift @@ -0,0 +1,348 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import Testing + +@Suite +struct JNIAsyncTests { + + @Test("Import: async -> Void (Java, CompletableFuture)") + func completableFuture_asyncVoid_java() throws { + try assertOutput( + input: "public func asyncVoid() async", + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func asyncVoid() async + * } + */ + public static java.util.concurrent.CompletableFuture asyncVoid() { + java.util.concurrent.CompletableFuture $future = new java.util.concurrent.CompletableFuture(); + SwiftModule.$asyncVoid($future); + return $future.thenApply((futureResult$) -> { + return futureResult$; + } + ); + } + """, + """ + private static native void $asyncVoid(java.util.concurrent.CompletableFuture result_future); + """, + ] + ) + } + + @Test("Import: async -> Void (Swift, CompletableFuture)") + func completableFuture_asyncVoid_swift() throws { + try assertOutput( + input: "public func asyncVoid() async", + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024asyncVoid__Ljava_util_concurrent_CompletableFuture_2") + func Java_com_example_swift_SwiftModule__00024asyncVoid__Ljava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, result_future: jobject?) { + let globalFuture = environment.interface.NewGlobalRef(environment, result_future) + if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { + Task.immediate { + var environment = environment! + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.asyncVoid() + environment = try JavaVirtualMachine.shared().environment() + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) + } + } + else { + Task { + var environment = try! JavaVirtualMachine.shared().environment() + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.asyncVoid() + environment = try JavaVirtualMachine.shared().environment() + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) + } + } + } + """ + ] + ) + } + + @Test("Import: async throws -> Void (Java, CompletableFuture)") + func completableFuture_asyncThrowsVoid_java() throws { + try assertOutput( + input: "public func async() async throws", + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func async() async throws + * } + */ + public static java.util.concurrent.CompletableFuture async() { + java.util.concurrent.CompletableFuture $future = new java.util.concurrent.CompletableFuture(); + SwiftModule.$async($future); + return $future.thenApply((futureResult$) -> { + return futureResult$; + } + ); + } + """, + """ + private static native void $async(java.util.concurrent.CompletableFuture result_future); + """, + ] + ) + } + + @Test("Import: async throws -> Void (Swift, CompletableFuture)") + func completableFuture_asyncThrowsVoid_swift() throws { + try assertOutput( + input: "public func async() async throws", + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024async__Ljava_util_concurrent_CompletableFuture_2") + func Java_com_example_swift_SwiftModule__00024async__Ljava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, result_future: jobject?) { + let globalFuture = environment.interface.NewGlobalRef(environment, result_future) + if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { + Task.immediate { + var environment = environment! + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + do { + let swiftResult$ = await try SwiftModule.async() + environment = try JavaVirtualMachine.shared().environment() + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) + } + catch { + let catchEnvironment = try! JavaVirtualMachine.shared().environment() + let exception = catchEnvironment.interface.NewObjectA(catchEnvironment, _JNIMethodIDCache.Exception.class, _JNIMethodIDCache.Exception.constructWithMessage, [String(describing: error).getJValue(in: catchEnvironment)]) + catchEnvironment.interface.CallBooleanMethodA(catchEnvironment, globalFuture, _JNIMethodIDCache.CompletableFuture.completeExceptionally, [jvalue(l: exception)]) + } + } + } + else { + Task { + var environment = try! JavaVirtualMachine.shared().environment() + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + do { + let swiftResult$ = await try SwiftModule.async() + environment = try JavaVirtualMachine.shared().environment() + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) + } + catch { + let catchEnvironment = try! JavaVirtualMachine.shared().environment() + let exception = catchEnvironment.interface.NewObjectA(catchEnvironment, _JNIMethodIDCache.Exception.class, _JNIMethodIDCache.Exception.constructWithMessage, [String(describing: error).getJValue(in: catchEnvironment)]) + catchEnvironment.interface.CallBooleanMethodA(catchEnvironment, globalFuture, _JNIMethodIDCache.CompletableFuture.completeExceptionally, [jvalue(l: exception)]) + } + } + } + } + """ + ] + ) + } + + @Test("Import: (Int64) async -> Int64 (Java, CompletableFuture)") + func completableFuture_asyncIntToInt_java() throws { + try assertOutput( + input: "public func async(i: Int64) async -> Int64", + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func async(i: Int64) async -> Int64 + * } + */ + public static java.util.concurrent.CompletableFuture async(long i) { + java.util.concurrent.CompletableFuture $future = new java.util.concurrent.CompletableFuture(); + SwiftModule.$async(i, $future); + return $future.thenApply((futureResult$) -> { + return futureResult$; + } + ); + } + """, + """ + private static native void $async(long i, java.util.concurrent.CompletableFuture result_future); + """, + ] + ) + } + + @Test("Import: (Int64) async -> Int64 (Swift, CompletableFuture)") + func completableFuture_asyncIntToInt_swift() throws { + try assertOutput( + input: "public func async(i: Int64) async -> Int64", + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024async__JLjava_util_concurrent_CompletableFuture_2") + func Java_com_example_swift_SwiftModule__00024async__JLjava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, i: jlong, result_future: jobject?) { + let globalFuture = environment.interface.NewGlobalRef(environment, result_future) + if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { + Task.immediate { + var environment = environment! + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment)) + environment = try JavaVirtualMachine.shared().environment() + withVaList([SwiftJavaRuntimeSupport._JNIBoxedConversions.box(swiftResult$.getJNIValue(in: environment), in: environment)]) { + environment.interface.CallBooleanMethodV(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, $0) + } + } + } + else { + Task { + var environment = try! JavaVirtualMachine.shared().environment() + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment)) + environment = try JavaVirtualMachine.shared().environment() + withVaList([SwiftJavaRuntimeSupport._JNIBoxedConversions.box(swiftResult$.getJNIValue(in: environment), in: environment)]) { + environment.interface.CallBooleanMethodV(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, $0) + } + } + } + return + } + """ + ] + ) + } + + @Test("Import: (MyClass) async -> MyClass (Java, CompletableFuture)") + func completableFuture_asyncMyClassToMyClass_java() throws { + try assertOutput( + input: """ + class MyClass { } + + public func async(c: MyClass) async -> MyClass + """, + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func async(c: MyClass) async -> MyClass + * } + */ + public static java.util.concurrent.CompletableFuture async(MyClass c, SwiftArena swiftArena$) { + java.util.concurrent.CompletableFuture $future = new java.util.concurrent.CompletableFuture(); + SwiftModule.$async(c.$memoryAddress(), $future); + return $future.thenApply((futureResult$) -> { + return MyClass.wrapMemoryAddressUnsafe(futureResult$, swiftArena$); + } + ); + } + """, + """ + private static native void $async(long c, java.util.concurrent.CompletableFuture result_future); + """, + ] + ) + } + + @Test("Import: (MyClass) async -> MyClass (Swift, CompletableFuture)") + func completableFuture_asyncMyClassToMyClass_swift() throws { + try assertOutput( + input: """ + class MyClass { } + + public func async(c: MyClass) async -> MyClass + """, + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024async__JLjava_util_concurrent_CompletableFuture_2") + func Java_com_example_swift_SwiftModule__00024async__JLjava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, c: jlong, result_future: jobject?) { + assert(c != 0, "c memory address was null") + let cBits$ = Int(Int64(fromJNI: c, in: environment)) + let c$ = UnsafeMutablePointer(bitPattern: cBits$) + guard let c$ else { + fatalError("c memory address was null in call to \\(#function)!") + } + let globalFuture = environment.interface.NewGlobalRef(environment, result_future) + if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { + Task.immediate { + var environment = environment! + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.async(c: c$.pointee) + environment = try JavaVirtualMachine.shared().environment() + let result$ = UnsafeMutablePointer.allocate(capacity: 1) + result$.initialize(to: swiftResult$) + let resultBits$ = Int64(Int(bitPattern: result$)) + withVaList([SwiftJavaRuntimeSupport._JNIBoxedConversions.box(resultBits$.getJNIValue(in: environment), in: environment)]) { + environment.interface.CallBooleanMethodV(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, $0) + } + } + } + else { + Task { + var environment = try! JavaVirtualMachine.shared().environment() + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.async(c: c$.pointee) + environment = try JavaVirtualMachine.shared().environment() + let result$ = UnsafeMutablePointer.allocate(capacity: 1) + result$.initialize(to: swiftResult$) + let resultBits$ = Int64(Int(bitPattern: result$)) + withVaList([SwiftJavaRuntimeSupport._JNIBoxedConversions.box(resultBits$.getJNIValue(in: environment), in: environment)]) { + environment.interface.CallBooleanMethodV(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, $0) + } + } + } + return + } + """ + ] + ) + } +} diff --git a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift index a7527aa8d..c3102a623 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClassTests.swift @@ -221,9 +221,9 @@ struct JNIClassTests { @_cdecl("Java_com_example_swift_MyClass__00024init__JJ") func Java_com_example_swift_MyClass__00024init__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jlong) -> jlong { let result$ = UnsafeMutablePointer.allocate(capacity: 1) - result$.initialize(to: MyClass.init(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) + result$.initialize(to: MyClass.init(x: Int64(fromJNI: x, in: environment), y: Int64(fromJNI: y, in: environment))) let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment!) + return resultBits$.getJNIValue(in: environment) } """, """ @@ -232,7 +232,7 @@ struct JNIClassTests { let result$ = UnsafeMutablePointer.allocate(capacity: 1) result$.initialize(to: MyClass.init()) let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment!) + return resultBits$.getJNIValue(in: environment) } """, ] @@ -303,12 +303,12 @@ struct JNIClassTests { @_cdecl("Java_com_example_swift_MyClass__00024doSomething__JJ") func Java_com_example_swift_MyClass__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, self: jlong) { assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) + let selfBits$ = Int(Int64(fromJNI: self, in: environment)) let self$ = UnsafeMutablePointer(bitPattern: selfBits$) guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } - self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!)) + self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment)) } """ ] @@ -352,7 +352,7 @@ struct JNIClassTests { @_cdecl("Java_com_example_swift_MyClass__00024copy__J") func Java_com_example_swift_MyClass__00024copy__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) + let selfBits$ = Int(Int64(fromJNI: self, in: environment)) let self$ = UnsafeMutablePointer(bitPattern: selfBits$) guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") @@ -360,7 +360,7 @@ struct JNIClassTests { let result$ = UnsafeMutablePointer.allocate(capacity: 1) result$.initialize(to: self$.pointee.copy()) let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment!) + return resultBits$.getJNIValue(in: environment) } """ ] @@ -404,18 +404,18 @@ struct JNIClassTests { @_cdecl("Java_com_example_swift_MyClass__00024isEqual__JJ") func Java_com_example_swift_MyClass__00024isEqual__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, other: jlong, self: jlong) -> jboolean { assert(other != 0, "other memory address was null") - let otherBits$ = Int(Int64(fromJNI: other, in: environment!)) + let otherBits$ = Int(Int64(fromJNI: other, in: environment)) let other$ = UnsafeMutablePointer(bitPattern: otherBits$) guard let other$ else { fatalError("other memory address was null in call to \\(#function)!") } assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) + let selfBits$ = Int(Int64(fromJNI: self, in: environment)) let self$ = UnsafeMutablePointer(bitPattern: selfBits$) guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } - return self$.pointee.isEqual(to: other$.pointee).getJNIValue(in: environment!) + return self$.pointee.isEqual(to: other$.pointee).getJNIValue(in: environment) } """ ] diff --git a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift index b374d24ec..b54749c13 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift @@ -63,10 +63,10 @@ struct JNIClosureTests { @_cdecl("Java_com_example_swift_SwiftModule__00024emptyClosure__Lcom_example_swift_SwiftModule_00024emptyClosure_00024closure_2") func Java_com_example_swift_SwiftModule__00024emptyClosure__Lcom_example_swift_SwiftModule_00024emptyClosure_00024closure_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject?) { SwiftModule.emptyClosure(closure: { - let class$ = environment!.interface.GetObjectClass(environment, closure) - let methodID$ = environment!.interface.GetMethodID(environment, class$, "apply", "()V")! + let class$ = environment.interface.GetObjectClass(environment, closure) + let methodID$ = environment.interface.GetMethodID(environment, class$, "apply", "()V")! let arguments$: [jvalue] = [] - environment!.interface.CallVoidMethodA(environment, closure, methodID$, arguments$) + environment.interface.CallVoidMethodA(environment, closure, methodID$, arguments$) } ) } @@ -115,10 +115,10 @@ struct JNIClosureTests { @_cdecl("Java_com_example_swift_SwiftModule__00024closureWithArgumentsAndReturn__Lcom_example_swift_SwiftModule_00024closureWithArgumentsAndReturn_00024closure_2") func Java_com_example_swift_SwiftModule__00024closureWithArgumentsAndReturn__Lcom_example_swift_SwiftModule_00024closureWithArgumentsAndReturn_00024closure_2(environment: UnsafeMutablePointer!, thisClass: jclass, closure: jobject?) { SwiftModule.closureWithArgumentsAndReturn(closure: { _0, _1 in - let class$ = environment!.interface.GetObjectClass(environment, closure) - let methodID$ = environment!.interface.GetMethodID(environment, class$, "apply", "(JZ)J")! - let arguments$: [jvalue] = [_0.getJValue(in: environment!), _1.getJValue(in: environment!)] - return Int64(fromJNI: environment!.interface.CallLongMethodA(environment, closure, methodID$, arguments$), in: environment!) + let class$ = environment.interface.GetObjectClass(environment, closure) + let methodID$ = environment.interface.GetMethodID(environment, class$, "apply", "(JZ)J")! + let arguments$: [jvalue] = [_0.getJValue(in: environment), _1.getJValue(in: environment)] + return Int64(fromJNI: environment.interface.CallLongMethodA(environment, closure, methodID$, arguments$), in: environment) } ) } diff --git a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift index c6aaf923d..ef422e426 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift @@ -224,25 +224,25 @@ struct JNIEnumTests { let result$ = UnsafeMutablePointer.allocate(capacity: 1) result$.initialize(to: MyEnum.first) let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment!) + return resultBits$.getJNIValue(in: environment) } """, """ @_cdecl("Java_com_example_swift_MyEnum__00024second__Ljava_lang_String_2") func Java_com_example_swift_MyEnum__00024second__Ljava_lang_String_2(environment: UnsafeMutablePointer!, thisClass: jclass, arg0: jstring?) -> jlong { let result$ = UnsafeMutablePointer.allocate(capacity: 1) - result$.initialize(to: MyEnum.second(String(fromJNI: arg0, in: environment!))) + result$.initialize(to: MyEnum.second(String(fromJNI: arg0, in: environment))) let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment!) + return resultBits$.getJNIValue(in: environment) } """, """ @_cdecl("Java_com_example_swift_MyEnum__00024third__JI") func Java_com_example_swift_MyEnum__00024third__JI(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jint) -> jlong { let result$ = UnsafeMutablePointer.allocate(capacity: 1) - result$.initialize(to: MyEnum.third(x: Int64(fromJNI: x, in: environment!), y: Int32(fromJNI: y, in: environment!))) + result$.initialize(to: MyEnum.third(x: Int64(fromJNI: x, in: environment), y: Int32(fromJNI: y, in: environment))) let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment!) + return resultBits$.getJNIValue(in: environment) } """ ]) @@ -302,7 +302,7 @@ struct JNIEnumTests { let class$ = cache$.javaClass let method$ = _JNIMethodIDCache.Method(name: "", signature: "(Ljava/lang/String;)V") let constructorID$ = cache$[method$] - return withVaList([_0.getJNIValue(in: environment!) ?? 0]) { + return withVaList([_0.getJNIValue(in: environment) ?? 0]) { return environment.interface.NewObjectV(environment, class$, constructorID$, $0) } } @@ -318,7 +318,7 @@ struct JNIEnumTests { let class$ = cache$.javaClass let method$ = _JNIMethodIDCache.Method(name: "", signature: "(JI)V") let constructorID$ = cache$[method$] - return withVaList([x.getJNIValue(in: environment!), y.getJNIValue(in: environment!)]) { + return withVaList([x.getJNIValue(in: environment), y.getJNIValue(in: environment)]) { return environment.interface.NewObjectV(environment, class$, constructorID$, $0) } } diff --git a/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift b/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift index ac6b83846..69d77b737 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIJavaKitTests.swift @@ -71,7 +71,7 @@ struct JNIJavaKitTests { guard let javaInteger_unwrapped$ = javaInteger else { fatalError("javaInteger was null in call to \\(#function), but Swift requires non-optional!") } - SwiftModule.function(javaLong: JavaLong(javaThis: javaLong_unwrapped$, environment: environment!), javaInteger: JavaInteger(javaThis: javaInteger_unwrapped$, environment: environment!), int: Int64(fromJNI: int, in: environment!)) + SwiftModule.function(javaLong: JavaLong(javaThis: javaLong_unwrapped$, environment: environment), javaInteger: JavaInteger(javaThis: javaInteger_unwrapped$, environment: environment), int: Int64(fromJNI: int, in: environment)) } """ ] diff --git a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift index 27b0cdea7..dddf11478 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIModuleTests.swift @@ -130,13 +130,13 @@ struct JNIModuleTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024takeIntegers__BSIJ") func Java_com_example_swift_SwiftModule__00024takeIntegers__BSIJ(environment: UnsafeMutablePointer!, thisClass: jclass, i1: jbyte, i2: jshort, i3: jint, i4: jlong) -> jchar { - return SwiftModule.takeIntegers(i1: Int8(fromJNI: i1, in: environment!), i2: Int16(fromJNI: i2, in: environment!), i3: Int32(fromJNI: i3, in: environment!), i4: Int64(fromJNI: i4, in: environment!)).getJNIValue(in: environment!) + return SwiftModule.takeIntegers(i1: Int8(fromJNI: i1, in: environment), i2: Int16(fromJNI: i2, in: environment), i3: Int32(fromJNI: i3, in: environment), i4: Int64(fromJNI: i4, in: environment)).getJNIValue(in: environment) } """, """ @_cdecl("Java_com_example_swift_SwiftModule__00024otherPrimitives__ZFD") func Java_com_example_swift_SwiftModule__00024otherPrimitives__ZFD(environment: UnsafeMutablePointer!, thisClass: jclass, b: jboolean, f: jfloat, d: jdouble) { - SwiftModule.otherPrimitives(b: Bool(fromJNI: b, in: environment!), f: Float(fromJNI: f, in: environment!), d: Double(fromJNI: d, in: environment!)) + SwiftModule.otherPrimitives(b: Bool(fromJNI: b, in: environment), f: Float(fromJNI: f, in: environment), d: Double(fromJNI: d, in: environment)) } """ ] @@ -179,7 +179,7 @@ struct JNIModuleTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024copy__Ljava_lang_String_2") func Java_com_example_swift_SwiftModule__00024copy__Ljava_lang_String_2(environment: UnsafeMutablePointer!, thisClass: jclass, string: jstring?) -> jstring? { - return SwiftModule.copy(String(fromJNI: string, in: environment!)).getJNIValue(in: environment!) + return SwiftModule.copy(String(fromJNI: string, in: environment)).getJNIValue(in: environment) } """, ] @@ -247,7 +247,7 @@ struct JNIModuleTests { @_cdecl("Java_com_example_swift_SwiftModule__00024methodB__") func Java_com_example_swift_SwiftModule__00024methodB__(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong { do { - return try SwiftModule.methodB().getJNIValue(in: environment!) + return try SwiftModule.methodB().getJNIValue(in: environment) } catch { environment.throwAsException(error) return Int64.jniPlaceholderValue diff --git a/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift index be2e0f6a9..6c931d7b4 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIOptionalTests.swift @@ -72,10 +72,10 @@ struct JNIOptionalTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024optionalSugar__BJ") func Java_com_example_swift_SwiftModule__00024optionalSugar__BJ(environment: UnsafeMutablePointer!, thisClass: jclass, arg_discriminator: jbyte, arg_value: jlong) -> jlong { - let result_value$ = SwiftModule.optionalSugar(arg_discriminator == 1 ? Int64(fromJNI: arg_value, in: environment!) : nil).map { + let result_value$ = SwiftModule.optionalSugar(arg_discriminator == 1 ? Int64(fromJNI: arg_value, in: environment) : nil).map { Int64($0) << 32 | Int64(1) } ?? 0 - return result_value$.getJNIValue(in: environment!) + return result_value$.getJNIValue(in: environment) } """ ] @@ -123,8 +123,8 @@ struct JNIOptionalTests { @_cdecl("Java_com_example_swift_SwiftModule__00024optionalExplicit__BLjava_lang_String_2_3B") func Java_com_example_swift_SwiftModule__00024optionalExplicit__BLjava_lang_String_2_3B(environment: UnsafeMutablePointer!, thisClass: jclass, arg_discriminator: jbyte, arg_value: jstring?, result_discriminator$: jbyteArray?) -> jstring? { let result$: jstring? - if let innerResult$ = SwiftModule.optionalExplicit(arg_discriminator == 1 ? String(fromJNI: arg_value, in: environment!) : nil) { - result$ = innerResult$.getJNIValue(in: environment!) + if let innerResult$ = SwiftModule.optionalExplicit(arg_discriminator == 1 ? String(fromJNI: arg_value, in: environment) : nil) { + result$ = innerResult$.getJNIValue(in: environment) var flag$ = Int8(1) environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) } @@ -180,14 +180,14 @@ struct JNIOptionalTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024optionalClass__J_3B") func Java_com_example_swift_SwiftModule__00024optionalClass__J_3B(environment: UnsafeMutablePointer!, thisClass: jclass, arg: jlong, result_discriminator$: jbyteArray?) -> jlong { - let argBits$ = Int(Int64(fromJNI: arg, in: environment!)) + let argBits$ = Int(Int64(fromJNI: arg, in: environment)) let arg$ = UnsafeMutablePointer(bitPattern: argBits$) let result$: jlong if let innerResult$ = SwiftModule.optionalClass(arg$?.pointee) { let _result$ = UnsafeMutablePointer.allocate(capacity: 1) _result$.initialize(to: innerResult$) let _resultBits$ = Int64(Int(bitPattern: _result$)) - result$ = _resultBits$.getJNIValue(in: environment!) + result$ = _resultBits$.getJNIValue(in: environment) var flag$ = Int8(1) environment.interface.SetByteArrayRegion(environment, result_discriminator$, 0, 1, &flag$) } @@ -242,7 +242,7 @@ struct JNIOptionalTests { @_cdecl("Java_com_example_swift_SwiftModule__00024optionalJavaKitClass__Ljava_lang_Long_2") func Java_com_example_swift_SwiftModule__00024optionalJavaKitClass__Ljava_lang_Long_2(environment: UnsafeMutablePointer!, thisClass: jclass, arg: jobject?) { SwiftModule.optionalJavaKitClass(arg.map { - return JavaLong(javaThis: $0, environment: environment!) + return JavaLong(javaThis: $0, environment: environment) } ) } diff --git a/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift b/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift index b5a0fcdbe..c5302ca64 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift @@ -107,11 +107,11 @@ struct JNIProtocolTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024takeProtocol__JJJJ") func Java_com_example_swift_SwiftModule__00024takeProtocol__JJJJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, x_typeMetadataAddress: jlong, y: jlong, y_typeMetadataAddress: jlong) { - guard let xTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: x_typeMetadataAddress, in: environment!))) else { + guard let xTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: x_typeMetadataAddress, in: environment))) else { fatalError("x_typeMetadataAddress memory address was null") } let xDynamicType$: Any.Type = unsafeBitCast(xTypeMetadataPointer$, to: Any.Type.self) - guard let xRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: x, in: environment!))) else { + guard let xRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: x, in: environment))) else { fatalError("x memory address was null") } #if hasFeature(ImplicitOpenExistentials) @@ -122,11 +122,11 @@ struct JNIProtocolTests { } let xExistential$ = _openExistential(xDynamicType$, do: xDoLoad) #endif - guard let yTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: y_typeMetadataAddress, in: environment!))) else { + guard let yTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: y_typeMetadataAddress, in: environment))) else { fatalError("y_typeMetadataAddress memory address was null") } let yDynamicType$: Any.Type = unsafeBitCast(yTypeMetadataPointer$, to: Any.Type.self) - guard let yRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: y, in: environment!))) else { + guard let yRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: y, in: environment))) else { fatalError("y memory address was null") } #if hasFeature(ImplicitOpenExistentials) @@ -172,11 +172,11 @@ struct JNIProtocolTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024takeGeneric__JJ") func Java_com_example_swift_SwiftModule__00024takeGeneric__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, s: jlong, s_typeMetadataAddress: jlong) { - guard let sTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: s_typeMetadataAddress, in: environment!))) else { + guard let sTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: s_typeMetadataAddress, in: environment))) else { fatalError("s_typeMetadataAddress memory address was null") } let sDynamicType$: Any.Type = unsafeBitCast(sTypeMetadataPointer$, to: Any.Type.self) - guard let sRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: s, in: environment!))) else { + guard let sRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: s, in: environment))) else { fatalError("s memory address was null") } #if hasFeature(ImplicitOpenExistentials) @@ -222,11 +222,11 @@ struct JNIProtocolTests { """ @_cdecl("Java_com_example_swift_SwiftModule__00024takeComposite__JJ") func Java_com_example_swift_SwiftModule__00024takeComposite__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, x_typeMetadataAddress: jlong) { - guard let xTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: x_typeMetadataAddress, in: environment!))) else { + guard let xTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: x_typeMetadataAddress, in: environment))) else { fatalError("x_typeMetadataAddress memory address was null") } let xDynamicType$: Any.Type = unsafeBitCast(xTypeMetadataPointer$, to: Any.Type.self) - guard let xRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: x, in: environment!))) else { + guard let xRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: x, in: environment))) else { fatalError("x memory address was null") } #if hasFeature(ImplicitOpenExistentials) diff --git a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift index a7c689aaf..f8830b644 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIStructTests.swift @@ -142,9 +142,9 @@ struct JNIStructTests { @_cdecl("Java_com_example_swift_MyStruct__00024init__JJ") func Java_com_example_swift_MyStruct__00024init__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jlong) -> jlong { let result$ = UnsafeMutablePointer.allocate(capacity: 1) - result$.initialize(to: MyStruct.init(x: Int64(fromJNI: x, in: environment!), y: Int64(fromJNI: y, in: environment!))) + result$.initialize(to: MyStruct.init(x: Int64(fromJNI: x, in: environment), y: Int64(fromJNI: y, in: environment))) let resultBits$ = Int64(Int(bitPattern: result$)) - return resultBits$.getJNIValue(in: environment!) + return resultBits$.getJNIValue(in: environment) } """ ] @@ -214,12 +214,12 @@ struct JNIStructTests { @_cdecl("Java_com_example_swift_MyStruct__00024doSomething__JJ") func Java_com_example_swift_MyStruct__00024doSomething__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, self: jlong) { assert(self != 0, "self memory address was null") - let selfBits$ = Int(Int64(fromJNI: self, in: environment!)) + let selfBits$ = Int(Int64(fromJNI: self, in: environment)) let self$ = UnsafeMutablePointer(bitPattern: selfBits$) guard let self$ else { fatalError("self memory address was null in call to \\(#function)!") } - self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment!)) + self$.pointee.doSomething(x: Int64(fromJNI: x, in: environment)) } """ ] diff --git a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift index c9d313a50..363c117d6 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift @@ -70,7 +70,7 @@ struct JNIVariablesTests { @_cdecl("Java_com_example_swift_MyClass__00024getConstant__J") func Java_com_example_swift_MyClass__00024getConstant__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { ... - return self$.pointee.constant.getJNIValue(in: environment!) + return self$.pointee.constant.getJNIValue(in: environment) } """ ] @@ -135,7 +135,7 @@ struct JNIVariablesTests { fatalError("self memory address was null in call to \\(#function)!") } ... - return self$.pointee.mutable.getJNIValue(in: environment!) + return self$.pointee.mutable.getJNIValue(in: environment) } """, """ @@ -143,7 +143,7 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024setMutable__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, self: jlong) { assert(self != 0, "self memory address was null") ... - self$.pointee.mutable = Int64(fromJNI: newValue, in: environment!) + self$.pointee.mutable = Int64(fromJNI: newValue, in: environment) } """ ] @@ -188,7 +188,7 @@ struct JNIVariablesTests { @_cdecl("Java_com_example_swift_MyClass__00024getComputed__J") func Java_com_example_swift_MyClass__00024getComputed__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { ... - return self$.pointee.computed.getJNIValue(in: environment!) + return self$.pointee.computed.getJNIValue(in: environment) } """, ] @@ -234,7 +234,7 @@ struct JNIVariablesTests { func Java_com_example_swift_MyClass__00024getComputedThrowing__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { ... do { - return try self$.pointee.computedThrowing.getJNIValue(in: environment!) + return try self$.pointee.computedThrowing.getJNIValue(in: environment) } catch { environment.throwAsException(error) return Int64.jniPlaceholderValue @@ -297,14 +297,14 @@ struct JNIVariablesTests { @_cdecl("Java_com_example_swift_MyClass__00024getGetterAndSetter__J") func Java_com_example_swift_MyClass__00024getGetterAndSetter__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong { ... - return self$.pointee.getterAndSetter.getJNIValue(in: environment!) + return self$.pointee.getterAndSetter.getJNIValue(in: environment) } """, """ @_cdecl("Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ") func Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, self: jlong) { ... - self$.pointee.getterAndSetter = Int64(fromJNI: newValue, in: environment!) + self$.pointee.getterAndSetter = Int64(fromJNI: newValue, in: environment) } """ ] @@ -363,14 +363,14 @@ struct JNIVariablesTests { @_cdecl("Java_com_example_swift_MyClass__00024isSomeBoolean__J") func Java_com_example_swift_MyClass__00024isSomeBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jboolean { ... - return self$.pointee.someBoolean.getJNIValue(in: environment!) + return self$.pointee.someBoolean.getJNIValue(in: environment) } """, """ @_cdecl("Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ") func Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, self: jlong) { ... - self$.pointee.someBoolean = Bool(fromJNI: newValue, in: environment!) + self$.pointee.someBoolean = Bool(fromJNI: newValue, in: environment) } """ ] @@ -429,14 +429,14 @@ struct JNIVariablesTests { @_cdecl("Java_com_example_swift_MyClass__00024isBoolean__J") func Java_com_example_swift_MyClass__00024isBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jboolean { ... - return self$.pointee.isBoolean.getJNIValue(in: environment!) + return self$.pointee.isBoolean.getJNIValue(in: environment) } """, """ @_cdecl("Java_com_example_swift_MyClass__00024setBoolean__ZJ") func Java_com_example_swift_MyClass__00024setBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, self: jlong) { ... - self$.pointee.isBoolean = Bool(fromJNI: newValue, in: environment!) + self$.pointee.isBoolean = Bool(fromJNI: newValue, in: environment) } """ ] From 1de727915763bb3101c6f4bc4e3bdcfc36b7fd99 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sat, 1 Nov 2025 09:08:20 +0100 Subject: [PATCH 099/141] remove withVaList (#416) --- .../MySwiftLibrary/MySwiftLibrary.swift | 2 ++ ...wift2JavaGenerator+NativeTranslation.swift | 9 ++++++--- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 10 ++++------ .../JNI/JNIAsyncTests.swift | 20 ++++++++----------- .../JExtractSwiftTests/JNI/JNIEnumTests.swift | 10 ++++------ 5 files changed, 24 insertions(+), 27 deletions(-) diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index 09903638e..e278326e0 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -19,6 +19,8 @@ #if os(Linux) import Glibc +#elseif os(Android) + import Android #else import Darwin.C #endif diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 45208d7b9..727294b18 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -830,9 +830,12 @@ extension JNISwift2JavaGenerator { if swiftFunctionResultType.isVoid { printer.print("environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)])") } else { - printer.printBraceBlock("withVaList([SwiftJavaRuntimeSupport._JNIBoxedConversions.box(\(inner), in: environment)])") { printer in - printer.print("environment.interface.CallBooleanMethodV(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, $0)") - } + printer.print( + """ + let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(\(inner), in: environment) + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) + """ + ) } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 5403ae400..c9f8af9ed 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -251,16 +251,14 @@ extension JNISwift2JavaGenerator { """ ) let upcallArguments = zip(enumCase.parameterConversions, caseNames).map { conversion, caseName in - // '0' is treated the same as a null pointer. - let nullConversion = !conversion.native.javaType.isPrimitive ? " ?? 0" : "" + let nullConversion = !conversion.native.javaType.isPrimitive ? " ?? nil" : "" let result = conversion.native.conversion.render(&printer, caseName) - return "\(result)\(nullConversion)" + return "jvalue(\(conversion.native.javaType.jniFieldName): \(result)\(nullConversion))" } printer.print( """ - return withVaList([\(upcallArguments.joined(separator: ", "))]) { - return environment.interface.NewObjectV(environment, class$, constructorID$, $0) - } + let newObjectArgs$: [jvalue] = [\(upcallArguments.joined(separator: ", "))] + return environment.interface.NewObjectA(environment, class$, constructorID$, newObjectArgs$) """ ) } diff --git a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift index 5f4881924..6b26c69ba 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift @@ -224,9 +224,8 @@ struct JNIAsyncTests { } let swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment)) environment = try JavaVirtualMachine.shared().environment() - withVaList([SwiftJavaRuntimeSupport._JNIBoxedConversions.box(swiftResult$.getJNIValue(in: environment), in: environment)]) { - environment.interface.CallBooleanMethodV(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, $0) - } + let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(swiftResult$.getJNIValue(in: environment), in: environment) + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) } } else { @@ -238,9 +237,8 @@ struct JNIAsyncTests { } let swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment)) environment = try JavaVirtualMachine.shared().environment() - withVaList([SwiftJavaRuntimeSupport._JNIBoxedConversions.box(swiftResult$.getJNIValue(in: environment), in: environment)]) { - environment.interface.CallBooleanMethodV(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, $0) - } + let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(swiftResult$.getJNIValue(in: environment), in: environment) + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) } } return @@ -317,9 +315,8 @@ struct JNIAsyncTests { let result$ = UnsafeMutablePointer.allocate(capacity: 1) result$.initialize(to: swiftResult$) let resultBits$ = Int64(Int(bitPattern: result$)) - withVaList([SwiftJavaRuntimeSupport._JNIBoxedConversions.box(resultBits$.getJNIValue(in: environment), in: environment)]) { - environment.interface.CallBooleanMethodV(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, $0) - } + let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(resultBits$.getJNIValue(in: environment), in: environment) + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) } } else { @@ -334,9 +331,8 @@ struct JNIAsyncTests { let result$ = UnsafeMutablePointer.allocate(capacity: 1) result$.initialize(to: swiftResult$) let resultBits$ = Int64(Int(bitPattern: result$)) - withVaList([SwiftJavaRuntimeSupport._JNIBoxedConversions.box(resultBits$.getJNIValue(in: environment), in: environment)]) { - environment.interface.CallBooleanMethodV(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, $0) - } + let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(resultBits$.getJNIValue(in: environment), in: environment) + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) } } return diff --git a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift index ef422e426..38ef87895 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift @@ -302,9 +302,8 @@ struct JNIEnumTests { let class$ = cache$.javaClass let method$ = _JNIMethodIDCache.Method(name: "", signature: "(Ljava/lang/String;)V") let constructorID$ = cache$[method$] - return withVaList([_0.getJNIValue(in: environment) ?? 0]) { - return environment.interface.NewObjectV(environment, class$, constructorID$, $0) - } + let newObjectArgs$: [jvalue] = [jvalue(l: _0.getJNIValue(in: environment) ?? nil)] + return environment.interface.NewObjectA(environment, class$, constructorID$, newObjectArgs$) } """, """ @@ -318,9 +317,8 @@ struct JNIEnumTests { let class$ = cache$.javaClass let method$ = _JNIMethodIDCache.Method(name: "", signature: "(JI)V") let constructorID$ = cache$[method$] - return withVaList([x.getJNIValue(in: environment), y.getJNIValue(in: environment)]) { - return environment.interface.NewObjectV(environment, class$, constructorID$, $0) - } + let newObjectArgs$: [jvalue] = [jvalue(j: x.getJNIValue(in: environment)), jvalue(i: y.getJNIValue(in: environment))] + return environment.interface.NewObjectA(environment, class$, constructorID$, newObjectArgs$) } """ ]) From c58bbed1e3dac074df3ea26b32f118b85e45e14d Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 4 Nov 2025 02:01:02 +0100 Subject: [PATCH 100/141] [jextract] Propagate build plugin config to command (#415) Co-authored-by: Konrad `ktoso` Malawski --- Plugins/PluginsShared/PluginUtils.swift | 4 +- .../SwiftJavaExtractJNISampleApp/build.gradle | 3 +- Sources/JExtractSwiftLib/Swift2Java.swift | 4 +- .../Configuration.swift | 3 ++ .../JExtract/JExtractGenerationMode.swift | 4 ++ .../JExtractUnsignedIntegerMode.swift | 1 - .../Documentation.docc/SupportedFeatures.md | 6 +-- .../Commands/JExtractCommand.swift | 52 +++++++++++-------- .../SwiftJavaBaseAsyncParsableCommand.swift | 2 +- 9 files changed, 45 insertions(+), 34 deletions(-) diff --git a/Plugins/PluginsShared/PluginUtils.swift b/Plugins/PluginsShared/PluginUtils.swift index 863cf99c4..8278c6455 100644 --- a/Plugins/PluginsShared/PluginUtils.swift +++ b/Plugins/PluginsShared/PluginUtils.swift @@ -66,12 +66,12 @@ extension PluginContext { .appending(path: "generated") .appending(path: "java") } - + var outputSwiftDirectory: URL { self.pluginWorkDirectoryURL .appending(path: "Sources") } - + func cachedClasspathFile(swiftModule: String) -> URL { self.pluginWorkDirectoryURL .appending(path: "\(swiftModule)", directoryHint: .notDirectory) diff --git a/Samples/SwiftJavaExtractJNISampleApp/build.gradle b/Samples/SwiftJavaExtractJNISampleApp/build.gradle index d92c96fbc..b1aa84906 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/build.gradle +++ b/Samples/SwiftJavaExtractJNISampleApp/build.gradle @@ -102,8 +102,7 @@ def jextract = tasks.register("jextract", Exec) { workingDir = layout.projectDirectory commandLine "swift" - // TODO: -v for debugging build issues... - args("build", "-v") // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build + args("build") // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build // If we wanted to execute a specific subcommand, we can like this: // args("run",/* // "swift-java", "jextract", diff --git a/Sources/JExtractSwiftLib/Swift2Java.swift b/Sources/JExtractSwiftLib/Swift2Java.swift index 192415922..ed76483e3 100644 --- a/Sources/JExtractSwiftLib/Swift2Java.swift +++ b/Sources/JExtractSwiftLib/Swift2Java.swift @@ -95,8 +95,8 @@ public struct SwiftToJava { try translator.analyze() - switch config.mode { - case .some(.ffm), .none: + switch config.effectiveMode { + case .ffm: let generator = FFMSwift2JavaGenerator( config: self.config, translator: translator, diff --git a/Sources/SwiftJavaConfigurationShared/Configuration.swift b/Sources/SwiftJavaConfigurationShared/Configuration.swift index 6d8d20e51..c9d0cedfc 100644 --- a/Sources/SwiftJavaConfigurationShared/Configuration.swift +++ b/Sources/SwiftJavaConfigurationShared/Configuration.swift @@ -39,6 +39,9 @@ public struct Configuration: Codable { public var outputJavaDirectory: String? public var mode: JExtractGenerationMode? + public var effectiveMode: JExtractGenerationMode { + mode ?? .default + } public var writeEmptyFiles: Bool? // FIXME: default it to false, but that plays not nice with Codable diff --git a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift index 8df00cf02..1ad331da7 100644 --- a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift @@ -19,4 +19,8 @@ public enum JExtractGenerationMode: String, Codable { /// Java Native Interface case jni + + public static var `default`: JExtractGenerationMode { + .ffm + } } diff --git a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractUnsignedIntegerMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractUnsignedIntegerMode.swift index d7a94070e..b53a2c6b7 100644 --- a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractUnsignedIntegerMode.swift +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractUnsignedIntegerMode.swift @@ -11,7 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - /// Configures how Swift unsigned integers should be extracted by jextract. public enum JExtractUnsignedIntegerMode: String, Codable { /// Treat unsigned Swift integers as their signed equivalents in Java signatures, diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index e97ed03ed..98dac732d 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -135,10 +135,10 @@ on the Java side. | `Float` | `float` | | `Double` | `double` | -#### Unsigned numbers mode: wrap-guava +#### Unsigned numbers mode: wrapGuava You can configure `jextract` (in FFM mode) to instead import unsigned values as their unsigned type-safe representations -as offered by the Guava library: `UnsignedLong` or `UnsignedInt`. To enable this mode pass the `--unsigned-numbers wrap-guava` +as offered by the Guava library: `UnsignedLong` or `UnsignedInt`. To enable this mode pass the `--unsigned-numbers-mode wrapGuava` command line option, or set the corresponding configuration value in `swift-java.config` (TODO). This approach is type-safe, however it incurs a performance penalty for allocating a wrapper class for every @@ -162,7 +162,7 @@ you are expected to add a Guava dependency to your Java project. | `Float` | `float` | | `Double` | `double` | -> Note: The `wrap-guava` mode is currently only available in FFM mode of jextract. +> Note: The `wrapGuava` mode is currently only available in FFM mode of jextract. ### Enums diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index aba79983d..b5942d2a4 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -61,14 +61,14 @@ extension SwiftJava { @Flag(help: "Some build systems require an output to be present when it was 'expected', even if empty. This is used by the JExtractSwiftPlugin build plugin, but otherwise should not be necessary.") var writeEmptyFiles: Bool = false - @Option(help: "The mode of generation to use for the output files. Used with jextract mode. By default, unsigned Swift types are imported as their bit-width compatible signed Java counterparts, and annotated using the '@Unsigned' annotation. You may choose the 'wrap-guava' mode in order to import types as class wrapper types (`UnsignedInteger` et al) defined by the Google Guava library's `com.google.common.primitives' package. that ensure complete type-safety with regards to unsigned values, however they incur an allocation and performance overhead.") - var unsignedNumbers: JExtractUnsignedIntegerMode = .default + @Option(help: "The mode of generation to use for the output files. Used with jextract mode. By default, unsigned Swift types are imported as their bit-width compatible signed Java counterparts, and annotated using the '@Unsigned' annotation. You may choose the 'wrapGuava' mode in order to import types as class wrapper types (`UnsignedInteger` et al) defined by the Google Guava library's `com.google.common.primitives' package. that ensure complete type-safety with regards to unsigned values, however they incur an allocation and performance overhead.") + var unsignedNumbersMode: JExtractUnsignedIntegerMode? @Option(help: "The lowest access level of Swift declarations that should be extracted, defaults to 'public'.") - var minimumInputAccessLevel: JExtractMinimumAccessLevelMode = .default + var minimumInputAccessLevelMode: JExtractMinimumAccessLevelMode? - @Option(help: "The memory management mode to use for the generated code. By default, the user must explicitly provide `SwiftArena` to all calls that require it. By choosing `allow-automatic`, user can omit this parameter and a global GC-based arena will be used. `force-automatic` removes all explicit memory management.") - var memoryManagementMode: JExtractMemoryManagementMode = .default + @Option(help: "The memory management mode to use for the generated code. By default, the user must explicitly provide `SwiftArena` to all calls that require it. By choosing `allowGlobalAutomatic`, user can omit this parameter and a global GC-based arena will be used.") + var memoryManagementMode: JExtractMemoryManagementMode? @Option( help: """ @@ -80,7 +80,7 @@ extension SwiftJava { var dependsOn: [String] = [] @Option(help: "The mode to use for extracting asynchronous Swift functions. By default async methods are extracted as Java functions returning CompletableFuture.") - var asyncFuncMode: JExtractAsyncFuncMode = .default + var asyncFuncMode: JExtractAsyncFuncMode? } } @@ -89,21 +89,21 @@ extension SwiftJava.JExtractCommand { if let javaPackage { config.javaPackage = javaPackage } - if let mode { - config.mode = mode - } else if config.mode == nil { - config.mode = .ffm - } + configure(&config.mode, overrideWith: self.mode) config.swiftModule = self.effectiveSwiftModule config.outputJavaDirectory = outputJava config.outputSwiftDirectory = outputSwift - config.writeEmptyFiles = writeEmptyFiles - config.unsignedNumbersMode = unsignedNumbers - config.minimumInputAccessLevelMode = minimumInputAccessLevel - config.memoryManagementMode = memoryManagementMode - config.asyncFuncMode = asyncFuncMode - try checkModeCompatibility() + // @Flag does not support optional, so we check ourself if it is passed + let writeEmptyFiles = CommandLine.arguments.contains("--write-empty-files") ? true : nil + configure(&config.writeEmptyFiles, overrideWith: writeEmptyFiles) + + configure(&config.unsignedNumbersMode, overrideWith: self.unsignedNumbersMode) + configure(&config.minimumInputAccessLevelMode, overrideWith: self.minimumInputAccessLevelMode) + configure(&config.memoryManagementMode, overrideWith: self.memoryManagementMode) + configure(&config.asyncFuncMode, overrideWith: self.asyncFuncMode) + + try checkModeCompatibility(config: config) if let inputSwift = commonOptions.inputSwift { config.inputSwiftDirectory = inputSwift @@ -112,7 +112,7 @@ extension SwiftJava.JExtractCommand { config.inputSwiftDirectory = "\(FileManager.default.currentDirectoryPath)/Sources/\(swiftModule)" } - print("[debug][swift-java] Running 'swift-java jextract' in mode: " + "\(config.mode ?? .ffm)".bold) + print("[debug][swift-java] Running 'swift-java jextract' in mode: " + "\(config.effectiveMode)".bold) // Load all of the dependent configurations and associate them with Swift modules. let dependentConfigs = try loadDependentConfigs(dependsOn: self.dependsOn) @@ -122,20 +122,26 @@ extension SwiftJava.JExtractCommand { } /// Check if the configured modes are compatible, and fail if not - func checkModeCompatibility() throws { - if self.mode == .jni { - switch self.unsignedNumbers { + func checkModeCompatibility(config: Configuration) throws { + if config.effectiveMode == .jni { + switch config.effectiveUnsignedNumbersMode { case .annotate: () // OK case .wrapGuava: throw IllegalModeCombinationError("JNI mode does not support '\(JExtractUnsignedIntegerMode.wrapGuava)' Unsigned integer mode! \(Self.helpMessage)") } - } else if self.mode == .ffm { - guard self.memoryManagementMode == .explicit else { + } else if config.effectiveMode == .ffm { + guard config.effectiveMemoryManagementMode == .explicit else { throw IllegalModeCombinationError("FFM mode does not support '\(self.memoryManagementMode)' memory management mode! \(Self.helpMessage)") } } } + + func configure(_ setting: inout T?, overrideWith value: T?) { + if let value { + setting = value + } + } } struct IncompatibleModeError: Error { diff --git a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift index 25b162e1b..6b8c0ec6b 100644 --- a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift +++ b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift @@ -167,4 +167,4 @@ extension SwiftJavaBaseAsyncParsableCommand { config.logLevel = command.logLevel return config } -} \ No newline at end of file +} From cc0ff8f6e478a05079c7206d7d4014d9c6eef776 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Tue, 4 Nov 2025 15:53:28 +0900 Subject: [PATCH 101/141] Adjust Task.immediate use so we can bring back Swift 6.1 CI until 6.2 is fixed (#417) --- .github/workflows/pull_request.yml | 4 +- Package.swift | 2 +- .../Package.swift | 2 +- Sources/JExtractSwiftLib/CodePrinter.swift | 14 ++ ...wift2JavaGenerator+NativeTranslation.swift | 24 ++-- .../Asserts/TextAssertions.swift | 22 ++-- .../JNI/JNIAsyncTests.swift | 122 ++++++++++-------- docker/Dockerfile | 2 +- 8 files changed, 110 insertions(+), 82 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 5e5b767a6..70cff51cd 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -140,7 +140,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.2', 'nightly'] + swift_version: ['6.1.3', '6.2', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -182,7 +182,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.2', 'nightly'] + swift_version: ['6.1.3', '6.2', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] sample_app: [ # TODO: use a reusable-workflow to generate those names diff --git a/Package.swift b/Package.swift index 09743135c..7ac7d1f9f 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 6.2 +// swift-tools-version: 6.1 // The swift-tools-version declares the minimum version of Swift required to build this package. import CompilerPluginSupport diff --git a/Samples/SwiftJavaExtractJNISampleApp/Package.swift b/Samples/SwiftJavaExtractJNISampleApp/Package.swift index 33a35c49e..6343e0dbd 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Package.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 6.2 +// swift-tools-version: 6.1 // The swift-tools-version declares the minimum version of Swift required to build this package. import CompilerPluginSupport diff --git a/Sources/JExtractSwiftLib/CodePrinter.swift b/Sources/JExtractSwiftLib/CodePrinter.swift index c5f04d51e..e6498683c 100644 --- a/Sources/JExtractSwiftLib/CodePrinter.swift +++ b/Sources/JExtractSwiftLib/CodePrinter.swift @@ -70,6 +70,20 @@ public struct CodePrinter { } } + public mutating func printHashIfBlock( + _ header: Any, + function: String = #function, + file: String = #fileID, + line: UInt = #line, + body: (inout CodePrinter) throws -> () + ) rethrows { + print("#if \(header)") + indent() + try body(&self) + outdent() + print("#endif // end of \(header)", .sloc, function: function, file: file, line: line) + } + public mutating func printBraceBlock( _ header: Any, function: String = #function, diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 727294b18..d4ce94269 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -825,7 +825,7 @@ extension JNISwift2JavaGenerator { func printDo(printer: inout CodePrinter) { printer.print("let swiftResult$ = await \(placeholder)") - printer.print("environment = try JavaVirtualMachine.shared().environment()") + printer.print("environment = try! JavaVirtualMachine.shared().environment()") let inner = inner.render(&printer, "swiftResult$") if swiftFunctionResultType.isVoid { printer.print("environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)])") @@ -839,7 +839,7 @@ extension JNISwift2JavaGenerator { } } - func printTask(printer: inout CodePrinter) { + func printTaskBody(printer: inout CodePrinter) { printer.printBraceBlock("defer") { printer in // Defer might on any thread, so we need to attach environment. printer.print("let deferEnvironment = try! JavaVirtualMachine.shared().environment()") @@ -867,18 +867,22 @@ extension JNISwift2JavaGenerator { } } - printer.printBraceBlock("if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *)") { printer in - printer.printBraceBlock("Task.immediate") { printer in - // Immediate runs on the caller thread, so we don't need to attach the environment again. - printer.print("var environment = environment!") // this is to ensure we always use the same environment name, even though we are rebinding it. - printTask(printer: &printer) + printer.print("var task: Task? = nil") + printer.printHashIfBlock("swift(>=6.2)") { printer in + printer.printBraceBlock("if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *)") { printer in + printer.printBraceBlock("task = Task.immediate") { printer in + // Immediate runs on the caller thread, so we don't need to attach the environment again. + printer.print("var environment = environment!") // this is to ensure we always use the same environment name, even though we are rebinding it. + printTaskBody(printer: &printer) + } } } - printer.printBraceBlock("else") { printer in - printer.printBraceBlock("Task") { printer in + + printer.printBraceBlock("if task == nil") { printer in + printer.printBraceBlock("task = Task") { printer in // We can be on any thread, so we need to attach the thread. printer.print("var environment = try! JavaVirtualMachine.shared().environment()") - printTask(printer: &printer) + printTaskBody(printer: &printer) } } diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index 001d34faf..67671548b 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -179,7 +179,9 @@ func assertOutput( print("==== ---------------------------------------------------------------") print("Expected output:") for (n, e) in expectedLines.enumerated() { - print("\(n): \(e)".yellow(if: diffLineNumbers.map({$0 - matchingOutputOffset}).contains(n))) + let isMismatch = diffLineNumbers.map({$0 - matchingOutputOffset}).contains(n) + let marker = isMismatch ? " // <<<<<<<<<<< mismatch" : "" + print("\(n): \(e)\(marker)".yellow(if: isMismatch)) } } @@ -188,15 +190,7 @@ func assertOutput( let printFromLineNo = matchingOutputOffset for (n, g) in gotLines.enumerated() where n >= printFromLineNo { let baseLine = "\(n): \(g)" - var line = baseLine - if diffLineNumbers.contains(n) { - line += "\n" - let leadingCount = "\(n): ".count - let message = "\(String(repeating: " ", count: leadingCount))\(String(repeating: "^", count: 8)) EXPECTED MATCH OR SEARCHING FROM HERE " - line += "\(message)\(String(repeating: "^", count: max(0, line.count - message.count)))" - line = line.red - } - print(line) + print(baseLine) } print("==== ---------------------------------------------------------------\n") } @@ -248,14 +242,18 @@ func assertOutput( print("==== ---------------------------------------------------------------") print("Expected output:") for (n, e) in expectedLines.enumerated() { - print("\(e)".yellow(if: diffLineNumbers.contains(n))) + let isMismatch = diffLineNumbers.contains(n) + let marker = isMismatch ? " // <<<<<<<< error: mismatch" : "" + print("\(e)\(marker)".yellow(if: isMismatch)) } } print("==== ---------------------------------------------------------------") print("Got output:") for (n, g) in gotLines.enumerated() { - print("\(g)".red(if: diffLineNumbers.contains(n))) + let isMismatch = diffLineNumbers.contains(n) + let marker = isMismatch ? "// <<<<<<<< error: mismatch" : "" + print("\(g)\(marker)".red(if: isMismatch)) } print("==== ---------------------------------------------------------------\n") } diff --git a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift index 6b26c69ba..82922c7d8 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift @@ -59,27 +59,30 @@ struct JNIAsyncTests { @_cdecl("Java_com_example_swift_SwiftModule__00024asyncVoid__Ljava_util_concurrent_CompletableFuture_2") func Java_com_example_swift_SwiftModule__00024asyncVoid__Ljava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, result_future: jobject?) { let globalFuture = environment.interface.NewGlobalRef(environment, result_future) - if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { - Task.immediate { - var environment = environment! - defer { - let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + var task: Task? = nil + #if swift(>=6.2) + if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { + task = Task.immediate { + var environment = environment! + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.asyncVoid() + environment = try! JavaVirtualMachine.shared().environment() + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) } - let swiftResult$ = await SwiftModule.asyncVoid() - environment = try JavaVirtualMachine.shared().environment() - environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) } - } - else { - Task { + #endif + if task == nil { + task = Task { var environment = try! JavaVirtualMachine.shared().environment() defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.asyncVoid() - environment = try JavaVirtualMachine.shared().environment() + environment = try! JavaVirtualMachine.shared().environment() environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) } } @@ -130,27 +133,30 @@ struct JNIAsyncTests { @_cdecl("Java_com_example_swift_SwiftModule__00024async__Ljava_util_concurrent_CompletableFuture_2") func Java_com_example_swift_SwiftModule__00024async__Ljava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, result_future: jobject?) { let globalFuture = environment.interface.NewGlobalRef(environment, result_future) - if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { - Task.immediate { - var environment = environment! - defer { - let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) - } - do { - let swiftResult$ = await try SwiftModule.async() - environment = try JavaVirtualMachine.shared().environment() - environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) - } - catch { - let catchEnvironment = try! JavaVirtualMachine.shared().environment() - let exception = catchEnvironment.interface.NewObjectA(catchEnvironment, _JNIMethodIDCache.Exception.class, _JNIMethodIDCache.Exception.constructWithMessage, [String(describing: error).getJValue(in: catchEnvironment)]) - catchEnvironment.interface.CallBooleanMethodA(catchEnvironment, globalFuture, _JNIMethodIDCache.CompletableFuture.completeExceptionally, [jvalue(l: exception)]) + var task: Task? = nil + #if swift(>=6.2) + if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { + task = Task.immediate { + var environment = environment! + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + do { + let swiftResult$ = await try SwiftModule.async() + environment = try! JavaVirtualMachine.shared().environment() + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) + } + catch { + let catchEnvironment = try! JavaVirtualMachine.shared().environment() + let exception = catchEnvironment.interface.NewObjectA(catchEnvironment, _JNIMethodIDCache.Exception.class, _JNIMethodIDCache.Exception.constructWithMessage, [String(describing: error).getJValue(in: catchEnvironment)]) + catchEnvironment.interface.CallBooleanMethodA(catchEnvironment, globalFuture, _JNIMethodIDCache.CompletableFuture.completeExceptionally, [jvalue(l: exception)]) + } } } - } - else { - Task { + #endif + if task == nil { + task = Task { var environment = try! JavaVirtualMachine.shared().environment() defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() @@ -158,7 +164,7 @@ struct JNIAsyncTests { } do { let swiftResult$ = await try SwiftModule.async() - environment = try JavaVirtualMachine.shared().environment() + environment = try! JavaVirtualMachine.shared().environment() environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) } catch { @@ -215,28 +221,31 @@ struct JNIAsyncTests { @_cdecl("Java_com_example_swift_SwiftModule__00024async__JLjava_util_concurrent_CompletableFuture_2") func Java_com_example_swift_SwiftModule__00024async__JLjava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, i: jlong, result_future: jobject?) { let globalFuture = environment.interface.NewGlobalRef(environment, result_future) + var task: Task? = nil + #if swift(>=6.2) if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { - Task.immediate { + task = Task.immediate { var environment = environment! defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment)) - environment = try JavaVirtualMachine.shared().environment() + environment = try! JavaVirtualMachine.shared().environment() let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(swiftResult$.getJNIValue(in: environment), in: environment) environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) } } - else { - Task { + #endif // end of swift(>=6.2) + if task == nil { + task = Task { var environment = try! JavaVirtualMachine.shared().environment() defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment)) - environment = try JavaVirtualMachine.shared().environment() + environment = try! JavaVirtualMachine.shared().environment() let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(swiftResult$.getJNIValue(in: environment), in: environment) environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) } @@ -303,31 +312,34 @@ struct JNIAsyncTests { fatalError("c memory address was null in call to \\(#function)!") } let globalFuture = environment.interface.NewGlobalRef(environment, result_future) - if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { - Task.immediate { - var environment = environment! - defer { - let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + var task: Task? = nil + #if swift(>=6.2) + if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { + task = Task.immediate { + var environment = environment! + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.async(c: c$.pointee) + environment = try! JavaVirtualMachine.shared().environment() + let result$ = UnsafeMutablePointer.allocate(capacity: 1) + result$.initialize(to: swiftResult$) + let resultBits$ = Int64(Int(bitPattern: result$)) + let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(resultBits$.getJNIValue(in: environment), in: environment) + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) } - let swiftResult$ = await SwiftModule.async(c: c$.pointee) - environment = try JavaVirtualMachine.shared().environment() - let result$ = UnsafeMutablePointer.allocate(capacity: 1) - result$.initialize(to: swiftResult$) - let resultBits$ = Int64(Int(bitPattern: result$)) - let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(resultBits$.getJNIValue(in: environment), in: environment) - environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) } - } - else { - Task { + #endif + if task == nil { + task = Task { var environment = try! JavaVirtualMachine.shared().environment() defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.async(c: c$.pointee) - environment = try JavaVirtualMachine.shared().environment() + environment = try! JavaVirtualMachine.shared().environment() let result$ = UnsafeMutablePointer.allocate(capacity: 1) result$.initialize(to: swiftResult$) let resultBits$ = Int64(Int(bitPattern: result$)) diff --git a/docker/Dockerfile b/docker/Dockerfile index 1109dab30..ef428b875 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -21,10 +21,10 @@ ENV LANGUAGE=en_US.UTF-8 # JDK dependency RUN curl -s "https://get.sdkman.io" | bash RUN bash -c "source /root/.sdkman/bin/sdkman-init.sh && sdk install java 25.0.1-amzn" +ENV JAVA_HOME="$(sdk home java current)" RUN curl -O https://download.swift.org/swiftly/linux/swiftly-$(uname -m).tar.gz && \ tar zxf swiftly-$(uname -m).tar.gz && \ ./swiftly init --quiet-shell-followup --assume-yes && \ . "${SWIFTLY_HOME_DIR:-$HOME/.local/share/swiftly}/env.sh" && \ hash -r - From b4706f3f744de3b27cef892af580ed11419811f2 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 5 Nov 2025 11:51:16 +0900 Subject: [PATCH 102/141] Disable swift-syntax prebuilts (primarily on linux) (#422) --- .github/workflows/pull_request.yml | 8 ++++---- Samples/JavaDependencySampleApp/ci-validate.sh | 6 +++++- Samples/JavaProbablyPrime/ci-validate.sh | 5 ++++- Samples/SwiftAndJavaJarSampleLib/build.gradle | 3 ++- Samples/SwiftJavaExtractFFMSampleApp/build.gradle | 3 ++- Samples/SwiftJavaExtractFFMSampleApp/ci-validate.sh | 2 +- Samples/SwiftJavaExtractJNISampleApp/build.gradle | 3 ++- Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh | 2 +- SwiftKitCore/build.gradle | 3 ++- SwiftKitFFM/build.gradle | 3 ++- 10 files changed, 25 insertions(+), 13 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 70cff51cd..161511689 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -38,7 +38,7 @@ jobs: - name: Prepare CI Environment uses: ./.github/actions/prepare_env - name: Swift Build - run: swift build + run: swift build --disable-experimental-prebuilts # FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 - name: Run documentation check run: ./.github/scripts/validate_docs.sh @@ -132,7 +132,7 @@ jobs: - name: Install jemalloc run: apt-get update && apt-get install -y libjemalloc-dev - name: Swift Benchmarks - run: swift package --package-path Benchmarks/ benchmark + run: swift package --package-path Benchmarks/ --disable-experimental-prebuilts benchmark # FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 test-swift: name: Test (Swift) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) @@ -152,9 +152,9 @@ jobs: - name: Prepare CI Environment uses: ./.github/actions/prepare_env - name: Swift Build - run: "swift build --build-tests --disable-sandbox" + run: swift build --build-tests --disable-sandbox --disable-experimental-prebuilts # FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 - name: Swift Test - run: "swift test" + run: swift test --disable-experimental-prebuilts # FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 test-swift-macos: name: Test (Swift) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) diff --git a/Samples/JavaDependencySampleApp/ci-validate.sh b/Samples/JavaDependencySampleApp/ci-validate.sh index 2758e6aaf..b6162fdce 100755 --- a/Samples/JavaDependencySampleApp/ci-validate.sh +++ b/Samples/JavaDependencySampleApp/ci-validate.sh @@ -8,7 +8,11 @@ swift run --disable-sandbox # explicitly invoke resolve without explicit path or dependency # the dependencies should be uses from the --swift-module -swift run swift-java resolve \ + +# FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 +swift run \ + --disable-experimental-prebuilts \ + swift-java resolve \ Sources/JavaCommonsCSV/swift-java.config \ --swift-module JavaCommonsCSV \ --output-directory .build/plugins/outputs/javadependencysampleapp/JavaCommonsCSV/destination/SwiftJavaPlugin/ diff --git a/Samples/JavaProbablyPrime/ci-validate.sh b/Samples/JavaProbablyPrime/ci-validate.sh index 0bdd86d17..dc6249969 100755 --- a/Samples/JavaProbablyPrime/ci-validate.sh +++ b/Samples/JavaProbablyPrime/ci-validate.sh @@ -3,4 +3,7 @@ set -e set -x -swift run JavaProbablyPrime 1337 \ No newline at end of file +# FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 +swift run \ + --disable-experimental-prebuilts \ + JavaProbablyPrime 1337 \ No newline at end of file diff --git a/Samples/SwiftAndJavaJarSampleLib/build.gradle b/Samples/SwiftAndJavaJarSampleLib/build.gradle index 3ad59fe5c..e6404adb1 100644 --- a/Samples/SwiftAndJavaJarSampleLib/build.gradle +++ b/Samples/SwiftAndJavaJarSampleLib/build.gradle @@ -116,7 +116,8 @@ def jextract = tasks.register("jextract", Exec) { workingDir = layout.projectDirectory commandLine "swift" - args("build") // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build + // FIXME: disable prebuilts until swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 + args("build", "--disable-experimental-prebuilts") // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build // If we wanted to execute a specific subcommand, we can like this: // args("run",/* // "swift-java", "jextract", diff --git a/Samples/SwiftJavaExtractFFMSampleApp/build.gradle b/Samples/SwiftJavaExtractFFMSampleApp/build.gradle index 0200f2341..dcfacdd39 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/build.gradle +++ b/Samples/SwiftJavaExtractFFMSampleApp/build.gradle @@ -101,7 +101,8 @@ def jextract = tasks.register("jextract", Exec) { workingDir = layout.projectDirectory commandLine "swift" - args("build") // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build + // FIXME: disable prebuilts until swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 + args("build", "--disable-experimental-prebuilts") // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build // If we wanted to execute a specific subcommand, we can like this: // args("run",/* // "swift-java", "jextract", diff --git a/Samples/SwiftJavaExtractFFMSampleApp/ci-validate.sh b/Samples/SwiftJavaExtractFFMSampleApp/ci-validate.sh index 8758bbee0..ff5c32c80 100755 --- a/Samples/SwiftJavaExtractFFMSampleApp/ci-validate.sh +++ b/Samples/SwiftJavaExtractFFMSampleApp/ci-validate.sh @@ -3,7 +3,7 @@ set -x set -e -swift build # as a workaround for building swift build from within gradle having issues on CI sometimes +swift build --disable-experimental-prebuilts # FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 ./gradlew run ./gradlew test \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/build.gradle b/Samples/SwiftJavaExtractJNISampleApp/build.gradle index b1aa84906..7f30ee42b 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/build.gradle +++ b/Samples/SwiftJavaExtractJNISampleApp/build.gradle @@ -102,7 +102,8 @@ def jextract = tasks.register("jextract", Exec) { workingDir = layout.projectDirectory commandLine "swift" - args("build") // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build + // FIXME: disable prebuilts until swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 + args("build", "--disable-experimental-prebuilts") // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build // If we wanted to execute a specific subcommand, we can like this: // args("run",/* // "swift-java", "jextract", diff --git a/Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh b/Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh index 8758bbee0..ff5c32c80 100755 --- a/Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh +++ b/Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh @@ -3,7 +3,7 @@ set -x set -e -swift build # as a workaround for building swift build from within gradle having issues on CI sometimes +swift build --disable-experimental-prebuilts # FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 ./gradlew run ./gradlew test \ No newline at end of file diff --git a/SwiftKitCore/build.gradle b/SwiftKitCore/build.gradle index f4376e94a..bc6bd2794 100644 --- a/SwiftKitCore/build.gradle +++ b/SwiftKitCore/build.gradle @@ -84,7 +84,8 @@ def compileSwift = tasks.register("compileSwift", Exec) { workingDir = rootDir commandLine "swift" - args("build", "--target", "SwiftRuntimeFunctions") + // FIXME: disable prebuilts until swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 + args("build", "--disable-experimental-prebuilts", "--target", "SwiftRuntimeFunctions") } tasks.build { dependsOn("compileSwift") diff --git a/SwiftKitFFM/build.gradle b/SwiftKitFFM/build.gradle index dcbbe8dfc..08a36a3e3 100644 --- a/SwiftKitFFM/build.gradle +++ b/SwiftKitFFM/build.gradle @@ -86,7 +86,8 @@ def compileSwift = tasks.register("compileSwift", Exec) { workingDir = rootDir commandLine "swift" - args("build", "--target", "SwiftRuntimeFunctions") + // FIXME: disable prebuilts until swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 + args("build", "--disable-experimental-prebuilts", "--target", "SwiftRuntimeFunctions") } tasks.build { dependsOn("compileSwift") From a9f4f07a94acc8af07ba6c118072235d8ed70054 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Wed, 5 Nov 2025 23:08:00 +0100 Subject: [PATCH 103/141] fix docs about init and add tests (#426) --- .../Sources/MySwiftLibrary/MySwiftClass.swift | 8 ++++++++ .../Sources/MySwiftLibrary/MySwiftStruct.swift | 8 ++++++++ .../java/com/example/swift/MySwiftClassTest.java | 11 +++++++++++ .../java/com/example/swift/MySwiftStructTest.java | 13 +++++++++++++ .../Documentation.docc/SupportedFeatures.md | 2 +- 5 files changed, 41 insertions(+), 1 deletion(-) diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index 4b334a5e6..e6aed287b 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -59,6 +59,14 @@ public class MySwiftClass { self.y = 5 } + convenience public init(throwing: Bool) throws { + if throwing { + throw MySwiftError.swiftError + } else { + self.init() + } + } + deinit { p("deinit called!") } diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift index 00e2533c8..ddd77132d 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift @@ -21,6 +21,14 @@ public struct MySwiftStruct { self.len = len } + public init?(doInit: Bool) { + if doInit { + self.init(cap: 10, len: 10) + } else { + return nil + } + } + public func getCapacity() -> Int64 { self.cap } diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java index fba8f13f3..a58386299 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -40,6 +40,17 @@ void init_withParameters() { } } + @Test + void init_throwing() { + try (var arena = SwiftArena.ofConfined()) { + Exception exception = assertThrows(Exception.class, () -> MySwiftClass.init(true, arena)); + assertEquals("swiftError", exception.getMessage()); + + MySwiftClass c = assertDoesNotThrow(() -> MySwiftClass.init(false, arena)); + assertNotNull(c); + } + } + @Test void sum() { try (var arena = SwiftArena.ofConfined()) { diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java index c2c1170b7..e52e19591 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java @@ -18,6 +18,8 @@ import org.swift.swiftkit.core.ConfinedSwiftMemorySession; import org.swift.swiftkit.core.SwiftArena; +import java.util.Optional; + import static org.junit.jupiter.api.Assertions.*; public class MySwiftStructTest { @@ -30,6 +32,17 @@ void init() { } } + @Test + void init_optional() { + try (var arena = SwiftArena.ofConfined()) { + assertEquals(Optional.empty(), MySwiftStruct.init(false, arena)); + + Optional optionalStruct = MySwiftStruct.init(true, arena); + assertTrue(optionalStruct.isPresent()); + assertEquals(10, optionalStruct.get().getLen()); + } + } + @Test void getAndSetLen() { try (var arena = SwiftArena.ofConfined()) { diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 98dac732d..4bbaee400 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -47,7 +47,7 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Swift Feature | FFM | JNI | |--------------------------------------------------------------------------------------|----------|-----| | Initializers: `class`, `struct` | ✅ | ✅ | -| Optional Initializers / Throwing Initializers | ❌ | ❌ | +| Optional Initializers / Throwing Initializers | ❌ | ✅ | | Deinitializers: `class`, `struct` | ✅ | ✅ | | `enum` | ❌ | ✅ | | `actor` | ❌ | ❌ | From 3ebdfbb309c327c3dae87ecf345ec4c53cd5d509 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 6 Nov 2025 10:45:47 +0100 Subject: [PATCH 104/141] Add CI to build JNI Sample for Android (#427) Co-authored-by: Konrad `ktoso` Malawski --- .github/workflows/pull_request.yml | 24 +++++++++++++++++++ .../SwiftJavaExtractJNISampleApp/build.gradle | 13 ++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 161511689..a0f2eda5f 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -176,6 +176,30 @@ jobs: - name: Swift Test run: "swift test" + build-swift-android: + name: Sample SwiftJavaExtractJNISampleApp (Android) (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} android:${{matrix.sdk_triple}}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + swift_version: ['nightly-main'] + os_version: ['jammy'] + jdk_vendor: ['corretto'] + sdk_triple: ['x86_64-unknown-linux-android28'] + ndk_version: ['r27d'] + container: + image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} + steps: + - uses: actions/checkout@v4 + - name: Prepare CI Environment + uses: ./.github/actions/prepare_env + - name: Install Swift SDK for Android and build + run: | + apt-get -q update && apt-get -yq install curl + cd Samples/SwiftJavaExtractJNISampleApp + curl -s --retry 3 https://raw.githubusercontent.com/swiftlang/github-workflows/refs/heads/main/.github/workflows/scripts/install-and-build-with-sdk.sh | \ + bash -s -- --android --build-command="swift build" --android-sdk-triple="${{ matrix.sdk_triple }}" --android-ndk-version="${{ matrix.ndk_version }}" ${{ matrix.swift_version }} + verify-samples: name: Sample ${{ matrix.sample_app }} (${{ matrix.os_version }} swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}}) runs-on: ubuntu-latest diff --git a/Samples/SwiftJavaExtractJNISampleApp/build.gradle b/Samples/SwiftJavaExtractJNISampleApp/build.gradle index 7f30ee42b..a8ce51d27 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/build.gradle +++ b/Samples/SwiftJavaExtractJNISampleApp/build.gradle @@ -100,10 +100,19 @@ def jextract = tasks.register("jextract", Exec) { } } + // FIXME: disable prebuilts until swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 + def cmdArgs = ["build", "--disable-experimental-prebuilts"] + + // Check if the 'swiftSdk' project property was passed + if (project.hasProperty('swiftSdk')) { + // If it was, add the --sdk argument and its value + cmdArgs.add("--swift-sdk") + cmdArgs.add(project.property('swiftSdk').toString()) + } + workingDir = layout.projectDirectory commandLine "swift" - // FIXME: disable prebuilts until swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 - args("build", "--disable-experimental-prebuilts") // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build + args(cmdArgs) // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build // If we wanted to execute a specific subcommand, we can like this: // args("run",/* // "swift-java", "jextract", From 79f1f8e0f336806362bcdc6e090ff7afa968f504 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Thu, 6 Nov 2025 18:56:56 +0900 Subject: [PATCH 105/141] Enhance README with simple introduction, presentation link Updated project description and added introduction section with a reference to a WWDC presentation. --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6f9a5fde8..abca3b9dd 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,16 @@ # Swift Java Interoperability Tools and Libraries -This repository contains two approaches to Swift/Java interoperability. +This project contains tools and libraries that facilitate **Swift & Java Interoperability**. - Swift library (`SwiftJava`) and bindings generator that allows a Swift program to make use of Java libraries by wrapping Java classes in corresponding Swift types, allowing Swift to directly call any wrapped Java API. - The `swift-java` tool which offers automated ways to import or "extract" bindings to sources or libraries in either language. The results are bindings for Swift or Java. +## Introduction + +If you'd like to check out a quick introduction to Swift & Java interoperability, you may be interested in this presentation from WWDC25: [WWDC25: Explore Swift and Java interoperability](https://www.youtube.com/watch?v=QSHO-GUGidA). + +While we work on more quickstarts and documentation, please refer to the Sample projects located in [Samples/](Samples/) that showcase the various ways you can use swift-java in your Swift or Java projects. + ## Dependencies ### Required JDK versions From cc728395a68bdc89d24250ac8f1979e1fe8b853b Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 7 Nov 2025 06:50:54 +0100 Subject: [PATCH 106/141] [jextract] fix support for objects in async (#428) Co-authored-by: Konrad `ktoso` Malawski --- .../Sources/MySwiftLibrary/Async.swift | 4 ++ .../java/com/example/swift/AsyncTest.java | 6 ++ .../Convenience/JavaType+Extensions.swift | 9 +++ ...ISwift2JavaGenerator+JavaTranslation.swift | 6 +- ...wift2JavaGenerator+NativeTranslation.swift | 41 ++++++++------ Sources/JExtractSwiftLib/JavaParameter.swift | 9 +++ .../JNI/JNIAsyncTests.swift | 55 +++++++++++++++++++ 7 files changed, 109 insertions(+), 21 deletions(-) diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift index ebef18924..99f3a3933 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Async.swift @@ -36,3 +36,7 @@ public func asyncOptional(i: Int64) async throws -> Int64? { public func asyncThrows() async throws { throw MySwiftError.swiftError } + +public func asyncString(input: String) async -> String { + return input +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java index ae6e7cc7a..5fe7c1310 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java @@ -74,4 +74,10 @@ void asyncOptional() { CompletableFuture future = MySwiftLibrary.asyncOptional(42); assertEquals(OptionalLong.of(42), future.join()); } + + @Test + void asyncString() { + CompletableFuture future = MySwiftLibrary.asyncString("hey"); + assertEquals("hey", future.join()); + } } \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift index 3b29fcd30..645e5aa48 100644 --- a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift @@ -137,4 +137,13 @@ extension JavaType { return self } } + + var requiresBoxing: Bool { + switch self { + case .boolean, .byte, .char, .short, .int, .long, .float, .double: + true + default: + false + } + } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 56174e3ed..a4e5ab451 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -514,14 +514,12 @@ extension JNISwift2JavaGenerator { ) // Update native function - nativeFunctionSignature.result.javaType = .void nativeFunctionSignature.result.conversion = .asyncCompleteFuture( - nativeFunctionSignature.result.conversion, swiftFunctionResultType: originalFunctionSignature.result.type, - nativeReturnType: nativeFunctionSignature.result.javaType, - outParameters: nativeFunctionSignature.result.outParameters, + nativeFunctionSignature: nativeFunctionSignature, isThrowing: originalFunctionSignature.isThrowing ) + nativeFunctionSignature.result.javaType = .void nativeFunctionSignature.result.outParameters.append(.init(name: "result_future", type: nativeFutureType)) } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index d4ce94269..13e8c676e 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -570,10 +570,8 @@ extension JNISwift2JavaGenerator { indirect case unwrapOptional(NativeSwiftConversionStep, name: String, fatalErrorMessage: String) indirect case asyncCompleteFuture( - NativeSwiftConversionStep, swiftFunctionResultType: SwiftType, - nativeReturnType: JavaType, - outParameters: [JavaParameter], + nativeFunctionSignature: NativeFunctionSignature, isThrowing: Bool ) @@ -806,15 +804,22 @@ extension JNISwift2JavaGenerator { return unwrappedName case .asyncCompleteFuture( - let inner, let swiftFunctionResultType, - let nativeReturnType, - let outParameters, + let nativeFunctionSignature, let isThrowing ): + var globalRefs: [String] = ["globalFuture"] + // Global ref all indirect returns - for outParameter in outParameters { + for outParameter in nativeFunctionSignature.result.outParameters { printer.print("let \(outParameter.name) = environment.interface.NewGlobalRef(environment, \(outParameter.name))") + globalRefs.append(outParameter.name) + } + + // We also need to global ref any objects passed in + for parameter in nativeFunctionSignature.parameters.flatMap(\.parameters) where !parameter.type.isPrimitive { + printer.print("let \(parameter.name) = environment.interface.NewGlobalRef(environment, \(parameter.name))") + globalRefs.append(parameter.name) } printer.print( @@ -826,16 +831,19 @@ extension JNISwift2JavaGenerator { func printDo(printer: inout CodePrinter) { printer.print("let swiftResult$ = await \(placeholder)") printer.print("environment = try! JavaVirtualMachine.shared().environment()") - let inner = inner.render(&printer, "swiftResult$") + let inner = nativeFunctionSignature.result.conversion.render(&printer, "swiftResult$") if swiftFunctionResultType.isVoid { printer.print("environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)])") } else { - printer.print( - """ - let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(\(inner), in: environment) - environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) - """ - ) + let result: String + if nativeFunctionSignature.result.javaType.requiresBoxing { + printer.print("let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(\(inner), in: environment)") + result = "boxedResult$" + } else { + result = inner + } + + printer.print("environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: \(result))])") } } @@ -843,9 +851,8 @@ extension JNISwift2JavaGenerator { printer.printBraceBlock("defer") { printer in // Defer might on any thread, so we need to attach environment. printer.print("let deferEnvironment = try! JavaVirtualMachine.shared().environment()") - printer.print("environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture)") - for outParameter in outParameters { - printer.print("environment.interface.DeleteGlobalRef(deferEnvironment, \(outParameter.name))") + for globalRef in globalRefs { + printer.print("environment.interface.DeleteGlobalRef(deferEnvironment, \(globalRef))") } } if isThrowing { diff --git a/Sources/JExtractSwiftLib/JavaParameter.swift b/Sources/JExtractSwiftLib/JavaParameter.swift index 2670e6ee2..0b243b3a4 100644 --- a/Sources/JExtractSwiftLib/JavaParameter.swift +++ b/Sources/JExtractSwiftLib/JavaParameter.swift @@ -35,6 +35,15 @@ struct JavaParameter { } } + var isPrimitive: Bool { + switch self { + case .concrete(let javaType): + javaType.isPrimitive + case .generic(let name, let extends): + false + } + } + var jniTypeName: String { switch self { case .concrete(let type): type.jniTypeName diff --git a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift index 82922c7d8..975cccc6a 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift @@ -353,4 +353,59 @@ struct JNIAsyncTests { ] ) } + + @Test("Import: (String) async -> String (Java, CompletableFuture)") + func completableFuture_asyncStringToString_java() throws { + try assertOutput( + input: """ + public func async(s: String) async -> String + """, + .jni, .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + public static java.util.concurrent.CompletableFuture async(java.lang.String s) { + java.util.concurrent.CompletableFuture $future = new java.util.concurrent.CompletableFuture(); + SwiftModule.$async(s, $future); + return $future.thenApply((futureResult$) -> { + return futureResult$; + } + ); + } + """, + """ + private static native void $async(java.lang.String s, java.util.concurrent.CompletableFuture result_future); + """, + ] + ) + } + + @Test("Import: (String) async -> String (Swift, CompletableFuture)") + func completableFuture_asyncStringToString_swift() throws { + try assertOutput( + input: """ + public func async(s: String) async -> String + """, + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024async__Ljava_lang_String_2Ljava_util_concurrent_CompletableFuture_2") + func Java_com_example_swift_SwiftModule__00024async__Ljava_lang_String_2Ljava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, s: jstring?, result_future: jobject?) { + let s = environment.interface.NewGlobalRef(environment, s) + let globalFuture = environment.interface.NewGlobalRef(environment, result_future) + ... + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + environment.interface.DeleteGlobalRef(deferEnvironment, s) + } + ... + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: swiftResult$.getJNIValue(in: environment))]) + ... + } + """ + ] + ) + } } From cee5e020ac29444d59ebb29afb246ac3cad6cf30 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 7 Nov 2025 14:51:28 +0900 Subject: [PATCH 107/141] Start replacing ad-hoc logging with swift-log adoption (#421) --- Package.swift | 3 +++ .../Commands/ConfigureCommand.swift | 6 ++++- .../Commands/WrapJavaCommand.swift | 7 ++++-- Sources/SwiftJavaTool/CommonOptions.swift | 25 ++++++++++--------- .../SwiftJavaBaseAsyncParsableCommand.swift | 20 ++++++++++----- 5 files changed, 40 insertions(+), 21 deletions(-) diff --git a/Package.swift b/Package.swift index 7ac7d1f9f..722859eed 100644 --- a/Package.swift +++ b/Package.swift @@ -205,6 +205,7 @@ let package = Package( .package(url: "https://github.com/swiftlang/swift-syntax", from: "602.0.0"), .package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"), .package(url: "https://github.com/apple/swift-system", from: "1.4.0"), + .package(url: "https://github.com/apple/swift-log", from: "1.2.0"), // // FIXME: swift-subprocess stopped supporting 6.0 when it moved into a package; // // we'll need to drop 6.0 as well, but currently blocked on doing so by swiftpm plugin pending design questions @@ -395,6 +396,7 @@ let package = Package( .target( name: "SwiftJavaToolLib", dependencies: [ + .product(name: "Logging", package: "swift-log"), .product(name: "SwiftBasicFormat", package: "swift-syntax"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), @@ -418,6 +420,7 @@ let package = Package( .executableTarget( name: "SwiftJavaTool", dependencies: [ + .product(name: "Logging", package: "swift-log"), .product(name: "SwiftBasicFormat", package: "swift-syntax"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), diff --git a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift index 3bce6c18f..9a05a2867 100644 --- a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift +++ b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +import Logging import ArgumentParser import Foundation import SwiftJavaToolLib @@ -27,6 +28,9 @@ import SwiftJavaShared extension SwiftJava { struct ConfigureCommand: SwiftJavaBaseAsyncParsableCommand, HasCommonOptions, HasCommonJVMOptions { + + static let log: Logging.Logger = Logger(label: "swift-java:\(configuration.commandName!)") + static let configuration = CommandConfiguration( commandName: "configure", abstract: "Configure and emit a swift-java.config file based on an input dependency or jar file") @@ -63,7 +67,7 @@ extension SwiftJava { extension SwiftJava.ConfigureCommand { mutating func runSwiftJavaCommand(config: inout Configuration) async throws { let classpathEntries = self.configureCommandJVMClasspath( - searchDirs: [self.effectiveSwiftModuleURL], config: config) + searchDirs: [self.effectiveSwiftModuleURL], config: config, log: Self.log) let jvm = try self.makeJVM(classpathEntries: classpathEntries) diff --git a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift index 69ad3ebe4..43362edbd 100644 --- a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift +++ b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift @@ -14,15 +14,18 @@ import Foundation import ArgumentParser +import Logging import SwiftJavaToolLib import SwiftJava import JavaUtilJar -import SwiftJavaToolLib import SwiftJavaConfigurationShared extension SwiftJava { struct WrapJavaCommand: SwiftJavaBaseAsyncParsableCommand, HasCommonOptions, HasCommonJVMOptions { + + static let log: Logging.Logger = .init(label: "swift-java:\(configuration.commandName!)") + static let configuration = CommandConfiguration( commandName: "wrap-java", abstract: "Wrap Java classes with corresponding Swift bindings.") @@ -74,7 +77,7 @@ extension SwiftJava.WrapJavaCommand { print("[trace][swift-java] INPUT: \(input)") var classpathEntries = self.configureCommandJVMClasspath( - searchDirs: classpathSearchDirs, config: config) + searchDirs: classpathSearchDirs, config: config, log: Self.log) // Load all of the dependent configurations and associate them with Swift modules. let dependentConfigs = try loadDependentConfigs(dependsOn: self.dependsOn).map { moduleName, config in diff --git a/Sources/SwiftJavaTool/CommonOptions.swift b/Sources/SwiftJavaTool/CommonOptions.swift index c88ecbc88..e8aee62df 100644 --- a/Sources/SwiftJavaTool/CommonOptions.swift +++ b/Sources/SwiftJavaTool/CommonOptions.swift @@ -20,6 +20,7 @@ import SwiftJava import JavaUtilJar import JavaNet import SwiftSyntax +import Logging import SwiftJavaConfigurationShared import SwiftJavaShared @@ -43,7 +44,7 @@ extension SwiftJava { var inputSwift: String? = nil @Option(name: .shortAndLong, help: "Configure the level of logs that should be printed") - var logLevel: Logger.Level = .info + var logLevel: JExtractSwiftLib.Logger.Level = .info } struct CommonJVMOptions: ParsableArguments { @@ -78,46 +79,46 @@ extension HasCommonJVMOptions { /// swift-java.classpath files as configured. /// Parameters: /// - searchDirs: search directories where we can find swift.java.classpath files to include in the configuration - func configureCommandJVMClasspath(searchDirs: [Foundation.URL], config: Configuration) -> [String] { + func configureCommandJVMClasspath(searchDirs: [Foundation.URL], config: Configuration, log: Logging.Logger) -> [String] { // Form a class path from all of our input sources: // * Command-line option --classpath let classpathOptionEntries: [String] = self.classpathEntries let classpathFromEnv = ProcessInfo.processInfo.environment["CLASSPATH"]?.split(separator: ":").map(String.init) ?? [] - print("[debug][swift-java] Base classpath from CLASSPATH environment: \(classpathFromEnv)") + log.debug("Base classpath from CLASSPATH environment: \(classpathFromEnv)") let classpathFromConfig: [String] = config.classpath?.split(separator: ":").map(String.init) ?? [] - print("[debug][swift-java] Base classpath from config: \(classpathFromConfig)") + log.debug("Base classpath from config: \(classpathFromConfig)") var classpathEntries: [String] = classpathFromConfig for searchDir in searchDirs { let classPathFilesSearchDirectory = searchDir.path - print("[debug][swift-java] Search *.swift-java.classpath in: \(classPathFilesSearchDirectory)") + log.debug("Search *.swift-java.classpath in: \(classPathFilesSearchDirectory)") let foundSwiftJavaClasspath = findSwiftJavaClasspaths(in: classPathFilesSearchDirectory) - print("[debug][swift-java] Classpath from *.swift-java.classpath files: \(foundSwiftJavaClasspath)") + log.debug("Classpath from *.swift-java.classpath files: \(foundSwiftJavaClasspath)") classpathEntries += foundSwiftJavaClasspath } if !classpathOptionEntries.isEmpty { - print("[debug][swift-java] Classpath from options: \(classpathOptionEntries)") + log.debug("Classpath from options: \(classpathOptionEntries)") classpathEntries += classpathOptionEntries } else { // * Base classpath from CLASSPATH env variable - print("[debug][swift-java] Classpath from environment: \(classpathFromEnv)") + log.debug("Classpath from environment: \(classpathFromEnv)") classpathEntries += classpathFromEnv } let extraClasspath = self.commonJVMOptions.classpath let extraClasspathEntries = extraClasspath.split(separator: ":").map(String.init) - print("[debug][swift-java] Extra classpath: \(extraClasspathEntries)") + log.debug("Extra classpath: \(extraClasspathEntries)") classpathEntries += extraClasspathEntries // Bring up the Java VM when necessary - // if logLevel >= .debug { + if log.logLevel >= .debug { let classpathString = classpathEntries.joined(separator: ":") - print("[debug][swift-java] Initialize JVM with classpath: \(classpathString)") - // } + log.debug("Initialize JVM with classpath: \(classpathString)") + } return classpathEntries } diff --git a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift index 6b8c0ec6b..9763b4b38 100644 --- a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift +++ b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift @@ -24,9 +24,13 @@ import SwiftSyntax import SwiftSyntaxBuilder import SwiftJavaConfigurationShared import SwiftJavaShared +import Logging protocol SwiftJavaBaseAsyncParsableCommand: AsyncParsableCommand { - var logLevel: Logger.Level { get set } + + var log: Logging.Logger { get } + + var logLevel: JExtractSwiftLib.Logger.Level { get set } /// Must be implemented with an `@OptionGroup` in Command implementations var commonOptions: SwiftJava.CommonOptions { get set } @@ -45,8 +49,8 @@ extension SwiftJavaBaseAsyncParsableCommand { extension SwiftJavaBaseAsyncParsableCommand { public mutating func run() async { - print("[info][swift-java] Run \(Self.self): \(CommandLine.arguments.joined(separator: " "))") - print("[info][swift-java] Current work directory: \(URL(fileURLWithPath: ".").path)") + self.log.info("Run \(Self.self): \(CommandLine.arguments.joined(separator: " "))") + self.log.info("Current work directory: \(URL(fileURLWithPath: ".").path)") do { var config = try readInitialConfiguration(command: self) @@ -54,12 +58,12 @@ extension SwiftJavaBaseAsyncParsableCommand { } catch { // We fail like this since throwing out of the run often ends up hiding the failure reason when it is executed as SwiftPM plugin (!) let message = "Failed with error: \(error)" - print("[error][java-swift] \(message)") + self.log.error("\(message)") fatalError(message) } // Just for debugging so it is clear which command has finished - print("[debug][swift-java] " + "Done: ".green + CommandLine.arguments.joined(separator: " ").green) + self.log.debug("\("Done: ".green) \(CommandLine.arguments.joined(separator: " ").green)") } } @@ -95,7 +99,11 @@ extension SwiftJavaBaseAsyncParsableCommand { extension SwiftJavaBaseAsyncParsableCommand { - var logLevel: Logger.Level { + var log: Logging.Logger { // FIXME: replace with stored property inside specific commands + .init(label: "swift-java") + } + + var logLevel: JExtractSwiftLib.Logger.Level { get { self.commonOptions.logLevel } From cafcc1d5c0e45bc31f73087596c1fe521eb57c94 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 7 Nov 2025 11:02:10 +0100 Subject: [PATCH 108/141] [jextract/jni] Add support for importing nested types (#429) --- .../Sources/MySwiftLibrary/NestedTypes.swift | 47 ++++++ .../com/example/swift/NestedTypesTest.java | 45 ++++++ Sources/JExtractSwiftLib/ImportedDecls.swift | 2 + Sources/JExtractSwiftLib/JNI/JNICaching.swift | 8 +- ...t2JavaGenerator+JavaBindingsPrinting.swift | 20 ++- ...ISwift2JavaGenerator+JavaTranslation.swift | 6 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 6 +- .../SwiftTypes/SwiftModuleSymbolTable.swift | 2 +- .../SwiftTypes/SwiftTypeLookupContext.swift | 10 +- .../Documentation.docc/SupportedFeatures.md | 2 +- .../JNI/JNINestedTypesTests.swift | 137 ++++++++++++++++++ 11 files changed, 269 insertions(+), 16 deletions(-) create mode 100644 Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/NestedTypes.swift create mode 100644 Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/NestedTypesTest.java create mode 100644 Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/NestedTypes.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/NestedTypes.swift new file mode 100644 index 000000000..b7edefa93 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/NestedTypes.swift @@ -0,0 +1,47 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftJava + +public class A { + public init() {} + + public class B { + public init() {} + + public struct C { + public init() {} + + public func g(a: A, b: B, bbc: BB.C) {} + } + } + + public class BB { + public init() {} + + public struct C { + public init() {} + } + } + + public func f(a: A, b: A.B, c: A.B.C, bb: BB, bbc: BB.C) {} +} + +public enum NestedEnum { + case one(OneStruct) + + public struct OneStruct { + public init() {} + } +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/NestedTypesTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/NestedTypesTest.java new file mode 100644 index 000000000..b006ea914 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/NestedTypesTest.java @@ -0,0 +1,45 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.SwiftArena; + +import static org.junit.jupiter.api.Assertions.*; + +public class NestedTypesTest { + @Test + void testClassesAndStructs() { + try (var arena = SwiftArena.ofConfined()) { + var a = A.init(arena); + var b = A.B.init(arena); + var c = A.B.C.init(arena); + var bb = A.BB.init(arena); + var abbc = A.BB.C.init(arena); + + a.f(a, b, c, bb, abbc); + c.g(a, b, abbc); + } + } + + @Test + void testStructInEnum() { + try (var arena = SwiftArena.ofConfined()) { + var obj = NestedEnum.one(NestedEnum.OneStruct.init(arena), arena); + var one = obj.getAsOne(arena); + assertTrue(one.isPresent()); + } + } +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 84cc43c07..9ba1cd6f6 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -41,12 +41,14 @@ package final class ImportedNominalType: ImportedDecl { package var variables: [ImportedFunc] = [] package var cases: [ImportedEnumCase] = [] var inheritedTypes: [SwiftType] + package var parent: SwiftNominalTypeDeclaration? init(swiftNominal: SwiftNominalTypeDeclaration, lookupContext: SwiftTypeLookupContext) throws { self.swiftNominal = swiftNominal self.inheritedTypes = swiftNominal.inheritanceTypes?.compactMap { try? SwiftType($0.type, lookupContext: lookupContext) } ?? [] + self.parent = swiftNominal.parent } var swiftType: SwiftType { diff --git a/Sources/JExtractSwiftLib/JNI/JNICaching.swift b/Sources/JExtractSwiftLib/JNI/JNICaching.swift index cdb13e3fc..b0521946d 100644 --- a/Sources/JExtractSwiftLib/JNI/JNICaching.swift +++ b/Sources/JExtractSwiftLib/JNI/JNICaching.swift @@ -14,15 +14,15 @@ enum JNICaching { static func cacheName(for type: ImportedNominalType) -> String { - cacheName(for: type.swiftNominal.name) + cacheName(for: type.swiftNominal.qualifiedName) } static func cacheName(for type: SwiftNominalType) -> String { - cacheName(for: type.nominalTypeDecl.name) + cacheName(for: type.nominalTypeDecl.qualifiedName) } - private static func cacheName(for name: String) -> String { - "_JNI_\(name)" + private static func cacheName(for qualifiedName: String) -> String { + "_JNI_\(qualifiedName.replacingOccurrences(of: ".", with: "_"))" } static func cacheMemberName(for enumCase: ImportedEnumCase) -> String { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index ce6ec18ed..1e59c196d 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -40,7 +40,9 @@ extension JNISwift2JavaGenerator { package func writeExportedJavaSources(_ printer: inout CodePrinter) throws { let importedTypes = analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) - for (_, ty) in importedTypes { + // Each parent type goes into its own file + // any nested types are printed inside the body as `static class` + for (_, ty) in importedTypes.filter({ _, type in type.parent == nil }) { let filename = "\(ty.swiftNominal.name).java" logger.debug("Printing contents: \(filename)") printImportedNominal(&printer, ty) @@ -145,6 +147,15 @@ extension JNISwift2JavaGenerator { """ ) + let nestedTypes = self.analysis.importedTypes.filter { _, type in + type.parent == decl.swiftNominal + } + + for nestedType in nestedTypes { + printConcreteType(&printer, nestedType.value) + printer.println() + } + printer.print( """ /** @@ -255,13 +266,18 @@ extension JNISwift2JavaGenerator { if decl.swiftNominal.isSendable { printer.print("@ThreadSafe // Sendable") } + var modifiers = ["public"] + if decl.parent != nil { + modifiers.append("static") + } + modifiers.append("final") var implements = ["JNISwiftInstance"] implements += decl.inheritedTypes .compactMap(\.asNominalTypeDeclaration) .filter { $0.kind == .protocol } .map(\.name) let implementsClause = implements.joined(separator: ", ") - printer.printBraceBlock("public final class \(decl.swiftNominal.name) implements \(implementsClause)") { printer in + printer.printBraceBlock("\(modifiers.joined(separator: " ")) class \(decl.swiftNominal.name) implements \(implementsClause)") { printer in body(&printer) } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index a4e5ab451..d9a8f8d9e 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -307,9 +307,7 @@ extension JNISwift2JavaGenerator { genericParameters: [SwiftGenericParameterDeclaration], genericRequirements: [SwiftGenericRequirement] ) throws -> [TranslatedParameter] { - try parameters.enumerated().map { - idx, - param in + try parameters.enumerated().map { idx, param in let parameterName = param.name ?? "arg\(idx)" return try translateParameter( swiftType: param.type, @@ -373,7 +371,7 @@ extension JNISwift2JavaGenerator { switch swiftType { case .nominal(let nominalType): - let nominalTypeName = nominalType.nominalTypeDecl.name + let nominalTypeName = nominalType.nominalTypeDecl.qualifiedName if let knownType = nominalType.nominalTypeDecl.knownTypeKind { switch knownType { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index c9f8af9ed..91a0b0e73 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -430,7 +430,7 @@ extension JNISwift2JavaGenerator { let cName = "Java_" + self.javaPackage.replacingOccurrences(of: ".", with: "_") - + "_\(parentName.escapedJNIIdentifier)_" + + "_\(parentName.replacingOccurrences(of: ".", with: "$").escapedJNIIdentifier)_" + javaMethodName.escapedJNIIdentifier + "__" + jniSignature.escapedJNIIdentifier @@ -474,7 +474,7 @@ extension JNISwift2JavaGenerator { printCDecl( &printer, javaMethodName: "$typeMetadataAddressDowncall", - parentName: type.swiftNominal.name, + parentName: type.swiftNominal.qualifiedName, parameters: [], resultType: .long ) { printer in @@ -493,7 +493,7 @@ extension JNISwift2JavaGenerator { printCDecl( &printer, javaMethodName: "$destroy", - parentName: type.swiftNominal.name, + parentName: type.swiftNominal.qualifiedName, parameters: [ selfPointerParam ], diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftModuleSymbolTable.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftModuleSymbolTable.swift index 6328045f5..2db19928e 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftModuleSymbolTable.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftModuleSymbolTable.swift @@ -56,4 +56,4 @@ extension SwiftModuleSymbolTable { /// Names of modules which are alternative for currently checked module. let moduleNames: Set } -} \ No newline at end of file +} diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift index 13f42cfca..33759a2cf 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift @@ -119,7 +119,15 @@ class SwiftTypeLookupContext { /// Create a nominal type declaration instance for the specified syntax node. private func nominalTypeDeclaration(for node: NominalTypeDeclSyntaxNode, sourceFilePath: String) throws -> SwiftNominalTypeDeclaration { - SwiftNominalTypeDeclaration( + + if let symbolTableDeclaration = self.symbolTable.lookupType( + node.name.text, + parent: try parentTypeDecl(for: node) + ) { + return symbolTableDeclaration + } + + return SwiftNominalTypeDeclaration( sourceFilePath: sourceFilePath, moduleName: self.symbolTable.moduleName, parent: try parentTypeDecl(for: node), diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 4bbaee400..3178f60d2 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -86,7 +86,7 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Subscripts: `subscript()` | ❌ | ❌ | | Equatable | ❌ | ❌ | | Pointers: `UnsafeRawPointer`, UnsafeBufferPointer (?) | 🟡 | ❌ | -| Nested types: `struct Hello { struct World {} }` | ❌ | ❌ | +| Nested types: `struct Hello { struct World {} }` | ❌ | ✅ | | Inheritance: `class Caplin: Capybara` | ❌ | ❌ | | Non-escaping `Void` closures: `func callMe(maybe: () -> ())` | ✅ | ✅ | | Non-escaping closures with primitive arguments/results: `func callMe(maybe: (Int) -> (Double))` | ✅ | ✅ | diff --git a/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift b/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift new file mode 100644 index 000000000..50a0ae264 --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift @@ -0,0 +1,137 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import Testing + +@Suite +struct JNINestedTypesTests { + let source1 = """ + public class A { + public class B { + public func g(c: C) {} + + public struct C { + public func h(b: B) {} + } + } + } + + public func f(a: A, b: A.B, c: A.B.C) {} + """ + + @Test("Import: class and struct A.B.C (Java)") + func nestedClassesAndStructs_java() throws { + try assertOutput( + input: source1, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public final class A implements JNISwiftInstance { + ... + public static final class B implements JNISwiftInstance { + ... + public static final class C implements JNISwiftInstance { + ... + public void h(A.B b) { + ... + } + ... + public void g(A.B.C c) { + ... + } + ... + } + """, + """ + public static void f(A a, A.B b, A.B.C c) { + ... + } + ... + """ + ] + ) + } + + @Test("Import: class and struct A.B.C (Swift)") + func nestedClassesAndStructs_swift() throws { + try assertOutput( + input: source1, + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_A__00024destroy__J") + func Java_com_example_swift_A__00024destroy__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) { + ... + } + """, + """ + @_cdecl("Java_com_example_swift_A_00024B__00024destroy__J") + func Java_com_example_swift_A_00024B__00024destroy__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) { + ... + } + """, + """ + @_cdecl("Java_com_example_swift_A_00024B__00024destroy__J") + func Java_com_example_swift_A_00024B__00024destroy__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) { + ... + } + """, + """ + @_cdecl("Java_com_example_swift_A_00024B_00024C__00024h__JJ") + func Java_com_example_swift_A_00024B_00024C__00024h__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, b: jlong, self: jlong) { + ... + } + """ + ] + ) + } + + @Test("Import: nested in enum") + func nestedEnums_java() throws { + try assertOutput( + input: """ + public enum MyError { + case text(TextMessage) + + public struct TextMessage {} + } + + public func f(text: MyError.TextMessage) {} + """, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public final class MyError implements JNISwiftInstance { + ... + public static final class TextMessage implements JNISwiftInstance { + ... + } + ... + public static MyError text(MyError.TextMessage arg0, SwiftArena swiftArena$) { + ... + } + """, + """ + public static void f(MyError.TextMessage text) { + ... + } + """ + ] + ) + } +} From 6419865dddef9bee0e670a1c5752342ccde0f837 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 11 Nov 2025 13:22:35 +0100 Subject: [PATCH 109/141] jextract: add support for arrays (#435) --- .../Sources/MySwiftLibrary/Arrays.swift | 60 ++++++ .../java/com/example/swift/ArraysTest.java | 105 ++++++++++ Sources/JExtractSwiftLib/CodePrinter.swift | 7 +- .../FFM/CDeclLowering/CRepresentation.swift | 4 +- ...Swift2JavaGenerator+FunctionLowering.swift | 9 +- ...MSwift2JavaGenerator+JavaTranslation.swift | 5 +- .../JNI/JNIJavaTypeTranslator.swift | 5 +- ...t2JavaGenerator+JavaBindingsPrinting.swift | 2 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 143 +++++++++++++ ...wift2JavaGenerator+NativeTranslation.swift | 161 +++++++++++++-- .../Swift2JavaTranslator.swift | 2 + .../SwiftTypes/SwiftKnownModules.swift | 4 +- .../SwiftTypes/SwiftKnownTypeDecls.swift | 1 + .../SwiftTypes/SwiftType.swift | 17 +- .../Documentation.docc/SupportedFeatures.md | 2 +- .../JExtractSwiftTests/JNI/JNIArrayTest.swift | 188 ++++++++++++++++++ 16 files changed, 684 insertions(+), 31 deletions(-) create mode 100644 Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Arrays.swift create mode 100644 Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ArraysTest.java create mode 100644 Tests/JExtractSwiftTests/JNI/JNIArrayTest.swift diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Arrays.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Arrays.swift new file mode 100644 index 000000000..b6f050c12 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Arrays.swift @@ -0,0 +1,60 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftJava + +public func booleanArray(array: [Bool]) -> [Bool] { + return array +} + +public func byteArray(array: [UInt8]) -> [UInt8] { + return array +} + +public func byteArrayExplicit(array: Array) -> Array { + return array +} + +public func charArray(array: [UInt16]) -> [UInt16] { + return array +} + +public func shortArray(array: [Int16]) -> [Int16] { + return array +} + +public func intArray(array: [Int32]) -> [Int32] { + return array +} + +public func longArray(array: [Int64]) -> [Int64] { + return array +} + +public func floatArray(array: [Float]) -> [Float] { + return array +} + +public func doubleArray(array: [Double]) -> [Double] { + return array +} + +public func stringArray(array: [String]) -> [String] { + return array +} + +public func objectArray(array: [MySwiftClass]) -> [MySwiftClass] { + return array +} + diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ArraysTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ArraysTest.java new file mode 100644 index 000000000..bf19b370e --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ArraysTest.java @@ -0,0 +1,105 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.SwiftArena; + +import java.util.OptionalLong; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import static org.junit.jupiter.api.Assertions.*; + +public class ArraysTest { + @Test + void booleanArray() { + boolean[] input = new boolean[] { true, false, false, true }; + assertArrayEquals(input, MySwiftLibrary.booleanArray(input)); + } + + @Test + void byteArray() { + byte[] input = new byte[] { 10, 20, 30, 40 }; + assertArrayEquals(input, MySwiftLibrary.byteArray(input)); + } + + @Test + void byteArray_empty() { + byte[] input = new byte[] {}; + assertArrayEquals(input, MySwiftLibrary.byteArray(input)); + } + + @Test + void byteArray_null() { + assertThrows(NullPointerException.class, () -> MySwiftLibrary.byteArray(null)); + } + + @Test + void byteArrayExplicit() { + byte[] input = new byte[] { 10, 20, 30, 40 }; + assertArrayEquals(input, MySwiftLibrary.byteArrayExplicit(input)); + } + + @Test + void charArray() { + char[] input = new char[] { 10, 20, 30, 40 }; + assertArrayEquals(input, MySwiftLibrary.charArray(input)); + } + + @Test + void shortArray() { + short[] input = new short[] { 10, 20, 30, 40 }; + assertArrayEquals(input, MySwiftLibrary.shortArray(input)); + } + + @Test + void intArray() { + int[] input = new int[] { 10, 20, 30, 40 }; + assertArrayEquals(input, MySwiftLibrary.intArray(input)); + } + + @Test + void longArray() { + long[] input = new long[] { 10, 20, 30, 40 }; + assertArrayEquals(input, MySwiftLibrary.longArray(input)); + } + + @Test + void stringArray() { + String[] input = new String[] { "hey", "there", "my", "friend" }; + assertArrayEquals(input, MySwiftLibrary.stringArray(input)); + } + + @Test + void floatArray() { + float[] input = new float[] { 10, 20, 30, 40 }; + assertArrayEquals(input, MySwiftLibrary.floatArray(input)); + } + + @Test + void doubleArray() { + double[] input = new double[] { 10, 20, 30, 40 }; + assertArrayEquals(input, MySwiftLibrary.doubleArray(input)); + } + + @Test + void objectArray() { + try (var arena = SwiftArena.ofConfined()) { + MySwiftClass[] input = new MySwiftClass[]{MySwiftClass.init(arena), MySwiftClass.init(arena), MySwiftClass.init(arena) }; + assertEquals(3, MySwiftLibrary.objectArray(input, arena).length); + } + } +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/CodePrinter.swift b/Sources/JExtractSwiftLib/CodePrinter.swift index e6498683c..1497a61ab 100644 --- a/Sources/JExtractSwiftLib/CodePrinter.swift +++ b/Sources/JExtractSwiftLib/CodePrinter.swift @@ -86,12 +86,17 @@ public struct CodePrinter { public mutating func printBraceBlock( _ header: Any, + parameters: [String]? = nil, function: String = #function, file: String = #fileID, line: UInt = #line, body: (inout CodePrinter) throws -> () ) rethrows { - print("\(header) {") + print("\(header) {", .continue) + if let parameters { + print(" (\(parameters.joined(separator: ", "))) in", .continue) + } + println() indent() try body(&self) outdent() diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift index 2621dd8bc..3f821a1b4 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift @@ -68,7 +68,7 @@ extension CType { case .optional(let wrapped) where wrapped.isPointer: try self.init(cdeclType: wrapped) - case .genericParameter, .metatype, .optional, .tuple, .opaque, .existential, .composite: + case .genericParameter, .metatype, .optional, .tuple, .opaque, .existential, .composite, .array: throw CDeclToCLoweringError.invalidCDeclType(cdeclType) } } @@ -127,7 +127,7 @@ extension SwiftKnownTypeDeclKind { case .void: .void case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string, .foundationData, .foundationDataProtocol, - .essentialsData, .essentialsDataProtocol, .optional: + .essentialsData, .essentialsDataProtocol, .optional, .array: nil } } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index 7c8d7ab21..832aff262 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -347,6 +347,9 @@ struct CdeclLowering { case .composite: throw LoweringError.unhandledType(type) + + case .array: + throw LoweringError.unhandledType(type) } } @@ -415,7 +418,7 @@ struct CdeclLowering { } throw LoweringError.unhandledType(.optional(wrappedType)) - case .function, .metatype, .optional, .composite: + case .function, .metatype, .optional, .composite, .array: throw LoweringError.unhandledType(.optional(wrappedType)) } } @@ -516,7 +519,7 @@ struct CdeclLowering { // Custom types are not supported yet. throw LoweringError.unhandledType(type) - case .genericParameter, .function, .metatype, .optional, .tuple, .existential, .opaque, .composite: + case .genericParameter, .function, .metatype, .optional, .tuple, .existential, .opaque, .composite, .array: // TODO: Implement throw LoweringError.unhandledType(type) } @@ -670,7 +673,7 @@ struct CdeclLowering { conversion: .tupleExplode(conversions, name: outParameterName) ) - case .genericParameter, .function, .optional, .existential, .opaque, .composite: + case .genericParameter, .function, .optional, .existential, .opaque, .composite, .array: throw LoweringError.unhandledType(type) } } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 5d38a0f95..72da323c1 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -475,6 +475,9 @@ extension FFMSwift2JavaGenerator { case .composite: throw JavaTranslationError.unhandledType(swiftType) + + case .array(let elementType): + throw JavaTranslationError.unhandledType(swiftType) } } @@ -694,7 +697,7 @@ extension FFMSwift2JavaGenerator { // TODO: Implement. throw JavaTranslationError.unhandledType(swiftType) - case .genericParameter, .optional, .function, .existential, .opaque, .composite: + case .genericParameter, .optional, .function, .existential, .opaque, .composite, .array: throw JavaTranslationError.unhandledType(swiftType) } diff --git a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift index 0d1fc675a..9bd9a7d83 100644 --- a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift @@ -45,13 +45,14 @@ enum JNIJavaTypeTranslator { case .void: return .void case .string: return .javaLangString + case .int, .uint, // FIXME: why not supported int/uint? .unsafeRawPointer, .unsafeMutableRawPointer, .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, - .optional, .foundationData, .foundationDataProtocol, .essentialsData, .essentialsDataProtocol: + .optional, .foundationData, .foundationDataProtocol, .essentialsData, .essentialsDataProtocol, .array: return nil } } -} \ No newline at end of file +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 1e59c196d..07d23dc0f 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -25,7 +25,7 @@ extension JNISwift2JavaGenerator { "java.util.concurrent.atomic.AtomicBoolean", // NonNull, Unsigned and friends - "org.swift.swiftkit.core.annotations.*", + "org.swift.swiftkit.core.annotations.*" ] } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index d9a8f8d9e..9c3cdbf1a 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -384,6 +384,15 @@ extension JNISwift2JavaGenerator { parameterName: parameterName ) + case .array: + guard let elementType = nominalType.genericArguments?.first else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + return try translateArrayParameter( + elementType: elementType, + parameterName: parameterName + ) + default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { throw JavaTranslationError.unsupportedSwiftType(swiftType) @@ -461,6 +470,12 @@ extension JNISwift2JavaGenerator { throw JavaTranslationError.unsupportedSwiftType(swiftType) + case .array(let elementType): + return try translateArrayParameter( + elementType: elementType, + parameterName: parameterName + ) + case .metatype, .tuple, .composite: throw JavaTranslationError.unsupportedSwiftType(swiftType) } @@ -651,6 +666,14 @@ extension JNISwift2JavaGenerator { } return try translateOptionalResult(wrappedType: genericArgs[0], resultName: resultName) + case .array: + guard let elementType = nominalType.genericArguments?.first else { + throw JavaTranslationError.unsupportedSwiftType(swiftType) + } + return try translateArrayResult( + elementType: elementType + ) + default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { throw JavaTranslationError.unsupportedSwiftType(swiftType) @@ -684,6 +707,11 @@ extension JNISwift2JavaGenerator { case .optional(let wrapped): return try translateOptionalResult(wrappedType: wrapped, resultName: resultName) + case .array(let elementType): + return try translateArrayResult( + elementType: elementType + ) + case .metatype, .tuple, .function, .existential, .opaque, .genericParameter, .composite: throw JavaTranslationError.unsupportedSwiftType(swiftType) } @@ -771,6 +799,107 @@ extension JNISwift2JavaGenerator { throw JavaTranslationError.unsupportedSwiftType(swiftType) } } + + func translateArrayParameter( + elementType: SwiftType, + parameterName: String + ) throws -> TranslatedParameter { + let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: elementType, config: config) + + switch elementType { + case .nominal(let nominalType): + let nominalTypeName = nominalType.nominalTypeDecl.qualifiedName + + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + + return TranslatedParameter( + parameter: JavaParameter(name: parameterName, type: .array(javaType), annotations: parameterAnnotations), + conversion: .requireNonNull(.placeholder, message: "\(parameterName) must not be null") + ) + } + + guard !nominalType.isJavaKitWrapper else { + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + + // Assume JExtract imported class + return TranslatedParameter( + parameter: JavaParameter( + name: parameterName, + type: .array(.class(package: nil, name: nominalTypeName)), + annotations: parameterAnnotations + ), + conversion: .method( + .method( + .arraysStream(.requireNonNull(.placeholder, message: "\(parameterName) must not be null")), + function: "mapToLong", + arguments: [.constant("\(nominalTypeName)::$memoryAddress")] + ), + function: "toArray", + arguments: [] + ) + ) + + default: + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + } + + func translateArrayResult( + elementType: SwiftType + ) throws -> TranslatedResult { + let annotations: [JavaAnnotation] = getTypeAnnotations(swiftType: elementType, config: config) + + switch elementType { + case .nominal(let nominalType): + let nominalTypeName = nominalType.nominalTypeDecl.qualifiedName + + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + + return TranslatedResult( + javaType: .array(javaType), + annotations: annotations, + outParameters: [], + conversion: .placeholder + ) + } + + guard !nominalType.isJavaKitWrapper else { + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + + let objectType = JavaType.class(package: nil, name: nominalTypeName) + // We assume this is a JExtract class. + return TranslatedResult( + javaType: .array(objectType), + annotations: annotations, + outParameters: [], + conversion: .method( + .method( + .arraysStream(.placeholder), + function: "mapToObj", + arguments: [ + .lambda( + args: ["pointer"], + body: .wrapMemoryAddressUnsafe(.constant("pointer"), objectType) + ) + ] + ), + function: "toArray", + arguments: [.constant("\(objectType)[]::new")] + ) + ) + + default: + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + } } struct TranslatedEnumCase { @@ -976,6 +1105,13 @@ extension JNISwift2JavaGenerator { /// Prints the conversion step, ignoring the output. indirect case print(JavaNativeConversionStep) + indirect case requireNonNull(JavaNativeConversionStep, message: String) + + /// `Arrays.stream(args)` + static func arraysStream(_ argument: JavaNativeConversionStep) -> JavaNativeConversionStep { + .method(.constant("Arrays"), function: "stream", arguments: [argument]) + } + /// Returns the conversion string applied to the placeholder. func render(_ printer: inout CodePrinter, _ placeholder: String) -> String { // NOTE: 'printer' is used if the conversion wants to cause side-effects. @@ -1105,6 +1241,10 @@ extension JNISwift2JavaGenerator { let inner = inner.render(&printer, placeholder) printer.print("\(inner);") return "" + + case .requireNonNull(let inner, let message): + let inner = inner.render(&printer, placeholder) + return #"Objects.requireNonNull(\#(inner), "\#(message)")"# } } @@ -1161,6 +1301,9 @@ extension JNISwift2JavaGenerator { case .print(let inner): return inner.requiresSwiftArena + + case .requireNonNull(let inner, _): + return inner.requiresSwiftArena } } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 13e8c676e..35935038d 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -91,6 +91,12 @@ extension JNISwift2JavaGenerator { parameterName: parameterName ) + case .array: + guard let elementType = nominalType.genericArguments?.first else { + throw JavaTranslationError.unsupportedSwiftType(type) + } + return try translateArrayParameter(elementType: elementType, parameterName: parameterName) + default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), javaType.implementsJavaValue else { @@ -188,6 +194,12 @@ extension JNISwift2JavaGenerator { throw JavaTranslationError.unsupportedSwiftType(type) + case .array(let elementType): + return try translateArrayParameter( + elementType: elementType, + parameterName: parameterName + ) + case .metatype, .tuple, .composite: throw JavaTranslationError.unsupportedSwiftType(type) } @@ -356,7 +368,7 @@ extension JNISwift2JavaGenerator { return NativeResult( javaType: .long, conversion: .optionalRaisingIndirectReturn( - .getJNIValue(.allocateSwiftValue(name: "_result", swiftType: swiftType)), + .getJNIValue(.allocateSwiftValue(.placeholder, name: "_result", swiftType: swiftType)), returnType: .long, discriminatorParameterName: discriminatorName, placeholderValue: .constant("0") @@ -400,7 +412,7 @@ extension JNISwift2JavaGenerator { outParameters: [] ) - case .function, .metatype, .optional, .tuple, .existential, .opaque, .genericParameter, .composite: + case .function, .metatype, .optional, .tuple, .existential, .opaque, .genericParameter, .composite, .array: throw JavaTranslationError.unsupportedSwiftType(type) } } @@ -429,7 +441,7 @@ extension JNISwift2JavaGenerator { // Custom types are not supported yet. throw JavaTranslationError.unsupportedSwiftType(type) - case .function, .metatype, .optional, .tuple, .existential, .opaque, .genericParameter, .composite: + case .function, .metatype, .optional, .tuple, .existential, .opaque, .genericParameter, .composite, .array: throw JavaTranslationError.unsupportedSwiftType(type) } } @@ -448,6 +460,12 @@ extension JNISwift2JavaGenerator { } return try translateOptionalResult(wrappedType: genericArgs[0], resultName: resultName) + case .array: + guard let elementType = nominalType.genericArguments?.first else { + throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) + } + return try translateArrayResult(elementType: elementType, resultName: resultName) + default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), javaType.implementsJavaValue else { throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) @@ -467,7 +485,7 @@ extension JNISwift2JavaGenerator { return NativeResult( javaType: .long, - conversion: .getJNIValue(.allocateSwiftValue(name: resultName, swiftType: swiftResult.type)), + conversion: .getJNIValue(.allocateSwiftValue(.placeholder, name: resultName, swiftType: swiftResult.type)), outParameters: [] ) @@ -481,10 +499,108 @@ extension JNISwift2JavaGenerator { case .optional(let wrapped): return try translateOptionalResult(wrappedType: wrapped, resultName: resultName) + case .array(let elementType): + return try translateArrayResult(elementType: elementType, resultName: resultName) + case .metatype, .tuple, .function, .existential, .opaque, .genericParameter, .composite: throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) } } + + func translateArrayResult( + elementType: SwiftType, + resultName: String + ) throws -> NativeResult { + switch elementType { + case .nominal(let nominalType): + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), + javaType.implementsJavaValue else { + throw JavaTranslationError.unsupportedSwiftType(.array(elementType)) + } + + return NativeResult( + javaType: .array(javaType), + conversion: .getJNIValue(.placeholder), + outParameters: [] + ) + } + + guard !nominalType.isJavaKitWrapper else { + throw JavaTranslationError.unsupportedSwiftType(.array(elementType)) + } + + // Assume JExtract imported class + return NativeResult( + javaType: .array(.long), + conversion: + .getJNIValue( + .method( + .placeholder, + function: "map", + arguments: [ + (nil, .closure( + args: ["object$"], + body: .allocateSwiftValue(.constant("object$"), name: "object$", swiftType: elementType) + )) + ] + ) + ), + outParameters: [] + ) + + default: + throw JavaTranslationError.unsupportedSwiftType(.array(elementType)) + } + } + + func translateArrayParameter( + elementType: SwiftType, + parameterName: String + ) throws -> NativeParameter { + switch elementType { + case .nominal(let nominalType): + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), + javaType.implementsJavaValue else { + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + + return NativeParameter( + parameters: [ + JavaParameter(name: parameterName, type: .array(javaType)), + ], + conversion: .initFromJNI(.placeholder, swiftType: .array(elementType)) + ) + } + + guard !nominalType.isJavaKitWrapper else { + throw JavaTranslationError.unsupportedSwiftType(.array(elementType)) + } + + // Assume JExtract wrapped class + return NativeParameter( + parameters: [JavaParameter(name: parameterName, type: .array(.long))], + conversion: .method( + .initFromJNI(.placeholder, swiftType: .array(self.knownTypes.int64)), + function: "map", + arguments: [ + (nil, .closure( + args: ["pointer$"], + body: .pointee(.extractSwiftValue( + .constant("pointer$"), + swiftType: elementType, + allowNil: false, + convertLongFromJNI: false + )))) + ] + ) + ) + + default: + throw JavaTranslationError.unsupportedSwiftType(elementType) + } + } } struct NativeFunctionSignature { @@ -539,11 +655,12 @@ extension JNISwift2JavaGenerator { indirect case extractSwiftValue( NativeSwiftConversionStep, swiftType: SwiftType, - allowNil: Bool = false + allowNil: Bool = false, + convertLongFromJNI: Bool = true ) /// Allocate memory for a Swift value and outputs the pointer - case allocateSwiftValue(name: String, swiftType: SwiftType) + indirect case allocateSwiftValue(NativeSwiftConversionStep, name: String, swiftType: SwiftType) /// The thing to which the pointer typed, which is the `pointee` property /// of the `Unsafe(Mutable)Pointer` types in Swift. @@ -575,6 +692,9 @@ extension JNISwift2JavaGenerator { isThrowing: Bool ) + /// `{ (args) -> return body }` + indirect case closure(args: [String] = [], body: NativeSwiftConversionStep) + /// Returns the conversion string applied to the placeholder. func render(_ printer: inout CodePrinter, _ placeholder: String) -> String { // NOTE: 'printer' is used if the conversion wants to cause side-effects. @@ -630,18 +750,18 @@ extension JNISwift2JavaGenerator { ) return existentialName - case .extractSwiftValue(let inner, let swiftType, let allowNil): + case .extractSwiftValue(let inner, let swiftType, let allowNil, let convertLongFromJNI): let inner = inner.render(&printer, placeholder) let pointerName = "\(inner)$" if !allowNil { printer.print(#"assert(\#(inner) != 0, "\#(inner) memory address was null")"#) } - printer.print( - """ - let \(inner)Bits$ = Int(Int64(fromJNI: \(inner), in: environment)) - let \(pointerName) = UnsafeMutablePointer<\(swiftType)>(bitPattern: \(inner)Bits$) - """ - ) + if convertLongFromJNI { + printer.print("let \(inner)Bits$ = Int(Int64(fromJNI: \(inner), in: environment))") + } else { + printer.print("let \(inner)Bits$ = Int(\(inner))") + } + printer.print("let \(pointerName) = UnsafeMutablePointer<\(swiftType)>(bitPattern: \(inner)Bits$)") if !allowNil { printer.print( """ @@ -653,13 +773,14 @@ extension JNISwift2JavaGenerator { } return pointerName - case .allocateSwiftValue(let name, let swiftType): + case .allocateSwiftValue(let inner, let name, let swiftType): + let inner = inner.render(&printer, placeholder) let pointerName = "\(name)$" let bitsName = "\(name)Bits$" printer.print( """ let \(pointerName) = UnsafeMutablePointer<\(swiftType)>.allocate(capacity: 1) - \(pointerName).initialize(to: \(placeholder)) + \(pointerName).initialize(to: \(inner)) let \(bitsName) = Int64(Int(bitPattern: \(pointerName))) """ ) @@ -894,6 +1015,16 @@ extension JNISwift2JavaGenerator { } return "" + + case .closure(let args, let body): + var printer = CodePrinter() + printer.printBraceBlock("", parameters: args) { printer in + let body = body.render(&printer, placeholder) + if !body.isEmpty { + printer.print("return \(body)") + } + } + return printer.finalize() } } } diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index 7de792c0f..60b99a632 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -142,6 +142,8 @@ extension Swift2JavaTranslator { return types.contains(where: check) case .genericParameter: return false + case .array(let ty): + return check(ty) } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift index 7acb11997..1be8071c4 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift @@ -94,6 +94,8 @@ private let swiftSourceFile: SourceFileSyntax = """ public struct Optional {} + public struct Array {} + // FIXME: Support 'typealias Void = ()' public struct Void {} @@ -117,4 +119,4 @@ private var foundationSourceFile: SourceFileSyntax { // On platforms other than Darwin, imports such as FoundationEssentials, FoundationNetworking, etc. are used, // so this file should be created by combining the files of the aforementioned modules. foundationEssentialsSourceFile -} \ No newline at end of file +} diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift index 809f2aa16..4a0cb9e8a 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift @@ -40,6 +40,7 @@ enum SwiftKnownTypeDeclKind: String, Hashable { case optional = "Swift.Optional" case void = "Swift.Void" case string = "Swift.String" + case array = "Swift.Array" // Foundation case foundationDataProtocol = "Foundation.DataProtocol" diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index 3840b3e13..b2f8d6eac 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -40,6 +40,9 @@ enum SwiftType: Equatable { /// `type1` & `type2` indirect case composite([SwiftType]) + /// `[type]` + indirect case array(SwiftType) + static var void: Self { return .tuple([]) } @@ -48,7 +51,7 @@ enum SwiftType: Equatable { switch self { case .nominal(let nominal): nominal case .tuple(let elements): elements.count == 1 ? elements[0].asNominalType : nil - case .genericParameter, .function, .metatype, .optional, .existential, .opaque, .composite: nil + case .genericParameter, .function, .metatype, .optional, .existential, .opaque, .composite, .array: nil } } @@ -91,7 +94,7 @@ enum SwiftType: Equatable { return nominal.nominalTypeDecl.isReferenceType case .metatype, .function: return true - case .genericParameter, .optional, .tuple, .existential, .opaque, .composite: + case .genericParameter, .optional, .tuple, .existential, .opaque, .composite, .array: return false } } @@ -127,7 +130,7 @@ extension SwiftType: CustomStringConvertible { private var postfixRequiresParentheses: Bool { switch self { case .function, .existential, .opaque, .composite: true - case .genericParameter, .metatype, .nominal, .optional, .tuple: false + case .genericParameter, .metatype, .nominal, .optional, .tuple, .array: false } } @@ -152,6 +155,8 @@ extension SwiftType: CustomStringConvertible { return "some \(constraintType)" case .composite(let types): return types.map(\.description).joined(separator: " & ") + case .array(let type): + return "[\(type)]" } } } @@ -213,7 +218,7 @@ extension SwiftNominalType { extension SwiftType { init(_ type: TypeSyntax, lookupContext: SwiftTypeLookupContext) throws { switch type.as(TypeSyntaxEnum.self) { - case .arrayType, .classRestrictionType, + case .classRestrictionType, .dictionaryType, .missingType, .namedOpaqueReturnType, .packElementType, .packExpansionType, .suppressedType, .inlineArrayType: throw TypeTranslationError.unimplementedType(type) @@ -323,6 +328,10 @@ extension SwiftType { } self = .composite(types) + + case .arrayType(let arrayType): + let elementType = try SwiftType(arrayType.element, lookupContext: lookupContext) + self = .array(elementType) } } diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 3178f60d2..7e8c66d3d 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -58,7 +58,7 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Stored properties: `var`, `let` (with `willSet`, `didSet`) | ✅ | ✅ | | Computed properties: `var` (incl. `throws`) | ✅ / TODO | ✅ | | Async functions `func async` and properties: `var { get async {} }` | ❌ | ✅ | -| Arrays: `[UInt8]`, `[T]` | ❌ | ❌ | +| Arrays: `[UInt8]`, `[MyType]`, `Array` etc | ❌ | ✅ | | Dictionaries: `[String: Int]`, `[K:V]` | ❌ | ❌ | | Generic parameters in functions: `func f(x: T)` | ❌ | ✅ | | Generic return values in functions: `func f() -> T` | ❌ | ❌ | diff --git a/Tests/JExtractSwiftTests/JNI/JNIArrayTest.swift b/Tests/JExtractSwiftTests/JNI/JNIArrayTest.swift new file mode 100644 index 000000000..ebe3f807b --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIArrayTest.swift @@ -0,0 +1,188 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import Testing + +@Suite +struct JNIArrayTest { + + @Test("Import: (Array) -> Array (Java)") + func uint8Array_explicitType_java() throws { + try assertOutput( + input: "public func f(array: Array) -> Array {}", + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public static byte[] f(@Unsigned byte[] array) { + return SwiftModule.$f(Objects.requireNonNull(array, "array must not be null")); + } + """, + """ + private static native byte[] $f(byte[] array); + """, + ] + ) + } + + @Test("Import: (Array) -> Array (Swift)") + func uint8Array_explicitType_swift() throws { + try assertOutput( + input: "public func f(array: Array) -> Array {}", + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024f___3B") + func Java_com_example_swift_SwiftModule__00024f___3B(environment: UnsafeMutablePointer!, thisClass: jclass, array: jbyteArray?) -> jbyteArray? { + return SwiftModule.f(array: [UInt8](fromJNI: array, in: environment)).getJNIValue(in: environment) + } + """ + ] + ) + } + + @Test("Import: ([UInt8]) -> [UInt8] (Java)") + func uint8Array_syntaxSugar_java() throws { + try assertOutput( + input: "public func f(array: Array) -> Array {}", + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public static byte[] f(@Unsigned byte[] array) { + return SwiftModule.$f(Objects.requireNonNull(array, "array must not be null")); + } + """, + """ + private static native byte[] $f(byte[] array); + """, + ] + ) + } + + @Test("Import: ([UInt8]) -> [UInt8] (Swift)") + func uint8Array_syntaxSugar_swift() throws { + try assertOutput( + input: "public func f(array: [UInt8]) -> [UInt8] {}", + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024f___3B") + func Java_com_example_swift_SwiftModule__00024f___3B(environment: UnsafeMutablePointer!, thisClass: jclass, array: jbyteArray?) -> jbyteArray? { + return SwiftModule.f(array: [UInt8](fromJNI: array, in: environment)).getJNIValue(in: environment) + } + """ + ] + ) + } + + @Test("Import: ([Int64]) -> [Int64] (Java)") + func int64Array_syntaxSugar_java() throws { + try assertOutput( + input: "public func f(array: [Int64]) -> [Int64] {}", + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public static long[] f(long[] array) { + return SwiftModule.$f(Objects.requireNonNull(array, "array must not be null")); + } + """, + """ + private static native long[] $f(long[] array); + """, + ] + ) + } + + @Test("Import: ([Int64]) -> [Int64] (Swift)") + func int64Array_syntaxSugar_swift() throws { + try assertOutput( + input: "public func f(array: [Int64]) -> [Int64] {}", + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024f___3J") + func Java_com_example_swift_SwiftModule__00024f___3J(environment: UnsafeMutablePointer!, thisClass: jclass, array: jlongArray?) -> jlongArray? { + return SwiftModule.f(array: [Int64](fromJNI: array, in: environment)).getJNIValue(in: environment) + } + """ + ] + ) + } + + @Test("Import: ([MySwiftClass]) -> [MySwiftClass] (Java)") + func swiftClassArray_syntaxSugar_java() throws { + try assertOutput( + input: """ + public class MySwiftClass {} + public func f(array: [MySwiftClass]) -> [MySwiftClass] {} + """, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public static MySwiftClass[] f(MySwiftClass[] array, SwiftArena swiftArena$) { + return Arrays.stream(SwiftModule.$f(Arrays.stream(Objects.requireNonNull(array, "array must not be null")).mapToLong(MySwiftClass::$memoryAddress).toArray())).mapToObj((pointer) -> { + return MySwiftClass.wrapMemoryAddressUnsafe(pointer, swiftArena$); + } + ).toArray(MySwiftClass[]::new); + } + """, + """ + private static native long[] $f(long[] array); + """, + ] + ) + } + + @Test("Import: ([MySwiftClass]) -> [MySwiftClass] (Swift)") + func swiftClassArray_syntaxSugar_swift() throws { + try assertOutput( + input: """ + public class MySwiftClass {} + public func f(array: [MySwiftClass]) -> [MySwiftClass] {} + """, + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024f___3J") + func Java_com_example_swift_SwiftModule__00024f___3J(environment: UnsafeMutablePointer!, thisClass: jclass, array: jlongArray?) -> jlongArray? { + return SwiftModule.f(array: [Int64](fromJNI: array, in: environment).map( { (pointer$) in + assert(pointer$ != 0, "pointer$ memory address was null") + let pointer$Bits$ = Int(pointer$) + let pointer$$ = UnsafeMutablePointer(bitPattern: pointer$Bits$) + guard let pointer$$ else { + fatalError("pointer$ memory address was null in call to \\(#function)!") + } + return pointer$$.pointee + } + )).map( { (object$) in + let object$$ = UnsafeMutablePointer.allocate(capacity: 1) + object$$.initialize(to: object$) + let object$Bits$ = Int64(Int(bitPattern: object$$)) + return object$Bits$ + } + ).getJNIValue(in: environment) + } + """ + ] + ) + } +} From e907ab050e4cac6d22bd6c72a1449b3a1f55a106 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 12 Nov 2025 12:56:36 +0900 Subject: [PATCH 110/141] Delete WIP.md (#438) --- WIP.md | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 WIP.md diff --git a/WIP.md b/WIP.md deleted file mode 100644 index 88671233b..000000000 --- a/WIP.md +++ /dev/null @@ -1,25 +0,0 @@ -## Work In Progress - -This package is a work in progress, and many details are subject to change. - -Here is a long yet still very incomplete list of things we would like to do or -improve: - -- Expressivity gaps: - - [ ] Automatically turn get/set method pairs into Swift properties? - - [ ] Implement a global registry that lets us find the Swift type corresponding to a canonical Java class name (e.g., `java.net.URL` -> `JavaKitNetwork.URL`) - - [ ] Introduce overloads of `is` and `as` on the Swift projections so that conversion to any implemented interface or extended superclass returns non-optional. - - [ ] Figure out how to express the equivalent of `super.foo()` that calls the superclass's method from the subclass method. - - [ ] Recognize Java's enum classes and map them into Swift well - - [ ] Translate Java constants into Swift constants - - [ ] Support nested classes - - [ ] Figure out how to subclass a Java class from Swift -- Tooling - - [ ] Generate Swift projections for more common Java types into JavaKit libraries to make it easier to get started - - [ ] Teach `Java2Swift` when to create extensions of already-translated types that pick up any members that couldn't be translated because of missing types. See, for example, how `JavaKitReflection` adds extensions to `JavaClass` based on types like `Method` and `Parameter` -- Performance: - - [ ] Cache method/field IDs when we can - - [ ] Investigate noncopyable types to remove excess copies - - [ ] Investigate "unbridged" variants of String, Array, etc. - - [ ] Investigate the new [Foreign Function & Memory API](https://bugs.openjdk.org/browse/JDK-8312523) (aka Project Panama) for exposing Swift APIs to Java. - From 46699587ec25374ec6f0431beb6ea29dfa8d9758 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 12 Nov 2025 14:17:09 +0900 Subject: [PATCH 111/141] jextract/jni: use deferEnvironment instead of environment interface (#437) --- ...wift2JavaGenerator+NativeTranslation.swift | 2 +- .../JNI/JNIAsyncTests.swift | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 35935038d..1994dce04 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -973,7 +973,7 @@ extension JNISwift2JavaGenerator { // Defer might on any thread, so we need to attach environment. printer.print("let deferEnvironment = try! JavaVirtualMachine.shared().environment()") for globalRef in globalRefs { - printer.print("environment.interface.DeleteGlobalRef(deferEnvironment, \(globalRef))") + printer.print("deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, \(globalRef))") } } if isThrowing { diff --git a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift index 975cccc6a..1930e601a 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift @@ -66,7 +66,7 @@ struct JNIAsyncTests { var environment = environment! defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.asyncVoid() environment = try! JavaVirtualMachine.shared().environment() @@ -79,7 +79,7 @@ struct JNIAsyncTests { var environment = try! JavaVirtualMachine.shared().environment() defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.asyncVoid() environment = try! JavaVirtualMachine.shared().environment() @@ -140,7 +140,7 @@ struct JNIAsyncTests { var environment = environment! defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } do { let swiftResult$ = await try SwiftModule.async() @@ -160,7 +160,7 @@ struct JNIAsyncTests { var environment = try! JavaVirtualMachine.shared().environment() defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } do { let swiftResult$ = await try SwiftModule.async() @@ -228,7 +228,7 @@ struct JNIAsyncTests { var environment = environment! defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment)) environment = try! JavaVirtualMachine.shared().environment() @@ -242,7 +242,7 @@ struct JNIAsyncTests { var environment = try! JavaVirtualMachine.shared().environment() defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment)) environment = try! JavaVirtualMachine.shared().environment() @@ -319,7 +319,7 @@ struct JNIAsyncTests { var environment = environment! defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.async(c: c$.pointee) environment = try! JavaVirtualMachine.shared().environment() @@ -336,7 +336,7 @@ struct JNIAsyncTests { var environment = try! JavaVirtualMachine.shared().environment() defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.async(c: c$.pointee) environment = try! JavaVirtualMachine.shared().environment() @@ -397,8 +397,8 @@ struct JNIAsyncTests { ... defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) - environment.interface.DeleteGlobalRef(deferEnvironment, s) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, s) } ... environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: swiftResult$.getJNIValue(in: environment))]) From f010c8e33d528085057bb8e82eb53136df30effd Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 12 Nov 2025 19:32:42 +0900 Subject: [PATCH 112/141] wrap-java revamp and add generic methods, types, and supertypes support (#397) --- Package.swift | 4 +- Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift | 31 ++ .../Sources/JavaCommonsCSV/swift-java.config | 4 +- .../JavaDependencySampleApp/ci-validate.sh | 4 +- .../Sources/MySwiftLibrary/NestedTypes.swift | 2 +- .../com/example/swift/HelloJava2SwiftJNI.java | 2 - .../JavaLangReflect/Method+Utilities.swift | 26 +- .../TypeVariable+Extensions.swift | 63 ++++ .../generated/Executable.swift | 3 - .../generated/ParameterizedType.swift | 21 ++ .../JavaLangReflect/generated/Type.swift | 9 + .../generated/TypeVariable.swift | 4 +- .../JavaStdlib/JavaNet/URL+Extensions.swift | 34 ++ .../JavaNet/URLClassLoader+Workaround.swift | 22 ++ Sources/SwiftJava/AnyJavaObject.swift | 6 +- .../JavaVirtualMachine.swift | 2 +- .../{JavaKitVM => JVM}/LockedState.swift | 0 .../ThreadLocalStorage.swift | 0 .../JavaClass+CustomStringConvertible.swift | 21 ++ Sources/SwiftJava/JavaObjectHolder.swift | 4 +- .../String+Extensions.swift | 18 +- .../SwiftJava/SwiftJavaConversionError.swift | 22 ++ .../SwiftJava/generated/CharSequence.swift | 3 + .../SwiftJava/generated/JavaCharacter.swift | 328 +++++++++--------- Sources/SwiftJava/generated/JavaClass.swift | 34 +- .../SwiftJava/generated/JavaClassLoader.swift | 8 +- Sources/SwiftJava/generated/JavaEnum.swift | 11 + Sources/SwiftJava/generated/JavaInteger.swift | 29 +- Sources/SwiftJava/generated/JavaLong.swift | 18 +- .../SwiftJava/generated/JavaOptional.swift | 2 +- .../generated/JavaReflectArray.swift | 71 ++++ .../JavaReflectParameterizedType.swift | 17 + .../SwiftJava/generated/JavaReflectType.swift | 8 + Sources/SwiftJava/swift-java.config | 2 + .../Configuration.swift | 8 +- .../Commands/ConfigureCommand.swift | 56 ++- .../Commands/JExtractCommand.swift | 10 +- .../Commands/ResolveCommand.swift | 22 +- .../Commands/WrapJavaCommand.swift | 52 ++- Sources/SwiftJavaTool/CommonOptions.swift | 21 +- Sources/SwiftJavaTool/ExcludedJDKTypes.swift | 42 +++ .../SwiftJavaTool/Java/JavaClassLoader.swift | 1 + .../JavaClassTranslator.swift | 309 +++++++++-------- .../JavaGenericsSupport.swift | 89 +++++ .../SwiftJavaToolLib/JavaHomeSupport.swift | 92 +++++ .../JavaParameterizedType.swift | 38 ++ .../JavaTranslator+Configuration.swift | 10 +- .../JavaTranslator+Validation.swift | 53 ++- Sources/SwiftJavaToolLib/JavaTranslator.swift | 114 ++++-- .../SwiftJavaToolLib/JavaType+Equality.swift | 135 +++++++ .../FFMNestedTypesTests.swift | 56 +++ .../JNI/JNINestedTypesTests.swift | 2 +- .../JNI/JNIUnsignedNumberTests.swift | 1 - .../CompileJavaWrapTools.swift | 174 ++++++++++ .../Java2SwiftTests.swift | 278 ++++++++------- .../JavaTranslatorTests.swift | 58 ++++ .../JavaTranslatorValidationTests.swift | 13 +- .../SwiftJavaToolLibTests/TempFileTools.swift | 34 ++ .../SwiftJavaToolLibTests/WrapJavaTests.swift | 273 +++++++++++++++ 59 files changed, 2150 insertions(+), 624 deletions(-) create mode 100644 Sources/JavaStdlib/JavaLangReflect/TypeVariable+Extensions.swift create mode 100644 Sources/JavaStdlib/JavaNet/URL+Extensions.swift create mode 100644 Sources/JavaStdlib/JavaNet/URLClassLoader+Workaround.swift rename Sources/SwiftJava/{JavaKitVM => JVM}/JavaVirtualMachine.swift (99%) rename Sources/SwiftJava/{JavaKitVM => JVM}/LockedState.swift (100%) rename Sources/SwiftJava/{JavaKitVM => JVM}/ThreadLocalStorage.swift (100%) create mode 100644 Sources/SwiftJava/JavaClass+CustomStringConvertible.swift rename Sources/{SwiftJavaTool => SwiftJava}/String+Extensions.swift (82%) create mode 100644 Sources/SwiftJava/SwiftJavaConversionError.swift create mode 100644 Sources/SwiftJava/generated/JavaEnum.swift create mode 100644 Sources/SwiftJava/generated/JavaReflectArray.swift create mode 100644 Sources/SwiftJava/generated/JavaReflectParameterizedType.swift create mode 100644 Sources/SwiftJava/generated/JavaReflectType.swift create mode 100644 Sources/SwiftJavaTool/ExcludedJDKTypes.swift create mode 100644 Sources/SwiftJavaToolLib/JavaGenericsSupport.swift create mode 100644 Sources/SwiftJavaToolLib/JavaHomeSupport.swift create mode 100644 Sources/SwiftJavaToolLib/JavaParameterizedType.swift create mode 100644 Sources/SwiftJavaToolLib/JavaType+Equality.swift create mode 100644 Tests/JExtractSwiftTests/FFMNestedTypesTests.swift create mode 100644 Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift create mode 100644 Tests/SwiftJavaToolLibTests/JavaTranslatorTests.swift create mode 100644 Tests/SwiftJavaToolLibTests/TempFileTools.swift create mode 100644 Tests/SwiftJavaToolLibTests/WrapJavaTests.swift diff --git a/Package.swift b/Package.swift index 722859eed..7d872795a 100644 --- a/Package.swift +++ b/Package.swift @@ -14,6 +14,9 @@ func findJavaHome() -> String { print("JAVA_HOME = \(home)") return home } + if let opts = ProcessInfo.processInfo.environment["SWIFT_JAVA_JAVA_OPTS"] { + print("SWIFT_JAVA_JAVA_OPTS = \(opts)") + } // This is a workaround for envs (some IDEs) which have trouble with // picking up env variables during the build process @@ -420,7 +423,6 @@ let package = Package( .executableTarget( name: "SwiftJavaTool", dependencies: [ - .product(name: "Logging", package: "swift-log"), .product(name: "SwiftBasicFormat", package: "swift-syntax"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), diff --git a/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift index 57641eff9..7908932d1 100644 --- a/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift +++ b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift @@ -165,6 +165,11 @@ struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { log("No dependencies to fetch for target \(sourceModule.name)") } + // Add all the core Java stdlib modules as --depends-on + let javaStdlibModules = getExtractedJavaStdlibModules() + log("Include Java standard library SwiftJava modules: \(javaStdlibModules)") + arguments += javaStdlibModules.flatMap { ["--depends-on", $0] } + if !outputSwiftFiles.isEmpty { arguments += [ configFile.path(percentEncoded: false) ] @@ -236,3 +241,29 @@ extension SwiftJavaBuildToolPlugin { outputDirectory(context: context, generated: generated).appending(path: filename) } } + +func getExtractedJavaStdlibModules() -> [String] { + let fileManager = FileManager.default + let sourcesPath = URL(fileURLWithPath: #filePath) + .deletingLastPathComponent() + .deletingLastPathComponent() + .appendingPathComponent("Sources") + .appendingPathComponent("JavaStdlib") + + guard let stdlibDirContents = try? fileManager.contentsOfDirectory( + at: sourcesPath, + includingPropertiesForKeys: [.isDirectoryKey], + options: [.skipsHiddenFiles] + ) else { + return [] + } + + return stdlibDirContents.compactMap { url in + guard let resourceValues = try? url.resourceValues(forKeys: [.isDirectoryKey]), + let isDirectory = resourceValues.isDirectory, + isDirectory else { + return nil + } + return url.lastPathComponent + }.sorted() +} \ No newline at end of file diff --git a/Samples/JavaDependencySampleApp/Sources/JavaCommonsCSV/swift-java.config b/Samples/JavaDependencySampleApp/Sources/JavaCommonsCSV/swift-java.config index 3b685159d..dc38efd9f 100644 --- a/Samples/JavaDependencySampleApp/Sources/JavaCommonsCSV/swift-java.config +++ b/Samples/JavaDependencySampleApp/Sources/JavaCommonsCSV/swift-java.config @@ -1,11 +1,13 @@ { "classes" : { "org.apache.commons.io.FilenameUtils" : "FilenameUtils", - "org.apache.commons.io.IOCase" : "IOCase", "org.apache.commons.csv.CSVFormat" : "CSVFormat", "org.apache.commons.csv.CSVParser" : "CSVParser", "org.apache.commons.csv.CSVRecord" : "CSVRecord" }, + "filterExclude" : [ + "org.apache.commons.csv.CSVFormat$Predefined", + ], "dependencies" : [ "org.apache.commons:commons-csv:1.12.0" ] diff --git a/Samples/JavaDependencySampleApp/ci-validate.sh b/Samples/JavaDependencySampleApp/ci-validate.sh index b6162fdce..feeb87675 100755 --- a/Samples/JavaDependencySampleApp/ci-validate.sh +++ b/Samples/JavaDependencySampleApp/ci-validate.sh @@ -4,7 +4,9 @@ set -e set -x # invoke resolve as part of a build run -swift run --disable-sandbox +swift build \ + --disable-experimental-prebuilts \ + --disable-sandbox # explicitly invoke resolve without explicit path or dependency # the dependencies should be uses from the --swift-module diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/NestedTypes.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/NestedTypes.swift index b7edefa93..fb2b49248 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/NestedTypes.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/NestedTypes.swift @@ -44,4 +44,4 @@ public enum NestedEnum { public struct OneStruct { public init() {} } -} +} \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java b/Samples/SwiftJavaExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java index 3109f64e9..407ebe7ce 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java @@ -16,8 +16,6 @@ // Import swift-extract generated sources -// Import javakit/swiftkit support libraries - import org.swift.swiftkit.core.SwiftArena; import org.swift.swiftkit.core.SwiftLibraries; diff --git a/Sources/JavaStdlib/JavaLangReflect/Method+Utilities.swift b/Sources/JavaStdlib/JavaLangReflect/Method+Utilities.swift index 71ee864ce..ecc11b507 100644 --- a/Sources/JavaStdlib/JavaLangReflect/Method+Utilities.swift +++ b/Sources/JavaStdlib/JavaLangReflect/Method+Utilities.swift @@ -13,23 +13,41 @@ //===----------------------------------------------------------------------===// extension Method { + /// Whether this is a 'public' method. public var isPublic: Bool { - return (getModifiers() & 1) != 0 + return (getModifiers() & 0x00000001) != 0 + } + + /// Whether this is a 'private' method. + public var isPrivate: Bool { + return (getModifiers() & 0x00000002) != 0 } /// Whether this is a 'protected' method. public var isProtected: Bool { - return (getModifiers() & 4) != 0 + return (getModifiers() & 0x00000004) != 0 + } + + /// Whether this is a 'package' method. + /// + /// The "default" access level in Java is 'package', it is signified by lack of a different access modifier. + public var isPackage: Bool { + return !isPublic && !isPrivate && !isProtected } /// Whether this is a 'static' method. public var isStatic: Bool { - return (getModifiers() & 0x08) != 0 + return (getModifiers() & 0x00000008) != 0 } /// Whether this is a 'native' method. public var isNative: Bool { - return (getModifiers() & 256) != 0 + return (getModifiers() & 0x00000100) != 0 + } + + /// Whether this is a 'final' method. + public var isFinal: Bool { + return (getModifiers() & 0x00000010) != 0 } } diff --git a/Sources/JavaStdlib/JavaLangReflect/TypeVariable+Extensions.swift b/Sources/JavaStdlib/JavaLangReflect/TypeVariable+Extensions.swift new file mode 100644 index 000000000..157ae353d --- /dev/null +++ b/Sources/JavaStdlib/JavaLangReflect/TypeVariable+Extensions.swift @@ -0,0 +1,63 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import CSwiftJavaJNI +import SwiftJava + +// FIXME: all interfaces should ahve these https://github.com/swiftlang/swift-java/issues/430 +extension TypeVariable { + + @JavaMethod + public func toString() -> String + + @JavaMethod + public func getClass() -> JavaClass! + + @JavaMethod + public func equals(_ arg0: JavaObject?) -> Bool + + @JavaMethod + public func hashCode() -> Int32 + +} + +// FIXME: All Java objects are Hashable, we should handle that accordingly. +extension TypeVariable: Hashable { + + public func hash(into hasher: inout Hasher) { + guard let pojo = self.as(JavaObject.self) else { + return + } + + hasher.combine(pojo.hashCode()) + } + + public static func == (lhs: TypeVariable, rhs: TypeVariable) -> Bool { + guard let lhpojo: JavaObject = lhs.as(JavaObject.self) else { + return false + } + guard let rhpojo: JavaObject = rhs.as(JavaObject.self) else { + return false + } + + return lhpojo.equals(rhpojo) + } + +} + +extension TypeVariable { + public var description: String { + toString() + } +} \ No newline at end of file diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift index 3a6df8eab..af9931d35 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Executable.swift @@ -11,9 +11,6 @@ open class Executable: AccessibleObject { @JavaMethod open func getModifiers() -> Int32 - @JavaMethod - open func getTypeParameters() -> [TypeVariable?] - @JavaMethod open func getParameterTypes() -> [JavaClass?] diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift b/Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift index 5e29ee052..984c2b16e 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/ParameterizedType.swift @@ -16,3 +16,24 @@ public struct ParameterizedType { @JavaMethod public func getTypeName() -> String } + +extension ParameterizedType { + + @JavaMethod + public func toString() -> String + + @JavaMethod + public func getClass() -> JavaClass! + + @JavaMethod + public func equals(_ arg0: JavaObject?) -> Bool + + @JavaMethod + public func hashCode() -> Int32 +} + +extension ParameterizedType: CustomStringConvertible { + public var description: String { + toString() + } +} \ No newline at end of file diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/Type.swift b/Sources/JavaStdlib/JavaLangReflect/generated/Type.swift index ff52b41a7..2e85c3842 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/Type.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/Type.swift @@ -6,4 +6,13 @@ import CSwiftJavaJNI public struct Type { @JavaMethod public func getTypeName() -> String + + @JavaMethod + public func toString() -> String +} + +extension Type: CustomStringConvertible { + public var description: String { + "JavaLangReflect.Type(\(self.toString()))" + } } diff --git a/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift b/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift index 736fcfde6..7bf8f7ba2 100644 --- a/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift +++ b/Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift @@ -3,10 +3,10 @@ import SwiftJava import CSwiftJavaJNI @JavaInterface("java.lang.reflect.TypeVariable", extends: Type.self) -public struct TypeVariable { +public struct TypeVariable: CustomStringConvertible { @JavaMethod public func getGenericDeclaration() -> GenericDeclaration! - + @JavaMethod public func getAnnotatedBounds() -> [AnnotatedType?] diff --git a/Sources/JavaStdlib/JavaNet/URL+Extensions.swift b/Sources/JavaStdlib/JavaNet/URL+Extensions.swift new file mode 100644 index 000000000..2b220d1c8 --- /dev/null +++ b/Sources/JavaStdlib/JavaNet/URL+Extensions.swift @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftJava +import CSwiftJavaJNI + +import Foundation +public typealias SwiftJavaFoundationURL = Foundation.URL + +extension SwiftJavaFoundationURL { + public static func fromJava(_ url: URL) throws -> SwiftJavaFoundationURL { + guard let converted = SwiftJavaFoundationURL(string: try url.toURI().toString()) else { + throw SwiftJavaConversionError("Failed to convert \(URL.self) to \(SwiftJavaFoundationURL.self)") + } + return converted + } +} + +extension URL { + public static func fromSwift(_ url: SwiftJavaFoundationURL) throws -> URL { + return try URL(url.absoluteString) + } +} \ No newline at end of file diff --git a/Sources/JavaStdlib/JavaNet/URLClassLoader+Workaround.swift b/Sources/JavaStdlib/JavaNet/URLClassLoader+Workaround.swift new file mode 100644 index 000000000..4c55f6af6 --- /dev/null +++ b/Sources/JavaStdlib/JavaNet/URLClassLoader+Workaround.swift @@ -0,0 +1,22 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import CSwiftJavaJNI +import SwiftJava + +// FIXME: workaround until importing properly would make UCL inherit from CL https://github.com/swiftlang/swift-java/issues/423 +extension URLClassLoader /* workaround for missing inherits from ClassLoader */ { + @JavaMethod + public func loadClass(_ name: String) throws -> JavaClass? +} diff --git a/Sources/SwiftJava/AnyJavaObject.swift b/Sources/SwiftJava/AnyJavaObject.swift index e514d3e6a..bf749820f 100644 --- a/Sources/SwiftJava/AnyJavaObject.swift +++ b/Sources/SwiftJava/AnyJavaObject.swift @@ -52,7 +52,7 @@ public protocol AnyJavaObject { /// Protocol that allows Swift types to specify a custom Java class loader on /// initialization. This is useful for platforms (e.g. Android) where the default /// class loader does not make all application classes visible. -public protocol CustomJavaClassLoader: AnyJavaObject { +public protocol AnyJavaObjectWithCustomClassLoader: AnyJavaObject { static func getJavaClassLoader(in environment: JNIEnvironment) throws -> JavaClassLoader! } @@ -118,8 +118,8 @@ extension AnyJavaObject { in environment: JNIEnvironment, _ body: (jclass) throws -> Result ) throws -> Result { - if let customJavaClassLoader = self as? CustomJavaClassLoader.Type, - let customClassLoader = try customJavaClassLoader.getJavaClassLoader(in: environment) { + if let AnyJavaObjectWithCustomClassLoader = self as? AnyJavaObjectWithCustomClassLoader.Type, + let customClassLoader = try AnyJavaObjectWithCustomClassLoader.getJavaClassLoader(in: environment) { try _withJNIClassFromCustomClassLoader(customClassLoader, in: environment, body) } else { try _withJNIClassFromDefaultClassLoader(in: environment, body) diff --git a/Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift b/Sources/SwiftJava/JVM/JavaVirtualMachine.swift similarity index 99% rename from Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift rename to Sources/SwiftJava/JVM/JavaVirtualMachine.swift index bb574c8ad..1c7936a34 100644 --- a/Sources/SwiftJava/JavaKitVM/JavaVirtualMachine.swift +++ b/Sources/SwiftJava/JVM/JavaVirtualMachine.swift @@ -350,4 +350,4 @@ extension JavaVirtualMachine { enum JavaKitError: Error { case classpathEntryNotFound(entry: String, classpath: [String]) } -} +} \ No newline at end of file diff --git a/Sources/SwiftJava/JavaKitVM/LockedState.swift b/Sources/SwiftJava/JVM/LockedState.swift similarity index 100% rename from Sources/SwiftJava/JavaKitVM/LockedState.swift rename to Sources/SwiftJava/JVM/LockedState.swift diff --git a/Sources/SwiftJava/JavaKitVM/ThreadLocalStorage.swift b/Sources/SwiftJava/JVM/ThreadLocalStorage.swift similarity index 100% rename from Sources/SwiftJava/JavaKitVM/ThreadLocalStorage.swift rename to Sources/SwiftJava/JVM/ThreadLocalStorage.swift diff --git a/Sources/SwiftJava/JavaClass+CustomStringConvertible.swift b/Sources/SwiftJava/JavaClass+CustomStringConvertible.swift new file mode 100644 index 000000000..e7eb25105 --- /dev/null +++ b/Sources/SwiftJava/JavaClass+CustomStringConvertible.swift @@ -0,0 +1,21 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import CSwiftJavaJNI + +extension JavaClass: CustomStringConvertible { + public var description: String { + toString() + } +} \ No newline at end of file diff --git a/Sources/SwiftJava/JavaObjectHolder.swift b/Sources/SwiftJava/JavaObjectHolder.swift index 319a09e83..5930da59b 100644 --- a/Sources/SwiftJava/JavaObjectHolder.swift +++ b/Sources/SwiftJava/JavaObjectHolder.swift @@ -17,8 +17,8 @@ import CSwiftJavaJNI /// Stores a reference to a Java object, managing it as a global reference so /// that the Java virtual machine will not move or deallocate the object /// while this instance is live. -public class JavaObjectHolder { - public private(set) var object: jobject? +public final class JavaObjectHolder { + public private(set) var object: jobject? // TODO: thread-safety public let environment: JNIEnvironment /// Take a reference to a Java object and promote it to a global reference diff --git a/Sources/SwiftJavaTool/String+Extensions.swift b/Sources/SwiftJava/String+Extensions.swift similarity index 82% rename from Sources/SwiftJavaTool/String+Extensions.swift rename to Sources/SwiftJava/String+Extensions.swift index 304e217d5..94fb19286 100644 --- a/Sources/SwiftJavaTool/String+Extensions.swift +++ b/Sources/SwiftJava/String+Extensions.swift @@ -13,17 +13,15 @@ //===----------------------------------------------------------------------===// import Foundation -import ArgumentParser -import SwiftJavaToolLib -import SwiftJava -import JavaUtilJar -import SwiftJavaToolLib -import SwiftJavaConfigurationShared +// import SwiftJavaToolLib +// import SwiftJava +// import JavaUtilJar +// import SwiftJavaConfigurationShared extension String { /// For a String that's of the form java.util.Vector, return the "Vector" /// part. - var defaultSwiftNameForJavaClass: String { + package var defaultSwiftNameForJavaClass: String { if let dotLoc = lastIndex(of: ".") { let afterDot = index(after: dotLoc) return String(self[afterDot...]).javaClassNameToCanonicalName.adjustedSwiftTypeName @@ -36,12 +34,12 @@ extension String { extension String { /// Replace all of the $'s for nested names with "." to turn a Java class /// name into a Java canonical class name, - var javaClassNameToCanonicalName: String { + package var javaClassNameToCanonicalName: String { return replacing("$", with: ".") } /// Whether this is the name of an anonymous class. - var isLocalJavaClass: Bool { + package var isLocalJavaClass: Bool { for segment in split(separator: "$") { if let firstChar = segment.first, firstChar.isNumber { return true @@ -52,7 +50,7 @@ extension String { } /// Adjust type name for "bad" type names that don't work well in Swift. - var adjustedSwiftTypeName: String { + package var adjustedSwiftTypeName: String { switch self { case "Type": return "JavaType" default: return self diff --git a/Sources/SwiftJava/SwiftJavaConversionError.swift b/Sources/SwiftJava/SwiftJavaConversionError.swift new file mode 100644 index 000000000..5b29741ce --- /dev/null +++ b/Sources/SwiftJava/SwiftJavaConversionError.swift @@ -0,0 +1,22 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +/// Used to indicate Swift/Java conversion failures. +public struct SwiftJavaConversionError: Error { + public let message: String + + public init(_ message: String) { + self.message = message + } +} \ No newline at end of file diff --git a/Sources/SwiftJava/generated/CharSequence.swift b/Sources/SwiftJava/generated/CharSequence.swift index eadc509eb..ee5dca369 100644 --- a/Sources/SwiftJava/generated/CharSequence.swift +++ b/Sources/SwiftJava/generated/CharSequence.swift @@ -9,6 +9,9 @@ public struct CharSequence { @JavaMethod public func toString() -> String + @JavaMethod + public func getChars(_ arg0: Int32, _ arg1: Int32, _ arg2: [UInt16], _ arg3: Int32) + @JavaMethod public func charAt(_ arg0: Int32) -> UInt16 diff --git a/Sources/SwiftJava/generated/JavaCharacter.swift b/Sources/SwiftJava/generated/JavaCharacter.swift index 406b45ee2..f79742a3b 100644 --- a/Sources/SwiftJava/generated/JavaCharacter.swift +++ b/Sources/SwiftJava/generated/JavaCharacter.swift @@ -1557,985 +1557,985 @@ extension JavaCharacter { if let COMMON = classObj.COMMON { self.init(javaHolder: COMMON.javaHolder) } else { - fatalError("Enum value COMMON was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value COMMON was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LATIN: if let LATIN = classObj.LATIN { self.init(javaHolder: LATIN.javaHolder) } else { - fatalError("Enum value LATIN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LATIN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GREEK: if let GREEK = classObj.GREEK { self.init(javaHolder: GREEK.javaHolder) } else { - fatalError("Enum value GREEK was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GREEK was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CYRILLIC: if let CYRILLIC = classObj.CYRILLIC { self.init(javaHolder: CYRILLIC.javaHolder) } else { - fatalError("Enum value CYRILLIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CYRILLIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ARMENIAN: if let ARMENIAN = classObj.ARMENIAN { self.init(javaHolder: ARMENIAN.javaHolder) } else { - fatalError("Enum value ARMENIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ARMENIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .HEBREW: if let HEBREW = classObj.HEBREW { self.init(javaHolder: HEBREW.javaHolder) } else { - fatalError("Enum value HEBREW was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value HEBREW was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ARABIC: if let ARABIC = classObj.ARABIC { self.init(javaHolder: ARABIC.javaHolder) } else { - fatalError("Enum value ARABIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ARABIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SYRIAC: if let SYRIAC = classObj.SYRIAC { self.init(javaHolder: SYRIAC.javaHolder) } else { - fatalError("Enum value SYRIAC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SYRIAC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .THAANA: if let THAANA = classObj.THAANA { self.init(javaHolder: THAANA.javaHolder) } else { - fatalError("Enum value THAANA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value THAANA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .DEVANAGARI: if let DEVANAGARI = classObj.DEVANAGARI { self.init(javaHolder: DEVANAGARI.javaHolder) } else { - fatalError("Enum value DEVANAGARI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value DEVANAGARI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BENGALI: if let BENGALI = classObj.BENGALI { self.init(javaHolder: BENGALI.javaHolder) } else { - fatalError("Enum value BENGALI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BENGALI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GURMUKHI: if let GURMUKHI = classObj.GURMUKHI { self.init(javaHolder: GURMUKHI.javaHolder) } else { - fatalError("Enum value GURMUKHI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GURMUKHI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GUJARATI: if let GUJARATI = classObj.GUJARATI { self.init(javaHolder: GUJARATI.javaHolder) } else { - fatalError("Enum value GUJARATI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GUJARATI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ORIYA: if let ORIYA = classObj.ORIYA { self.init(javaHolder: ORIYA.javaHolder) } else { - fatalError("Enum value ORIYA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ORIYA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TAMIL: if let TAMIL = classObj.TAMIL { self.init(javaHolder: TAMIL.javaHolder) } else { - fatalError("Enum value TAMIL was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TAMIL was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TELUGU: if let TELUGU = classObj.TELUGU { self.init(javaHolder: TELUGU.javaHolder) } else { - fatalError("Enum value TELUGU was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TELUGU was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KANNADA: if let KANNADA = classObj.KANNADA { self.init(javaHolder: KANNADA.javaHolder) } else { - fatalError("Enum value KANNADA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KANNADA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MALAYALAM: if let MALAYALAM = classObj.MALAYALAM { self.init(javaHolder: MALAYALAM.javaHolder) } else { - fatalError("Enum value MALAYALAM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MALAYALAM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SINHALA: if let SINHALA = classObj.SINHALA { self.init(javaHolder: SINHALA.javaHolder) } else { - fatalError("Enum value SINHALA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SINHALA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .THAI: if let THAI = classObj.THAI { self.init(javaHolder: THAI.javaHolder) } else { - fatalError("Enum value THAI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value THAI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LAO: if let LAO = classObj.LAO { self.init(javaHolder: LAO.javaHolder) } else { - fatalError("Enum value LAO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LAO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TIBETAN: if let TIBETAN = classObj.TIBETAN { self.init(javaHolder: TIBETAN.javaHolder) } else { - fatalError("Enum value TIBETAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TIBETAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MYANMAR: if let MYANMAR = classObj.MYANMAR { self.init(javaHolder: MYANMAR.javaHolder) } else { - fatalError("Enum value MYANMAR was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MYANMAR was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GEORGIAN: if let GEORGIAN = classObj.GEORGIAN { self.init(javaHolder: GEORGIAN.javaHolder) } else { - fatalError("Enum value GEORGIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GEORGIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .HANGUL: if let HANGUL = classObj.HANGUL { self.init(javaHolder: HANGUL.javaHolder) } else { - fatalError("Enum value HANGUL was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value HANGUL was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ETHIOPIC: if let ETHIOPIC = classObj.ETHIOPIC { self.init(javaHolder: ETHIOPIC.javaHolder) } else { - fatalError("Enum value ETHIOPIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ETHIOPIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CHEROKEE: if let CHEROKEE = classObj.CHEROKEE { self.init(javaHolder: CHEROKEE.javaHolder) } else { - fatalError("Enum value CHEROKEE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CHEROKEE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CANADIAN_ABORIGINAL: if let CANADIAN_ABORIGINAL = classObj.CANADIAN_ABORIGINAL { self.init(javaHolder: CANADIAN_ABORIGINAL.javaHolder) } else { - fatalError("Enum value CANADIAN_ABORIGINAL was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CANADIAN_ABORIGINAL was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OGHAM: if let OGHAM = classObj.OGHAM { self.init(javaHolder: OGHAM.javaHolder) } else { - fatalError("Enum value OGHAM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OGHAM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .RUNIC: if let RUNIC = classObj.RUNIC { self.init(javaHolder: RUNIC.javaHolder) } else { - fatalError("Enum value RUNIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value RUNIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KHMER: if let KHMER = classObj.KHMER { self.init(javaHolder: KHMER.javaHolder) } else { - fatalError("Enum value KHMER was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KHMER was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MONGOLIAN: if let MONGOLIAN = classObj.MONGOLIAN { self.init(javaHolder: MONGOLIAN.javaHolder) } else { - fatalError("Enum value MONGOLIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MONGOLIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .HIRAGANA: if let HIRAGANA = classObj.HIRAGANA { self.init(javaHolder: HIRAGANA.javaHolder) } else { - fatalError("Enum value HIRAGANA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value HIRAGANA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KATAKANA: if let KATAKANA = classObj.KATAKANA { self.init(javaHolder: KATAKANA.javaHolder) } else { - fatalError("Enum value KATAKANA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KATAKANA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BOPOMOFO: if let BOPOMOFO = classObj.BOPOMOFO { self.init(javaHolder: BOPOMOFO.javaHolder) } else { - fatalError("Enum value BOPOMOFO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BOPOMOFO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .HAN: if let HAN = classObj.HAN { self.init(javaHolder: HAN.javaHolder) } else { - fatalError("Enum value HAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value HAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .YI: if let YI = classObj.YI { self.init(javaHolder: YI.javaHolder) } else { - fatalError("Enum value YI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value YI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_ITALIC: if let OLD_ITALIC = classObj.OLD_ITALIC { self.init(javaHolder: OLD_ITALIC.javaHolder) } else { - fatalError("Enum value OLD_ITALIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_ITALIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GOTHIC: if let GOTHIC = classObj.GOTHIC { self.init(javaHolder: GOTHIC.javaHolder) } else { - fatalError("Enum value GOTHIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GOTHIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .DESERET: if let DESERET = classObj.DESERET { self.init(javaHolder: DESERET.javaHolder) } else { - fatalError("Enum value DESERET was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value DESERET was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .INHERITED: if let INHERITED = classObj.INHERITED { self.init(javaHolder: INHERITED.javaHolder) } else { - fatalError("Enum value INHERITED was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value INHERITED was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TAGALOG: if let TAGALOG = classObj.TAGALOG { self.init(javaHolder: TAGALOG.javaHolder) } else { - fatalError("Enum value TAGALOG was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TAGALOG was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .HANUNOO: if let HANUNOO = classObj.HANUNOO { self.init(javaHolder: HANUNOO.javaHolder) } else { - fatalError("Enum value HANUNOO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value HANUNOO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BUHID: if let BUHID = classObj.BUHID { self.init(javaHolder: BUHID.javaHolder) } else { - fatalError("Enum value BUHID was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BUHID was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TAGBANWA: if let TAGBANWA = classObj.TAGBANWA { self.init(javaHolder: TAGBANWA.javaHolder) } else { - fatalError("Enum value TAGBANWA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TAGBANWA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LIMBU: if let LIMBU = classObj.LIMBU { self.init(javaHolder: LIMBU.javaHolder) } else { - fatalError("Enum value LIMBU was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LIMBU was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TAI_LE: if let TAI_LE = classObj.TAI_LE { self.init(javaHolder: TAI_LE.javaHolder) } else { - fatalError("Enum value TAI_LE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TAI_LE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LINEAR_B: if let LINEAR_B = classObj.LINEAR_B { self.init(javaHolder: LINEAR_B.javaHolder) } else { - fatalError("Enum value LINEAR_B was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LINEAR_B was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .UGARITIC: if let UGARITIC = classObj.UGARITIC { self.init(javaHolder: UGARITIC.javaHolder) } else { - fatalError("Enum value UGARITIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value UGARITIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SHAVIAN: if let SHAVIAN = classObj.SHAVIAN { self.init(javaHolder: SHAVIAN.javaHolder) } else { - fatalError("Enum value SHAVIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SHAVIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OSMANYA: if let OSMANYA = classObj.OSMANYA { self.init(javaHolder: OSMANYA.javaHolder) } else { - fatalError("Enum value OSMANYA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OSMANYA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CYPRIOT: if let CYPRIOT = classObj.CYPRIOT { self.init(javaHolder: CYPRIOT.javaHolder) } else { - fatalError("Enum value CYPRIOT was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CYPRIOT was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BRAILLE: if let BRAILLE = classObj.BRAILLE { self.init(javaHolder: BRAILLE.javaHolder) } else { - fatalError("Enum value BRAILLE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BRAILLE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BUGINESE: if let BUGINESE = classObj.BUGINESE { self.init(javaHolder: BUGINESE.javaHolder) } else { - fatalError("Enum value BUGINESE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BUGINESE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .COPTIC: if let COPTIC = classObj.COPTIC { self.init(javaHolder: COPTIC.javaHolder) } else { - fatalError("Enum value COPTIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value COPTIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NEW_TAI_LUE: if let NEW_TAI_LUE = classObj.NEW_TAI_LUE { self.init(javaHolder: NEW_TAI_LUE.javaHolder) } else { - fatalError("Enum value NEW_TAI_LUE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NEW_TAI_LUE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GLAGOLITIC: if let GLAGOLITIC = classObj.GLAGOLITIC { self.init(javaHolder: GLAGOLITIC.javaHolder) } else { - fatalError("Enum value GLAGOLITIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GLAGOLITIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TIFINAGH: if let TIFINAGH = classObj.TIFINAGH { self.init(javaHolder: TIFINAGH.javaHolder) } else { - fatalError("Enum value TIFINAGH was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TIFINAGH was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SYLOTI_NAGRI: if let SYLOTI_NAGRI = classObj.SYLOTI_NAGRI { self.init(javaHolder: SYLOTI_NAGRI.javaHolder) } else { - fatalError("Enum value SYLOTI_NAGRI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SYLOTI_NAGRI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_PERSIAN: if let OLD_PERSIAN = classObj.OLD_PERSIAN { self.init(javaHolder: OLD_PERSIAN.javaHolder) } else { - fatalError("Enum value OLD_PERSIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_PERSIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KHAROSHTHI: if let KHAROSHTHI = classObj.KHAROSHTHI { self.init(javaHolder: KHAROSHTHI.javaHolder) } else { - fatalError("Enum value KHAROSHTHI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KHAROSHTHI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BALINESE: if let BALINESE = classObj.BALINESE { self.init(javaHolder: BALINESE.javaHolder) } else { - fatalError("Enum value BALINESE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BALINESE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CUNEIFORM: if let CUNEIFORM = classObj.CUNEIFORM { self.init(javaHolder: CUNEIFORM.javaHolder) } else { - fatalError("Enum value CUNEIFORM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CUNEIFORM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .PHOENICIAN: if let PHOENICIAN = classObj.PHOENICIAN { self.init(javaHolder: PHOENICIAN.javaHolder) } else { - fatalError("Enum value PHOENICIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value PHOENICIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .PHAGS_PA: if let PHAGS_PA = classObj.PHAGS_PA { self.init(javaHolder: PHAGS_PA.javaHolder) } else { - fatalError("Enum value PHAGS_PA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value PHAGS_PA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NKO: if let NKO = classObj.NKO { self.init(javaHolder: NKO.javaHolder) } else { - fatalError("Enum value NKO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NKO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SUNDANESE: if let SUNDANESE = classObj.SUNDANESE { self.init(javaHolder: SUNDANESE.javaHolder) } else { - fatalError("Enum value SUNDANESE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SUNDANESE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BATAK: if let BATAK = classObj.BATAK { self.init(javaHolder: BATAK.javaHolder) } else { - fatalError("Enum value BATAK was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BATAK was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LEPCHA: if let LEPCHA = classObj.LEPCHA { self.init(javaHolder: LEPCHA.javaHolder) } else { - fatalError("Enum value LEPCHA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LEPCHA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OL_CHIKI: if let OL_CHIKI = classObj.OL_CHIKI { self.init(javaHolder: OL_CHIKI.javaHolder) } else { - fatalError("Enum value OL_CHIKI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OL_CHIKI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .VAI: if let VAI = classObj.VAI { self.init(javaHolder: VAI.javaHolder) } else { - fatalError("Enum value VAI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value VAI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SAURASHTRA: if let SAURASHTRA = classObj.SAURASHTRA { self.init(javaHolder: SAURASHTRA.javaHolder) } else { - fatalError("Enum value SAURASHTRA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SAURASHTRA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KAYAH_LI: if let KAYAH_LI = classObj.KAYAH_LI { self.init(javaHolder: KAYAH_LI.javaHolder) } else { - fatalError("Enum value KAYAH_LI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KAYAH_LI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .REJANG: if let REJANG = classObj.REJANG { self.init(javaHolder: REJANG.javaHolder) } else { - fatalError("Enum value REJANG was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value REJANG was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LYCIAN: if let LYCIAN = classObj.LYCIAN { self.init(javaHolder: LYCIAN.javaHolder) } else { - fatalError("Enum value LYCIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LYCIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CARIAN: if let CARIAN = classObj.CARIAN { self.init(javaHolder: CARIAN.javaHolder) } else { - fatalError("Enum value CARIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CARIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LYDIAN: if let LYDIAN = classObj.LYDIAN { self.init(javaHolder: LYDIAN.javaHolder) } else { - fatalError("Enum value LYDIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LYDIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CHAM: if let CHAM = classObj.CHAM { self.init(javaHolder: CHAM.javaHolder) } else { - fatalError("Enum value CHAM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CHAM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TAI_THAM: if let TAI_THAM = classObj.TAI_THAM { self.init(javaHolder: TAI_THAM.javaHolder) } else { - fatalError("Enum value TAI_THAM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TAI_THAM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TAI_VIET: if let TAI_VIET = classObj.TAI_VIET { self.init(javaHolder: TAI_VIET.javaHolder) } else { - fatalError("Enum value TAI_VIET was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TAI_VIET was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .AVESTAN: if let AVESTAN = classObj.AVESTAN { self.init(javaHolder: AVESTAN.javaHolder) } else { - fatalError("Enum value AVESTAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value AVESTAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .EGYPTIAN_HIEROGLYPHS: if let EGYPTIAN_HIEROGLYPHS = classObj.EGYPTIAN_HIEROGLYPHS { self.init(javaHolder: EGYPTIAN_HIEROGLYPHS.javaHolder) } else { - fatalError("Enum value EGYPTIAN_HIEROGLYPHS was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value EGYPTIAN_HIEROGLYPHS was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SAMARITAN: if let SAMARITAN = classObj.SAMARITAN { self.init(javaHolder: SAMARITAN.javaHolder) } else { - fatalError("Enum value SAMARITAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SAMARITAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MANDAIC: if let MANDAIC = classObj.MANDAIC { self.init(javaHolder: MANDAIC.javaHolder) } else { - fatalError("Enum value MANDAIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MANDAIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LISU: if let LISU = classObj.LISU { self.init(javaHolder: LISU.javaHolder) } else { - fatalError("Enum value LISU was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LISU was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BAMUM: if let BAMUM = classObj.BAMUM { self.init(javaHolder: BAMUM.javaHolder) } else { - fatalError("Enum value BAMUM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BAMUM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .JAVANESE: if let JAVANESE = classObj.JAVANESE { self.init(javaHolder: JAVANESE.javaHolder) } else { - fatalError("Enum value JAVANESE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value JAVANESE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MEETEI_MAYEK: if let MEETEI_MAYEK = classObj.MEETEI_MAYEK { self.init(javaHolder: MEETEI_MAYEK.javaHolder) } else { - fatalError("Enum value MEETEI_MAYEK was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MEETEI_MAYEK was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .IMPERIAL_ARAMAIC: if let IMPERIAL_ARAMAIC = classObj.IMPERIAL_ARAMAIC { self.init(javaHolder: IMPERIAL_ARAMAIC.javaHolder) } else { - fatalError("Enum value IMPERIAL_ARAMAIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value IMPERIAL_ARAMAIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_SOUTH_ARABIAN: if let OLD_SOUTH_ARABIAN = classObj.OLD_SOUTH_ARABIAN { self.init(javaHolder: OLD_SOUTH_ARABIAN.javaHolder) } else { - fatalError("Enum value OLD_SOUTH_ARABIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_SOUTH_ARABIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .INSCRIPTIONAL_PARTHIAN: if let INSCRIPTIONAL_PARTHIAN = classObj.INSCRIPTIONAL_PARTHIAN { self.init(javaHolder: INSCRIPTIONAL_PARTHIAN.javaHolder) } else { - fatalError("Enum value INSCRIPTIONAL_PARTHIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value INSCRIPTIONAL_PARTHIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .INSCRIPTIONAL_PAHLAVI: if let INSCRIPTIONAL_PAHLAVI = classObj.INSCRIPTIONAL_PAHLAVI { self.init(javaHolder: INSCRIPTIONAL_PAHLAVI.javaHolder) } else { - fatalError("Enum value INSCRIPTIONAL_PAHLAVI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value INSCRIPTIONAL_PAHLAVI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_TURKIC: if let OLD_TURKIC = classObj.OLD_TURKIC { self.init(javaHolder: OLD_TURKIC.javaHolder) } else { - fatalError("Enum value OLD_TURKIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_TURKIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BRAHMI: if let BRAHMI = classObj.BRAHMI { self.init(javaHolder: BRAHMI.javaHolder) } else { - fatalError("Enum value BRAHMI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BRAHMI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KAITHI: if let KAITHI = classObj.KAITHI { self.init(javaHolder: KAITHI.javaHolder) } else { - fatalError("Enum value KAITHI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KAITHI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MEROITIC_HIEROGLYPHS: if let MEROITIC_HIEROGLYPHS = classObj.MEROITIC_HIEROGLYPHS { self.init(javaHolder: MEROITIC_HIEROGLYPHS.javaHolder) } else { - fatalError("Enum value MEROITIC_HIEROGLYPHS was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MEROITIC_HIEROGLYPHS was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MEROITIC_CURSIVE: if let MEROITIC_CURSIVE = classObj.MEROITIC_CURSIVE { self.init(javaHolder: MEROITIC_CURSIVE.javaHolder) } else { - fatalError("Enum value MEROITIC_CURSIVE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MEROITIC_CURSIVE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SORA_SOMPENG: if let SORA_SOMPENG = classObj.SORA_SOMPENG { self.init(javaHolder: SORA_SOMPENG.javaHolder) } else { - fatalError("Enum value SORA_SOMPENG was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SORA_SOMPENG was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CHAKMA: if let CHAKMA = classObj.CHAKMA { self.init(javaHolder: CHAKMA.javaHolder) } else { - fatalError("Enum value CHAKMA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CHAKMA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SHARADA: if let SHARADA = classObj.SHARADA { self.init(javaHolder: SHARADA.javaHolder) } else { - fatalError("Enum value SHARADA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SHARADA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TAKRI: if let TAKRI = classObj.TAKRI { self.init(javaHolder: TAKRI.javaHolder) } else { - fatalError("Enum value TAKRI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TAKRI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MIAO: if let MIAO = classObj.MIAO { self.init(javaHolder: MIAO.javaHolder) } else { - fatalError("Enum value MIAO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MIAO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CAUCASIAN_ALBANIAN: if let CAUCASIAN_ALBANIAN = classObj.CAUCASIAN_ALBANIAN { self.init(javaHolder: CAUCASIAN_ALBANIAN.javaHolder) } else { - fatalError("Enum value CAUCASIAN_ALBANIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CAUCASIAN_ALBANIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BASSA_VAH: if let BASSA_VAH = classObj.BASSA_VAH { self.init(javaHolder: BASSA_VAH.javaHolder) } else { - fatalError("Enum value BASSA_VAH was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BASSA_VAH was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .DUPLOYAN: if let DUPLOYAN = classObj.DUPLOYAN { self.init(javaHolder: DUPLOYAN.javaHolder) } else { - fatalError("Enum value DUPLOYAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value DUPLOYAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ELBASAN: if let ELBASAN = classObj.ELBASAN { self.init(javaHolder: ELBASAN.javaHolder) } else { - fatalError("Enum value ELBASAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ELBASAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GRANTHA: if let GRANTHA = classObj.GRANTHA { self.init(javaHolder: GRANTHA.javaHolder) } else { - fatalError("Enum value GRANTHA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GRANTHA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .PAHAWH_HMONG: if let PAHAWH_HMONG = classObj.PAHAWH_HMONG { self.init(javaHolder: PAHAWH_HMONG.javaHolder) } else { - fatalError("Enum value PAHAWH_HMONG was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value PAHAWH_HMONG was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KHOJKI: if let KHOJKI = classObj.KHOJKI { self.init(javaHolder: KHOJKI.javaHolder) } else { - fatalError("Enum value KHOJKI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KHOJKI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .LINEAR_A: if let LINEAR_A = classObj.LINEAR_A { self.init(javaHolder: LINEAR_A.javaHolder) } else { - fatalError("Enum value LINEAR_A was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value LINEAR_A was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MAHAJANI: if let MAHAJANI = classObj.MAHAJANI { self.init(javaHolder: MAHAJANI.javaHolder) } else { - fatalError("Enum value MAHAJANI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MAHAJANI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MANICHAEAN: if let MANICHAEAN = classObj.MANICHAEAN { self.init(javaHolder: MANICHAEAN.javaHolder) } else { - fatalError("Enum value MANICHAEAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MANICHAEAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MENDE_KIKAKUI: if let MENDE_KIKAKUI = classObj.MENDE_KIKAKUI { self.init(javaHolder: MENDE_KIKAKUI.javaHolder) } else { - fatalError("Enum value MENDE_KIKAKUI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MENDE_KIKAKUI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MODI: if let MODI = classObj.MODI { self.init(javaHolder: MODI.javaHolder) } else { - fatalError("Enum value MODI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MODI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MRO: if let MRO = classObj.MRO { self.init(javaHolder: MRO.javaHolder) } else { - fatalError("Enum value MRO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MRO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_NORTH_ARABIAN: if let OLD_NORTH_ARABIAN = classObj.OLD_NORTH_ARABIAN { self.init(javaHolder: OLD_NORTH_ARABIAN.javaHolder) } else { - fatalError("Enum value OLD_NORTH_ARABIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_NORTH_ARABIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NABATAEAN: if let NABATAEAN = classObj.NABATAEAN { self.init(javaHolder: NABATAEAN.javaHolder) } else { - fatalError("Enum value NABATAEAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NABATAEAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .PALMYRENE: if let PALMYRENE = classObj.PALMYRENE { self.init(javaHolder: PALMYRENE.javaHolder) } else { - fatalError("Enum value PALMYRENE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value PALMYRENE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .PAU_CIN_HAU: if let PAU_CIN_HAU = classObj.PAU_CIN_HAU { self.init(javaHolder: PAU_CIN_HAU.javaHolder) } else { - fatalError("Enum value PAU_CIN_HAU was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value PAU_CIN_HAU was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_PERMIC: if let OLD_PERMIC = classObj.OLD_PERMIC { self.init(javaHolder: OLD_PERMIC.javaHolder) } else { - fatalError("Enum value OLD_PERMIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_PERMIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .PSALTER_PAHLAVI: if let PSALTER_PAHLAVI = classObj.PSALTER_PAHLAVI { self.init(javaHolder: PSALTER_PAHLAVI.javaHolder) } else { - fatalError("Enum value PSALTER_PAHLAVI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value PSALTER_PAHLAVI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SIDDHAM: if let SIDDHAM = classObj.SIDDHAM { self.init(javaHolder: SIDDHAM.javaHolder) } else { - fatalError("Enum value SIDDHAM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SIDDHAM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KHUDAWADI: if let KHUDAWADI = classObj.KHUDAWADI { self.init(javaHolder: KHUDAWADI.javaHolder) } else { - fatalError("Enum value KHUDAWADI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KHUDAWADI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TIRHUTA: if let TIRHUTA = classObj.TIRHUTA { self.init(javaHolder: TIRHUTA.javaHolder) } else { - fatalError("Enum value TIRHUTA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TIRHUTA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .WARANG_CITI: if let WARANG_CITI = classObj.WARANG_CITI { self.init(javaHolder: WARANG_CITI.javaHolder) } else { - fatalError("Enum value WARANG_CITI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value WARANG_CITI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .AHOM: if let AHOM = classObj.AHOM { self.init(javaHolder: AHOM.javaHolder) } else { - fatalError("Enum value AHOM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value AHOM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ANATOLIAN_HIEROGLYPHS: if let ANATOLIAN_HIEROGLYPHS = classObj.ANATOLIAN_HIEROGLYPHS { self.init(javaHolder: ANATOLIAN_HIEROGLYPHS.javaHolder) } else { - fatalError("Enum value ANATOLIAN_HIEROGLYPHS was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ANATOLIAN_HIEROGLYPHS was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .HATRAN: if let HATRAN = classObj.HATRAN { self.init(javaHolder: HATRAN.javaHolder) } else { - fatalError("Enum value HATRAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value HATRAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MULTANI: if let MULTANI = classObj.MULTANI { self.init(javaHolder: MULTANI.javaHolder) } else { - fatalError("Enum value MULTANI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MULTANI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_HUNGARIAN: if let OLD_HUNGARIAN = classObj.OLD_HUNGARIAN { self.init(javaHolder: OLD_HUNGARIAN.javaHolder) } else { - fatalError("Enum value OLD_HUNGARIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_HUNGARIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SIGNWRITING: if let SIGNWRITING = classObj.SIGNWRITING { self.init(javaHolder: SIGNWRITING.javaHolder) } else { - fatalError("Enum value SIGNWRITING was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SIGNWRITING was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ADLAM: if let ADLAM = classObj.ADLAM { self.init(javaHolder: ADLAM.javaHolder) } else { - fatalError("Enum value ADLAM was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ADLAM was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .BHAIKSUKI: if let BHAIKSUKI = classObj.BHAIKSUKI { self.init(javaHolder: BHAIKSUKI.javaHolder) } else { - fatalError("Enum value BHAIKSUKI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value BHAIKSUKI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MARCHEN: if let MARCHEN = classObj.MARCHEN { self.init(javaHolder: MARCHEN.javaHolder) } else { - fatalError("Enum value MARCHEN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MARCHEN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NEWA: if let NEWA = classObj.NEWA { self.init(javaHolder: NEWA.javaHolder) } else { - fatalError("Enum value NEWA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NEWA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OSAGE: if let OSAGE = classObj.OSAGE { self.init(javaHolder: OSAGE.javaHolder) } else { - fatalError("Enum value OSAGE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OSAGE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TANGUT: if let TANGUT = classObj.TANGUT { self.init(javaHolder: TANGUT.javaHolder) } else { - fatalError("Enum value TANGUT was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TANGUT was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MASARAM_GONDI: if let MASARAM_GONDI = classObj.MASARAM_GONDI { self.init(javaHolder: MASARAM_GONDI.javaHolder) } else { - fatalError("Enum value MASARAM_GONDI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MASARAM_GONDI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NUSHU: if let NUSHU = classObj.NUSHU { self.init(javaHolder: NUSHU.javaHolder) } else { - fatalError("Enum value NUSHU was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NUSHU was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SOYOMBO: if let SOYOMBO = classObj.SOYOMBO { self.init(javaHolder: SOYOMBO.javaHolder) } else { - fatalError("Enum value SOYOMBO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SOYOMBO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ZANABAZAR_SQUARE: if let ZANABAZAR_SQUARE = classObj.ZANABAZAR_SQUARE { self.init(javaHolder: ZANABAZAR_SQUARE.javaHolder) } else { - fatalError("Enum value ZANABAZAR_SQUARE was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ZANABAZAR_SQUARE was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .HANIFI_ROHINGYA: if let HANIFI_ROHINGYA = classObj.HANIFI_ROHINGYA { self.init(javaHolder: HANIFI_ROHINGYA.javaHolder) } else { - fatalError("Enum value HANIFI_ROHINGYA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value HANIFI_ROHINGYA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_SOGDIAN: if let OLD_SOGDIAN = classObj.OLD_SOGDIAN { self.init(javaHolder: OLD_SOGDIAN.javaHolder) } else { - fatalError("Enum value OLD_SOGDIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_SOGDIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .SOGDIAN: if let SOGDIAN = classObj.SOGDIAN { self.init(javaHolder: SOGDIAN.javaHolder) } else { - fatalError("Enum value SOGDIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value SOGDIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .DOGRA: if let DOGRA = classObj.DOGRA { self.init(javaHolder: DOGRA.javaHolder) } else { - fatalError("Enum value DOGRA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value DOGRA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .GUNJALA_GONDI: if let GUNJALA_GONDI = classObj.GUNJALA_GONDI { self.init(javaHolder: GUNJALA_GONDI.javaHolder) } else { - fatalError("Enum value GUNJALA_GONDI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value GUNJALA_GONDI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MAKASAR: if let MAKASAR = classObj.MAKASAR { self.init(javaHolder: MAKASAR.javaHolder) } else { - fatalError("Enum value MAKASAR was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MAKASAR was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .MEDEFAIDRIN: if let MEDEFAIDRIN = classObj.MEDEFAIDRIN { self.init(javaHolder: MEDEFAIDRIN.javaHolder) } else { - fatalError("Enum value MEDEFAIDRIN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value MEDEFAIDRIN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .ELYMAIC: if let ELYMAIC = classObj.ELYMAIC { self.init(javaHolder: ELYMAIC.javaHolder) } else { - fatalError("Enum value ELYMAIC was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value ELYMAIC was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NANDINAGARI: if let NANDINAGARI = classObj.NANDINAGARI { self.init(javaHolder: NANDINAGARI.javaHolder) } else { - fatalError("Enum value NANDINAGARI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NANDINAGARI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NYIAKENG_PUACHUE_HMONG: if let NYIAKENG_PUACHUE_HMONG = classObj.NYIAKENG_PUACHUE_HMONG { self.init(javaHolder: NYIAKENG_PUACHUE_HMONG.javaHolder) } else { - fatalError("Enum value NYIAKENG_PUACHUE_HMONG was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NYIAKENG_PUACHUE_HMONG was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .WANCHO: if let WANCHO = classObj.WANCHO { self.init(javaHolder: WANCHO.javaHolder) } else { - fatalError("Enum value WANCHO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value WANCHO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .YEZIDI: if let YEZIDI = classObj.YEZIDI { self.init(javaHolder: YEZIDI.javaHolder) } else { - fatalError("Enum value YEZIDI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value YEZIDI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CHORASMIAN: if let CHORASMIAN = classObj.CHORASMIAN { self.init(javaHolder: CHORASMIAN.javaHolder) } else { - fatalError("Enum value CHORASMIAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CHORASMIAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .DIVES_AKURU: if let DIVES_AKURU = classObj.DIVES_AKURU { self.init(javaHolder: DIVES_AKURU.javaHolder) } else { - fatalError("Enum value DIVES_AKURU was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value DIVES_AKURU was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KHITAN_SMALL_SCRIPT: if let KHITAN_SMALL_SCRIPT = classObj.KHITAN_SMALL_SCRIPT { self.init(javaHolder: KHITAN_SMALL_SCRIPT.javaHolder) } else { - fatalError("Enum value KHITAN_SMALL_SCRIPT was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KHITAN_SMALL_SCRIPT was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .VITHKUQI: if let VITHKUQI = classObj.VITHKUQI { self.init(javaHolder: VITHKUQI.javaHolder) } else { - fatalError("Enum value VITHKUQI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value VITHKUQI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .OLD_UYGHUR: if let OLD_UYGHUR = classObj.OLD_UYGHUR { self.init(javaHolder: OLD_UYGHUR.javaHolder) } else { - fatalError("Enum value OLD_UYGHUR was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value OLD_UYGHUR was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .CYPRO_MINOAN: if let CYPRO_MINOAN = classObj.CYPRO_MINOAN { self.init(javaHolder: CYPRO_MINOAN.javaHolder) } else { - fatalError("Enum value CYPRO_MINOAN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value CYPRO_MINOAN was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TANGSA: if let TANGSA = classObj.TANGSA { self.init(javaHolder: TANGSA.javaHolder) } else { - fatalError("Enum value TANGSA was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TANGSA was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .TOTO: if let TOTO = classObj.TOTO { self.init(javaHolder: TOTO.javaHolder) } else { - fatalError("Enum value TOTO was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value TOTO was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .KAWI: if let KAWI = classObj.KAWI { self.init(javaHolder: KAWI.javaHolder) } else { - fatalError("Enum value KAWI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value KAWI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .NAG_MUNDARI: if let NAG_MUNDARI = classObj.NAG_MUNDARI { self.init(javaHolder: NAG_MUNDARI.javaHolder) } else { - fatalError("Enum value NAG_MUNDARI was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value NAG_MUNDARI was unexpectedly nil, please re-run swift-java on the most updated Java class") } case .UNKNOWN: if let UNKNOWN = classObj.UNKNOWN { self.init(javaHolder: UNKNOWN.javaHolder) } else { - fatalError("Enum value UNKNOWN was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value UNKNOWN was unexpectedly nil, please re-run swift-java on the most updated Java class") } } } diff --git a/Sources/SwiftJava/generated/JavaClass.swift b/Sources/SwiftJava/generated/JavaClass.swift index 0f1af1cd0..a1147e3c2 100644 --- a/Sources/SwiftJava/generated/JavaClass.swift +++ b/Sources/SwiftJava/generated/JavaClass.swift @@ -1,7 +1,7 @@ // Auto-generated by Java-to-Swift wrapper generator. import CSwiftJavaJNI -@JavaClass("java.lang.Class") +@JavaClass("java.lang.Class", implements: JavaReflectType.self) open class JavaClass: JavaObject { @JavaMethod open func getName() -> String @@ -52,13 +52,16 @@ open class JavaClass: JavaObject { open func isRecord() -> Bool @JavaMethod - open func getClassLoader() -> JavaClassLoader! + open func isSealed() -> Bool @JavaMethod - open func newInstance() throws -> JavaObject! + open func getInterfaces() -> [JavaClass?] @JavaMethod - open func getInterfaces() -> [JavaClass?] + open func getClassLoader() -> JavaClassLoader! + + @JavaMethod + open func newInstance() throws -> T! @JavaMethod open func isMemberClass() -> Bool @@ -70,7 +73,7 @@ open class JavaClass: JavaObject { open func isAnonymousClass() -> Bool @JavaMethod - open func getEnclosingClass() throws -> JavaClass! + open func getEnclosingClass() -> JavaClass! @JavaMethod open func arrayType() -> JavaClass! @@ -81,6 +84,9 @@ open class JavaClass: JavaObject { @JavaMethod open func getCanonicalName() -> String + @JavaMethod + open func getDeclaredClasses() -> [JavaClass?] + @JavaMethod open func getPackageName() -> String @@ -102,11 +108,17 @@ open class JavaClass: JavaObject { @JavaMethod open func isSynthetic() -> Bool + @JavaMethod + open func getGenericSuperclass() -> JavaReflectType! + + @JavaMethod + open func getGenericInterfaces() -> [JavaReflectType?] + @JavaMethod open func getSigners() -> [JavaObject?] @JavaMethod - open func getDeclaringClass() throws -> JavaClass! + open func getDeclaringClass() -> JavaClass! @JavaMethod open func getTypeName() -> String @@ -114,9 +126,6 @@ open class JavaClass: JavaObject { @JavaMethod open func getClasses() -> [JavaClass?] - @JavaMethod - open func getDeclaredClasses() throws -> [JavaClass?] - @JavaMethod open func getEnumConstants() -> [JavaObject?] @@ -128,16 +137,13 @@ open class JavaClass: JavaObject { @JavaMethod open func getNestMembers() -> [JavaClass?] - - @JavaMethod - open func isSealed() -> Bool } extension JavaClass { @JavaStaticMethod - public func forName(_ arg0: String, _ arg1: Bool, _ arg2: JavaClassLoader?) throws -> JavaClass! where ObjectType == JavaClass + public func forName(_ arg0: String) throws -> JavaClass! where ObjectType == JavaClass @JavaStaticMethod - public func forName(_ arg0: String) throws -> JavaClass! where ObjectType == JavaClass + public func forName(_ arg0: String, _ arg1: Bool, _ arg2: JavaClassLoader?) throws -> JavaClass! where ObjectType == JavaClass @JavaStaticMethod public func forPrimitiveName(_ arg0: String) -> JavaClass! where ObjectType == JavaClass diff --git a/Sources/SwiftJava/generated/JavaClassLoader.swift b/Sources/SwiftJava/generated/JavaClassLoader.swift index 349cba8db..0cd64aa15 100644 --- a/Sources/SwiftJava/generated/JavaClassLoader.swift +++ b/Sources/SwiftJava/generated/JavaClassLoader.swift @@ -7,10 +7,10 @@ open class JavaClassLoader: JavaObject { open func getName() -> String @JavaMethod - open func loadClass(_ arg0: String, _ arg1: Bool) throws -> JavaClass! + open func loadClass(_ arg0: String) throws -> JavaClass! @JavaMethod - open func loadClass(_ arg0: String) throws -> JavaClass! + open func loadClass(_ arg0: String, _ arg1: Bool) throws -> JavaClass! @JavaMethod open func setSigners(_ arg0: JavaClass?, _ arg1: [JavaObject?]) @@ -22,10 +22,10 @@ open class JavaClassLoader: JavaObject { open func findLoadedClass(_ arg0: String) -> JavaClass! @JavaMethod - open func findClass(_ arg0: String) throws -> JavaClass! + open func findClass(_ arg0: String, _ arg1: String) -> JavaClass! @JavaMethod - open func findClass(_ arg0: String, _ arg1: String) -> JavaClass! + open func findClass(_ arg0: String) throws -> JavaClass! @JavaMethod open func resolveClass(_ arg0: JavaClass?) diff --git a/Sources/SwiftJava/generated/JavaEnum.swift b/Sources/SwiftJava/generated/JavaEnum.swift new file mode 100644 index 000000000..2b8e102c9 --- /dev/null +++ b/Sources/SwiftJava/generated/JavaEnum.swift @@ -0,0 +1,11 @@ +// // Auto-generated by Java-to-Swift wrapper generator. +// import CSwiftJavaJNI + +// @JavaClass("java.lang.Enum") +// open class JavaEnum: JavaObject { +// @JavaMethod +// public func name() -> String + +// @JavaMethod +// public func ordinal() -> Int32 +// } diff --git a/Sources/SwiftJava/generated/JavaInteger.swift b/Sources/SwiftJava/generated/JavaInteger.swift index 948000373..df57ba665 100644 --- a/Sources/SwiftJava/generated/JavaInteger.swift +++ b/Sources/SwiftJava/generated/JavaInteger.swift @@ -3,7 +3,6 @@ import CSwiftJavaJNI @JavaClass("java.lang.Integer") open class JavaInteger: JavaNumber { - @JavaMethod @_nonoverride public convenience init(_ arg0: Int32, environment: JNIEnvironment? = nil) @@ -121,10 +120,10 @@ extension JavaClass { public func valueOf(_ arg0: String) throws -> JavaInteger! @JavaStaticMethod - public func valueOf(_ arg0: String, _ arg1: Int32) throws -> JavaInteger! + public func valueOf(_ arg0: Int32) -> JavaInteger! @JavaStaticMethod - public func valueOf(_ arg0: Int32) -> JavaInteger! + public func valueOf(_ arg0: String, _ arg1: Int32) throws -> JavaInteger! @JavaStaticMethod public func toHexString(_ arg0: Int32) -> String @@ -133,31 +132,37 @@ extension JavaClass { public func decode(_ arg0: String) throws -> JavaInteger! @JavaStaticMethod - public func parseInt(_ arg0: String) throws -> Int32 + public func parseInt(_ arg0: CharSequence?, _ arg1: Int32, _ arg2: Int32, _ arg3: Int32) throws -> Int32 @JavaStaticMethod public func parseInt(_ arg0: String, _ arg1: Int32) throws -> Int32 @JavaStaticMethod - public func toUnsignedLong(_ arg0: Int32) -> Int64 + public func parseInt(_ arg0: String) throws -> Int32 @JavaStaticMethod - public func sum(_ arg0: Int32, _ arg1: Int32) -> Int32 + public func highestOneBit(_ arg0: Int32) -> Int32 @JavaStaticMethod - public func toUnsignedString(_ arg0: Int32, _ arg1: Int32) -> String + public func toUnsignedLong(_ arg0: Int32) -> Int64 + + @JavaStaticMethod + public func sum(_ arg0: Int32, _ arg1: Int32) -> Int32 @JavaStaticMethod public func toUnsignedString(_ arg0: Int32) -> String @JavaStaticMethod - public func parseUnsignedInt(_ arg0: String) throws -> Int32 + public func toUnsignedString(_ arg0: Int32, _ arg1: Int32) -> String @JavaStaticMethod public func parseUnsignedInt(_ arg0: String, _ arg1: Int32) throws -> Int32 @JavaStaticMethod - public func getInteger(_ arg0: String, _ arg1: JavaInteger?) -> JavaInteger! + public func parseUnsignedInt(_ arg0: String) throws -> Int32 + + @JavaStaticMethod + public func parseUnsignedInt(_ arg0: CharSequence?, _ arg1: Int32, _ arg2: Int32, _ arg3: Int32) throws -> Int32 @JavaStaticMethod public func getInteger(_ arg0: String, _ arg1: Int32) -> JavaInteger! @@ -166,13 +171,13 @@ extension JavaClass { public func getInteger(_ arg0: String) -> JavaInteger! @JavaStaticMethod - public func toOctalString(_ arg0: Int32) -> String + public func getInteger(_ arg0: String, _ arg1: JavaInteger?) -> JavaInteger! @JavaStaticMethod - public func toBinaryString(_ arg0: Int32) -> String + public func toOctalString(_ arg0: Int32) -> String @JavaStaticMethod - public func highestOneBit(_ arg0: Int32) -> Int32 + public func toBinaryString(_ arg0: Int32) -> String @JavaStaticMethod public func lowestOneBit(_ arg0: Int32) -> Int32 diff --git a/Sources/SwiftJava/generated/JavaLong.swift b/Sources/SwiftJava/generated/JavaLong.swift index a986e9ef8..7ea8fc09e 100644 --- a/Sources/SwiftJava/generated/JavaLong.swift +++ b/Sources/SwiftJava/generated/JavaLong.swift @@ -126,10 +126,10 @@ extension JavaClass { public func compare(_ arg0: Int64, _ arg1: Int64) -> Int32 @JavaStaticMethod - public func valueOf(_ arg0: String) throws -> JavaLong! + public func valueOf(_ arg0: Int64) -> JavaLong! @JavaStaticMethod - public func valueOf(_ arg0: Int64) -> JavaLong! + public func valueOf(_ arg0: String) throws -> JavaLong! @JavaStaticMethod public func valueOf(_ arg0: String, _ arg1: Int32) throws -> JavaLong! @@ -140,6 +140,9 @@ extension JavaClass { @JavaStaticMethod public func decode(_ arg0: String) throws -> JavaLong! + @JavaStaticMethod + public func highestOneBit(_ arg0: Int64) -> Int64 + @JavaStaticMethod public func sum(_ arg0: Int64, _ arg1: Int64) -> Int64 @@ -155,9 +158,6 @@ extension JavaClass { @JavaStaticMethod public func toBinaryString(_ arg0: Int64) -> String - @JavaStaticMethod - public func highestOneBit(_ arg0: Int64) -> Int64 - @JavaStaticMethod public func lowestOneBit(_ arg0: Int64) -> Int64 @@ -168,20 +168,20 @@ extension JavaClass { public func rotateRight(_ arg0: Int64, _ arg1: Int32) -> Int64 @JavaStaticMethod - public func parseLong(_ arg0: CharSequence?, _ arg1: Int32, _ arg2: Int32, _ arg3: Int32) throws -> Int64 + public func parseLong(_ arg0: String) throws -> Int64 @JavaStaticMethod public func parseLong(_ arg0: String, _ arg1: Int32) throws -> Int64 @JavaStaticMethod - public func parseLong(_ arg0: String) throws -> Int64 + public func parseLong(_ arg0: CharSequence?, _ arg1: Int32, _ arg2: Int32, _ arg3: Int32) throws -> Int64 @JavaStaticMethod - public func parseUnsignedLong(_ arg0: CharSequence?, _ arg1: Int32, _ arg2: Int32, _ arg3: Int32) throws -> Int64 + public func parseUnsignedLong(_ arg0: String) throws -> Int64 @JavaStaticMethod public func parseUnsignedLong(_ arg0: String, _ arg1: Int32) throws -> Int64 @JavaStaticMethod - public func parseUnsignedLong(_ arg0: String) throws -> Int64 + public func parseUnsignedLong(_ arg0: CharSequence?, _ arg1: Int32, _ arg2: Int32, _ arg3: Int32) throws -> Int64 } diff --git a/Sources/SwiftJava/generated/JavaOptional.swift b/Sources/SwiftJava/generated/JavaOptional.swift index 08cc764a1..5f10005fc 100644 --- a/Sources/SwiftJava/generated/JavaOptional.swift +++ b/Sources/SwiftJava/generated/JavaOptional.swift @@ -4,7 +4,7 @@ import CSwiftJavaJNI @JavaClass("java.util.Optional") open class JavaOptional: JavaObject { @JavaMethod - open func get() -> JavaObject! + open func get() -> JavaObject! // FIXME: Currently we do generate -> T https://github.com/swiftlang/swift-java/issues/439 @JavaMethod open override func equals(_ arg0: JavaObject?) -> Bool diff --git a/Sources/SwiftJava/generated/JavaReflectArray.swift b/Sources/SwiftJava/generated/JavaReflectArray.swift new file mode 100644 index 000000000..4cae1202d --- /dev/null +++ b/Sources/SwiftJava/generated/JavaReflectArray.swift @@ -0,0 +1,71 @@ +// Auto-generated by Java-to-Swift wrapper generator. +import CSwiftJavaJNI + +@JavaClass("java.lang.reflect.Array") +open class JavaReflectArray: JavaObject { + +} +extension JavaClass { + @JavaStaticMethod + public func get(_ arg0: JavaObject?, _ arg1: Int32) throws -> JavaObject! + + @JavaStaticMethod + public func getLength(_ arg0: JavaObject?) throws -> Int32 + + @JavaStaticMethod + public func getBoolean(_ arg0: JavaObject?, _ arg1: Int32) throws -> Bool + + @JavaStaticMethod + public func getByte(_ arg0: JavaObject?, _ arg1: Int32) throws -> Int8 + + @JavaStaticMethod + public func getShort(_ arg0: JavaObject?, _ arg1: Int32) throws -> Int16 + + @JavaStaticMethod + public func getChar(_ arg0: JavaObject?, _ arg1: Int32) throws -> UInt16 + + @JavaStaticMethod + public func getInt(_ arg0: JavaObject?, _ arg1: Int32) throws -> Int32 + + @JavaStaticMethod + public func getLong(_ arg0: JavaObject?, _ arg1: Int32) throws -> Int64 + + @JavaStaticMethod + public func getFloat(_ arg0: JavaObject?, _ arg1: Int32) throws -> Float + + @JavaStaticMethod + public func getDouble(_ arg0: JavaObject?, _ arg1: Int32) throws -> Double + + @JavaStaticMethod + public func newInstance(_ arg0: JavaClass?, _ arg1: Int32) throws -> JavaObject! + + @JavaStaticMethod + public func newInstance(_ arg0: JavaClass?, _ arg1: [Int32]) throws -> JavaObject! + + @JavaStaticMethod + public func set(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: JavaObject?) throws + + @JavaStaticMethod + public func setBoolean(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: Bool) throws + + @JavaStaticMethod + public func setByte(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: Int8) throws + + @JavaStaticMethod + public func setChar(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: UInt16) throws + + @JavaStaticMethod + public func setShort(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: Int16) throws + + @JavaStaticMethod + public func setInt(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: Int32) throws + + @JavaStaticMethod + public func setLong(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: Int64) throws + + @JavaStaticMethod + public func setFloat(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: Float) throws + + @JavaStaticMethod + public func setDouble(_ arg0: JavaObject?, _ arg1: Int32, _ arg2: Double) throws +} diff --git a/Sources/SwiftJava/generated/JavaReflectParameterizedType.swift b/Sources/SwiftJava/generated/JavaReflectParameterizedType.swift new file mode 100644 index 000000000..a08016445 --- /dev/null +++ b/Sources/SwiftJava/generated/JavaReflectParameterizedType.swift @@ -0,0 +1,17 @@ +// Auto-generated by Java-to-Swift wrapper generator. +import CSwiftJavaJNI + +@JavaInterface("java.lang.reflect.ParameterizedType", extends: JavaReflectType.self) +public struct JavaReflectParameterizedType { + @JavaMethod + public func getOwnerType() -> JavaReflectType! + + @JavaMethod + public func getRawType() -> JavaReflectType! + + @JavaMethod + public func getActualTypeArguments() -> [JavaReflectType?] + + @JavaMethod + public func getTypeName() -> String +} diff --git a/Sources/SwiftJava/generated/JavaReflectType.swift b/Sources/SwiftJava/generated/JavaReflectType.swift new file mode 100644 index 000000000..fdf3b5726 --- /dev/null +++ b/Sources/SwiftJava/generated/JavaReflectType.swift @@ -0,0 +1,8 @@ +// Auto-generated by Java-to-Swift wrapper generator. +import CSwiftJavaJNI + +@JavaInterface("java.lang.reflect.Type") +public struct JavaReflectType { + @JavaMethod + public func getTypeName() -> String +} diff --git a/Sources/SwiftJava/swift-java.config b/Sources/SwiftJava/swift-java.config index b45671a7a..d07ff1620 100644 --- a/Sources/SwiftJava/swift-java.config +++ b/Sources/SwiftJava/swift-java.config @@ -5,6 +5,8 @@ "java.lang.Byte" : "JavaByte", "java.lang.Character" : "JavaCharacter", "java.lang.Class" : "JavaClass", + "java.lang.reflect.Type" : "JavaReflectType", + "java.lang.reflect.ParameterizedType" : "JavaReflectParameterizedType", "java.lang.ClassLoader" : "JavaClassLoader", "java.lang.Double" : "JavaDouble", "java.lang.Error" : "JavaError", diff --git a/Sources/SwiftJavaConfigurationShared/Configuration.swift b/Sources/SwiftJavaConfigurationShared/Configuration.swift index c9d0cedfc..0fb5494f1 100644 --- a/Sources/SwiftJavaConfigurationShared/Configuration.swift +++ b/Sources/SwiftJavaConfigurationShared/Configuration.swift @@ -65,7 +65,7 @@ public struct Configuration: Codable { asyncFuncMode ?? .default } - // ==== java 2 swift --------------------------------------------------------- + // ==== wrap-java --------------------------------------------------------- /// The Java class path that should be passed along to the swift-java tool. public var classpath: String? = nil @@ -85,6 +85,12 @@ public struct Configuration: Codable { // Generate class files suitable for the specified Java SE release. public var targetCompatibility: JavaVersion? + /// Filter input Java types by their package prefix if set. + public var filterInclude: [String]? + + /// Exclude input Java types by their package prefix or exact match. + public var filterExclude: [String]? + // ==== dependencies --------------------------------------------------------- // Java dependencies we need to fetch for this target. diff --git a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift index 9a05a2867..837d5e2f2 100644 --- a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift +++ b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift @@ -72,7 +72,7 @@ extension SwiftJava.ConfigureCommand { let jvm = try self.makeJVM(classpathEntries: classpathEntries) - try emitConfiguration(classpath: self.commonJVMOptions.classpath, environment: jvm.environment()) + try emitConfiguration(classpathEntries: classpathEntries, environment: jvm.environment()) } /// Get base configuration, depending on if we are to 'amend' or 'overwrite' the existing configuration. @@ -97,32 +97,41 @@ extension SwiftJava.ConfigureCommand { // TODO: make this perhaps "emit type mappings" mutating func emitConfiguration( - classpath: [String], + classpathEntries: [String], environment: JNIEnvironment ) throws { - if let filterJavaPackage = self.commonJVMOptions.filterJavaPackage { - print("[java-swift][debug] Generate Java->Swift type mappings. Active filter: \(filterJavaPackage)") + var log = Self.log + log.logLevel = .init(rawValue: self.logLevel.rawValue)! + + log.info("Run: emit configuration...") + var (amendExistingConfig, configuration) = try getBaseConfigurationForWrite() + + if !self.commonOptions.filterInclude.isEmpty { + log.debug("Generate Java->Swift type mappings. Active include filters: \(self.commonOptions.filterInclude)") + } else if let filters = configuration.filterInclude, !filters.isEmpty { + // take the package filter from the configuration file + self.commonOptions.filterInclude = filters + } else { + log.debug("Generate Java->Swift type mappings. No package include filter applied.") } - print("[java-swift][debug] Classpath: \(classpath)") + log.debug("Classpath: \(classpathEntries)") - if classpath.isEmpty { - print("[java-swift][warning] Classpath is empty!") + if classpathEntries.isEmpty { + log.warning("Classpath is empty!") } // Get a fresh or existing configuration we'll amend - var (amendExistingConfig, configuration) = try getBaseConfigurationForWrite() if amendExistingConfig { - print("[swift-java] Amend existing swift-java.config file...") + log.info("Amend existing swift-java.config file...") } - configuration.classpath = classpath.joined(separator: ":") // TODO: is this correct? + configuration.classpath = classpathEntries.joined(separator: ":") // TODO: is this correct? // Import types from all the classpath entries; // Note that we use the package level filtering, so users have some control over what gets imported. - let classpathEntries = classpath.split(separator: ":").map(String.init) for entry in classpathEntries { guard fileOrDirectoryExists(at: entry) else { // We only log specific jars missing, as paths may be empty directories that won't hurt not existing. - print("[debug][swift-java] Classpath entry does not exist: \(entry)") + log.debug("Classpath entry does not exist: \(entry)") continue } @@ -135,9 +144,9 @@ extension SwiftJava.ConfigureCommand { environment: environment ) } else if FileManager.default.fileExists(atPath: entry) { - print("[warning][swift-java] Currently unable handle directory classpath entries for config generation! Skipping: \(entry)") + log.warning("Currently unable handle directory classpath entries for config generation! Skipping: \(entry)") } else { - print("[warning][swift-java] Classpath entry does not exist, skipping: \(entry)") + log.warning("Classpath entry does not exist, skipping: \(entry)") } } @@ -158,6 +167,8 @@ extension SwiftJava.ConfigureCommand { forJar jarFile: JarFile, environment: JNIEnvironment ) throws { + let log = Self.log + for entry in jarFile.entries()! { // We only look at class files in the Jar file. guard entry.getName().hasSuffix(".class") else { @@ -183,9 +194,8 @@ extension SwiftJava.ConfigureCommand { let javaCanonicalName = String(entry.getName().replacing("/", with: ".") .dropLast(".class".count)) - if let filterJavaPackage = self.commonJVMOptions.filterJavaPackage, - !javaCanonicalName.hasPrefix(filterJavaPackage) { - // Skip classes which don't match our expected prefix + guard SwiftJava.shouldImport(javaCanonicalName: javaCanonicalName, commonOptions: self.commonOptions) else { + log.info("Skip importing class: \(javaCanonicalName) due to include/exclude filters") continue } @@ -195,7 +205,17 @@ extension SwiftJava.ConfigureCommand { continue } - configuration.classes?[javaCanonicalName] = + if configuration.classes == nil { + configuration.classes = [:] + } + + if let configuredSwiftName = configuration.classes![javaCanonicalName] { + log.info("Java type '\(javaCanonicalName)' already configured as '\(configuredSwiftName)' Swift type.") + } else { + log.info("Configure Java type '\(javaCanonicalName)' as '\(javaCanonicalName.defaultSwiftNameForJavaClass.bold)' Swift type.") + } + + configuration.classes![javaCanonicalName] = javaCanonicalName.defaultSwiftNameForJavaClass } } diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index b5942d2a4..b5c3a7bb9 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -86,9 +86,7 @@ extension SwiftJava { extension SwiftJava.JExtractCommand { func runSwiftJavaCommand(config: inout Configuration) async throws { - if let javaPackage { - config.javaPackage = javaPackage - } + configure(&config.javaPackage, overrideWith: self.javaPackage) configure(&config.mode, overrideWith: self.mode) config.swiftModule = self.effectiveSwiftModule config.outputJavaDirectory = outputJava @@ -136,12 +134,6 @@ extension SwiftJava.JExtractCommand { } } } - - func configure(_ setting: inout T?, overrideWith value: T?) { - if let value { - setting = value - } - } } struct IncompatibleModeError: Error { diff --git a/Sources/SwiftJavaTool/Commands/ResolveCommand.swift b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift index 58c64690b..7cc4c477d 100644 --- a/Sources/SwiftJavaTool/Commands/ResolveCommand.swift +++ b/Sources/SwiftJavaTool/Commands/ResolveCommand.swift @@ -104,7 +104,10 @@ extension SwiftJava.ResolveCommand { let deps = dependencies.map { $0.descriptionGradleStyle } print("[debug][swift-java] Resolve and fetch dependencies for: \(deps)") - let dependenciesClasspath = await resolveDependencies(dependencies: dependencies) + let workDir = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) + .appendingPathComponent(".build") + + let dependenciesClasspath = await resolveDependencies(workDir: workDir, dependencies: dependencies) let classpathEntries = dependenciesClasspath.split(separator: ":") print("[info][swift-java] Resolved classpath for \(deps.count) dependencies of '\(swiftModule)', classpath entries: \(classpathEntries.count), ", terminator: "") @@ -122,10 +125,15 @@ extension SwiftJava.ResolveCommand { /// /// - Parameter dependencies: maven-style dependencies to resolve /// - Returns: Colon-separated classpath - func resolveDependencies(dependencies: [JavaDependencyDescriptor]) async -> String { - let workDir = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) - .appendingPathComponent(".build") - let resolverDir = try! createTemporaryDirectory(in: workDir) + func resolveDependencies(workDir: URL, dependencies: [JavaDependencyDescriptor]) async -> String { + print("Create directory: \(workDir.absoluteString)") + + let resolverDir: URL + do { + resolverDir = try createTemporaryDirectory(in: workDir) + } catch { + fatalError("Unable to create temp directory at: \(workDir.absoluteString)! \(error)") + } defer { try? FileManager.default.removeItem(at: resolverDir) } @@ -162,7 +170,9 @@ extension SwiftJava.ResolveCommand { } else { let suggestDisablingSandbox = "It may be that the Sandbox has prevented dependency fetching, please re-run with '--disable-sandbox'." fatalError("Gradle output had no SWIFT_JAVA_CLASSPATH! \(suggestDisablingSandbox). \n" + - "Output was:<<<\(outString)>>>; Err was:<<<\(errString ?? "")>>>") + "Command was: \(CommandLine.arguments.joined(separator: " ").bold)\n" + + "Output was: <<<\(outString)>>>;\n" + + "Err was: <<<\(errString)>>>") } return String(classpathOutput.dropFirst(SwiftJavaClasspathPrefix.count)) diff --git a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift index 43362edbd..ae985e772 100644 --- a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift +++ b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift @@ -66,6 +66,9 @@ extension SwiftJava { extension SwiftJava.WrapJavaCommand { mutating func runSwiftJavaCommand(config: inout Configuration) async throws { + configure(&config.filterInclude, append: self.commonOptions.filterInclude) + configure(&config.filterExclude, append: self.commonOptions.filterExclude) + // Get base classpath configuration for this target and configuration var classpathSearchDirs = [self.effectiveSwiftModuleURL] if let cacheDir = self.cacheDirectory { @@ -113,16 +116,19 @@ extension SwiftJava.WrapJavaCommand { extension SwiftJava.WrapJavaCommand { mutating func generateWrappers( config: Configuration, - // classpathEntries: [String], dependentConfigs: [(String, Configuration)], environment: JNIEnvironment ) throws { let translator = JavaTranslator( + config: config, swiftModuleName: effectiveSwiftModule, environment: environment, translateAsClass: true ) + log.info("Active include filters: \(config.filterInclude ?? [])") + log.info("Active exclude filters: \(config.filterExclude ?? [])") + // Keep track of all of the Java classes that will have // Swift-native implementations. translator.swiftNativeImplementations = Set(swiftNativeImplementation) @@ -139,12 +145,30 @@ extension SwiftJava.WrapJavaCommand { translator.addConfiguration(config, forSwiftModule: effectiveSwiftModule) // Load all of the explicitly-requested classes. - let classLoader = try JavaClass(environment: environment) + let classLoader = try! JavaClass(environment: environment) .getSystemClassLoader()! var javaClasses: [JavaClass] = [] - for (javaClassName, _) in config.classes ?? [:] { + eachClass: for (javaClassName, _) in config.classes ?? [:] { + + // If we have an inclusive filter, import only types from it + for include in config.filterInclude ?? [] { + guard javaClassName.starts(with: include) else { + log.info("Skip Java type: \(javaClassName) (does not match filter)") + continue + } + } + // If we have an exclude filter, check for it as well + for exclude in config.filterExclude ?? [] { + if javaClassName.starts(with: exclude) { + log.info("Skip Java type: \(javaClassName) (does match exclude filter: \(exclude))") + continue eachClass + } + } + + log.info("Wrapping java type: \(javaClassName)") + guard let javaClass = try classLoader.loadClass(javaClassName) else { - print("warning: could not find Java class '\(javaClassName)'") + log.warning("Could not load Java class '\(javaClassName)', skipping.") continue } @@ -178,8 +202,23 @@ extension SwiftJava.WrapJavaCommand { return nil } + // If we have an inclusive filter, import only types from it + for include in config.filterInclude ?? [] { + guard javaClassName.starts(with: include) else { + log.info("Skip Java type: \(javaClassName) (does not match filter)") + return nil + } + } + // If we have an exclude filter, check for it as well + for exclude in config.filterExclude ?? [] { + if javaClassName.starts(with: exclude) { + log.info("Skip Java type: \(javaClassName) (does match exclude filter: \(exclude))") + return nil + } + } + // If this class has been explicitly mentioned, we're done. - if translator.translatedClasses[javaClassName] != nil { + guard translator.translatedClasses[javaClassName] == nil else { return nil } @@ -187,9 +226,8 @@ extension SwiftJava.WrapJavaCommand { let swiftUnqualifiedName = javaClassName.javaClassNameToCanonicalName .defaultSwiftNameForJavaClass - let swiftName = "\(currentSwiftName).\(swiftUnqualifiedName)" - translator.translatedClasses[javaClassName] = (swiftName, nil) + translator.translatedClasses[javaClassName] = SwiftTypeName(module: nil, name: swiftName) return nestedClass } diff --git a/Sources/SwiftJavaTool/CommonOptions.swift b/Sources/SwiftJavaTool/CommonOptions.swift index e8aee62df..ebdfdbea0 100644 --- a/Sources/SwiftJavaTool/CommonOptions.swift +++ b/Sources/SwiftJavaTool/CommonOptions.swift @@ -30,6 +30,18 @@ protocol HasCommonOptions { var commonOptions: SwiftJava.CommonOptions { get set } } extension HasCommonOptions { + func configure(_ setting: inout T?, overrideWith value: T?) { + if let value { + setting = value + } + } + + func configure(_ setting: inout [T]?, append value: [T]?) { + if let value { + setting?.append(contentsOf: value) + } + } + var outputDirectory: String? { self.commonOptions.outputDirectory } @@ -45,6 +57,12 @@ extension SwiftJava { @Option(name: .shortAndLong, help: "Configure the level of logs that should be printed") var logLevel: JExtractSwiftLib.Logger.Level = .info + + @Option(name: .long, help: "While scanning a classpath, inspect ONLY types included in these packages") + var filterInclude: [String] = [] + + @Option(name: .long, help: "While scanning a classpath, skip types which match the filter prefix") + var filterExclude: [String] = [] } struct CommonJVMOptions: ParsableArguments { @@ -53,9 +71,6 @@ extension SwiftJava { help: "Class search path of directories and zip/jar files from which Java classes can be loaded." ) var classpath: [String] = [] - - @Option(name: .shortAndLong, help: "While scanning a classpath, inspect only types included in this package") - var filterJavaPackage: String? = nil } } diff --git a/Sources/SwiftJavaTool/ExcludedJDKTypes.swift b/Sources/SwiftJavaTool/ExcludedJDKTypes.swift new file mode 100644 index 000000000..1d24022f5 --- /dev/null +++ b/Sources/SwiftJavaTool/ExcludedJDKTypes.swift @@ -0,0 +1,42 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +extension SwiftJava { + /// Some types we cannot handle importing, so we hardcode skipping them. + public static let ExcludedJDKTypes: Set = [ + "java.lang.Enum", + "java.lang.Enum$EnumDesc", + ] + + static func shouldImport(javaCanonicalName: String, commonOptions: SwiftJava.CommonOptions) -> Bool { + if SwiftJava.ExcludedJDKTypes.contains(javaCanonicalName) { + return false + } + + for include in commonOptions.filterInclude { + guard javaCanonicalName.hasPrefix(include) else { + // Skip classes which don't match our expected prefix + return false + } + } + + for exclude in commonOptions.filterExclude { + if javaCanonicalName.hasPrefix(exclude) { + return false + } + } + + return true + } +} \ No newline at end of file diff --git a/Sources/SwiftJavaTool/Java/JavaClassLoader.swift b/Sources/SwiftJavaTool/Java/JavaClassLoader.swift index d465a2062..276508855 100644 --- a/Sources/SwiftJavaTool/Java/JavaClassLoader.swift +++ b/Sources/SwiftJavaTool/Java/JavaClassLoader.swift @@ -17,6 +17,7 @@ import SwiftJavaShared import CSwiftJavaJNI import SwiftJava +// FIXME: do we need this here or can we rely on the generated one? @JavaClass("java.lang.ClassLoader") public struct ClassLoader { @JavaMethod diff --git a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift index a7c4d76f7..795639e5a 100644 --- a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift @@ -15,6 +15,8 @@ import SwiftJava import JavaLangReflect import SwiftSyntax +import SwiftJavaConfigurationShared +import Logging /// Utility type that translates a single Java class into its corresponding /// Swift type and any additional helper types or functions. @@ -23,6 +25,10 @@ struct JavaClassTranslator { /// needed for translation. let translator: JavaTranslator + var log: Logger { + translator.log + } + /// The Java class (or interface) being translated. let javaClass: JavaClass @@ -47,7 +53,7 @@ struct JavaClassTranslator { let effectiveJavaSuperclass: JavaClass? /// The Swift name of the superclass. - let swiftSuperclass: String? + let swiftSuperclass: SwiftJavaParameterizedType? /// The Swift names of the interfaces that this class implements. let swiftInterfaces: [String] @@ -98,16 +104,16 @@ struct JavaClassTranslator { } /// The generic parameter clause for the Swift version of the Java class. - var genericParameterClause: String { + var genericParameters: [String] { if javaTypeParameters.isEmpty { - return "" + return [] } let genericParameters = javaTypeParameters.map { param in "\(param.getName()): AnyJavaObject" } - return "<\(genericParameters.joined(separator: ", "))>" + return genericParameters } /// Prepare translation for the given Java class (or interface). @@ -126,23 +132,38 @@ struct JavaClassTranslator { self.javaTypeParameters = javaClass.getTypeParameters().compactMap { $0 } self.nestedClasses = translator.nestedClasses[fullName] ?? [] - // Superclass. + // Superclass, incl parameter types (if any) if !javaClass.isInterface() { var javaSuperclass = javaClass.getSuperclass() - var swiftSuperclass: String? = nil + var javaGenericSuperclass: JavaReflectType? = javaClass.getGenericSuperclass() + var swiftSuperclassName: String? = nil + var swiftSuperclassTypeArgs: [String] = [] while let javaSuperclassNonOpt = javaSuperclass { do { - swiftSuperclass = try translator.getSwiftTypeName(javaSuperclassNonOpt, preferValueTypes: false).swiftName + swiftSuperclassName = try translator.getSwiftTypeName(javaSuperclassNonOpt, preferValueTypes: false).swiftName + if let javaGenericSuperclass = javaGenericSuperclass?.as(JavaReflectParameterizedType.self) { + for typeArg in javaGenericSuperclass.getActualTypeArguments() { + let javaTypeArgName = typeArg?.getTypeName() ?? "" + if let swiftTypeArgName = self.translator.translatedClasses[javaTypeArgName] { + swiftSuperclassTypeArgs.append(swiftTypeArgName.qualifiedName) + } else { + swiftSuperclassTypeArgs.append("/* MISSING MAPPING FOR */ \(javaTypeArgName)") + } + } + } break } catch { translator.logUntranslated("Unable to translate '\(fullName)' superclass: \(error)") } javaSuperclass = javaSuperclassNonOpt.getSuperclass() + javaGenericSuperclass = javaClass.getGenericSuperclass() } self.effectiveJavaSuperclass = javaSuperclass - self.swiftSuperclass = swiftSuperclass + self.swiftSuperclass = SwiftJavaParameterizedType( + name: swiftSuperclassName, + typeArguments: swiftSuperclassTypeArgs) } else { self.effectiveJavaSuperclass = nil self.swiftSuperclass = nil @@ -192,8 +213,9 @@ struct JavaClassTranslator { for method in methods { guard let method else { continue } - // Only look at public and protected methods here. - guard method.isPublic || method.isProtected else { continue } + guard shouldExtract(method: method) else { + continue + } // Skip any methods that are expected to be implemented in Swift. We will // visit them in the second pass, over the *declared* methods, because @@ -226,6 +248,20 @@ struct JavaClassTranslator { /// MARK: Collection of Java class members. extension JavaClassTranslator { + + /// Determines whether a method should be extracted for translation. + /// Only look at public and protected methods here. + private func shouldExtract(method: Method) -> Bool { + switch self.translator.config.effectiveMinimumInputAccessLevelMode { + case .internal: + return method.isPublic || method.isProtected || method.isPackage + case .package: + return method.isPublic || method.isProtected || method.isPackage + case .public: + return method.isPublic || method.isProtected + } + } + /// Add a field to the appropriate lists(s) for later translation. private mutating func addField(_ field: Field) { // Static fields go into a separate list. @@ -325,13 +361,25 @@ extension JavaClassTranslator { // Compute the "extends" clause for the superclass (of the struct // formulation) or the inheritance clause (for the class // formulation). - let extends: String + let extendsClause: String let inheritanceClause: String if translateAsClass { - extends = "" - inheritanceClause = swiftSuperclass.map { ": \($0)" } ?? "" + extendsClause = "" + inheritanceClause = + if let swiftSuperclass, swiftSuperclass.typeArguments.isEmpty { + ": \(swiftSuperclass.name)" + } else if let swiftSuperclass { + ": \(swiftSuperclass.name)<\(swiftSuperclass.typeArguments.joined(separator: ", "))>" + } else { + "" + } } else { - extends = swiftSuperclass.map { ", extends: \($0).self" } ?? "" + extendsClause = + if let swiftSuperclass { + ", extends: \(swiftSuperclass.render()).self" + } else { + "" + } inheritanceClause = "" } @@ -344,12 +392,19 @@ extension JavaClassTranslator { interfacesStr = ", \(prefix): \(swiftInterfaces.map { "\($0).self" }.joined(separator: ", "))" } + let genericParameterClause = + if genericParameters.isEmpty { + "" + } else { + "<\(genericParameters.joined(separator: ", "))>" + } + // Emit the struct declaration describing the java class. let classOrInterface: String = isInterface ? "JavaInterface" : "JavaClass"; let introducer = translateAsClass ? "open class" : "public struct" var classDecl: DeclSyntax = """ - @\(raw: classOrInterface)(\(literal: javaClass.getName())\(raw: extends)\(raw: interfacesStr)) + @\(raw: classOrInterface)(\(literal: javaClass.getName())\(raw: extendsClause)\(raw: interfacesStr)) \(raw: introducer) \(raw: swiftInnermostTypeName)\(raw: genericParameterClause)\(raw: inheritanceClause) { \(raw: members.map { $0.description }.joined(separator: "\n\n")) } @@ -396,7 +451,7 @@ extension JavaClassTranslator { } let genericArgumentClause = "<\(genericParameterNames.joined(separator: ", "))>" - staticMemberWhereClause = " where ObjectType == \(swiftTypeName)\(genericArgumentClause)" + staticMemberWhereClause = " where ObjectType == \(swiftTypeName)\(genericArgumentClause)" // FIXME: move the 'where ...' part into the render bit } else { staticMemberWhereClause = "" } @@ -418,7 +473,7 @@ extension JavaClassTranslator { do { return try renderMethod( method, implementedInSwift: /*FIXME:*/false, - genericParameterClause: genericParameterClause, + genericParameters: genericParameters, whereClause: staticMemberWhereClause ) } catch { @@ -435,7 +490,7 @@ extension JavaClassTranslator { // Specify the specialization arguments when needed. let extSpecialization: String - if genericParameterClause.isEmpty { + if genericParameters.isEmpty { extSpecialization = "<\(swiftTypeName)>" } else { extSpecialization = "" @@ -523,31 +578,76 @@ extension JavaClassTranslator { let accessModifier = javaConstructor.isPublic ? "public " : "" let convenienceModifier = translateAsClass ? "convenience " : "" let nonoverrideAttribute = translateAsClass ? "@_nonoverride " : "" + + // FIXME: handle generics in constructors return """ @JavaMethod \(raw: nonoverrideAttribute)\(raw: accessModifier)\(raw: convenienceModifier)init(\(raw: parametersStr))\(raw: throwsStr) """ } + func genericParameterIsUsedInSignature(_ typeParam: TypeVariable, in method: Method) -> Bool { + // --- Return type + // Is the return type exactly the type param + // FIXME: make this equals based? + if method.getGenericReturnType().getTypeName() == typeParam.getTypeName() { + return true + } + + if let parameterizedReturnType = method.getGenericReturnType().as(ParameterizedType.self) { + for actualTypeParam in parameterizedReturnType.getActualTypeArguments() { + guard let actualTypeParam else { continue } + if actualTypeParam.isEqualTo(typeParam.as(Type.self)) { + return true + } + } + } + + return false + } + /// Translates the given Java method into a Swift declaration. package func renderMethod( _ javaMethod: Method, implementedInSwift: Bool, - genericParameterClause: String = "", + genericParameters: [String] = [], whereClause: String = "" ) throws -> DeclSyntax { - // Map the parameters. - let parameters = try translateJavaParameters(javaMethod.getParameters()) + // Map the generic params on the method. + var allGenericParameters = genericParameters + let typeParameters = javaMethod.getTypeParameters() + if typeParameters.contains(where: {$0 != nil }) { + allGenericParameters += typeParameters.compactMap { typeParam in + guard let typeParam else { return nil } + guard genericParameterIsUsedInSignature(typeParam, in: javaMethod) else { + return nil + } + return "\(typeParam.getTypeName()): AnyJavaObject" + } + } + let genericParameterClauseStr = + if allGenericParameters.isEmpty { + "" + } else { + "<\(allGenericParameters.joined(separator: ", "))>" + } + // Map the parameters. + let parameters = try translateJavaParameters(javaMethod) let parametersStr = parameters.map { $0.description }.joined(separator: ", ") // Map the result type. let resultTypeStr: String - let resultType = try translator.getSwiftTypeNameAsString( - javaMethod.getGenericReturnType()!, - preferValueTypes: true, + let resultType = try translator.getSwiftReturnTypeNameAsString( + method: javaMethod, + preferValueTypes: true, outerOptional: .implicitlyUnwrappedOptional ) + // let resultType = try translator.getSwiftTypeNameAsString( + // javaMethod.getGenericReturnType()!, + // preferValueTypes: true, + // outerOptional: .implicitlyUnwrappedOptional + // ) // FIXME: cleanup the checking here if resultType != "Void" && resultType != "Swift.Void" { @@ -556,6 +656,7 @@ extension JavaClassTranslator { resultTypeStr = "" } + // --- Handle other effects let throwsStr = javaMethod.throwsCheckedException ? "throws" : "" let swiftMethodName = javaMethod.getName().escapedSwiftName let methodAttribute: AttributeSyntax = implementedInSwift @@ -581,23 +682,24 @@ extension JavaClassTranslator { let resultOptional: String = resultType.optionalWrappedType() ?? resultType let baseBody: ExprSyntax = "\(raw: javaMethod.throwsCheckedException ? "try " : "")\(raw: swiftMethodName)(\(raw: parameters.map(\.passedArg).joined(separator: ", ")))" - let body: ExprSyntax = if let optionalType = resultType.optionalWrappedType() { - "Optional(javaOptional: \(baseBody))" - } else { - baseBody - } + let body: ExprSyntax = + if resultType.optionalWrappedType() != nil { + "Optional(javaOptional: \(baseBody))" + } else { + baseBody + } return """ - \(methodAttribute)\(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)\(raw: genericParameterClause)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause) + \(methodAttribute)\(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)\(raw: genericParameterClauseStr)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause) - \(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)Optional\(raw: genericParameterClause)(\(raw: parameters.map(\.clause.description).joined(separator: ", ")))\(raw: throwsStr) -> \(raw: resultOptional)\(raw: whereClause) { + \(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)Optional\(raw: genericParameterClauseStr)(\(raw: parameters.map(\.clause.description).joined(separator: ", ")))\(raw: throwsStr) -> \(raw: resultOptional)\(raw: whereClause) { \(body) } """ } else { return """ - \(methodAttribute)\(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)\(raw: genericParameterClause)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause) + \(methodAttribute)\(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)\(raw: genericParameterClauseStr)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause) """ } } @@ -688,7 +790,7 @@ extension JavaClassTranslator { ? "self.init(javaHolder: \($0.getName()).javaHolder)" : "self = \($0.getName())") } else { - fatalError("Enum value \($0.getName()) was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value \($0.getName()) was unexpectedly nil, please re-run swift-java on the most updated Java class") } """ }.joined(separator: "\n")) @@ -700,7 +802,30 @@ extension JavaClassTranslator { } // Translate a Java parameter list into Swift parameters. - private func translateJavaParameters(_ parameters: [Parameter?]) throws -> [FunctionParameterSyntax] { + private func translateJavaParameters( + _ javaMethod: JavaLangReflect.Method + ) throws -> [FunctionParameterSyntax] { + let parameters: [Parameter?] = javaMethod.getParameters() + + return try parameters.compactMap { javaParameter in + guard let javaParameter else { return nil } + + let typeName = try translator.getSwiftTypeNameAsString( + method: javaMethod, + javaParameter.getParameterizedType()!, + preferValueTypes: true, + outerOptional: .optional + ) + let paramName = javaParameter.getName() + return "_ \(raw: paramName): \(raw: typeName)" + } + } + + // Translate a Java parameter list into Swift parameters. + @available(*, deprecated, message: "Prefer the method based version") // FIXME: constructors are not well handled + private func translateJavaParameters( + _ parameters: [Parameter?] + ) throws -> [FunctionParameterSyntax] { return try parameters.compactMap { javaParameter in guard let javaParameter else { return nil } @@ -778,9 +903,7 @@ extension JavaClassTranslator { continue } - // Ignore non-public, non-protected methods because they would not - // have been render into the Swift superclass. - if !overriddenMethod.isPublic && !overriddenMethod.isProtected { + guard shouldExtract(method: overriddenMethod) else { continue } @@ -791,6 +914,7 @@ extension JavaClassTranslator { return true } } catch { + // FIXME: logging } } @@ -815,114 +939,3 @@ extension [Type?] { } } -extension Type { - /// Adjust the given type to use its bounds, mirroring what we do in - /// mapping Java types into Swift. - func adjustToJavaBounds(adjusted: inout Bool) -> Type { - if let typeVariable = self.as(TypeVariable.self), - typeVariable.getBounds().count == 1, - let bound = typeVariable.getBounds()[0] { - adjusted = true - return bound - } - - if let wildcardType = self.as(WildcardType.self), - wildcardType.getUpperBounds().count == 1, - let bound = wildcardType.getUpperBounds()[0] { - adjusted = true - return bound - } - - return self - } - - /// Determine whether this type is equivalent to or a subtype of the other - /// type. - func isEqualTo(_ other: Type) -> Bool { - // First, adjust types to their bounds, if we need to. - var anyAdjusted: Bool = false - let adjustedSelf = self.adjustToJavaBounds(adjusted: &anyAdjusted) - let adjustedOther = other.adjustToJavaBounds(adjusted: &anyAdjusted) - if anyAdjusted { - return adjustedSelf.isEqualTo(adjustedOther) - } - - // If both are classes, check for equivalence. - if let selfClass = self.as(JavaClass.self), - let otherClass = other.as(JavaClass.self) { - return selfClass.equals(otherClass.as(JavaObject.self)) - } - - // If both are arrays, check that their component types are equivalent. - if let selfArray = self.as(GenericArrayType.self), - let otherArray = other.as(GenericArrayType.self) { - return selfArray.getGenericComponentType().isEqualTo(otherArray.getGenericComponentType()) - } - - // If both are parameterized types, check their raw type and type - // arguments for equivalence. - if let selfParameterizedType = self.as(ParameterizedType.self), - let otherParameterizedType = other.as(ParameterizedType.self) { - if !selfParameterizedType.getRawType().isEqualTo(otherParameterizedType.getRawType()) { - return false - } - - return selfParameterizedType.getActualTypeArguments() - .allTypesEqual(otherParameterizedType.getActualTypeArguments()) - } - - // If both are type variables, compare their bounds. - // FIXME: This is a hack. - if let selfTypeVariable = self.as(TypeVariable.self), - let otherTypeVariable = other.as(TypeVariable.self) { - return selfTypeVariable.getBounds().allTypesEqual(otherTypeVariable.getBounds()) - } - - // If both are wildcards, compare their upper and lower bounds. - if let selfWildcard = self.as(WildcardType.self), - let otherWildcard = other.as(WildcardType.self) { - return selfWildcard.getUpperBounds().allTypesEqual(otherWildcard.getUpperBounds()) - && selfWildcard.getLowerBounds().allTypesEqual(otherWildcard.getLowerBounds()) - } - - return false - } - - /// Determine whether this type is equivalent to or a subtype of the - /// other type. - func isEqualToOrSubtypeOf(_ other: Type) -> Bool { - // First, adjust types to their bounds, if we need to. - var anyAdjusted: Bool = false - let adjustedSelf = self.adjustToJavaBounds(adjusted: &anyAdjusted) - let adjustedOther = other.adjustToJavaBounds(adjusted: &anyAdjusted) - if anyAdjusted { - return adjustedSelf.isEqualToOrSubtypeOf(adjustedOther) - } - - if isEqualTo(other) { - return true - } - - // If both are classes, check for subclassing. - if let selfClass = self.as(JavaClass.self), - let otherClass = other.as(JavaClass.self) { - // If either is a Java array, then this cannot be a subtype relationship - // in Swift. - if selfClass.isArray() || otherClass.isArray() { - return false - } - - return selfClass.isSubclass(of: otherClass) - } - - // Anything object-like is a subclass of java.lang.Object - if let otherClass = other.as(JavaClass.self), - otherClass.getName() == "java.lang.Object" { - if self.is(GenericArrayType.self) || self.is(ParameterizedType.self) || - self.is(WildcardType.self) || self.is(TypeVariable.self) { - return true - } - } - return false - } -} diff --git a/Sources/SwiftJavaToolLib/JavaGenericsSupport.swift b/Sources/SwiftJavaToolLib/JavaGenericsSupport.swift new file mode 100644 index 000000000..7c7819629 --- /dev/null +++ b/Sources/SwiftJavaToolLib/JavaGenericsSupport.swift @@ -0,0 +1,89 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JavaLangReflect +import JavaTypes +import SwiftBasicFormat +import SwiftJava +import SwiftJavaConfigurationShared +import SwiftSyntax +import SwiftSyntaxBuilder + +struct GenericJavaTypeOriginInfo { + enum GenericSource { + /// The source of the generic + case `class`([Type]) + case method + } + + var source: GenericSource + var type: Type +} + +/// if the type (that is used by the Method) is generic, return if the use originates from the method, or a surrounding class. +func getGenericJavaTypeOriginInfo(_ type: Type?, from method: Method) -> [GenericJavaTypeOriginInfo] { + guard let type else { + return [] + } + + guard isGenericJavaType(type) else { + return [] // it's not a generic type, no "origin" of the use to detect + } + + var methodTypeVars = method.getTypeParameters() + + // TODO: also handle nested classes here... + var classTypeVars = method.getDeclaringClass().getTypeParameters() + + var usedTypeVars: [TypeVariable] = [] + + return [] +} + +func isGenericJavaType(_ type: Type?) -> Bool { + guard let type else { + return false + } + + // Check if it's a type variable (e.g., T, E, etc.) + if type.as(TypeVariable.self) != nil { + return true + } + + // Check if it's a parameterized type (e.g., List, Map) + if let paramType = type.as(ParameterizedType.self) { + let typeArgs: [Type?] = paramType.getActualTypeArguments() + + // Check if any of the type arguments are generic + for typeArg in typeArgs { + guard let typeArg else { continue } + if isGenericJavaType(typeArg) { + return true + } + } + } + + // Check if it's a generic array type (e.g., T[], List[]) + if let arrayType = type.as(GenericArrayType.self) { + let componentType = arrayType.getGenericComponentType() + return isGenericJavaType(componentType) + } + + // Check if it's a wildcard type (e.g., ? extends Number, ? super String) + if type.as(WildcardType.self) != nil { + return true + } + + return false +} diff --git a/Sources/SwiftJavaToolLib/JavaHomeSupport.swift b/Sources/SwiftJavaToolLib/JavaHomeSupport.swift new file mode 100644 index 000000000..cd18f7a82 --- /dev/null +++ b/Sources/SwiftJavaToolLib/JavaHomeSupport.swift @@ -0,0 +1,92 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Foundation + +/// Detected JAVA_HOME for this process. +package let javaHome: String = findJavaHome() + +// Note: the JAVA_HOME environment variable must be set to point to where +// Java is installed, e.g., +// Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home. +public func findJavaHome() -> String { + if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] { + return home + } + + // This is a workaround for envs (some IDEs) which have trouble with + // picking up env variables during the build process + let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home" + if let home = try? String(contentsOfFile: path, encoding: .utf8) { + if let lastChar = home.last, lastChar.isNewline { + return String(home.dropLast()) + } + + return home + } + + if let home = getJavaHomeFromLibexecJavaHome(), + !home.isEmpty + { + return home + } + + if ProcessInfo.processInfo.environment["SPI_PROCESSING"] == "1" + && ProcessInfo.processInfo.environment["SPI_BUILD"] == nil + { + // Just ignore that we're missing a JAVA_HOME when building in Swift Package Index during general processing where no Java is needed. However, do _not_ suppress the error during SPI's compatibility build stage where Java is required. + return "" + } + fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.") +} + +/// On MacOS we can use the java_home tool as a fallback if we can't find JAVA_HOME environment variable. +public func getJavaHomeFromLibexecJavaHome() -> String? { + let task = Process() + task.executableURL = URL(fileURLWithPath: "/usr/libexec/java_home") + + // Check if the executable exists before trying to run it + guard FileManager.default.fileExists(atPath: task.executableURL!.path) else { + print("/usr/libexec/java_home does not exist") + return nil + } + + let pipe = Pipe() + task.standardOutput = pipe + task.standardError = pipe // Redirect standard error to the same pipe for simplicity + + do { + try task.run() + task.waitUntilExit() + + let data = pipe.fileHandleForReading.readDataToEndOfFile() + let output = String(data: data, encoding: .utf8)?.trimmingCharacters( + in: .whitespacesAndNewlines) + + if task.terminationStatus == 0 { + return output + } else { + print("java_home terminated with status: \(task.terminationStatus)") + // Optionally, log the error output for debugging + if let errorOutput = String( + data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) { + print("Error output: \(errorOutput)") + } + return nil + } + } catch { + print("Error running java_home: \(error)") + return nil + } +} diff --git a/Sources/SwiftJavaToolLib/JavaParameterizedType.swift b/Sources/SwiftJavaToolLib/JavaParameterizedType.swift new file mode 100644 index 000000000..a1b2c6b60 --- /dev/null +++ b/Sources/SwiftJavaToolLib/JavaParameterizedType.swift @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// E.g. `Another` +struct SwiftJavaParameterizedType { + let name: String + let typeArguments: [String] + + init?(name: String?, typeArguments: [String]) { + guard let name else { + return nil + } + + self.name = name + self.typeArguments = typeArguments + } + + func render() -> String { + if typeArguments.isEmpty { + name + } else { + "\(name)<\(typeArguments.joined(separator: ", "))>" + } + } + + +} diff --git a/Sources/SwiftJavaToolLib/JavaTranslator+Configuration.swift b/Sources/SwiftJavaToolLib/JavaTranslator+Configuration.swift index 4fc9feb04..655c67650 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator+Configuration.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator+Configuration.swift @@ -16,11 +16,6 @@ import Foundation import SwiftJavaConfigurationShared extension JavaTranslator { -// /// Read a configuration file from the given URL. -// package static func readConfiguration(from url: URL) throws -> Configuration { -// let contents = try Data(contentsOf: url) -// return try JSONDecoder().decode(Configuration.self, from: contents) -// } /// Load the configuration file with the given name to populate the known set of /// translated Java classes. @@ -30,10 +25,7 @@ extension JavaTranslator { } for (javaClassName, swiftName) in classes { - translatedClasses[javaClassName] = ( - swiftType: swiftName, - swiftModule: swiftModule - ) + translatedClasses[javaClassName] = SwiftTypeName(module: swiftModule, name: swiftName) } } } diff --git a/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift b/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift index 9d4d00ca4..3a6468d57 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift @@ -12,24 +12,45 @@ // //===----------------------------------------------------------------------===// -package extension JavaTranslator { - struct SwiftTypeName: Hashable { - let swiftType: String - let swiftModule: String? +public typealias JavaFullyQualifiedTypeName = String - package init(swiftType: String, swiftModule: String?) { - self.swiftType = swiftType - self.swiftModule = swiftModule +package struct SwiftTypeName: Hashable, CustomStringConvertible { + package let swiftModule: String? + package let swiftType: String + + package init(module: String?, name: String) { + self.swiftModule = module + self.swiftType = name + } + + package var qualifiedName: String { + if let swiftModule { + "\(swiftModule).\(swiftType)" + } else { + "\(swiftType)" + } + } + + package var description: String { + if let swiftModule { + "`\(swiftModule)/\(swiftType)`" + } else { + "`\(swiftType)`" } } +} + +package extension JavaTranslator { struct SwiftToJavaMapping: Equatable { let swiftType: SwiftTypeName - let javaTypes: [String] + let javaTypes: [JavaFullyQualifiedTypeName] - package init(swiftType: SwiftTypeName, javaTypes: [String]) { + package init(swiftType: SwiftTypeName, javaTypes: [JavaFullyQualifiedTypeName]) { self.swiftType = swiftType self.javaTypes = javaTypes + precondition(!javaTypes.contains("com.google.protobuf.AbstractMessage$Builder"), + "\(swiftType) mapped as \(javaTypes)\n\(CommandLine.arguments.joined(separator: " "))") // XXX } } @@ -48,15 +69,23 @@ package extension JavaTranslator { private func mappingDescription(mapping: SwiftToJavaMapping) -> String { let javaTypes = mapping.javaTypes.map { "'\($0)'" }.joined(separator: ", ") - return "Swift Type: '\(mapping.swiftType.swiftModule ?? "")'.'\(mapping.swiftType.swiftType)', Java Types: \(javaTypes)" + return "Swift module: '\(mapping.swiftType.swiftModule ?? "")', type: '\(mapping.swiftType.swiftType)', Java Types: \(javaTypes)" } } func validateClassConfiguration() throws(ValidationError) { + // for a in translatedClasses { + // print("MAPPING = \(a.key) -> \(a.value.swiftModule?.escapedSwiftName ?? "").\(a.value.swiftType.escapedSwiftName)") + // } + // Group all classes by swift name - let groupedDictionary: [SwiftTypeName: [(String, (String, String?))]] = Dictionary(grouping: translatedClasses, by: { SwiftTypeName(swiftType: $0.value.swiftType, swiftModule: $0.value.swiftModule) }) + let groupedDictionary: [SwiftTypeName: [(JavaFullyQualifiedTypeName, SwiftTypeName)]] = Dictionary(grouping: translatedClasses, by: { + // SwiftTypeName(swiftType: $0.value.swiftType, swiftModule: $0.value.swiftModule) + $0.value + }) // Find all that are mapped to multiple names - let multipleClassesMappedToSameName: [SwiftTypeName: [(String, (String, String?))]] = groupedDictionary.filter { (key: SwiftTypeName, value: [(String, (String, String?))]) in + let multipleClassesMappedToSameName: [SwiftTypeName: [(JavaFullyQualifiedTypeName, SwiftTypeName)]] = groupedDictionary.filter { + (key: SwiftTypeName, value: [(JavaFullyQualifiedTypeName, SwiftTypeName)]) in value.count > 1 } diff --git a/Sources/SwiftJavaToolLib/JavaTranslator.swift b/Sources/SwiftJavaToolLib/JavaTranslator.swift index 7cb8a7eeb..71e011794 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator.swift @@ -19,10 +19,16 @@ import SwiftBasicFormat import SwiftSyntax import SwiftJavaConfigurationShared import SwiftSyntaxBuilder +import Foundation +import Logging /// Utility that translates Java classes into Swift source code to access /// those Java classes. package class JavaTranslator { + let config: Configuration + + let log: Logger + /// The name of the Swift module that we are translating into. let swiftModuleName: String @@ -35,7 +41,10 @@ package class JavaTranslator { /// A mapping from the name of each known Java class to the corresponding /// Swift type name and its Swift module. - package var translatedClasses: [String: (swiftType: String, swiftModule: String?)] = [:] + package var translatedClasses: [JavaFullyQualifiedTypeName: SwiftTypeName] = [ + "java.lang.Object": SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "byte[]": SwiftTypeName(module: nil, name: "[UInt8]") + ] /// A mapping from the name of each known Java class with the Swift value type /// (and its module) to which it is mapped. @@ -44,8 +53,8 @@ package class JavaTranslator { /// `translatedClasses` should map to a representation of the Java class (i.e., /// an AnyJavaObject-conforming type) whereas the entry here should map to /// a value type. - package let translatedToValueTypes: [String: (swiftType: String, swiftModule: String) ] = [ - "java.lang.String": ("String", "SwiftJava"), + package let translatedToValueTypes: [JavaFullyQualifiedTypeName: SwiftTypeName] = [ + "java.lang.String": SwiftTypeName(module: "SwiftJava", name: "String"), ] /// The set of Swift modules that need to be imported to make the generated @@ -64,15 +73,21 @@ package class JavaTranslator { package var nestedClasses: [String: [JavaClass]] = [:] package init( + config: Configuration, swiftModuleName: String, environment: JNIEnvironment, translateAsClass: Bool = false, format: BasicFormat = JavaTranslator.defaultFormat ) { + self.config = config self.swiftModuleName = swiftModuleName self.environment = environment self.translateAsClass = translateAsClass self.format = format + + var l = Logger(label: "swift-java") + l.logLevel = .init(rawValue: (config.logLevel ?? .info).rawValue)! + self.log = l } /// Clear out any per-file state when we want to start a new file. @@ -112,8 +127,28 @@ extension JavaTranslator { // MARK: Type translation extension JavaTranslator { + + func getSwiftReturnTypeNameAsString( + method: JavaLangReflect.Method, + preferValueTypes: Bool, + outerOptional: OptionalKind + ) throws -> String { + // let returnType = method.getReturnType() + let genericReturnType = method.getGenericReturnType() + + // Special handle the case when the return type is the generic type of the method: ` T foo()` + + // if isGenericJavaType(genericReturnType) { + // print("[swift] generic method! \(method.getDeclaringClass().getName()).\(method.getName())") + // getGenericJavaTypeOriginInfo(genericReturnType, from: method) + // } + + return try getSwiftTypeNameAsString(method: method, genericReturnType!, preferValueTypes: preferValueTypes, outerOptional: outerOptional) + } + /// Turn a Java type into a string. func getSwiftTypeNameAsString( + method: JavaLangReflect.Method? = nil, _ javaType: Type, preferValueTypes: Bool, outerOptional: OptionalKind @@ -123,11 +158,7 @@ extension JavaTranslator { typeVariable.getBounds().count == 1, let bound = typeVariable.getBounds()[0] { - return try getSwiftTypeNameAsString( - bound, - preferValueTypes: preferValueTypes, - outerOptional: outerOptional - ) + return outerOptional.adjustTypeName(typeVariable.getName()) } // Replace wildcards with their upper bound. @@ -164,30 +195,42 @@ extension JavaTranslator { // Handle parameterized types by recursing on the raw type and the type // arguments. - if let parameterizedType = javaType.as(ParameterizedType.self), - let rawJavaType = parameterizedType.getRawType() - { - var rawSwiftType = try getSwiftTypeNameAsString( - rawJavaType, - preferValueTypes: false, - outerOptional: outerOptional - ) + if let parameterizedType = javaType.as(ParameterizedType.self) { + if let rawJavaType = parameterizedType.getRawType() { + var rawSwiftType = try getSwiftTypeNameAsString( + rawJavaType, + preferValueTypes: false, + outerOptional: outerOptional + ) - let optionalSuffix: String - if let lastChar = rawSwiftType.last, lastChar == "?" || lastChar == "!" { - optionalSuffix = "\(lastChar)" - rawSwiftType.removeLast() - } else { - optionalSuffix = "" - } + let optionalSuffix: String + if let lastChar = rawSwiftType.last, lastChar == "?" || lastChar == "!" { + optionalSuffix = "\(lastChar)" + rawSwiftType.removeLast() + } else { + optionalSuffix = "" + } - let typeArguments = try parameterizedType.getActualTypeArguments().compactMap { typeArg in - try typeArg.map { typeArg in - try getSwiftTypeNameAsString(typeArg, preferValueTypes: false, outerOptional: .nonoptional) + let typeArguments: [String] = try parameterizedType.getActualTypeArguments().compactMap { typeArg in + guard let typeArg else { return nil } + + let mappedSwiftName = try getSwiftTypeNameAsString(method: method, typeArg, preferValueTypes: false, outerOptional: .nonoptional) + + // FIXME: improve the get instead... + if mappedSwiftName == nil || mappedSwiftName == "JavaObject" { + // Try to salvage it, is it perhaps a type parameter? + if let method { + if method.getTypeParameters().contains(where: { $0?.getTypeName() == typeArg.getTypeName() }) { + return typeArg.getTypeName() + } + } + } + + return mappedSwiftName } - } - return "\(rawSwiftType)<\(typeArguments.joined(separator: ", "))>\(optionalSuffix)" + return "\(rawSwiftType)<\(typeArguments.joined(separator: ", "))>\(optionalSuffix)" + } } // Handle direct references to Java classes. @@ -196,10 +239,12 @@ extension JavaTranslator { } let (swiftName, isOptional) = try getSwiftTypeName(javaClass, preferValueTypes: preferValueTypes) - var resultString = swiftName - if isOptional { - resultString = outerOptional.adjustTypeName(resultString) - } + let resultString = + if isOptional { + outerOptional.adjustTypeName(swiftName) + } else { + swiftName + } return resultString } @@ -233,7 +278,10 @@ extension JavaTranslator { if preferValueTypes, let translatedValueType = translatedToValueTypes[name] { // Note that we need to import this Swift module. if translatedValueType.swiftModule != swiftModuleName { - importedSwiftModules.insert(translatedValueType.swiftModule) + guard let module = translatedValueType.swiftModule else { + preconditionFailure("Translated value type must have Swift module, but was nil! Type: \(translatedValueType)") + } + importedSwiftModules.insert(module) } return translatedValueType.swiftType diff --git a/Sources/SwiftJavaToolLib/JavaType+Equality.swift b/Sources/SwiftJavaToolLib/JavaType+Equality.swift new file mode 100644 index 000000000..b3d4a37a4 --- /dev/null +++ b/Sources/SwiftJavaToolLib/JavaType+Equality.swift @@ -0,0 +1,135 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftJava +import JavaLangReflect +import SwiftSyntax +import SwiftJavaConfigurationShared +import Logging + +extension Type { + /// Adjust the given type to use its bounds, mirroring what we do in + /// mapping Java types into Swift. + func adjustToJavaBounds(adjusted: inout Bool) -> Type { + if let typeVariable = self.as(TypeVariable.self), + typeVariable.getBounds().count == 1, + let bound = typeVariable.getBounds()[0] { + adjusted = true + return bound + } + + if let wildcardType = self.as(WildcardType.self), + wildcardType.getUpperBounds().count == 1, + let bound = wildcardType.getUpperBounds()[0] { + adjusted = true + return bound + } + + return self + } + + /// Determine whether this type is equivalent to or a subtype of the other + /// type. + func isEqualTo(_ other: Type, file: String = #file, line: Int = #line, function: String = #function) -> Bool { + if self.javaHolder.object == other.javaHolder.object { + return true + } + + // First, adjust types to their bounds, if we need to. + var anyAdjusted: Bool = false + let adjustedSelf = self.adjustToJavaBounds(adjusted: &anyAdjusted) + let adjustedOther = other.adjustToJavaBounds(adjusted: &anyAdjusted) + if anyAdjusted { + return adjustedSelf.isEqualTo(adjustedOther) + } + + // If both are classes, check for equivalence. + if let selfClass = self.as(JavaClass.self), + let otherClass = other.as(JavaClass.self) { + return selfClass.equals(otherClass.as(JavaObject.self)) + } + + // If both are arrays, check that their component types are equivalent. + if let selfArray = self.as(GenericArrayType.self), + let otherArray = other.as(GenericArrayType.self) { + return selfArray.getGenericComponentType().isEqualTo(otherArray.getGenericComponentType()) + } + + // If both are parameterized types, check their raw type and type + // arguments for equivalence. + if let selfParameterizedType = self.as(ParameterizedType.self), + let otherParameterizedType = other.as(ParameterizedType.self) { + if !selfParameterizedType.getRawType().isEqualTo(otherParameterizedType.getRawType()) { + return false + } + + return selfParameterizedType.getActualTypeArguments() + .allTypesEqual(otherParameterizedType.getActualTypeArguments()) + } + + // If both are type variables, compare their bounds. + // FIXME: This is a hack. + if let selfTypeVariable = self.as(TypeVariable.self), + let otherTypeVariable = other.as(TypeVariable.self) { + return selfTypeVariable.getBounds().allTypesEqual(otherTypeVariable.getBounds()) + } + + // If both are wildcards, compare their upper and lower bounds. + if let selfWildcard = self.as(WildcardType.self), + let otherWildcard = other.as(WildcardType.self) { + return selfWildcard.getUpperBounds().allTypesEqual(otherWildcard.getUpperBounds()) + && selfWildcard.getLowerBounds().allTypesEqual(otherWildcard.getLowerBounds()) + } + + return false + } + + /// Determine whether this type is equivalent to or a subtype of the + /// other type. + func isEqualToOrSubtypeOf(_ other: Type) -> Bool { + // First, adjust types to their bounds, if we need to. + var anyAdjusted: Bool = false + let adjustedSelf = self.adjustToJavaBounds(adjusted: &anyAdjusted) + let adjustedOther = other.adjustToJavaBounds(adjusted: &anyAdjusted) + if anyAdjusted { + return adjustedSelf.isEqualToOrSubtypeOf(adjustedOther) + } + + if isEqualTo(other) { + return true + } + + // If both are classes, check for subclassing. + if let selfClass = self.as(JavaClass.self), + let otherClass = other.as(JavaClass.self) { + // If either is a Java array, then this cannot be a subtype relationship + // in Swift. + if selfClass.isArray() || otherClass.isArray() { + return false + } + + return selfClass.isSubclass(of: otherClass) + } + + // Anything object-like is a subclass of java.lang.Object + if let otherClass = other.as(JavaClass.self), + otherClass.getName() == "java.lang.Object" { + if self.is(GenericArrayType.self) || self.is(ParameterizedType.self) || + self.is(WildcardType.self) || self.is(TypeVariable.self) { + return true + } + } + return false + } +} diff --git a/Tests/JExtractSwiftTests/FFMNestedTypesTests.swift b/Tests/JExtractSwiftTests/FFMNestedTypesTests.swift new file mode 100644 index 000000000..5bb422eaa --- /dev/null +++ b/Tests/JExtractSwiftTests/FFMNestedTypesTests.swift @@ -0,0 +1,56 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import SwiftJavaConfigurationShared +import Testing + +final class FFMNestedTypesTests { + let class_interfaceFile = + """ + public enum MyNamespace { } + + extension MyNamespace { + public struct MyNestedStruct { + public func test() {} + } + } + """ + + @Test("Import: Nested type in extension MyNamespace { struct MyName {} }") + func test_nested_in_extension() throws { + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = Swift2JavaTranslator(config: config) + st.log.logLevel = .error + + try st.analyze(path: "Fake.swift", text: class_interfaceFile) + + let generator = FFMSwift2JavaGenerator( + config: config, + translator: st, + javaPackage: "com.example.swift", + swiftOutputDirectory: "/fake", + javaOutputDirectory: "/fake" + ) + + guard let ty = st.importedTypes["MyNamespace.MyNestedStruct"] else { + fatalError("Didn't import nested type!") + } + + + + } + +} \ No newline at end of file diff --git a/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift b/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift index 50a0ae264..67d966e3a 100644 --- a/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNINestedTypesTests.swift @@ -134,4 +134,4 @@ struct JNINestedTypesTests { ] ) } -} +} \ No newline at end of file diff --git a/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift b/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift index 61a94062b..0833b21f3 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIUnsignedNumberTests.swift @@ -47,7 +47,6 @@ final class JNIUnsignedNumberTests { func jni_unsignedInt_annotate() throws { var config = Configuration() config.unsignedNumbersMode = .annotate - config.logLevel = .trace try assertOutput( input: "public func unsignedInt(_ arg: UInt32)", diff --git a/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift b/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift new file mode 100644 index 000000000..68702b621 --- /dev/null +++ b/Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift @@ -0,0 +1,174 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +@_spi(Testing) import SwiftJava +import SwiftJavaToolLib +import JavaUtilJar +import JavaNet +import SwiftJavaShared +import SwiftJavaConfigurationShared +import _Subprocess +import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 +import Foundation + +fileprivate func createTemporaryDirectory(in directory: Foundation.URL) throws -> Foundation.URL { + let uuid = UUID().uuidString + let resolverDirectoryURL = directory.appendingPathComponent("swift-java-testing-\(uuid)") + + try FileManager.default.createDirectory(at: resolverDirectoryURL, withIntermediateDirectories: true, attributes: nil) + + return resolverDirectoryURL +} + +/// Returns the directory that should be added to the classpath of the JVM to analyze the sources. +func compileJava(_ sourceText: String) async throws -> Foundation.URL { + let sourceFile = try TempFile.create(suffix: "java", sourceText) + + let classesDirectory = try createTemporaryDirectory(in: FileManager.default.temporaryDirectory) + + let javacProcess = try await _Subprocess.run( + .path(.init("\(javaHome)" + "/bin/javac")), + arguments: [ + "-d", classesDirectory.path, // output directory for .class files + sourceFile.path + ], + output: .string(limit: Int.max, encoding: UTF8.self), + error: .string(limit: Int.max, encoding: UTF8.self) + ) + + // Check if compilation was successful + guard javacProcess.terminationStatus.isSuccess else { + let outString = javacProcess.standardOutput ?? "" + let errString = javacProcess.standardError ?? "" + fatalError("javac '\(sourceFile)' failed (\(javacProcess.terminationStatus));\n" + + "OUT: \(outString)\n" + + "ERROR: \(errString)") + } + + print("Compiled java sources to: \(classesDirectory)") + return classesDirectory +} + +func withJavaTranslator( + javaClassNames: [String], + classpath: [Foundation.URL], + body: (JavaTranslator) throws -> (), + function: String = #function, + file: StaticString = #filePath, + line: UInt = #line +) throws { + print("New withJavaTranslator, for classpath: \(classpath)") + let jvm = try JavaVirtualMachine.shared( + classpath: classpath.map(\.path), + replace: true + ) + + var config = Configuration() + config.minimumInputAccessLevelMode = .package + + let environment = try jvm.environment() + let translator = JavaTranslator( + config: config, + swiftModuleName: "SwiftModule", + environment: environment, + translateAsClass: true) + + try body(translator) +} + +/// Translate a Java class and assert that the translated output contains +/// each of the expected "chunks" of text. +func assertWrapJavaOutput( + javaClassNames: [String], + classpath: [Foundation.URL], + assert assertBody: (JavaTranslator) throws -> Void = { _ in }, + expectedChunks: [String], + function: String = #function, + file: StaticString = #filePath, + line: UInt = #line +) throws { + let jvm = try JavaVirtualMachine.shared( + //classpath: classpath.map(\.path), + replace: false + ) + // Do NOT destroy the jvm here, because the JavaClasses will need to deinit, + // and do so while the env is still valid... + + var config = Configuration() + config.minimumInputAccessLevelMode = .package + + let environment = try jvm.environment() + let translator = JavaTranslator( + config: config, + swiftModuleName: "SwiftModule", + environment: environment, + translateAsClass: true) + + let classpathJavaURLs = classpath.map({ try! URL.init("\($0)/") }) // we MUST have a trailing slash for JVM to consider it a search directory + let classLoader = URLClassLoader(classpathJavaURLs, environment: environment) + + // FIXME: deduplicate this + translator.startNewFile() + + var swiftCompleteOutputText = "" + + var javaClasses: [JavaClass] = [] + for javaClassName in javaClassNames { + guard let javaClass = try! classLoader.loadClass(javaClassName) else { + fatalError("Could not load Java class '\(javaClassName)' in test \(function) @ \(file):\(line)!") + } + javaClasses.append(javaClass) + + // FIXME: deduplicate this with SwiftJava.WrapJavaCommand.runCommand !!! + // TODO: especially because nested classes + // WrapJavaCommand(). + + let swiftUnqualifiedName = javaClassName.javaClassNameToCanonicalName + .defaultSwiftNameForJavaClass + translator.translatedClasses[javaClassName] = + .init(module: nil, name: swiftUnqualifiedName) + + try translator.validateClassConfiguration() + + let swiftClassDecls = try translator.translateClass(javaClass) + let importDecls = translator.getImportDecls() + + let swiftFileText = + """ + // --------------------------------------------------------------------------- + // Auto-generated by Java-to-Swift wrapper generator. + \(importDecls.map { $0.description }.joined()) + \(swiftClassDecls.map { $0.description }.joined(separator: "\n")) + \n + """ + swiftCompleteOutputText += swiftFileText + } + + // Run any additional user defined assertions: + try assertBody(translator) + + for expectedChunk in expectedChunks { + // We make the matching in-sensitive to whitespace: + let checkAgainstText = swiftCompleteOutputText.replacing(" ", with: "") + let checkAgainstExpectedChunk = expectedChunk.replacing(" ", with: "") + +let failureMessage = "Expected chunk: \n" + + "\(expectedChunk.yellow)" + + "\n" + + "not found in:\n" + + "\(swiftCompleteOutputText)" + XCTAssertTrue(checkAgainstText.contains(checkAgainstExpectedChunk), + "\(failureMessage)") + } +} \ No newline at end of file diff --git a/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift index f251766ac..e302fdc5a 100644 --- a/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift +++ b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift @@ -14,6 +14,7 @@ @_spi(Testing) import SwiftJava +import SwiftJavaConfigurationShared import SwiftJavaToolLib import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 @@ -48,12 +49,12 @@ class Java2SwiftTests: XCTestCase { public struct MyJavaObject { """, """ - @JavaMethod - public func toString() -> String + @JavaMethod + public func toString() -> String """, """ - @JavaMethod - public func wait() throws + @JavaMethod + public func wait() throws """ ] ) @@ -64,7 +65,7 @@ class Java2SwiftTests: XCTestCase { JavaClass.self, swiftTypeName: "MyJavaClass", translatedClasses: [ - "java.lang.Object": ("JavaObject", nil), + "java.lang.Object": SwiftTypeName(module: nil, name: "JavaObject"), ], expectedChunks: [ "import SwiftJava", @@ -73,8 +74,8 @@ class Java2SwiftTests: XCTestCase { public struct MyJavaClass { """, """ - @JavaStaticMethod - public func forName(_ arg0: String) throws -> MyJavaClass! where ObjectType == MyJavaClass + @JavaStaticMethod + public func forName(_ arg0: String) throws -> MyJavaClass! where ObjectType == MyJavaClass """, ] ) @@ -100,12 +101,12 @@ class Java2SwiftTests: XCTestCase { if let APRIL = classObj.APRIL { self = APRIL } else { - fatalError("Enum value APRIL was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value APRIL was unexpectedly nil, please re-run swift-java on the most updated Java class") } """, """ - @JavaStaticField(isFinal: true) - public var APRIL: Month! + @JavaStaticField(isFinal: true) + public var APRIL: Month! """ ]) } @@ -115,19 +116,19 @@ class Java2SwiftTests: XCTestCase { MyArrayList.self, swiftTypeName: "JavaArrayList", translatedClasses: [ - "java.lang.Object": ("JavaObject", nil), - "java.lang.reflect.Array": ("JavaArray", nil), - "java.util.List": ("JavaList", nil), - "java.util.function.IntFunction": ("MyJavaIntFunction", nil), + "java.lang.Object": SwiftTypeName(module: nil, name: "JavaObject"), + "java.lang.reflect.Array": SwiftTypeName(module: nil, name: "JavaArray"), + "java.util.List": SwiftTypeName(module: nil, name: "JavaList"), + "java.util.function.IntFunction": SwiftTypeName(module: nil, name: "MyJavaIntFunction"), ], expectedChunks: [ """ - @JavaMethod - public func subList(_ arg0: Int32, _ arg1: Int32) -> JavaList! + @JavaMethod + public func subList(_ arg0: Int32, _ arg1: Int32) -> JavaList! """, """ - @JavaMethod - public func toArray(_ arg0: MyJavaIntFunction?) -> [JavaObject?] + @JavaMethod + public func toArray(_ arg0: MyJavaIntFunction?) -> [T?] """ ] ) @@ -138,13 +139,13 @@ class Java2SwiftTests: XCTestCase { MyLinkedList.self, swiftTypeName: "JavaLinkedList", translatedClasses: [ - "java.lang.Object": ("JavaObject", nil), - "java.util.List": ("JavaList", nil), + "java.lang.Object": SwiftTypeName(module: nil, name: "JavaObject"), + "java.util.List": SwiftTypeName(module: nil, name: "JavaList"), ], expectedChunks: [ """ - @JavaMethod - public func subList(_ arg0: Int32, _ arg1: Int32) -> JavaList! + @JavaMethod + public func subList(_ arg0: Int32, _ arg1: Int32) -> JavaList! """ ] ) @@ -155,9 +156,9 @@ class Java2SwiftTests: XCTestCase { ProcessBuilder.self, swiftTypeName: "ProcessBuilder", translatedClasses: [ - "java.lang.ProcessBuilder": ("ProcessBuilder", nil), - "java.lang.ProcessBuilder$Redirect": ("ProcessBuilder.Redirect", nil), - "java.lang.ProcessBuilder$Redirect$Type": ("ProcessBuilder.Redirect.Type", nil), + "java.lang.ProcessBuilder": SwiftTypeName(module: nil, name: "ProcessBuilder"), + "java.lang.ProcessBuilder$Redirect": SwiftTypeName(module: nil, name: "ProcessBuilder.Redirect"), + "java.lang.ProcessBuilder$Redirect$Type": SwiftTypeName(module: nil, name: "ProcessBuilder.Redirect.Type"), ], nestedClasses: [ "java.lang.ProcessBuilder": [JavaClass().as(JavaClass.self)!], @@ -166,8 +167,8 @@ class Java2SwiftTests: XCTestCase { expectedChunks: [ "import SwiftJava", """ - @JavaMethod - public func redirectInput() -> ProcessBuilder.Redirect! + @JavaMethod + public func redirectInput() -> ProcessBuilder.Redirect! """, """ extension ProcessBuilder { @@ -183,8 +184,8 @@ class Java2SwiftTests: XCTestCase { public struct Type { """, """ - @JavaMethod - public func type() -> ProcessBuilder.Redirect.`Type`! + @JavaMethod + public func type() -> ProcessBuilder.Redirect.`Type`! """, ] ) @@ -195,9 +196,9 @@ class Java2SwiftTests: XCTestCase { ProcessBuilder.self, swiftTypeName: "ProcessBuilder", translatedClasses: [ - "java.lang.ProcessBuilder": ("ProcessBuilder", nil), - "java.lang.ProcessBuilder$Redirect": ("ProcessBuilder.PBRedirect", nil), - "java.lang.ProcessBuilder$Redirect$Type": ("ProcessBuilder.PBRedirect.JavaType", nil), + "java.lang.ProcessBuilder": SwiftTypeName(module: nil, name: "ProcessBuilder"), + "java.lang.ProcessBuilder$Redirect": SwiftTypeName(module: nil, name: "ProcessBuilder.PBRedirect"), + "java.lang.ProcessBuilder$Redirect$Type": SwiftTypeName(module: nil, name: "ProcessBuilder.PBRedirect.JavaType"), ], nestedClasses: [ "java.lang.ProcessBuilder": [JavaClass().as(JavaClass.self)!], @@ -206,8 +207,8 @@ class Java2SwiftTests: XCTestCase { expectedChunks: [ "import SwiftJava", """ - @JavaMethod - public func redirectInput() -> ProcessBuilder.PBRedirect! + @JavaMethod + public func redirectInput() -> ProcessBuilder.PBRedirect! """, """ extension ProcessBuilder { @@ -223,8 +224,8 @@ class Java2SwiftTests: XCTestCase { public struct JavaType { """, """ - @JavaMethod - public func type() -> ProcessBuilder.PBRedirect.JavaType! + @JavaMethod + public func type() -> ProcessBuilder.PBRedirect.JavaType! """ ] ) @@ -248,9 +249,9 @@ class Java2SwiftTests: XCTestCase { MyObjects.self, swiftTypeName: "MyJavaObjects", translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.util.function.Supplier" : ("MySupplier", "JavaUtilFunction"), - "java.lang.String" : ("JavaString", "SwiftJava"), + "java.lang.Object" : SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.util.function.Supplier" : SwiftTypeName(module: "JavaUtilFunction", name: "MySupplier"), + "java.lang.String" : SwiftTypeName(module: "SwiftJava", name: "JavaString"), ], expectedChunks: [ """ @@ -261,8 +262,8 @@ class Java2SwiftTests: XCTestCase { public struct MyJavaObjects { """, """ - @JavaStaticMethod - public func requireNonNull(_ arg0: JavaObject?, _ arg1: MySupplier?) -> JavaObject! + @JavaStaticMethod + public func requireNonNull(_ arg0: T?, _ arg1: MySupplier?) -> T """, ] ) @@ -280,20 +281,20 @@ class Java2SwiftTests: XCTestCase { open class JavaObject { """, """ - @JavaMethod - @_nonoverride public convenience init(environment: JNIEnvironment? = nil) + @JavaMethod + @_nonoverride public convenience init(environment: JNIEnvironment? = nil) """, """ - @JavaMethod - open func toString() -> String + @JavaMethod + open func toString() -> String """, """ - @JavaMethod - open func wait() throws + @JavaMethod + open func wait() throws """, """ - @JavaMethod - open func clone() throws -> JavaObject! + @JavaMethod + open func clone() throws -> JavaObject! """, ] ) @@ -305,7 +306,7 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "JavaString", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), + "java.lang.Object" : SwiftTypeName(module: "SwiftJava", name: "JavaObject"), ], expectedChunks: [ "import SwiftJava", @@ -314,24 +315,24 @@ class Java2SwiftTests: XCTestCase { open class JavaString: JavaObject { """, """ - @JavaMethod - @_nonoverride public convenience init(environment: JNIEnvironment? = nil) + @JavaMethod + @_nonoverride public convenience init(environment: JNIEnvironment? = nil) """, """ - @JavaMethod - open override func toString() -> String + @JavaMethod + open override func toString() -> String """, """ - @JavaMethod - open override func equals(_ arg0: JavaObject?) -> Bool + @JavaMethod + open override func equals(_ arg0: JavaObject?) -> Bool """, """ - @JavaMethod - open func intern() -> String + @JavaMethod + open func intern() -> String """, """ - @JavaStaticMethod - public func valueOf(_ arg0: Int64) -> String + @JavaStaticMethod + public func valueOf(_ arg0: Int64) -> String """, ] ) @@ -361,12 +362,12 @@ class Java2SwiftTests: XCTestCase { if let APRIL = classObj.APRIL { self.init(javaHolder: APRIL.javaHolder) } else { - fatalError("Enum value APRIL was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value APRIL was unexpectedly nil, please re-run swift-java on the most updated Java class") } """, """ - @JavaStaticField(isFinal: true) - public var APRIL: Month! + @JavaStaticField(isFinal: true) + public var APRIL: Month! """ ]) } @@ -380,9 +381,9 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "URLClassLoader", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.lang.ClassLoader" : ("ClassLoader", "SwiftJava"), - "java.net.URL" : ("URL", "JavaNet"), + "java.lang.Object" : SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.lang.ClassLoader" : SwiftTypeName(module: "SwiftJava", name: "ClassLoader"), + "java.net.URL" : SwiftTypeName(module: "JavaNet", name: "URL"), ], expectedChunks: [ "import SwiftJava", @@ -391,12 +392,12 @@ class Java2SwiftTests: XCTestCase { open class URLClassLoader: ClassLoader { """, """ - @JavaMethod - open func close() throws + @JavaMethod + open func close() throws """, """ - @JavaMethod - open override func findResource(_ arg0: String) -> URL! + @JavaMethod + open override func findResource(_ arg0: String) -> URL! """, ] ) @@ -411,8 +412,8 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "URLClassLoader", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.net.URL" : ("URL", "JavaNet"), + "java.lang.Object": SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.net.URL": SwiftTypeName(module: "JavaNet", name: "URL"), ], expectedChunks: [ "import SwiftJava", @@ -421,12 +422,12 @@ class Java2SwiftTests: XCTestCase { open class URLClassLoader: JavaObject { """, """ - @JavaMethod - open func close() throws + @JavaMethod + open func close() throws """, """ - @JavaMethod - open func findResource(_ arg0: String) -> URL! + @JavaMethod + open func findResource(_ arg0: String) -> URL! """, ] ) @@ -440,9 +441,9 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "JavaByte", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.lang.Number" : ("JavaNumber", "SwiftJava"), - "java.lang.Byte" : ("JavaByte", "SwiftJava"), + "java.lang.Object": SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.lang.Number": SwiftTypeName(module: "SwiftJava", name: "JavaNumber"), + "java.lang.Byte": SwiftTypeName(module: "SwiftJava", name: "JavaByte"), ], expectedChunks: [ "import SwiftJava", @@ -451,8 +452,8 @@ class Java2SwiftTests: XCTestCase { open class JavaByte: JavaNumber { """, """ - @JavaMethod - open override func equals(_ arg0: JavaObject?) -> Bool + @JavaMethod + open override func equals(_ arg0: JavaObject?) -> Bool """, ] ) @@ -464,8 +465,8 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "MyJavaIntFunction", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.util.function.IntFunction": ("MyJavaIntFunction", nil), + "java.lang.Object" : SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.util.function.IntFunction": SwiftTypeName(module: nil, name: "MyJavaIntFunction"), ], expectedChunks: [ "import SwiftJava", @@ -474,8 +475,8 @@ class Java2SwiftTests: XCTestCase { public struct MyJavaIntFunction { """, """ - @JavaMethod - public func apply(_ arg0: Int32) -> JavaObject! + @JavaMethod + public func apply(_ arg0: Int32) -> R! """, ] ) @@ -487,11 +488,11 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "Method", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.lang.Class" : ("JavaClass", "SwiftJava"), - "java.lang.reflect.Executable": ("Executable", "JavaLangReflect"), - "java.lang.reflect.Method": ("Method", "JavaLangReflect"), - "java.lang.reflect.TypeVariable" : ("TypeVariable", "JavaLangReflect"), + "java.lang.Object" : SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.lang.Class" : SwiftTypeName(module: "SwiftJava", name: "JavaClass"), + "java.lang.reflect.Executable": SwiftTypeName(module: "JavaLangReflect", name: "Executable"), + "java.lang.reflect.Method": SwiftTypeName(module: "JavaLangReflect", name: "Method"), + "java.lang.reflect.TypeVariable" : SwiftTypeName(module: "JavaLangReflect", name: "TypeVariable"), ], expectedChunks: [ "import JavaLangReflect", @@ -500,16 +501,16 @@ class Java2SwiftTests: XCTestCase { open class Method: Executable { """, """ - @JavaMethod - open func getTypeParameters() -> [TypeVariable?] + @JavaMethod + open func getTypeParameters() -> [TypeVariable?] """, """ - @JavaMethod - open override func getParameterTypes() -> [JavaClass?] + @JavaMethod + open override func getParameterTypes() -> [JavaClass?] """, """ - @JavaMethod - open override func getDeclaringClass() -> JavaClass! + @JavaMethod + open override func getDeclaringClass() -> JavaClass! """, ] ) @@ -521,11 +522,11 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "Constructor", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.lang.Class" : ("JavaClass", "SwiftJava"), - "java.lang.reflect.Executable": ("Executable", "JavaLangReflect"), - "java.lang.reflect.Method": ("Method", "JavaLangReflect"), - "java.lang.reflect.TypeVariable" : ("TypeVariable", "JavaLangReflect"), + "java.lang.Object" : SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.lang.Class" : SwiftTypeName(module: "SwiftJava", name: "JavaClass"), + "java.lang.reflect.Executable": SwiftTypeName(module: "JavaLangReflect", name: "Executable"), + "java.lang.reflect.Method": SwiftTypeName(module: "JavaLangReflect", name: "Method"), + "java.lang.reflect.TypeVariable" : SwiftTypeName(module: "JavaLangReflect", name: "TypeVariable"), ], expectedChunks: [ "import JavaLangReflect", @@ -534,16 +535,16 @@ class Java2SwiftTests: XCTestCase { open class Constructor: Executable { """, """ - @JavaMethod - open func getTypeParameters() -> [TypeVariable>?] + @JavaMethod + open func getTypeParameters() -> [TypeVariable>?] """, """ - @JavaMethod - open override func getParameterTypes() -> [JavaClass?] + @JavaMethod + open override func getParameterTypes() -> [JavaClass?] """, """ - @JavaMethod - open override func getDeclaringClass() -> JavaClass! + @JavaMethod + open override func getDeclaringClass() -> JavaClass! """, ] ) @@ -555,10 +556,10 @@ class Java2SwiftTests: XCTestCase { swiftTypeName: "NIOByteBuffer", asClass: true, translatedClasses: [ - "java.lang.Object" : ("JavaObject", "SwiftJava"), - "java.lang.Class" : ("JavaClass", "SwiftJava"), - "java.nio.Buffer": ("NIOBuffer", "JavaNio"), - "java.nio.ByteBuffer": ("NIOByteBuffer", "JavaNio"), + "java.lang.Object": SwiftTypeName(module: "SwiftJava", name: "JavaObject"), + "java.lang.Class": SwiftTypeName(module: "SwiftJava", name: "JavaClass"), + "java.nio.Buffer": SwiftTypeName(module: "JavaNio", name: "NIOBuffer"), + "java.nio.ByteBuffer": SwiftTypeName(module: "JavaNio", name: "NIOByteBuffer"), ], expectedChunks: [ "import JavaNio", @@ -580,61 +581,58 @@ class Java2SwiftTests: XCTestCase { } @JavaClass("java.lang.ClassLoader") -public struct ClassLoader { } +fileprivate struct ClassLoader { } @JavaClass("java.security.SecureClassLoader") -public struct SecureClassLoader { } +fileprivate struct SecureClassLoader { } @JavaClass("java.net.URLClassLoader") -public struct URLClassLoader { } - +fileprivate struct URLClassLoader { } @JavaClass("java.util.ArrayList") -public struct MyArrayList { +fileprivate struct MyArrayList { } @JavaClass("java.util.LinkedList") -public struct MyLinkedList { +fileprivate struct MyLinkedList { } @JavaClass("java.lang.String") -public struct MyJavaString { +fileprivate struct MyJavaString { } @JavaClass("java.util.Objects") -public struct MyObjects { } +fileprivate struct MyObjects { } @JavaInterface("java.util.function.Supplier") -public struct MySupplier { } +fileprivate struct MySupplier { } @JavaInterface("java.util.function.IntFunction") -public struct MyJavaIntFunction { +fileprivate struct MyJavaIntFunction { } @JavaClass("java.lang.reflect.Method", extends: Executable.self) -public struct Method { +fileprivate struct Method { } @JavaClass("java.lang.reflect.Constructor", extends: Executable.self) -public struct Constructor { +fileprivate struct Constructor { } @JavaClass("java.lang.reflect.Executable") -public struct Executable { +fileprivate struct Executable { } @JavaInterface("java.lang.reflect.TypeVariable") -public struct TypeVariable { +fileprivate struct TypeVariable { } @JavaClass("java.nio.Buffer") -open class NIOBuffer: JavaObject { - +fileprivate class NIOBuffer: JavaObject { } @JavaClass("java.nio.ByteBuffer") -open class NIOByteBuffer: NIOBuffer { - +fileprivate class NIOByteBuffer: NIOBuffer { } /// Translate a Java class and assert that the translated output contains @@ -643,9 +641,8 @@ func assertTranslatedClass( _ javaType: JavaClassType.Type, swiftTypeName: String, asClass: Bool = false, - translatedClasses: [ - String: (swiftType: String, swiftModule: String?) - ] = [:], + config: Configuration = Configuration(), + translatedClasses: [String: SwiftTypeName] = [:], nestedClasses: [String: [JavaClass]] = [:], expectedChunks: [String], file: StaticString = #filePath, @@ -653,13 +650,14 @@ func assertTranslatedClass( ) throws { let environment = try jvm.environment() let translator = JavaTranslator( + config: config, swiftModuleName: "SwiftModule", environment: environment, translateAsClass: asClass ) translator.translatedClasses = translatedClasses - translator.translatedClasses[javaType.fullJavaClassName] = (swiftTypeName, nil) + translator.translatedClasses[javaType.fullJavaClassName] = SwiftTypeName(module: nil, name: swiftTypeName) translator.nestedClasses = nestedClasses translator.startNewFile() @@ -677,12 +675,22 @@ func assertTranslatedClass( \(translatedDecls.map { $0.description }.joined(separator: "\n")) """ + func normalizeWhitespace(_ text: String) -> String { + return text.components(separatedBy: .newlines) + .map { $0.trimmingCharacters(in: .whitespaces) } + .joined(separator: "\n") + } + + let normalizedSwiftFileText = normalizeWhitespace(swiftFileText) + for expectedChunk in expectedChunks { - if swiftFileText.contains(expectedChunk) { + let normalizedExpectedChunk = normalizeWhitespace(expectedChunk) + + if normalizedSwiftFileText.contains(normalizedExpectedChunk) { continue } - XCTFail("Expected chunk '\(expectedChunk)' not found in '\(swiftFileText)'", file: file, line: line) + XCTFail("Expected chunk:\n---\n\(expectedChunk.yellow)\n---\nnot found in:\n===\n\(swiftFileText)\n===", file: file, line: line) } } } diff --git a/Tests/SwiftJavaToolLibTests/JavaTranslatorTests.swift b/Tests/SwiftJavaToolLibTests/JavaTranslatorTests.swift new file mode 100644 index 000000000..9983813d7 --- /dev/null +++ b/Tests/SwiftJavaToolLibTests/JavaTranslatorTests.swift @@ -0,0 +1,58 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JavaUtilJar +@_spi(Testing) import SwiftJava +import SwiftJavaConfigurationShared +import SwiftJavaShared +import SwiftJavaToolLib +import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 +import _Subprocess + +class JavaTranslatorTests: XCTestCase { + + func translateGenericMethodParameters() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + class Item { + final T value; + Item(T item) { + this.value = item; + } + } + class Pair { } + + class ExampleSimpleClass { + Pair getPair( + String name, + Item key, + Item value + ) { return null; } + } + """) + + try withJavaTranslator( + javaClassNames: [ + "com.example.Item", + "com.example.Pair", + "com.example.ExampleSimpleClass", + ], + classpath: [classpathURL], + ) { translator in + + } + } +} diff --git a/Tests/SwiftJavaToolLibTests/JavaTranslatorValidationTests.swift b/Tests/SwiftJavaToolLibTests/JavaTranslatorValidationTests.swift index 220f1c613..558add20d 100644 --- a/Tests/SwiftJavaToolLibTests/JavaTranslatorValidationTests.swift +++ b/Tests/SwiftJavaToolLibTests/JavaTranslatorValidationTests.swift @@ -14,15 +14,16 @@ import SwiftJavaToolLib import XCTest +import SwiftJavaConfigurationShared final class JavaTranslatorValidationTests: XCTestCase { func testValidationError() throws { - let translator = try JavaTranslator(swiftModuleName: "SwiftModule", environment: jvm.environment()) + let translator = try JavaTranslator(config: Configuration(), swiftModuleName: "SwiftModule", environment: jvm.environment()) translator.translatedClasses = [ - "TestClass": ("Class1", "Module1"), - "TestClass2": ("Class1", "Module2"), - "TestClass3": ("Class1", "Module1"), - "TestClass4": ("Class1", nil) + "TestClass": SwiftTypeName(module: "Module1", name: "Class1"), + "TestClass2": SwiftTypeName(module: "Module2", name: "Class1"), + "TestClass3": SwiftTypeName(module: "Module1", name: "Class1"), + "TestClass4": SwiftTypeName(module: nil, name: "Class1") ] XCTAssertThrowsError(try translator.validateClassConfiguration()) { error in @@ -31,7 +32,7 @@ final class JavaTranslatorValidationTests: XCTestCase { switch validationError { case .multipleClassesMappedToSameName(let swiftToJavaMapping): XCTAssertEqual(swiftToJavaMapping, [ - JavaTranslator.SwiftToJavaMapping(swiftType: .init(swiftType: "Class1", swiftModule: "Module1"), + JavaTranslator.SwiftToJavaMapping(swiftType: .init(module: "Module1", name: "Class1"), javaTypes: ["TestClass", "TestClass3"]) ]) } diff --git a/Tests/SwiftJavaToolLibTests/TempFileTools.swift b/Tests/SwiftJavaToolLibTests/TempFileTools.swift new file mode 100644 index 000000000..5b133108d --- /dev/null +++ b/Tests/SwiftJavaToolLibTests/TempFileTools.swift @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024-2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Foundation + +/// Example demonstrating how to create a temporary file using Swift Foundation APIs +public class TempFile { + + public static func create( + suffix: String, + _ contents: String = "", + in tempDirectory: URL = FileManager.default.temporaryDirectory) throws -> URL { + let tempFileName = "tmp_\(UUID().uuidString).\(suffix)" + let tempFileURL = tempDirectory.appendingPathComponent(tempFileName) + + try contents.write(to: tempFileURL, atomically: true, encoding: .utf8) + + return tempFileURL + } + public static func delete(at fileURL: URL) throws { + try FileManager.default.removeItem(at: fileURL) + } +} \ No newline at end of file diff --git a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift new file mode 100644 index 000000000..1c3d10d24 --- /dev/null +++ b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift @@ -0,0 +1,273 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +@_spi(Testing) import SwiftJava +import SwiftJavaToolLib +import JavaUtilJar +import SwiftJavaShared +import JavaNet +import SwiftJavaConfigurationShared +import _Subprocess +import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 + +final class WrapJavaTests: XCTestCase { + + func testWrapJavaFromCompiledJavaSource() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + class ExampleSimpleClass {} + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.ExampleSimpleClass" + ], + classpath: [classpathURL], + expectedChunks: [ + """ + import CSwiftJavaJNI + import SwiftJava + """, + """ + @JavaClass("com.example.ExampleSimpleClass") + open class ExampleSimpleClass: JavaObject { + """ + ] + ) + } + + // @Test + func testWrapJavaGenericMethod_singleGeneric() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + class Item { + final T value; + Item(T item) { + this.value = item; + } + } + class Pair { } + + class ExampleSimpleClass { + KeyType getGeneric(Item key) { return null; } + } + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.Item", + "com.example.Pair", + "com.example.ExampleSimpleClass" + ], + classpath: [classpathURL], + expectedChunks: [ + """ + import CSwiftJavaJNI + import SwiftJava + """, + """ + @JavaClass("com.example.Pair") + open class Pair: JavaObject { + """, + """ + @JavaClass("com.example.ExampleSimpleClass") + open class ExampleSimpleClass: JavaObject { + """, + """ + @JavaMethod + open func getGeneric(_ arg0: Item?) -> KeyType + """, + ] + ) + } + + // This is just a warning in Java, but a hard error in Swift, so we must 'prune' generic params + func testWrapJavaGenericMethod_pruneNotUsedGenericParam() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + class Item { + final T value; + Item(T item) { + this.value = item; + } + } + class Pair { } + + final class ExampleSimpleClass { + // use in return type + KeyType getGeneric() { + return null; + } + } + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.Item", + "com.example.Pair", + "com.example.ExampleSimpleClass" + ], + classpath: [classpathURL], + expectedChunks: [ + """ + @JavaMethod + open func getGeneric() -> KeyType + """, + ] + ) + } + + func testWrapJavaGenericMethod_multipleGenerics() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + class Item { + final T value; + Item(T item) { + this.value = item; + } + } + class Pair { } + + class ExampleSimpleClass { + Pair getPair(String name, Item key, Item value) { return null; } + } + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.Item", + "com.example.Pair", + "com.example.ExampleSimpleClass" + ], + classpath: [classpathURL], + expectedChunks: [ + """ + import CSwiftJavaJNI + import SwiftJava + """, + """ + @JavaClass("com.example.Item") + open class Item: JavaObject { + """, + """ + @JavaClass("com.example.Pair") + open class Pair: JavaObject { + """, + """ + @JavaClass("com.example.ExampleSimpleClass") + open class ExampleSimpleClass: JavaObject { + """, + """ + @JavaMethod + open func getPair(_ arg0: String, _ arg1: Item?, _ arg2: Item?) -> Pair! + """, + ] + ) + } + + func test_Java2Swift_returnType_generic() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + final class List {} + final class Map {} + + class GenericClass { + public T getClassGeneric() { return null; } + + public M getMethodGeneric() { return null; } + + public Map getMixedGeneric() { return null; } + + public String getNonGeneric() { return null; } + + public List getParameterizedClassGeneric() { return null; } + + public List getWildcard() { return null; } + + public T[] getGenericArray() { return null; } + } + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.GenericClass", + ], + classpath: [classpathURL], + expectedChunks: [ + """ + @JavaMethod + open func getClassGeneric() -> T + """, + """ + @JavaMethod + open func getNonGeneric() -> String + """, + ] + ) + } + + func testGenericSuperclass() async throws { + return // FIXME: we need this + + let classpathURL = try await compileJava( + """ + package com.example; + + class ByteArray {} + class CompressingStore extends AbstractStore {} + abstract class AbstractStore {} // implements Store {} + // interface Store {} + + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.ByteArray", + // TODO: what if we visit in other order, does the wrap-java handle it + // "com.example.Store", + "com.example.AbstractStore", + "com.example.CompressingStore", + ], + classpath: [classpathURL], + expectedChunks: [ + """ + import CSwiftJavaJNI + import SwiftJava + """, + """ + @JavaClass("com.example.ByteArray") + open class ByteArray: JavaObject { + """, + // """ + // @JavaInterface("com.example.Store") + // public struct Store { + // """, + """ + @JavaClass("com.example.CompressingStore") + open class CompressingStore: AbstractStore { + """ + ] + ) + } +} From 542090ecfd36dd4dc31334c58d902eb327c40203 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Wed, 12 Nov 2025 20:56:33 +0900 Subject: [PATCH 113/141] Improve Optional handling, and generic get() -> T --- Samples/JavaKitSampleApp/Package.swift | 11 ++++ .../example/swift/ThreadSafeHelperClass.java | 16 +++++- .../JavaKitOptionalTests.swift | 56 +++++++++++++++++++ Samples/JavaKitSampleApp/ci-validate.sh | 4 +- Sources/SwiftJava/AnyJavaObject.swift | 6 +- .../SwiftJava/JavaObject+Inheritance.swift | 18 +++--- Sources/SwiftJava/JavaString+Extensions.swift | 34 +++++++++++ Sources/SwiftJava/Macros.swift | 4 +- Sources/SwiftJava/Optional+JavaOptional.swift | 14 +++++ Sources/SwiftJava/String+Extensions.swift | 4 -- .../SwiftJava/generated/JavaOptional.swift | 4 +- Sources/SwiftJavaMacros/JavaMethodMacro.swift | 52 +++++++++++++++-- .../JavaClassMacroTests.swift | 34 +++++++++++ 13 files changed, 232 insertions(+), 25 deletions(-) create mode 100644 Samples/JavaKitSampleApp/Tests/JavaKitExampleTests/JavaKitOptionalTests.swift create mode 100644 Sources/SwiftJava/JavaString+Extensions.swift diff --git a/Samples/JavaKitSampleApp/Package.swift b/Samples/JavaKitSampleApp/Package.swift index 881b8d0ba..082d61ac9 100644 --- a/Samples/JavaKitSampleApp/Package.swift +++ b/Samples/JavaKitSampleApp/Package.swift @@ -78,5 +78,16 @@ let package = Package( .plugin(name: "SwiftJavaPlugin", package: "swift-java"), ] ), + + .testTarget( + name: "JavaKitExampleTests", + dependencies: [ + "JavaKitExample" + ], + swiftSettings: [ + .swiftLanguageMode(.v5), + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) + ] + ), ] ) diff --git a/Samples/JavaKitSampleApp/Sources/JavaKitExample/com/example/swift/ThreadSafeHelperClass.java b/Samples/JavaKitSampleApp/Sources/JavaKitExample/com/example/swift/ThreadSafeHelperClass.java index 3b7793f07..38fe1a741 100644 --- a/Samples/JavaKitSampleApp/Sources/JavaKitExample/com/example/swift/ThreadSafeHelperClass.java +++ b/Samples/JavaKitSampleApp/Sources/JavaKitExample/com/example/swift/ThreadSafeHelperClass.java @@ -23,7 +23,7 @@ public class ThreadSafeHelperClass { public ThreadSafeHelperClass() { } - public Optional text = Optional.of(""); + public Optional text = Optional.of("cool string"); public final OptionalDouble val = OptionalDouble.of(2); @@ -31,6 +31,20 @@ public String getValue(Optional name) { return name.orElse(""); } + + public String getOrElse(Optional name) { + return name.orElse("or else value"); + } + + public Optional getNil() { + return Optional.empty(); + } + + // @NonNull + // public Optional getNil() { + // return Optional.empty(); + // } + public Optional getText() { return text; } diff --git a/Samples/JavaKitSampleApp/Tests/JavaKitExampleTests/JavaKitOptionalTests.swift b/Samples/JavaKitSampleApp/Tests/JavaKitExampleTests/JavaKitOptionalTests.swift new file mode 100644 index 000000000..9ed14cd05 --- /dev/null +++ b/Samples/JavaKitSampleApp/Tests/JavaKitExampleTests/JavaKitOptionalTests.swift @@ -0,0 +1,56 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JavaKitExample + +import SwiftJava +import JavaUtilFunction +import Testing + +@Suite +struct ManglingTests { + + @Test + func methodMangling() throws { + let jvm = try! JavaVirtualMachine.shared( + classpath: [ + ".build/plugins/outputs/javakitsampleapp/JavaKitExample/destination/JavaCompilerPlugin/Java" + ] + ) + let env = try! jvm.environment() + + let helper = ThreadSafeHelperClass(environment: env) + + let text: JavaString? = helper.textOptional + #expect(#"Optional("cool string")"# == String(describing: Optional("cool string"))) + #expect(#"Optional("cool string")"# == String(describing: text)) + + // let defaultValue: String? = helper.getOrElse(JavaOptional.empty()) + // #expect(#"Optional("or else value")"# == String(describing: defaultValue)) + + let noneValue: JavaOptional = helper.getNil()! + #expect(noneValue.isPresent() == false) + #expect("\(noneValue)" == "SwiftJava.JavaOptional") + + let textFunc: JavaString? = helper.getTextOptional() + #expect(#"Optional("cool string")"# == String(describing: textFunc)) + + let doubleOpt: Double? = helper.valOptional + #expect(#"Optional(2.0)"# == String(describing: doubleOpt)) + + let longOpt: Int64? = helper.fromOptional(21 as Int32?) + #expect(#"Optional(21)"# == String(describing: longOpt)) + } + +} \ No newline at end of file diff --git a/Samples/JavaKitSampleApp/ci-validate.sh b/Samples/JavaKitSampleApp/ci-validate.sh index f453a00be..297f5c885 100755 --- a/Samples/JavaKitSampleApp/ci-validate.sh +++ b/Samples/JavaKitSampleApp/ci-validate.sh @@ -3,7 +3,9 @@ set -e set -x -swift build +swift build \ + --disable-experimental-prebuilts # FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 + "$JAVA_HOME/bin/java" \ -cp .build/plugins/outputs/javakitsampleapp/JavaKitExample/destination/JavaCompilerPlugin/Java \ -Djava.library.path=.build/debug \ diff --git a/Sources/SwiftJava/AnyJavaObject.swift b/Sources/SwiftJava/AnyJavaObject.swift index bf749820f..fe77bdbd4 100644 --- a/Sources/SwiftJava/AnyJavaObject.swift +++ b/Sources/SwiftJava/AnyJavaObject.swift @@ -59,7 +59,11 @@ public protocol AnyJavaObjectWithCustomClassLoader: AnyJavaObject { extension AnyJavaObject { /// Retrieve the underlying Java object. public var javaThis: jobject { - javaHolder.object! + javaHolder.object! // FIXME: this is a bad idea, can be null + } + + public var javaThisOptional: jobject? { + javaHolder.object } /// Retrieve the environment in which this Java object resides. diff --git a/Sources/SwiftJava/JavaObject+Inheritance.swift b/Sources/SwiftJava/JavaObject+Inheritance.swift index f306b9c6f..0ddd79449 100644 --- a/Sources/SwiftJava/JavaObject+Inheritance.swift +++ b/Sources/SwiftJava/JavaObject+Inheritance.swift @@ -22,16 +22,16 @@ extension AnyJavaObject { private func isInstanceOf( _ otherClass: OtherClass.Type ) -> jclass? { - try? otherClass.withJNIClass(in: javaEnvironment) { otherJavaClass in - if javaEnvironment.interface.IsInstanceOf( - javaEnvironment, - javaThis, - otherJavaClass - ) == 0 { - return nil - } + guard let this: jobject = javaThisOptional else { + return nil + } + + return try? otherClass.withJNIClass(in: javaEnvironment) { otherJavaClass in + if javaEnvironment.interface.IsInstanceOf(javaEnvironment, this, otherJavaClass) == 0 { + return nil + } - return otherJavaClass + return otherJavaClass } } diff --git a/Sources/SwiftJava/JavaString+Extensions.swift b/Sources/SwiftJava/JavaString+Extensions.swift new file mode 100644 index 000000000..1836777d2 --- /dev/null +++ b/Sources/SwiftJava/JavaString+Extensions.swift @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import CSwiftJavaJNI +import JavaTypes + +extension JavaString: CustomStringConvertible, CustomDebugStringConvertible { + public var description: String { + return toString() + } + public var debugDescription: String { + return "\"" + toString() + "\"" + } +} + +extension Optional where Wrapped == JavaString { + public var description: String { + switch self { + case .some(let value): "Optional(\(value.toString())" + case .none: "nil" + } + } +} \ No newline at end of file diff --git a/Sources/SwiftJava/Macros.swift b/Sources/SwiftJava/Macros.swift index 8e1bb86c5..bda70f14e 100644 --- a/Sources/SwiftJava/Macros.swift +++ b/Sources/SwiftJava/Macros.swift @@ -124,7 +124,9 @@ public macro JavaStaticField(_ javaFieldName: String? = nil, isFinal: Bool = fal /// /// corresponds to the Java constructor `HelloSwift(String name)`. @attached(body) -public macro JavaMethod() = #externalMacro(module: "SwiftJavaMacros", type: "JavaMethodMacro") +public macro JavaMethod( + genericResult: String? = nil +) = #externalMacro(module: "SwiftJavaMacros", type: "JavaMethodMacro") /// Attached macro that turns a Swift method on JavaClass into one that wraps /// a Java static method on the underlying Java class object. diff --git a/Sources/SwiftJava/Optional+JavaOptional.swift b/Sources/SwiftJava/Optional+JavaOptional.swift index c9becd584..8e3924fb1 100644 --- a/Sources/SwiftJava/Optional+JavaOptional.swift +++ b/Sources/SwiftJava/Optional+JavaOptional.swift @@ -79,3 +79,17 @@ public extension Optional where Wrapped == Int64 { } } } + +extension JavaOptional { + public func empty(environment: JNIEnvironment? = nil) -> JavaOptional! { + guard let env = try? environment ?? JavaVirtualMachine.shared().environment() else { + return nil + } + + guard let opt = try? JavaClass>(environment: env).empty() else { + return nil + } + + return opt.as(JavaOptional.self) + } +} diff --git a/Sources/SwiftJava/String+Extensions.swift b/Sources/SwiftJava/String+Extensions.swift index 94fb19286..b45f60bfb 100644 --- a/Sources/SwiftJava/String+Extensions.swift +++ b/Sources/SwiftJava/String+Extensions.swift @@ -13,10 +13,6 @@ //===----------------------------------------------------------------------===// import Foundation -// import SwiftJavaToolLib -// import SwiftJava -// import JavaUtilJar -// import SwiftJavaConfigurationShared extension String { /// For a String that's of the form java.util.Vector, return the "Vector" diff --git a/Sources/SwiftJava/generated/JavaOptional.swift b/Sources/SwiftJava/generated/JavaOptional.swift index 5f10005fc..3ea010708 100644 --- a/Sources/SwiftJava/generated/JavaOptional.swift +++ b/Sources/SwiftJava/generated/JavaOptional.swift @@ -3,8 +3,8 @@ import CSwiftJavaJNI @JavaClass("java.util.Optional") open class JavaOptional: JavaObject { - @JavaMethod - open func get() -> JavaObject! // FIXME: Currently we do generate -> T https://github.com/swiftlang/swift-java/issues/439 + @JavaMethod(genericResult: "T") + open func get() -> T! @JavaMethod open override func equals(_ arg0: JavaObject?) -> Bool diff --git a/Sources/SwiftJavaMacros/JavaMethodMacro.swift b/Sources/SwiftJavaMacros/JavaMethodMacro.swift index db8a3b367..d265bd5fd 100644 --- a/Sources/SwiftJavaMacros/JavaMethodMacro.swift +++ b/Sources/SwiftJavaMacros/JavaMethodMacro.swift @@ -47,18 +47,41 @@ extension JavaMethodMacro: BodyMacro { } guard let funcDecl = declaration.as(FunctionDeclSyntax.self) else { - fatalError("not a function") + fatalError("not a function: \(declaration)") } let isStatic = node.attributeName.trimmedDescription == "JavaStaticMethod" let funcName = funcDecl.name.text let params = funcDecl.signature.parameterClause.parameters - let resultType: String = - funcDecl.signature.returnClause.map { result in - ", resultType: \(result.type.typeReferenceString).self" - } ?? "" let paramNames = params.map { param in param.parameterName?.text ?? "" }.joined(separator: ", ") + let genericResultType: String? = + if case let .argumentList(arguments) = node.arguments, + let firstElement = arguments.first, + let stringLiteral = firstElement.expression + .as(StringLiteralExprSyntax.self), + stringLiteral.segments.count == 1, + case let .stringSegment(wrapperName)? = stringLiteral.segments.first { + "\(wrapperName)" + } else { + nil + } + + // Determine the result type + let resultType: String = + if let returnClause = funcDecl.signature.returnClause { + if let genericResultType { + // we need to type-erase the signature, because on JVM level generics are erased and we'd otherwise + // form a signature with the "concrete" type, which would not match the real byte-code level signature + // of the method we're trying to call -- which would result in a MethodNotFound exception. + ", resultType: /*type-erased:\(genericResultType)*/JavaObject?.self" + } else { + ", resultType: \(returnClause.type.typeReferenceString).self" + } + } else { + "" + } + let parametersAsArgs: String if paramNames.isEmpty { parametersAsArgs = "" @@ -70,8 +93,25 @@ extension JavaMethodMacro: BodyMacro { funcDecl.signature.effectSpecifiers?.throwsClause != nil ? "try" : "try!" + let resultSyntax: CodeBlockItemSyntax = + "\(raw: tryKeyword) dynamicJava\(raw: isStatic ? "Static" : "")MethodCall(methodName: \(literal: funcName)\(raw: parametersAsArgs)\(raw: resultType))" + + if let genericResultType { + return [ + """ + /* convert erased return value to \(raw: genericResultType) */ + if let result$ = \(resultSyntax) { + return \(raw: genericResultType)(javaThis: result$.javaThis, environment: try! JavaVirtualMachine.shared().environment()) + } else { + return nil + } + """ + ] + } + + // no return type conversions return [ - "return \(raw: tryKeyword) dynamicJava\(raw: isStatic ? "Static" : "")MethodCall(methodName: \(literal: funcName)\(raw: parametersAsArgs)\(raw: resultType))" + "return \(resultSyntax)" ] } diff --git a/Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift b/Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift index 7eead3d25..759e0cb89 100644 --- a/Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift +++ b/Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift @@ -296,5 +296,39 @@ class JavaKitMacroTests: XCTestCase { macros: Self.javaKitMacros ) } + + func testJavaOptionalGenericGet() throws { + assertMacroExpansion(""" + @JavaClass("java.lang.Optional") + open class JavaOptional: JavaObject { + @JavaMethod(genericResult: "T") + open func get() -> T! + } + """, + expandedSource: """ + + open class JavaOptional: JavaObject { + open func get() -> T! { + /* convert erased return value to T */ + if let result$ = try! dynamicJavaMethodCall(methodName: "get", resultType: /*type-erased:T*/ JavaObject?.self) { + return T(javaThis: result$.javaThis, environment: try! JavaVirtualMachine.shared().environment()) + } else { + return nil + } + } + + /// The full Java class name for this Swift type. + open override class var fullJavaClassName: String { + "java.lang.Optional" + } + + public required init(javaHolder: JavaObjectHolder) { + super.init(javaHolder: javaHolder) + } + } + """, + macros: Self.javaKitMacros + ) + } } From 705563d8fb9ace63aa8deed454903ba2c6241de0 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Thu, 13 Nov 2025 12:18:09 +0900 Subject: [PATCH 114/141] wrap-java: generate genericResult info in @JavaMethods --- Sources/SwiftJava/Macros.swift | 16 ++++++++ .../JavaClassTranslator.swift | 37 +++++++++++++++---- .../SwiftJavaToolLibTests/WrapJavaTests.swift | 33 ++++++++++++++++- 3 files changed, 77 insertions(+), 9 deletions(-) diff --git a/Sources/SwiftJava/Macros.swift b/Sources/SwiftJava/Macros.swift index bda70f14e..cc6891bbb 100644 --- a/Sources/SwiftJava/Macros.swift +++ b/Sources/SwiftJava/Macros.swift @@ -123,6 +123,22 @@ public macro JavaStaticField(_ javaFieldName: String? = nil, isFinal: Bool = fal /// ``` /// /// corresponds to the Java constructor `HelloSwift(String name)`. +/// +/// ### Generics and type-erasure +/// Swift and Java differ in how they represent generics at runtime. +/// In Java, generics are type-erased and the JVM representation of generic types is erased to `java.lang.Object`. +/// Swift on the other hand, reifies types which means a `Test` in practice will be a specific type with +/// the generic substituted `Test`. This means that at runtime, calling a generic @JavaMethod needs to know +/// which of the parameters (or result type) must be subjected to type-erasure as we form the call into the Java function. +/// +/// In order to mark a generic return type you must indicate it to the @JavaMethod macro like this: +/// ```swift +/// // Java: class Test { public get(); } +/// @JavaMethod(genericResult: "T!") +/// func get() -> T! +/// ``` +/// This allows the macro to form a call into the get() method, which at runtime, will have an `java.lang.Object` +/// returning method signature, and then, convert the result to the expected `T` type on the Swift side. @attached(body) public macro JavaMethod( genericResult: String? = nil diff --git a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift index 795639e5a..9ca2a4abc 100644 --- a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift @@ -643,11 +643,8 @@ extension JavaClassTranslator { preferValueTypes: true, outerOptional: .implicitlyUnwrappedOptional ) - // let resultType = try translator.getSwiftTypeNameAsString( - // javaMethod.getGenericReturnType()!, - // preferValueTypes: true, - // outerOptional: .implicitlyUnwrappedOptional - // ) + let typeEraseGenericResultType: Bool = + isGenericJavaType(javaMethod.getGenericReturnType()) // FIXME: cleanup the checking here if resultType != "Void" && resultType != "Swift.Void" { @@ -659,9 +656,33 @@ extension JavaClassTranslator { // --- Handle other effects let throwsStr = javaMethod.throwsCheckedException ? "throws" : "" let swiftMethodName = javaMethod.getName().escapedSwiftName - let methodAttribute: AttributeSyntax = implementedInSwift - ? "" - : javaMethod.isStatic ? "@JavaStaticMethod\n" : "@JavaMethod\n"; + + // Compute the '@...JavaMethod(...)' details + let methodAttribute: AttributeSyntax + if implementedInSwift { + methodAttribute = "" + } else { + var methodAttributeStr = + if javaMethod.isStatic { + "@JavaStaticMethod" + } else { + "@JavaMethod" + } + // Do we need to record any generic information, in order to enable type-erasure for the upcalls? + var parameters: [String] = [] + if typeEraseGenericResultType { + parameters.append("genericResult: \"\(resultType)\"") + } + // TODO: generic parameters? + if !parameters.isEmpty { + methodAttributeStr += "(" + methodAttributeStr.append(parameters.joined(separator: ", ")) + methodAttributeStr += ")" + } + methodAttributeStr += "\n" + methodAttribute = "\(raw: methodAttributeStr)" + } + let accessModifier = implementedInSwift ? "" : (javaMethod.isStatic || !translateAsClass) ? "public " : "open " diff --git a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift index 1c3d10d24..765a446e0 100644 --- a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift +++ b/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift @@ -227,7 +227,7 @@ final class WrapJavaTests: XCTestCase { ) } - func testGenericSuperclass() async throws { + func testWrapJavaGenericSuperclass() async throws { return // FIXME: we need this let classpathURL = try await compileJava( @@ -270,4 +270,35 @@ final class WrapJavaTests: XCTestCase { ] ) } + + func testWrapJavaGenericMethodTypeErasure_returnType() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + final class Kappa { + public T get() { return null; } + } + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.Kappa", + ], + classpath: [classpathURL], + expectedChunks: [ + """ + import CSwiftJavaJNI + import SwiftJava + """, + """ + @JavaClass("com.example.Kappa") + open class Kappa: JavaObject { + @JavaMethod(genericResult: "T!") + open func get() -> T! + } + """ + ] + ) + } } From 42cda8cbadc93d57f9a1f819ab32f20de08d28b5 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Thu, 13 Nov 2025 21:06:27 +0900 Subject: [PATCH 115/141] properly handle Optional::ofNullable, de-dupe generic params; use SortedSet --- Package.swift | 2 + Sources/SwiftJava/Macros.swift | 6 +- Sources/SwiftJava/generated/JavaLong.swift | 6 +- .../generated/JavaOptionalDouble.swift | 4 +- .../JavaClassTranslator.swift | 46 ++++++++----- Sources/SwiftJavaToolLib/JavaTranslator.swift | 11 ++-- .../WrapJavaTests/BasicWrapJavaTests.swift | 52 +++++++++++++++ .../GenericsWrapJavaTests.swift} | 66 ++++++++++--------- 8 files changed, 135 insertions(+), 58 deletions(-) create mode 100644 Tests/SwiftJavaToolLibTests/WrapJavaTests/BasicWrapJavaTests.swift rename Tests/SwiftJavaToolLibTests/{WrapJavaTests.swift => WrapJavaTests/GenericsWrapJavaTests.swift} (91%) diff --git a/Package.swift b/Package.swift index 7d872795a..b4c202082 100644 --- a/Package.swift +++ b/Package.swift @@ -209,6 +209,7 @@ let package = Package( .package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"), .package(url: "https://github.com/apple/swift-system", from: "1.4.0"), .package(url: "https://github.com/apple/swift-log", from: "1.2.0"), + .package(url: "https://github.com/apple/swift-collections", .upToNextMinor(from: "1.3.0")), // primarily for ordered collections // // FIXME: swift-subprocess stopped supporting 6.0 when it moved into a package; // // we'll need to drop 6.0 as well, but currently blocked on doing so by swiftpm plugin pending design questions @@ -400,6 +401,7 @@ let package = Package( name: "SwiftJavaToolLib", dependencies: [ .product(name: "Logging", package: "swift-log"), + .product(name: "OrderedCollections", package: "swift-collections"), .product(name: "SwiftBasicFormat", package: "swift-syntax"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), diff --git a/Sources/SwiftJava/Macros.swift b/Sources/SwiftJava/Macros.swift index cc6891bbb..f11c2fd3b 100644 --- a/Sources/SwiftJava/Macros.swift +++ b/Sources/SwiftJava/Macros.swift @@ -150,11 +150,13 @@ public macro JavaMethod( /// The macro must be used within a specific JavaClass instance. /// /// ```swift -/// @JavaMethod +/// @JavaStaticMethod /// func sayHelloBack(_ i: Int32) -> Double /// ``` @attached(body) -public macro JavaStaticMethod() = #externalMacro(module: "SwiftJavaMacros", type: "JavaMethodMacro") +public macro JavaStaticMethod( + genericResult: String? = nil +) = #externalMacro(module: "SwiftJavaMacros", type: "JavaMethodMacro") /// Macro that marks extensions to specify that all of the @JavaMethod /// methods are implementations of Java methods spelled as `native`. diff --git a/Sources/SwiftJava/generated/JavaLong.swift b/Sources/SwiftJava/generated/JavaLong.swift index 7ea8fc09e..4e993d65e 100644 --- a/Sources/SwiftJava/generated/JavaLong.swift +++ b/Sources/SwiftJava/generated/JavaLong.swift @@ -140,9 +140,6 @@ extension JavaClass { @JavaStaticMethod public func decode(_ arg0: String) throws -> JavaLong! - @JavaStaticMethod - public func highestOneBit(_ arg0: Int64) -> Int64 - @JavaStaticMethod public func sum(_ arg0: Int64, _ arg1: Int64) -> Int64 @@ -158,6 +155,9 @@ extension JavaClass { @JavaStaticMethod public func toBinaryString(_ arg0: Int64) -> String + @JavaStaticMethod + public func highestOneBit(_ arg0: Int64) -> Int64 + @JavaStaticMethod public func lowestOneBit(_ arg0: Int64) -> Int64 diff --git a/Sources/SwiftJava/generated/JavaOptionalDouble.swift b/Sources/SwiftJava/generated/JavaOptionalDouble.swift index 0d0e2eaeb..58aa94419 100644 --- a/Sources/SwiftJava/generated/JavaOptionalDouble.swift +++ b/Sources/SwiftJava/generated/JavaOptionalDouble.swift @@ -16,10 +16,10 @@ open class JavaOptionalDouble: JavaObject { open func isEmpty() -> Bool @JavaMethod - open func isPresent() -> Bool + open func orElse(_ arg0: Double) -> Double @JavaMethod - open func orElse(_ arg0: Double) -> Double + open func isPresent() -> Bool @JavaMethod open func orElseThrow() -> Double diff --git a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift index 9ca2a4abc..ab66004ca 100644 --- a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift @@ -15,6 +15,7 @@ import SwiftJava import JavaLangReflect import SwiftSyntax +import OrderedCollections import SwiftJavaConfigurationShared import Logging @@ -606,25 +607,35 @@ extension JavaClassTranslator { return false } - /// Translates the given Java method into a Swift declaration. - package func renderMethod( - _ javaMethod: Method, - implementedInSwift: Bool, - genericParameters: [String] = [], - whereClause: String = "" - ) throws -> DeclSyntax { - // Map the generic params on the method. - var allGenericParameters = genericParameters + // TODO: make it more precise with the "origin" of the generic parameter (outer class etc) + func collectMethodGenericParameters( + genericParameters: [String], + method javaMethod: Method + ) -> OrderedSet { + var allGenericParameters = OrderedSet(genericParameters) let typeParameters = javaMethod.getTypeParameters() if typeParameters.contains(where: {$0 != nil }) { - allGenericParameters += typeParameters.compactMap { typeParam in + allGenericParameters.appending(contentsOf: typeParameters.compactMap { typeParam in guard let typeParam else { return nil } guard genericParameterIsUsedInSignature(typeParam, in: javaMethod) else { return nil } return "\(typeParam.getTypeName()): AnyJavaObject" - } + }) } + + return allGenericParameters + } + + /// Translates the given Java method into a Swift declaration. + package func renderMethod( + _ javaMethod: Method, + implementedInSwift: Bool, + genericParameters: [String] = [], + whereClause: String = "" + ) throws -> DeclSyntax { + // Map the generic params on the method. + let allGenericParameters = collectMethodGenericParameters(genericParameters: genericParameters, method: javaMethod) let genericParameterClauseStr = if allGenericParameters.isEmpty { "" @@ -657,7 +668,7 @@ extension JavaClassTranslator { let throwsStr = javaMethod.throwsCheckedException ? "throws" : "" let swiftMethodName = javaMethod.getName().escapedSwiftName - // Compute the '@...JavaMethod(...)' details + // Compute the parameters for '@...JavaMethod(...)' let methodAttribute: AttributeSyntax if implementedInSwift { methodAttribute = "" @@ -674,6 +685,7 @@ extension JavaClassTranslator { parameters.append("genericResult: \"\(resultType)\"") } // TODO: generic parameters? + if !parameters.isEmpty { methodAttributeStr += "(" methodAttributeStr.append(parameters.joined(separator: ", ")) @@ -690,6 +702,7 @@ extension JavaClassTranslator { ? "override " : "" + // FIXME: refactor this so we don't have to duplicate the method signatures if resultType.optionalWrappedType() != nil || parameters.contains(where: { $0.type.trimmedDescription.optionalWrappedType() != nil }) { let parameters = parameters.map { param -> (clause: FunctionParameterSyntax, passedArg: String) in let name = param.secondName!.trimmedDescription @@ -711,7 +724,8 @@ extension JavaClassTranslator { } - return """ + return + """ \(methodAttribute)\(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)\(raw: genericParameterClauseStr)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause) \(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)Optional\(raw: genericParameterClauseStr)(\(raw: parameters.map(\.clause.description).joined(separator: ", ")))\(raw: throwsStr) -> \(raw: resultOptional)\(raw: whereClause) { @@ -719,7 +733,8 @@ extension JavaClassTranslator { } """ } else { - return """ + return + """ \(methodAttribute)\(raw: accessModifier)\(raw: overrideOpt)func \(raw: swiftMethodName)\(raw: genericParameterClauseStr)(\(raw: parametersStr))\(raw: throwsStr)\(raw: resultTypeStr)\(raw: whereClause) """ } @@ -900,6 +915,7 @@ struct MethodCollector { } // MARK: Utility functions + extension JavaClassTranslator { /// Determine whether this method is an override of another Java /// method. @@ -935,7 +951,7 @@ extension JavaClassTranslator { return true } } catch { - // FIXME: logging + log.warning("Failed to determine if method '\(method)' is an override, error: \(error)") } } diff --git a/Sources/SwiftJavaToolLib/JavaTranslator.swift b/Sources/SwiftJavaToolLib/JavaTranslator.swift index 71e011794..3fcd9a6de 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator.swift @@ -138,12 +138,11 @@ extension JavaTranslator { // Special handle the case when the return type is the generic type of the method: ` T foo()` - // if isGenericJavaType(genericReturnType) { - // print("[swift] generic method! \(method.getDeclaringClass().getName()).\(method.getName())") - // getGenericJavaTypeOriginInfo(genericReturnType, from: method) - // } - - return try getSwiftTypeNameAsString(method: method, genericReturnType!, preferValueTypes: preferValueTypes, outerOptional: outerOptional) + return try getSwiftTypeNameAsString( + method: method, + genericReturnType!, + preferValueTypes: preferValueTypes, + outerOptional: outerOptional) } /// Turn a Java type into a string. diff --git a/Tests/SwiftJavaToolLibTests/WrapJavaTests/BasicWrapJavaTests.swift b/Tests/SwiftJavaToolLibTests/WrapJavaTests/BasicWrapJavaTests.swift new file mode 100644 index 000000000..d4596c901 --- /dev/null +++ b/Tests/SwiftJavaToolLibTests/WrapJavaTests/BasicWrapJavaTests.swift @@ -0,0 +1,52 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +@_spi(Testing) import SwiftJava +import SwiftJavaToolLib +import JavaUtilJar +import SwiftJavaShared +import JavaNet +import SwiftJavaConfigurationShared +import _Subprocess +import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 + +final class BasicWrapJavaTests: XCTestCase { + + func testWrapJavaFromCompiledJavaSource() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + class ExampleSimpleClass {} + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.ExampleSimpleClass" + ], + classpath: [classpathURL], + expectedChunks: [ + """ + import CSwiftJavaJNI + import SwiftJava + """, + """ + @JavaClass("com.example.ExampleSimpleClass") + open class ExampleSimpleClass: JavaObject { + """ + ] + ) + } + +} \ No newline at end of file diff --git a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift b/Tests/SwiftJavaToolLibTests/WrapJavaTests/GenericsWrapJavaTests.swift similarity index 91% rename from Tests/SwiftJavaToolLibTests/WrapJavaTests.swift rename to Tests/SwiftJavaToolLibTests/WrapJavaTests/GenericsWrapJavaTests.swift index 765a446e0..c20b0d623 100644 --- a/Tests/SwiftJavaToolLibTests/WrapJavaTests.swift +++ b/Tests/SwiftJavaToolLibTests/WrapJavaTests/GenericsWrapJavaTests.swift @@ -21,35 +21,8 @@ import SwiftJavaConfigurationShared import _Subprocess import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 -final class WrapJavaTests: XCTestCase { +final class GenericsWrapJavaTests: XCTestCase { - func testWrapJavaFromCompiledJavaSource() async throws { - let classpathURL = try await compileJava( - """ - package com.example; - - class ExampleSimpleClass {} - """) - - try assertWrapJavaOutput( - javaClassNames: [ - "com.example.ExampleSimpleClass" - ], - classpath: [classpathURL], - expectedChunks: [ - """ - import CSwiftJavaJNI - import SwiftJava - """, - """ - @JavaClass("com.example.ExampleSimpleClass") - open class ExampleSimpleClass: JavaObject { - """ - ] - ) - } - - // @Test func testWrapJavaGenericMethod_singleGeneric() async throws { let classpathURL = try await compileJava( """ @@ -271,7 +244,7 @@ final class WrapJavaTests: XCTestCase { ) } - func testWrapJavaGenericMethodTypeErasure_returnType() async throws { + func test_wrapJava_genericMethodTypeErasure_returnType() async throws { let classpathURL = try await compileJava( """ package com.example; @@ -301,4 +274,37 @@ final class WrapJavaTests: XCTestCase { ] ) } -} + + func test_wrapJava_genericMethodTypeErasure_ofNullableOptional() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + final class Optional { + public static Optional ofNullable(T value) { return null; } + } + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.Optional" + ], + classpath: [classpathURL], + expectedChunks: [ + """ + @JavaClass("com.example.Optional") + open class Optional: JavaObject { + + } + """, + """ + extension JavaClass { + @JavaStaticMethod(genericResult: "Optional!") + public func ofNullable(_ arg0: T?) -> Optional! where ObjectType == Optional + } + """ + ] + ) + } + +} \ No newline at end of file From 29f088adfcc5dbeea89d89c08b35f7e27d1d35dd Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Thu, 13 Nov 2025 22:54:15 +0900 Subject: [PATCH 116/141] further improvements in handling type erased return types --- .../JavaClassTranslator.swift | 29 ++++++----- .../JavaGenericsSupport.swift | 22 ++++++++ Sources/SwiftJavaToolLib/JavaTranslator.swift | 1 - .../Java2SwiftTests.swift | 4 +- .../WrapJavaTests/GenericsWrapJavaTests.swift | 51 +++++++++++++++---- 5 files changed, 80 insertions(+), 27 deletions(-) diff --git a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift index ab66004ca..01c747c30 100644 --- a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift @@ -610,18 +610,19 @@ extension JavaClassTranslator { // TODO: make it more precise with the "origin" of the generic parameter (outer class etc) func collectMethodGenericParameters( genericParameters: [String], - method javaMethod: Method + method: Method ) -> OrderedSet { var allGenericParameters = OrderedSet(genericParameters) - let typeParameters = javaMethod.getTypeParameters() - if typeParameters.contains(where: {$0 != nil }) { - allGenericParameters.appending(contentsOf: typeParameters.compactMap { typeParam in - guard let typeParam else { return nil } - guard genericParameterIsUsedInSignature(typeParam, in: javaMethod) else { - return nil - } - return "\(typeParam.getTypeName()): AnyJavaObject" - }) + + let typeParameters = method.getTypeParameters() + for typeParameter in typeParameters { + guard let typeParameter else { continue } + + guard genericParameterIsUsedInSignature(typeParameter, in: method) else { + continue + } + + allGenericParameters.append("\(typeParameter.getTypeName()): AnyJavaObject") } return allGenericParameters @@ -654,8 +655,8 @@ extension JavaClassTranslator { preferValueTypes: true, outerOptional: .implicitlyUnwrappedOptional ) - let typeEraseGenericResultType: Bool = - isGenericJavaType(javaMethod.getGenericReturnType()) + let hasTypeEraseGenericResultType: Bool = + isTypeErased(javaMethod.getGenericReturnType()) // FIXME: cleanup the checking here if resultType != "Void" && resultType != "Swift.Void" { @@ -681,7 +682,7 @@ extension JavaClassTranslator { } // Do we need to record any generic information, in order to enable type-erasure for the upcalls? var parameters: [String] = [] - if typeEraseGenericResultType { + if hasTypeEraseGenericResultType { parameters.append("genericResult: \"\(resultType)\"") } // TODO: generic parameters? @@ -951,7 +952,7 @@ extension JavaClassTranslator { return true } } catch { - log.warning("Failed to determine if method '\(method)' is an override, error: \(error)") + log.debug("Failed to determine if method '\(method)' is an override, error: \(error)") } } diff --git a/Sources/SwiftJavaToolLib/JavaGenericsSupport.swift b/Sources/SwiftJavaToolLib/JavaGenericsSupport.swift index 7c7819629..1d847276d 100644 --- a/Sources/SwiftJavaToolLib/JavaGenericsSupport.swift +++ b/Sources/SwiftJavaToolLib/JavaGenericsSupport.swift @@ -87,3 +87,25 @@ func isGenericJavaType(_ type: Type?) -> Bool { return false } + +/// Check if a type is type-erased att runtime. +/// +/// E.g. in a method returning a generic `T` the T is type erased and must +/// be represented as a `java.lang.Object` instead. +func isTypeErased(_ type: Type?) -> Bool { + guard let type else { + return false + } + + // Check if it's a type variable (e.g., T, E, etc.) + if type.as(TypeVariable.self) != nil { + return true + } + + // Check if it's a wildcard type (e.g., ? extends Number, ? super String) + if type.as(WildcardType.self) != nil { + return true + } + + return false +} diff --git a/Sources/SwiftJavaToolLib/JavaTranslator.swift b/Sources/SwiftJavaToolLib/JavaTranslator.swift index 3fcd9a6de..1c71afdae 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator.swift @@ -133,7 +133,6 @@ extension JavaTranslator { preferValueTypes: Bool, outerOptional: OptionalKind ) throws -> String { - // let returnType = method.getReturnType() let genericReturnType = method.getGenericReturnType() // Special handle the case when the return type is the generic type of the method: ` T foo()` diff --git a/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift index e302fdc5a..f2bf868c7 100644 --- a/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift +++ b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift @@ -262,7 +262,7 @@ class Java2SwiftTests: XCTestCase { public struct MyJavaObjects { """, """ - @JavaStaticMethod + @JavaStaticMethod(genericResult: "T!") public func requireNonNull(_ arg0: T?, _ arg1: MySupplier?) -> T """, ] @@ -475,7 +475,7 @@ class Java2SwiftTests: XCTestCase { public struct MyJavaIntFunction { """, """ - @JavaMethod + @JavaMethod(genericResult: "R!") public func apply(_ arg0: Int32) -> R! """, ] diff --git a/Tests/SwiftJavaToolLibTests/WrapJavaTests/GenericsWrapJavaTests.swift b/Tests/SwiftJavaToolLibTests/WrapJavaTests/GenericsWrapJavaTests.swift index c20b0d623..fa8f05b6a 100644 --- a/Tests/SwiftJavaToolLibTests/WrapJavaTests/GenericsWrapJavaTests.swift +++ b/Tests/SwiftJavaToolLibTests/WrapJavaTests/GenericsWrapJavaTests.swift @@ -62,8 +62,8 @@ final class GenericsWrapJavaTests: XCTestCase { open class ExampleSimpleClass: JavaObject { """, """ - @JavaMethod - open func getGeneric(_ arg0: Item?) -> KeyType + @JavaMethod(genericResult: "KeyType!") + open func getGeneric(_ arg0: Item?) -> KeyType! """, ] ) @@ -100,8 +100,8 @@ final class GenericsWrapJavaTests: XCTestCase { classpath: [classpathURL], expectedChunks: [ """ - @JavaMethod - open func getGeneric() -> KeyType + @JavaMethod(genericResult: "KeyType!") + open func getGeneric() -> KeyType! """, ] ) @@ -162,8 +162,10 @@ final class GenericsWrapJavaTests: XCTestCase { """ package com.example; + // Mini decls in order to avoid warnings about some funcs we're not yet importing cleanly final class List {} final class Map {} + final class Number {} class GenericClass { public T getClassGeneric() { return null; } @@ -184,18 +186,41 @@ final class GenericsWrapJavaTests: XCTestCase { try assertWrapJavaOutput( javaClassNames: [ + "com.example.Map", + "com.example.List", + "com.example.Number", "com.example.GenericClass", ], classpath: [classpathURL], expectedChunks: [ + """ + @JavaMethod(genericResult: "T!") + open func getClassGeneric() -> T! + """, + """ + @JavaMethod(genericResult: "M!") + open func getMethodGeneric() -> M! + """, """ @JavaMethod - open func getClassGeneric() -> T + open func getMixedGeneric() -> Map! """, """ @JavaMethod open func getNonGeneric() -> String """, + """ + @JavaMethod + open func getParameterizedClassGeneric() -> List! + """, + """ + @JavaMethod + open func getWildcard() -> List! + """, + """ + @JavaMethod + open func getGenericArray() -> [T?] + """, ] ) } @@ -275,13 +300,15 @@ final class GenericsWrapJavaTests: XCTestCase { ) } - func test_wrapJava_genericMethodTypeErasure_ofNullableOptional() async throws { + func test_wrapJava_genericMethodTypeErasure_ofNullableOptional_staticMethods() async throws { let classpathURL = try await compileJava( """ package com.example; final class Optional { public static Optional ofNullable(T value) { return null; } + + public static T nonNull(T value) { return null; } } """) @@ -294,14 +321,18 @@ final class GenericsWrapJavaTests: XCTestCase { """ @JavaClass("com.example.Optional") open class Optional: JavaObject { - - } """, """ extension JavaClass { - @JavaStaticMethod(genericResult: "Optional!") - public func ofNullable(_ arg0: T?) -> Optional! where ObjectType == Optional + """, + """ + @JavaStaticMethod + public func ofNullable(_ arg0: T?) -> Optional! where ObjectType == Optional } + """, + """ + @JavaStaticMethod(genericResult: "T!") + public func nonNull(_ arg0: T?) -> T! where ObjectType == Optional """ ] ) From 78311ba2bd32871571de65361747c061ef1f8a83 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Thu, 13 Nov 2025 23:08:38 +0900 Subject: [PATCH 117/141] rename genericResult: param to typeErasedResult: --- Sources/SwiftJava/Macros.swift | 6 +-- .../SwiftJava/generated/JavaOptional.swift | 2 +- Sources/SwiftJavaMacros/JavaMethodMacro.swift | 10 ++++- .../JavaClassTranslator.swift | 2 +- .../JavaClassMacroTests.swift | 2 +- .../Java2SwiftTests.swift | 4 +- .../WrapJavaTests/GenericsWrapJavaTests.swift | 44 ++++++++++++++++--- 7 files changed, 55 insertions(+), 15 deletions(-) diff --git a/Sources/SwiftJava/Macros.swift b/Sources/SwiftJava/Macros.swift index f11c2fd3b..eb9c43745 100644 --- a/Sources/SwiftJava/Macros.swift +++ b/Sources/SwiftJava/Macros.swift @@ -134,14 +134,14 @@ public macro JavaStaticField(_ javaFieldName: String? = nil, isFinal: Bool = fal /// In order to mark a generic return type you must indicate it to the @JavaMethod macro like this: /// ```swift /// // Java: class Test { public get(); } -/// @JavaMethod(genericResult: "T!") +/// @JavaMethod(typeErasedResult: "T!") /// func get() -> T! /// ``` /// This allows the macro to form a call into the get() method, which at runtime, will have an `java.lang.Object` /// returning method signature, and then, convert the result to the expected `T` type on the Swift side. @attached(body) public macro JavaMethod( - genericResult: String? = nil + typeErasedResult: String? = nil ) = #externalMacro(module: "SwiftJavaMacros", type: "JavaMethodMacro") /// Attached macro that turns a Swift method on JavaClass into one that wraps @@ -155,7 +155,7 @@ public macro JavaMethod( /// ``` @attached(body) public macro JavaStaticMethod( - genericResult: String? = nil + typeErasedResult: String? = nil ) = #externalMacro(module: "SwiftJavaMacros", type: "JavaMethodMacro") /// Macro that marks extensions to specify that all of the @JavaMethod diff --git a/Sources/SwiftJava/generated/JavaOptional.swift b/Sources/SwiftJava/generated/JavaOptional.swift index 3ea010708..f3b7adcab 100644 --- a/Sources/SwiftJava/generated/JavaOptional.swift +++ b/Sources/SwiftJava/generated/JavaOptional.swift @@ -3,7 +3,7 @@ import CSwiftJavaJNI @JavaClass("java.util.Optional") open class JavaOptional: JavaObject { - @JavaMethod(genericResult: "T") + @JavaMethod(typeErasedResult: "T") open func get() -> T! @JavaMethod diff --git a/Sources/SwiftJavaMacros/JavaMethodMacro.swift b/Sources/SwiftJavaMacros/JavaMethodMacro.swift index d265bd5fd..f992ee760 100644 --- a/Sources/SwiftJavaMacros/JavaMethodMacro.swift +++ b/Sources/SwiftJavaMacros/JavaMethodMacro.swift @@ -62,7 +62,15 @@ extension JavaMethodMacro: BodyMacro { .as(StringLiteralExprSyntax.self), stringLiteral.segments.count == 1, case let .stringSegment(wrapperName)? = stringLiteral.segments.first { - "\(wrapperName)" + // TODO: Improve this unwrapping a bit; + // Trim the trailing ! and ? from the type for purposes + // of initializing the type wrapper in the method body + if "\(wrapperName)".hasSuffix("!") || + "\(wrapperName)".hasSuffix("?") { + String("\(wrapperName)".dropLast()) + } else { + "\(wrapperName)" + } } else { nil } diff --git a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift index 01c747c30..0f7aa45d6 100644 --- a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift @@ -683,7 +683,7 @@ extension JavaClassTranslator { // Do we need to record any generic information, in order to enable type-erasure for the upcalls? var parameters: [String] = [] if hasTypeEraseGenericResultType { - parameters.append("genericResult: \"\(resultType)\"") + parameters.append("typeErasedResult: \"\(resultType)\"") } // TODO: generic parameters? diff --git a/Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift b/Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift index 759e0cb89..5cbeae162 100644 --- a/Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift +++ b/Tests/SwiftJavaMacrosTests/JavaClassMacroTests.swift @@ -301,7 +301,7 @@ class JavaKitMacroTests: XCTestCase { assertMacroExpansion(""" @JavaClass("java.lang.Optional") open class JavaOptional: JavaObject { - @JavaMethod(genericResult: "T") + @JavaMethod(typeErasedResult: "T") open func get() -> T! } """, diff --git a/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift index f2bf868c7..a3b33ba75 100644 --- a/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift +++ b/Tests/SwiftJavaToolLibTests/Java2SwiftTests.swift @@ -262,7 +262,7 @@ class Java2SwiftTests: XCTestCase { public struct MyJavaObjects { """, """ - @JavaStaticMethod(genericResult: "T!") + @JavaStaticMethod(typeErasedResult: "T!") public func requireNonNull(_ arg0: T?, _ arg1: MySupplier?) -> T """, ] @@ -475,7 +475,7 @@ class Java2SwiftTests: XCTestCase { public struct MyJavaIntFunction { """, """ - @JavaMethod(genericResult: "R!") + @JavaMethod(typeErasedResult: "R!") public func apply(_ arg0: Int32) -> R! """, ] diff --git a/Tests/SwiftJavaToolLibTests/WrapJavaTests/GenericsWrapJavaTests.swift b/Tests/SwiftJavaToolLibTests/WrapJavaTests/GenericsWrapJavaTests.swift index fa8f05b6a..cba7b38f5 100644 --- a/Tests/SwiftJavaToolLibTests/WrapJavaTests/GenericsWrapJavaTests.swift +++ b/Tests/SwiftJavaToolLibTests/WrapJavaTests/GenericsWrapJavaTests.swift @@ -62,7 +62,7 @@ final class GenericsWrapJavaTests: XCTestCase { open class ExampleSimpleClass: JavaObject { """, """ - @JavaMethod(genericResult: "KeyType!") + @JavaMethod(typeErasedResult: "KeyType!") open func getGeneric(_ arg0: Item?) -> KeyType! """, ] @@ -100,7 +100,7 @@ final class GenericsWrapJavaTests: XCTestCase { classpath: [classpathURL], expectedChunks: [ """ - @JavaMethod(genericResult: "KeyType!") + @JavaMethod(typeErasedResult: "KeyType!") open func getGeneric() -> KeyType! """, ] @@ -194,11 +194,11 @@ final class GenericsWrapJavaTests: XCTestCase { classpath: [classpathURL], expectedChunks: [ """ - @JavaMethod(genericResult: "T!") + @JavaMethod(typeErasedResult: "T!") open func getClassGeneric() -> T! """, """ - @JavaMethod(genericResult: "M!") + @JavaMethod(typeErasedResult: "M!") open func getMethodGeneric() -> M! """, """ @@ -292,7 +292,7 @@ final class GenericsWrapJavaTests: XCTestCase { """ @JavaClass("com.example.Kappa") open class Kappa: JavaObject { - @JavaMethod(genericResult: "T!") + @JavaMethod(typeErasedResult: "T!") open func get() -> T! } """ @@ -331,11 +331,43 @@ final class GenericsWrapJavaTests: XCTestCase { } """, """ - @JavaStaticMethod(genericResult: "T!") + @JavaStaticMethod(typeErasedResult: "T!") public func nonNull(_ arg0: T?) -> T! where ObjectType == Optional """ ] ) } + + // TODO: this should be improved some more, we need to generated a `: Map` on the Swift side + func test_wrapJava_genericMethodTypeErasure_genericExtendsMap() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + final class Map {} + + final class Something { + public > M putIn(M map) { return null; } + } + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.Map", + "com.example.Something", + ], + classpath: [classpathURL], + expectedChunks: [ + """ + @JavaClass("com.example.Something") + open class Something: JavaObject { + """, + """ + @JavaMethod(typeErasedResult: "M!") + open func putIn(_ arg0: M?) -> M! + """, + ] + ) + } } \ No newline at end of file From bed0e03fb89ae3ddaefa5f81a5b5bf7b7ef43e8d Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Fri, 14 Nov 2025 12:22:19 +0900 Subject: [PATCH 118/141] Allow // comments in swift-java.config "JSON with comments" --- .../Configuration.swift | 24 +++++++++++++- .../SwiftJavaConfigurationTests.swift | 33 +++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 Tests/SwiftJavaConfigurationSharedTests/SwiftJavaConfigurationTests.swift diff --git a/Sources/SwiftJavaConfigurationShared/Configuration.swift b/Sources/SwiftJavaConfigurationShared/Configuration.swift index 0fb5494f1..a840a7cff 100644 --- a/Sources/SwiftJavaConfigurationShared/Configuration.swift +++ b/Sources/SwiftJavaConfigurationShared/Configuration.swift @@ -157,15 +157,37 @@ public func readConfiguration(sourceDir: String, file: String = #fileID, line: U return try readConfiguration(configPath: configPath, file: file, line: line) } +/// Read a swift-java.config file at the specified path. +/// +/// Configuration is expected to be "JSON-with-comments". +/// Specifically "//" comments are allowed and will be trimmed before passing the rest of the config into a standard JSON parser. public func readConfiguration(configPath: URL, file: String = #fileID, line: UInt = #line) throws -> Configuration? { guard let configData = try? Data(contentsOf: configPath) else { return nil } + guard let configString = String(data: configData, encoding: .utf8) else { + return nil + } + + return try readConfiguration(string: configString, configPath: configPath) +} + +public func readConfiguration(string: String, configPath: URL?, file: String = #fileID, line: UInt = #line) throws -> Configuration? { + let cleanedConfigString = string + .split(separator: "\n") + .filter { line in + !line.trimmingCharacters(in: .whitespaces).starts(with: "//") + }.joined(separator: "\n") + + guard let configData = cleanedConfigString.data(using: .utf8) else { + return nil + } + do { return try JSONDecoder().decode(Configuration.self, from: configData) } catch { - throw ConfigurationError(message: "Failed to parse SwiftJava configuration at '\(configPath.absoluteURL)'! \(#fileID):\(#line)", error: error, + throw ConfigurationError(message: "Failed to parse SwiftJava configuration at '\(configPath.map({ $0.absoluteURL.description }) ?? "")'! \(#fileID):\(#line)", error: error, file: file, line: line) } } diff --git a/Tests/SwiftJavaConfigurationSharedTests/SwiftJavaConfigurationTests.swift b/Tests/SwiftJavaConfigurationSharedTests/SwiftJavaConfigurationTests.swift new file mode 100644 index 000000000..c94669866 --- /dev/null +++ b/Tests/SwiftJavaConfigurationSharedTests/SwiftJavaConfigurationTests.swift @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024-2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftJavaConfigurationShared +import Testing + +@Suite +struct SwiftJavaConfigurationTests { + + @Test + func parseJSONWithComments() throws { + let config = try readConfiguration(string: + """ + // some comments + { + // anywhere is ok + "classpath": "" + } + """, configPath: nil) + } +} + From 99ae03082fcdb604e3946edd2305d3ae52cedce4 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 14 Nov 2025 12:44:29 +0900 Subject: [PATCH 119/141] Remove hardcoded precondition about protobuf --- Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift b/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift index 3a6468d57..f56d26c5c 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift @@ -49,8 +49,6 @@ package extension JavaTranslator { package init(swiftType: SwiftTypeName, javaTypes: [JavaFullyQualifiedTypeName]) { self.swiftType = swiftType self.javaTypes = javaTypes - precondition(!javaTypes.contains("com.google.protobuf.AbstractMessage$Builder"), - "\(swiftType) mapped as \(javaTypes)\n\(CommandLine.arguments.joined(separator: " "))") // XXX } } From eb84283516c89a39c5c2c4290852f113e3b4de8d Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Fri, 14 Nov 2025 19:20:56 +0900 Subject: [PATCH 120/141] Allow JSON5 in JSONDecoder I hadn't realized JSONDecoder has gained JSON5 support, so we don't need to do the manual // stripping and can just enable 5 mode when decoding JSON :-) --- Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift | 4 +++- .../SwiftJavaConfigurationShared/Configuration.swift | 12 ++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift b/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift index f90150fd0..93c358df0 100644 --- a/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift +++ b/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift @@ -38,7 +38,9 @@ struct JavaCompilerBuildToolPlugin: BuildToolPlugin { let config: Configuration? if let configData = try? Data(contentsOf: configFile) { - config = try? JSONDecoder().decode(Configuration.self, from: configData) + let decoder = JSONDecoder() + decoder.allowsJSON5 = true + config = try? decoder.decode(Configuration.self, from: configData) } else { config = nil } diff --git a/Sources/SwiftJavaConfigurationShared/Configuration.swift b/Sources/SwiftJavaConfigurationShared/Configuration.swift index a840a7cff..493ee4bc4 100644 --- a/Sources/SwiftJavaConfigurationShared/Configuration.swift +++ b/Sources/SwiftJavaConfigurationShared/Configuration.swift @@ -174,18 +174,14 @@ public func readConfiguration(configPath: URL, file: String = #fileID, line: UIn } public func readConfiguration(string: String, configPath: URL?, file: String = #fileID, line: UInt = #line) throws -> Configuration? { - let cleanedConfigString = string - .split(separator: "\n") - .filter { line in - !line.trimmingCharacters(in: .whitespaces).starts(with: "//") - }.joined(separator: "\n") - - guard let configData = cleanedConfigString.data(using: .utf8) else { + guard let configData = string.data(using: .utf8) else { return nil } do { - return try JSONDecoder().decode(Configuration.self, from: configData) + let decoder = JSONDecoder() + decoder.allowsJSON5 = true + return try decoder.decode(Configuration.self, from: configData) } catch { throw ConfigurationError(message: "Failed to parse SwiftJava configuration at '\(configPath.map({ $0.absoluteURL.description }) ?? "")'! \(#fileID):\(#line)", error: error, file: file, line: line) From 7a314a639d7375c002e3d04e713bd505d4ca8697 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Fri, 14 Nov 2025 19:38:56 +0900 Subject: [PATCH 121/141] add docs about swift-java.config --- .../Documentation.docc/SwiftJavaCommandLineTool.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md index 32a82eb48..e600651b8 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md @@ -254,3 +254,14 @@ public final class SomeModule ... { public static void globalFunction() { ... } } ``` + +### The swift-java.config file + +Many of the tools–as well as SwiftPM plugin's–behaviors can be configured using the `swift-java.config` file. + +You can refer to the ``SwiftJavaSharedConfiguration/Configuration`` struct to learn about the supported options. + +Configuration from the config files may be overriden or augmented by explicit command line parameters, +please refer to the options documentation for details on their behavior. + +> Note: **Comments in configuration**: The configuration is a JSON 5 file, which among other things allows `//` and `/* */` comments, so feel free to add line comments explaining rationale for some of the settings in youf configuration. \ No newline at end of file From 64066bfbebd379476dc3d8956e1fde307d6c58da Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Mon, 17 Nov 2025 11:00:51 +0900 Subject: [PATCH 122/141] scripts: improve validate_docs to not add the dependency if already there --- .github/scripts/validate_docs.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/scripts/validate_docs.sh b/.github/scripts/validate_docs.sh index 8ee777b00..5e5539430 100755 --- a/.github/scripts/validate_docs.sh +++ b/.github/scripts/validate_docs.sh @@ -3,11 +3,17 @@ set -e set -x -cat <> Package.swift +DEPENDENCY='.package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.0.0")' + +if grep -q "$DEPENDENCY" Package.swift; then + echo "Package.swift already contains 'swift-docc-plugin" +else + cat <> Package.swift package.dependencies.append( - .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.0.0") + $DEPENDENCY ) EOF +fi swift package --disable-sandbox plugin generate-documentation --target "SwiftJavaDocumentation" --warnings-as-errors --analyze From 61fa6ab10e1c7b960b4d230a28d69cd565a24f78 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Mon, 17 Nov 2025 11:20:39 +0900 Subject: [PATCH 123/141] resolve docs issue --- .../Documentation.docc/SwiftJavaCommandLineTool.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md index e600651b8..823713836 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaCommandLineTool.md @@ -259,7 +259,7 @@ public final class SomeModule ... { Many of the tools–as well as SwiftPM plugin's–behaviors can be configured using the `swift-java.config` file. -You can refer to the ``SwiftJavaSharedConfiguration/Configuration`` struct to learn about the supported options. +You can refer to the `SwiftJavaConfigurationShared/Configuration` struct to learn about the supported options. Configuration from the config files may be overriden or augmented by explicit command line parameters, please refer to the options documentation for details on their behavior. From a9add7fe8017e84fde278b58e0866f8ce6d53fd4 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Fri, 14 Nov 2025 15:15:17 +0900 Subject: [PATCH 124/141] wrap-java: correct importing nested types We cannot just blindly visit all getClasses() because this includes types form super classes as well. This is useful in general, but in this context we specifically want types which are nested in this exact declaration, not its parens. --- Sources/SwiftJava/String+Extensions.swift | 3 +- .../Commands/WrapJavaCommand.swift | 27 +++++++++---- .../JavaTranslator+Validation.swift | 12 ++---- .../WrapJavaTests/BasicWrapJavaTests.swift | 39 +++++++++++++++++++ 4 files changed, 64 insertions(+), 17 deletions(-) diff --git a/Sources/SwiftJava/String+Extensions.swift b/Sources/SwiftJava/String+Extensions.swift index b45f60bfb..0af0de107 100644 --- a/Sources/SwiftJava/String+Extensions.swift +++ b/Sources/SwiftJava/String+Extensions.swift @@ -15,8 +15,7 @@ import Foundation extension String { - /// For a String that's of the form java.util.Vector, return the "Vector" - /// part. + /// For a String that's of the form java.util.Vector, return the "Vector" part. package var defaultSwiftNameForJavaClass: String { if let dotLoc = lastIndex(of: ".") { let afterDot = index(after: dotLoc) diff --git a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift index ae985e772..0af1e75bb 100644 --- a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift +++ b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift @@ -180,21 +180,24 @@ extension SwiftJava.WrapJavaCommand { // of classes to be translated if they were already specified. var allClassesToVisit = javaClasses var currentClassIndex: Int = 0 - while currentClassIndex < allClassesToVisit.count { + outerClassLoop: while currentClassIndex < allClassesToVisit.count { defer { currentClassIndex += 1 } - // The current class we're in. + // The current top-level class we're in. let currentClass = allClassesToVisit[currentClassIndex] + let currentClassName = currentClass.getName() guard let currentSwiftName = translator.translatedClasses[currentClass.getName()]?.swiftType else { continue } - // Find all of the nested classes that weren't explicitly translated - // already. - let nestedClasses: [JavaClass] = currentClass.getClasses().compactMap { nestedClass in - guard let nestedClass else { return nil } + // Find all of the nested classes that weren't explicitly translated already. + let nestedAndSuperclassNestedClasses = currentClass.getClasses() // watch out, this includes nested types from superclasses + let nestedClasses: [JavaClass] = nestedAndSuperclassNestedClasses.compactMap { nestedClass in + guard let nestedClass else { + return nil + } // If this is a local class, we're done. let javaClassName = nestedClass.getName() @@ -202,6 +205,14 @@ extension SwiftJava.WrapJavaCommand { return nil } + // We only want to visit and import types which are explicitly inside this decl, + // and NOT any of the types contained in the super classes. That would violate our "current class" + // nesting, because those are *actually* nested in the other class, not "the current one" (i.e. in a super class). + guard javaClassName.hasPrefix(currentClassName) else { + log.trace("Skip super-class nested class '\(javaClassName)', is not member of \(currentClassName). Will be visited independently.") + return nil + } + // If we have an inclusive filter, import only types from it for include in config.filterInclude ?? [] { guard javaClassName.starts(with: include) else { @@ -227,7 +238,9 @@ extension SwiftJava.WrapJavaCommand { .defaultSwiftNameForJavaClass let swiftName = "\(currentSwiftName).\(swiftUnqualifiedName)" - translator.translatedClasses[javaClassName] = SwiftTypeName(module: nil, name: swiftName) + let translatedSwiftName = SwiftTypeName(module: nil, name: swiftName) + translator.translatedClasses[javaClassName] = translatedSwiftName + log.debug("Record translated Java class '\(javaClassName)' -> \(translatedSwiftName)") return nestedClass } diff --git a/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift b/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift index f56d26c5c..8071c36ad 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator+Validation.swift @@ -58,10 +58,10 @@ package extension JavaTranslator { package var description: String { switch self { case .multipleClassesMappedToSameName(let swiftToJavaMapping): - """ - The following Java classes were mapped to the same Swift type name: - \(swiftToJavaMapping.map(mappingDescription(mapping:)).joined(separator: "\n")) - """ + """ + The following Java classes were mapped to the same Swift type name: + \(swiftToJavaMapping.map(mappingDescription(mapping:)).joined(separator: "\n")) + """ } } @@ -72,10 +72,6 @@ package extension JavaTranslator { } } func validateClassConfiguration() throws(ValidationError) { - // for a in translatedClasses { - // print("MAPPING = \(a.key) -> \(a.value.swiftModule?.escapedSwiftName ?? "").\(a.value.swiftType.escapedSwiftName)") - // } - // Group all classes by swift name let groupedDictionary: [SwiftTypeName: [(JavaFullyQualifiedTypeName, SwiftTypeName)]] = Dictionary(grouping: translatedClasses, by: { // SwiftTypeName(swiftType: $0.value.swiftType, swiftModule: $0.value.swiftModule) diff --git a/Tests/SwiftJavaToolLibTests/WrapJavaTests/BasicWrapJavaTests.swift b/Tests/SwiftJavaToolLibTests/WrapJavaTests/BasicWrapJavaTests.swift index d4596c901..74ed3ce28 100644 --- a/Tests/SwiftJavaToolLibTests/WrapJavaTests/BasicWrapJavaTests.swift +++ b/Tests/SwiftJavaToolLibTests/WrapJavaTests/BasicWrapJavaTests.swift @@ -49,4 +49,43 @@ final class BasicWrapJavaTests: XCTestCase { ) } + func test_wrapJava_doNotDupeImportNestedClassesFromSuperclassAutomatically() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + class SuperClass { + class SuperNested {} + } + + class ExampleSimpleClass { + class SimpleNested {} + } + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.SuperClass", + "com.example.SuperClass$SuperNested", + "com.example.ExampleSimpleClass", + ], + classpath: [classpathURL], + expectedChunks: [ + """ + @JavaClass("com.example.SuperClass") + open class SuperClass: JavaObject { + """, + // FIXME: the mapping configuration could be used to nest this properly but today we don't automatically? + """ + @JavaClass("com.example.SuperClass$SuperNested") + open class SuperNested: JavaObject { + """, + """ + @JavaClass("com.example.SuperClass") + open class SuperClass: JavaObject { + """, + ] + ) + } + } \ No newline at end of file From 5f7ac58f57c59b1cea36c321744519cb2ad9772d Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Mon, 17 Nov 2025 13:38:59 +0900 Subject: [PATCH 125/141] Fix how we handle appending to empty config Also cleanup handling filtering in wrap-java a bit --- Sources/JavaStdlib/JavaIO/swift-java.config | 2 +- .../Commands/ConfigureCommand.swift | 10 ++-- .../Commands/WrapJavaCommand.swift | 55 +++++++++---------- Sources/SwiftJavaTool/CommonOptions.swift | 6 +- 4 files changed, 38 insertions(+), 35 deletions(-) diff --git a/Sources/JavaStdlib/JavaIO/swift-java.config b/Sources/JavaStdlib/JavaIO/swift-java.config index 7b40b2da1..571e02162 100644 --- a/Sources/JavaStdlib/JavaIO/swift-java.config +++ b/Sources/JavaStdlib/JavaIO/swift-java.config @@ -10,10 +10,10 @@ "java.lang.Readable" : "Readable", "java.io.Writer" : "Writer", "java.io.File" : "File", + "java.io.Closeable" : "Closeable", "java.nio.file.Path" : "Path", "java.io.FileDescriptor" : "FileDescriptor", "java.nio.charset.Charset" : "Charset", - "java.io.Closeable" : "Closeable", "java.io.Flushable" : "Flushable", "java.io.Flushable" : "ByteBuffer", "java.nio.file.WatchService" : "WatchService", diff --git a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift index 837d5e2f2..455cb962d 100644 --- a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift +++ b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift @@ -104,11 +104,11 @@ extension SwiftJava.ConfigureCommand { log.logLevel = .init(rawValue: self.logLevel.rawValue)! log.info("Run: emit configuration...") - var (amendExistingConfig, configuration) = try getBaseConfigurationForWrite() + var (amendExistingConfig, config) = try getBaseConfigurationForWrite() if !self.commonOptions.filterInclude.isEmpty { log.debug("Generate Java->Swift type mappings. Active include filters: \(self.commonOptions.filterInclude)") - } else if let filters = configuration.filterInclude, !filters.isEmpty { + } else if let filters = config.filterInclude, !filters.isEmpty { // take the package filter from the configuration file self.commonOptions.filterInclude = filters } else { @@ -124,7 +124,7 @@ extension SwiftJava.ConfigureCommand { if amendExistingConfig { log.info("Amend existing swift-java.config file...") } - configuration.classpath = classpathEntries.joined(separator: ":") // TODO: is this correct? + config.classpath = classpathEntries.joined(separator: ":") // TODO: is this correct? // Import types from all the classpath entries; // Note that we use the package level filtering, so users have some control over what gets imported. @@ -139,7 +139,7 @@ extension SwiftJava.ConfigureCommand { if entry.hasSuffix(".jar") { let jarFile = try JarFile(entry, false, environment: environment) try addJavaToSwiftMappings( - to: &configuration, + to: &config, forJar: jarFile, environment: environment ) @@ -151,7 +151,7 @@ extension SwiftJava.ConfigureCommand { } // Encode the configuration. - let contents = try configuration.renderJSON() + let contents = try config.renderJSON() // Write the file. try writeContents( diff --git a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift index 0af1e75bb..0e70ad001 100644 --- a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift +++ b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift @@ -66,6 +66,7 @@ extension SwiftJava { extension SwiftJava.WrapJavaCommand { mutating func runSwiftJavaCommand(config: inout Configuration) async throws { + print("self.commonOptions.filterInclude = \(self.commonOptions.filterInclude)") configure(&config.filterInclude, append: self.commonOptions.filterInclude) configure(&config.filterExclude, append: self.commonOptions.filterExclude) @@ -114,6 +115,7 @@ extension SwiftJava.WrapJavaCommand { } extension SwiftJava.WrapJavaCommand { + mutating func generateWrappers( config: Configuration, dependentConfigs: [(String, Configuration)], @@ -148,21 +150,9 @@ extension SwiftJava.WrapJavaCommand { let classLoader = try! JavaClass(environment: environment) .getSystemClassLoader()! var javaClasses: [JavaClass] = [] - eachClass: for (javaClassName, _) in config.classes ?? [:] { - - // If we have an inclusive filter, import only types from it - for include in config.filterInclude ?? [] { - guard javaClassName.starts(with: include) else { - log.info("Skip Java type: \(javaClassName) (does not match filter)") - continue - } - } - // If we have an exclude filter, check for it as well - for exclude in config.filterExclude ?? [] { - if javaClassName.starts(with: exclude) { - log.info("Skip Java type: \(javaClassName) (does match exclude filter: \(exclude))") - continue eachClass - } + for (javaClassName, _) in config.classes ?? [:] { + guard shouldImportJavaClass(javaClassName, config: config) else { + continue } log.info("Wrapping java type: \(javaClassName)") @@ -213,19 +203,8 @@ extension SwiftJava.WrapJavaCommand { return nil } - // If we have an inclusive filter, import only types from it - for include in config.filterInclude ?? [] { - guard javaClassName.starts(with: include) else { - log.info("Skip Java type: \(javaClassName) (does not match filter)") - return nil - } - } - // If we have an exclude filter, check for it as well - for exclude in config.filterExclude ?? [] { - if javaClassName.starts(with: exclude) { - log.info("Skip Java type: \(javaClassName) (does match exclude filter: \(exclude))") - return nil - } + guard shouldImportJavaClass(javaClassName, config: config) else { + return nil } // If this class has been explicitly mentioned, we're done. @@ -285,4 +264,24 @@ extension SwiftJava.WrapJavaCommand { ) } } + + private func shouldImportJavaClass(_ javaClassName: String, config: Configuration) -> Bool { + // If we have an inclusive filter, import only types from it + for include in config.filterInclude ?? [] { + guard javaClassName.starts(with: include) else { + log.info("Skip Java type: \(javaClassName) (does not match include filter: \(include))") + return false + } + } + // If we have an exclude filter, check for it as well + for exclude in config.filterExclude ?? [] { + if javaClassName.starts(with: exclude) { + log.info("Skip Java type: \(javaClassName) (does match exclude filter: \(exclude))") + return false + } + } + + // The class matches import filters, if any, and was not excluded. + return true + } } diff --git a/Sources/SwiftJavaTool/CommonOptions.swift b/Sources/SwiftJavaTool/CommonOptions.swift index ebdfdbea0..c313202bc 100644 --- a/Sources/SwiftJavaTool/CommonOptions.swift +++ b/Sources/SwiftJavaTool/CommonOptions.swift @@ -38,7 +38,11 @@ extension HasCommonOptions { func configure(_ setting: inout [T]?, append value: [T]?) { if let value { - setting?.append(contentsOf: value) + if setting == nil { + setting = value + } else { + setting?.append(contentsOf: value) + } } } From c8023b7bc38140d911e15edb4026bcfc61e69af3 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Mon, 17 Nov 2025 16:39:35 +0900 Subject: [PATCH 126/141] fix mistake in how filtering applied to wrap java --- .../SwiftJavaTool/Commands/WrapJavaCommand.swift | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift index 0e70ad001..51315102a 100644 --- a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift +++ b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift @@ -267,9 +267,19 @@ extension SwiftJava.WrapJavaCommand { private func shouldImportJavaClass(_ javaClassName: String, config: Configuration) -> Bool { // If we have an inclusive filter, import only types from it - for include in config.filterInclude ?? [] { - guard javaClassName.starts(with: include) else { - log.info("Skip Java type: \(javaClassName) (does not match include filter: \(include))") + if let includes = config.filterInclude, !includes.isEmpty { + let anyIncludeFilterMatched = includes.contains { include in + if javaClassName.starts(with: include) { + // TODO: lower to trace level + log.info("Skip Java type: \(javaClassName) (does not match any include filter)") + return true + } + + return false + } + + guard anyIncludeFilterMatched else { + log.info("Skip Java type: \(javaClassName) (does not match any include filter)") return false } } From 0f65d46e02e328c165cc48f8aef4d0a8604781e8 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Thu, 20 Nov 2025 16:50:26 +0900 Subject: [PATCH 127/141] config: log-level should encode as string --- .../SwiftTypeInSubDirectory.swift | 0 .../Sources/MySwiftLibrary/swift-java.config | 3 +- .../swift/SwiftTypeInSubDirectoryTest.java | 71 +++++++++++++++++++ .../Sources/MySwiftLibrary/swift-java.config | 2 +- .../Configuration.swift | 17 +++-- 5 files changed, 87 insertions(+), 6 deletions(-) create mode 100644 Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/LibrarySubDirectory/SwiftTypeInSubDirectory.swift create mode 100644 Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/SwiftTypeInSubDirectoryTest.java diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/LibrarySubDirectory/SwiftTypeInSubDirectory.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/LibrarySubDirectory/SwiftTypeInSubDirectory.swift new file mode 100644 index 000000000..e69de29bb diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/swift-java.config b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/swift-java.config index 6e5bc2af1..3a76a8d89 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/swift-java.config +++ b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/swift-java.config @@ -1,3 +1,4 @@ { - "javaPackage": "com.example.swift" + "javaPackage": "com.example.swift", + "logLevel": "trace" } diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/SwiftTypeInSubDirectoryTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/SwiftTypeInSubDirectoryTest.java new file mode 100644 index 000000000..1f0464eb6 --- /dev/null +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/SwiftTypeInSubDirectoryTest.java @@ -0,0 +1,71 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.SwiftLibraries; +import org.swift.swiftkit.ffm.AllocatingSwiftArena; + +import java.io.File; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +public class MySwiftClassTest { + + void checkPaths(Throwable throwable) { + var paths = SwiftLibraries.getJavaLibraryPath().split(":"); + for (var path : paths) { + Stream.of(new File(path).listFiles()) + .filter(file -> !file.isDirectory()) + .forEach((file) -> { + System.out.println(" - " + file.getPath()); + }); + } + + throw new RuntimeException(throwable); + } + + @Test + void test_MySwiftClass_voidMethod() { + try(var arena = AllocatingSwiftArena.ofConfined()) { + MySwiftClass o = MySwiftClass.init(12, 42, arena); + o.voidMethod(); + } catch (Throwable throwable) { + checkPaths(throwable); + } + } + + @Test + void test_MySwiftClass_makeIntMethod() { + try(var arena = AllocatingSwiftArena.ofConfined()) { + MySwiftClass o = MySwiftClass.init(12, 42, arena); + var got = o.makeIntMethod(); + assertEquals(12, got); + } + } + + @Test + @Disabled // TODO: Need var mangled names in interfaces + void test_MySwiftClass_property_len() { + try(var arena = AllocatingSwiftArena.ofConfined()) { + MySwiftClass o = MySwiftClass.init(12, 42, arena); + var got = o.getLen(); + assertEquals(12, got); + } + } + +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config index be44c2fd7..3d6a12012 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config @@ -1,5 +1,5 @@ { "javaPackage": "com.example.swift", "mode": "jni", - "logLevel": ["debug"] + "logLevel": "debug" } diff --git a/Sources/SwiftJavaConfigurationShared/Configuration.swift b/Sources/SwiftJavaConfigurationShared/Configuration.swift index 493ee4bc4..661cf9633 100644 --- a/Sources/SwiftJavaConfigurationShared/Configuration.swift +++ b/Sources/SwiftJavaConfigurationShared/Configuration.swift @@ -183,7 +183,10 @@ public func readConfiguration(string: String, configPath: URL?, file: String = # decoder.allowsJSON5 = true return try decoder.decode(Configuration.self, from: configData) } catch { - throw ConfigurationError(message: "Failed to parse SwiftJava configuration at '\(configPath.map({ $0.absoluteURL.description }) ?? "")'! \(#fileID):\(#line)", error: error, + throw ConfigurationError( + message: "Failed to parse SwiftJava configuration at '\(configPath.map({ $0.absoluteURL.description }) ?? "")'! \(#fileID):\(#line)", + error: error, + text: string, file: file, line: line) } } @@ -276,19 +279,21 @@ extension Configuration { public struct ConfigurationError: Error { let message: String let error: any Error + let text: String? let file: String let line: UInt - init(message: String, error: any Error, file: String = #fileID, line: UInt = #line) { + init(message: String, error: any Error, text: String?, file: String = #fileID, line: UInt = #line) { self.message = message self.error = error + self.text = text self.file = file self.line = line } } -public enum LogLevel: String, Codable, Hashable { +public enum LogLevel: String, ExpressibleByStringLiteral, Codable, Hashable { case trace = "trace" case debug = "debug" case info = "info" @@ -296,11 +301,15 @@ public enum LogLevel: String, Codable, Hashable { case warning = "warning" case error = "error" case critical = "critical" + + public init(stringLiteral value: String) { + self = LogLevel(rawValue: value) ?? .info + } } extension LogLevel { public init(from decoder: any Decoder) throws { - var container = try decoder.unkeyedContainer() + var container = try decoder.singleValueContainer() let string = try container.decode(String.self) switch string { case "trace": self = .trace From 6728fc6d7f475ffde38a01214dfddd07ec744c14 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Thu, 20 Nov 2025 16:53:29 +0900 Subject: [PATCH 128/141] Delete Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/LibrarySubDirectory/SwiftTypeInSubDirectory.swift --- .../LibrarySubDirectory/SwiftTypeInSubDirectory.swift | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/LibrarySubDirectory/SwiftTypeInSubDirectory.swift diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/LibrarySubDirectory/SwiftTypeInSubDirectory.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/LibrarySubDirectory/SwiftTypeInSubDirectory.swift deleted file mode 100644 index e69de29bb..000000000 From 6a75aeb726e87da413b97682be2c966d8eaaabbe Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Thu, 20 Nov 2025 19:33:47 +0900 Subject: [PATCH 129/141] Delete Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/SwiftTypeInSubDirectoryTest.java --- .../swift/SwiftTypeInSubDirectoryTest.java | 71 ------------------- 1 file changed, 71 deletions(-) delete mode 100644 Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/SwiftTypeInSubDirectoryTest.java diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/SwiftTypeInSubDirectoryTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/SwiftTypeInSubDirectoryTest.java deleted file mode 100644 index 1f0464eb6..000000000 --- a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/SwiftTypeInSubDirectoryTest.java +++ /dev/null @@ -1,71 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -package com.example.swift; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.swift.swiftkit.core.SwiftLibraries; -import org.swift.swiftkit.ffm.AllocatingSwiftArena; - -import java.io.File; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.*; - -public class MySwiftClassTest { - - void checkPaths(Throwable throwable) { - var paths = SwiftLibraries.getJavaLibraryPath().split(":"); - for (var path : paths) { - Stream.of(new File(path).listFiles()) - .filter(file -> !file.isDirectory()) - .forEach((file) -> { - System.out.println(" - " + file.getPath()); - }); - } - - throw new RuntimeException(throwable); - } - - @Test - void test_MySwiftClass_voidMethod() { - try(var arena = AllocatingSwiftArena.ofConfined()) { - MySwiftClass o = MySwiftClass.init(12, 42, arena); - o.voidMethod(); - } catch (Throwable throwable) { - checkPaths(throwable); - } - } - - @Test - void test_MySwiftClass_makeIntMethod() { - try(var arena = AllocatingSwiftArena.ofConfined()) { - MySwiftClass o = MySwiftClass.init(12, 42, arena); - var got = o.makeIntMethod(); - assertEquals(12, got); - } - } - - @Test - @Disabled // TODO: Need var mangled names in interfaces - void test_MySwiftClass_property_len() { - try(var arena = AllocatingSwiftArena.ofConfined()) { - MySwiftClass o = MySwiftClass.init(12, 42, arena); - var got = o.getLen(); - assertEquals(12, got); - } - } - -} From 3fa3d449ec60bb524c34ed461973319ea7b10074 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Mon, 1 Dec 2025 08:24:05 +0100 Subject: [PATCH 130/141] Fix `allowGlobalAutomatic` mode for interfaces (#466) --- ...t2JavaGenerator+JavaBindingsPrinting.swift | 13 +++++++- .../MemoryManagementModeTests.swift | 30 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 07d23dc0f..1e18cbe8f 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -424,7 +424,11 @@ extension JNISwift2JavaGenerator { ) } - private func printJavaBindingWrapperMethod(_ printer: inout CodePrinter, _ decl: ImportedFunc, signaturesOnly: Bool) { + private func printJavaBindingWrapperMethod( + _ printer: inout CodePrinter, + _ decl: ImportedFunc, + signaturesOnly: Bool + ) { guard let translatedDecl = translatedDecl(for: decl) else { fatalError("Decl was not translated, \(decl)") } @@ -470,6 +474,13 @@ extension JNISwift2JavaGenerator { if let importedFunc { printDeclDocumentation(&printer, importedFunc) } + var modifiers = modifiers + + // If we are a protocol, we emit this as default method + if importedFunc?.parentType?.asNominalTypeDeclaration?.kind == .protocol { + modifiers.insert("default", at: 1) + } + printer.printBraceBlock("\(annotationsStr)\(modifiers.joined(separator: " ")) \(resultType) \(translatedDecl.name)(\(parametersStr))\(throwsClause)") { printer in let globalArenaName = "SwiftMemoryManagement.GLOBAL_SWIFT_JAVA_ARENA" let arguments = translatedDecl.translatedFunctionSignature.parameters.map(\.parameter.name) + [globalArenaName] diff --git a/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift b/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift index 6017c9875..2228aad88 100644 --- a/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift +++ b/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift @@ -74,4 +74,34 @@ struct MemoryManagementModeTests { ] ) } + + @Test + func allowGlobalAutomatic_protocol() throws { + var config = Configuration() + config.memoryManagementMode = .allowGlobalAutomatic + + try assertOutput( + input: + """ + public class MyClass {} + + public protocol MyProtocol { + public func f() -> MyClass + } + """, + config: config, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public default MyClass f() { + return f(SwiftMemoryManagement.GLOBAL_SWIFT_JAVA_ARENA); + } + """, + """ + public MyClass f(SwiftArena swiftArena$); + """ + ] + ) + } } From 6eb4705a483a1455563bf2d4f098c7c2e5e5cdf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Bukowiecki?= Date: Mon, 1 Dec 2025 08:24:38 +0100 Subject: [PATCH 131/141] Improve readme section of Java installation from SDKMan . (#461) Co-authored-by: pelekon <13712101+pelekon@users.noreply.github.com> --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index abca3b9dd..7494366e2 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,12 @@ Alternatively, you can use a JDK manager like [sdkman](https://sdkman.io/install $ export JAVA_HOME="$(sdk home java current)" ``` +E.g sdkman install command: + +```bash +sdk install java 25.0.1-amzn +``` + ## Self-publish supporting Java libraries Swift-java relies on supporting libraries that are under active development and not yet published to Maven Central. To use the project, you'll need to self-publish these libraries locally so your Java project can depend on them. From 8d6365c29072f339849e8e06a2dbd10a65b96d51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Bukowiecki?= Date: Mon, 1 Dec 2025 08:26:30 +0100 Subject: [PATCH 132/141] jextract (ffm, jni): Subscripts support (#459) Co-authored-by: pelekon <13712101+pelekon@users.noreply.github.com> --- .../MySwiftLibrary/MySwiftStruct.swift | 22 ++ .../swift/swiftkitffm/MySwiftStructTest.java | 22 ++ .../MySwiftLibrary/MySwiftStruct.swift | 22 ++ .../com/example/swift/MySwiftStructTest.java | 22 ++ ...Swift2JavaGenerator+FunctionLowering.swift | 19 +- ...MSwift2JavaGenerator+JavaTranslation.swift | 4 +- Sources/JExtractSwiftLib/ImportedDecls.swift | 4 + ...ISwift2JavaGenerator+JavaTranslation.swift | 4 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 13 ++ .../JExtractSwiftLib/Swift2JavaVisitor.swift | 151 +++++++++---- .../SwiftTypes/SwiftFunctionSignature.swift | 149 +++++++++--- .../JExtractSwiftLib/ThunkNameRegistry.swift | 4 +- .../Documentation.docc/SupportedFeatures.md | 2 +- .../FFM/FFMSubscriptsTests.swift | 212 ++++++++++++++++++ .../JNI/JNISubscriptsTests.swift | 155 +++++++++++++ 15 files changed, 717 insertions(+), 88 deletions(-) create mode 100644 Tests/JExtractSwiftTests/FFM/FFMSubscriptsTests.swift create mode 100644 Tests/JExtractSwiftTests/JNI/JNISubscriptsTests.swift diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift index 363e06834..5b5c2d322 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift +++ b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift @@ -16,10 +16,14 @@ public struct MySwiftStruct { private var cap: Int private var len: Int + private var subscriptValue: Int + private var subscriptArray: [Int] public init(cap: Int, len: Int) { self.cap = cap self.len = len + self.subscriptValue = 0 + self.subscriptArray = [10, 20, 15, 75] } public func voidMethod() { @@ -61,4 +65,22 @@ public struct MySwiftStruct { public func makeRandomIntMethod() -> Int { return Int.random(in: 1..<256) } + + public func getSubscriptValue() -> Int { + return self.subscriptValue + } + + public func getSubscriptArrayValue(index: Int) -> Int { + return self.subscriptArray[index] + } + + public subscript() -> Int { + get { return subscriptValue } + set { subscriptValue = newValue } + } + + public subscript(index: Int) -> Int { + get { return subscriptArray[index] } + set { subscriptArray[index] = newValue } + } } diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftStructTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftStructTest.java index 6b994137c..d904f7e82 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftStructTest.java +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/org/swift/swiftkitffm/MySwiftStructTest.java @@ -33,4 +33,26 @@ void create_struct() { assertEquals(len, struct.getLength()); } } + + @Test + void testSubscript() { + try (var arena = AllocatingSwiftArena.ofConfined()) { + MySwiftStruct s = MySwiftStruct.init(1337, 42, arena); + long currentValue = s.getSubscript(); + s.setSubscript(66); + assertEquals(0, currentValue); + assertEquals(66, s.getSubscriptValue()); + } + } + + @Test + void testSubscriptWithParams() { + try (var arena = AllocatingSwiftArena.ofConfined()) { + MySwiftStruct s = MySwiftStruct.init(1337, 42, arena); + long currentValue = s.getSubscript(1); + s.setSubscript(1, 66); + assertEquals(20, currentValue); + assertEquals(66, s.getSubscriptArrayValue(1)); + } + } } diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift index ddd77132d..34686f410 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift @@ -15,10 +15,14 @@ public struct MySwiftStruct { private var cap: Int64 public var len: Int64 + private var subscriptValue: Int64 + private var subscriptArray: [Int64] public init(cap: Int64, len: Int64) { self.cap = cap self.len = len + self.subscriptValue = 0 + self.subscriptArray = [10, 20, 15, 75] } public init?(doInit: Bool) { @@ -38,4 +42,22 @@ public struct MySwiftStruct { self.cap += value return self.cap } + + public func getSubscriptValue() -> Int64 { + return self.subscriptValue + } + + public func getSubscriptArrayValue(index: Int64) -> Int64 { + return self.subscriptArray[Int(index)] + } + + public subscript() -> Int64 { + get { return subscriptValue } + set { subscriptValue = newValue } + } + + public subscript(index: Int64) -> Int64 { + get { return subscriptArray[Int(index)] } + set { subscriptArray[Int(index)] = newValue } + } } diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java index e52e19591..24b1fdbf9 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftStructTest.java @@ -61,4 +61,26 @@ void increaseCap() { assertEquals(1347, s.getCapacity()); } } + + @Test + void testSubscript() { + try (var arena = SwiftArena.ofConfined()) { + MySwiftStruct s = MySwiftStruct.init(1337, 42, arena); + long currentValue = s.getSubscript(); + s.setSubscript(66); + assertEquals(0, currentValue); + assertEquals(66, s.getSubscriptValue()); + } + } + + @Test + void testSubscriptWithParams() { + try (var arena = SwiftArena.ofConfined()) { + MySwiftStruct s = MySwiftStruct.init(1337, 42, arena); + long currentValue = s.getSubscript(1); + s.setSubscript(1, 66); + assertEquals(20, currentValue); + assertEquals(66, s.getSubscriptArrayValue(1)); + } + } } \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index 832aff262..a7da370b3 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -812,11 +812,10 @@ extension LoweredFunctionSignature { // Build callee expression. let callee: ExprSyntax = if let selfExpr { - if case .initializer = apiKind { + switch apiKind { // Don't bother to create explicit ${Self}.init expression. - selfExpr - } else { - ExprSyntax(MemberAccessExprSyntax(base: selfExpr, name: .identifier(swiftAPIName))) + case .initializer, .subscriptGetter, .subscriptSetter: selfExpr + default: ExprSyntax(MemberAccessExprSyntax(base: selfExpr, name: .identifier(swiftAPIName))) } } else { ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(swiftAPIName))) @@ -845,6 +844,18 @@ extension LoweredFunctionSignature { case .enumCase: // This should not be called, but let's fatalError. fatalError("Enum cases are not supported with FFM.") + + case .subscriptGetter: + let parameters = paramExprs.map { $0.description }.joined(separator: ", ") + resultExpr = "\(callee)[\(raw: parameters)]" + case .subscriptSetter: + assert(paramExprs.count >= 1) + + var argumentsWithoutNewValue = paramExprs + let newValueArgument = argumentsWithoutNewValue.removeLast() + + let parameters = argumentsWithoutNewValue.map { $0.description }.joined(separator: ", ") + resultExpr = "\(callee)[\(raw: parameters)] = \(newValueArgument)" } // Lower the result. diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 72da323c1..76284b787 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -144,8 +144,8 @@ extension FFMSwift2JavaGenerator { // Name. let javaName = switch decl.apiKind { - case .getter: decl.javaGetterName - case .setter: decl.javaSetterName + case .getter, .subscriptGetter: decl.javaGetterName + case .setter, .subscriptSetter: decl.javaSetterName case .function, .initializer, .enumCase: decl.name } diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 9ba1cd6f6..b9cc2d497 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -23,6 +23,8 @@ package enum SwiftAPIKind { case getter case setter case enumCase + case subscriptGetter + case subscriptSetter } /// Describes a Swift nominal type (e.g., a class, struct, enum) that has been @@ -179,6 +181,8 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible { case .setter: "setter:" case .enumCase: "case:" case .function, .initializer: "" + case .subscriptGetter: "subscriptGetter:" + case .subscriptSetter: "subscriptSetter:" } let context = if let parentType { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 9c3cdbf1a..c8e4bbf34 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -176,8 +176,8 @@ extension JNISwift2JavaGenerator { // Name. let javaName = switch decl.apiKind { - case .getter: decl.javaGetterName - case .setter: decl.javaSetterName + case .getter, .subscriptGetter: decl.javaGetterName + case .setter, .subscriptSetter: decl.javaSetterName case .function, .initializer, .enumCase: decl.name } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 91a0b0e73..d3b477326 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -348,6 +348,19 @@ extension JNISwift2JavaGenerator { } result = "\(callee).\(decl.name) = \(newValueArgument)" + case .subscriptGetter: + let parameters = arguments.joined(separator: ", ") + result = "\(callee)[\(parameters)]" + case .subscriptSetter: + guard let newValueArgument = arguments.last else { + fatalError("Setter did not contain newValue parameter: \(decl)") + } + + var argumentsWithoutNewValue = arguments + argumentsWithoutNewValue.removeLast() + + let parameters = argumentsWithoutNewValue.joined(separator: ", ") + result = "\(callee)[\(parameters)] = \(newValueArgument)" } // Lower the result. diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index 247b26626..ab1ce32fe 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift @@ -13,9 +13,9 @@ //===----------------------------------------------------------------------===// import Foundation +import SwiftJavaConfigurationShared import SwiftParser import SwiftSyntax -import SwiftJavaConfigurationShared final class Swift2JavaVisitor { let translator: Swift2JavaTranslator @@ -53,9 +53,9 @@ final class Swift2JavaVisitor { case .extensionDecl(let node): self.visit(extensionDecl: node, in: parent, sourceFilePath: sourceFilePath) case .typeAliasDecl: - break // TODO: Implement; https://github.com/swiftlang/swift-java/issues/338 + break // TODO: Implement; https://github.com/swiftlang/swift-java/issues/338 case .associatedTypeDecl: - break // TODO: Implement associated types + break // TODO: Implement associated types case .initializerDecl(let node): self.visit(initializerDecl: node, in: parent) @@ -63,9 +63,8 @@ final class Swift2JavaVisitor { self.visit(functionDecl: node, in: parent, sourceFilePath: sourceFilePath) case .variableDecl(let node): self.visit(variableDecl: node, in: parent, sourceFilePath: sourceFilePath) - case .subscriptDecl: - // TODO: Implement subscripts - break + case .subscriptDecl(let node): + self.visit(subscriptDecl: node, in: parent) case .enumCaseDecl(let node): self.visit(enumCaseDecl: node, in: parent) @@ -75,7 +74,8 @@ final class Swift2JavaVisitor { } func visit( - nominalDecl node: some DeclSyntaxProtocol & DeclGroupSyntax & NamedDeclSyntax & WithAttributesSyntax & WithModifiersSyntax, + nominalDecl node: some DeclSyntaxProtocol & DeclGroupSyntax & NamedDeclSyntax + & WithAttributesSyntax & WithModifiersSyntax, in parent: ImportedNominalType?, sourceFilePath: String ) { @@ -115,7 +115,7 @@ final class Swift2JavaVisitor { } func visit( - functionDecl node: FunctionDeclSyntax, + functionDecl node: FunctionDeclSyntax, in typeContext: ImportedNominalType?, sourceFilePath: String ) { @@ -154,7 +154,7 @@ final class Swift2JavaVisitor { } func visit( - enumCaseDecl node: EnumCaseDeclSyntax, + enumCaseDecl node: EnumCaseDeclSyntax, in typeContext: ImportedNominalType? ) { guard let typeContext else { @@ -200,7 +200,7 @@ final class Swift2JavaVisitor { } func visit( - variableDecl node: VariableDeclSyntax, + variableDecl node: VariableDeclSyntax, in typeContext: ImportedNominalType?, sourceFilePath: String ) { @@ -216,37 +216,21 @@ final class Swift2JavaVisitor { self.log.debug("Import variable: \(node.kind) '\(node.qualifiedNameForDebug)'") - func importAccessor(kind: SwiftAPIKind) throws { - let signature = try SwiftFunctionSignature( - node, - isSet: kind == .setter, - enclosingType: typeContext?.swiftType, - lookupContext: translator.lookupContext - ) - - let imported = ImportedFunc( - module: translator.swiftModuleName, - swiftDecl: node, - name: varName, - apiKind: kind, - functionSignature: signature - ) - - log.debug("Record imported variable accessor \(kind == .getter ? "getter" : "setter"):\(node.qualifiedNameForDebug)") - if let typeContext { - typeContext.variables.append(imported) - } else { - translator.importedGlobalVariables.append(imported) - } - } - do { let supportedAccessors = node.supportedAccessorKinds(binding: binding) if supportedAccessors.contains(.get) { - try importAccessor(kind: .getter) + try importAccessor( + from: DeclSyntax(node), + in: typeContext, + kind: .getter, + name: varName) } if supportedAccessors.contains(.set) { - try importAccessor(kind: .setter) + try importAccessor( + from: DeclSyntax(node), + in: typeContext, + kind: .setter, + name: varName) } } catch { self.log.debug("Failed to import: \(node.qualifiedNameForDebug); \(error)") @@ -289,10 +273,89 @@ final class Swift2JavaVisitor { typeContext.initializers.append(imported) } + private func visit( + subscriptDecl node: SubscriptDeclSyntax, + in typeContext: ImportedNominalType?, + ) { + guard node.shouldExtract(config: config, log: log, in: typeContext) else { + return + } + + guard let accessorBlock = node.accessorBlock else { + return + } + + let name = "subscript" + let accessors = accessorBlock.supportedAccessorKinds() + + do { + if accessors.contains(.get) { + try importAccessor( + from: DeclSyntax(node), + in: typeContext, + kind: .subscriptGetter, + name: name) + } + if accessors.contains(.set) { + try importAccessor( + from: DeclSyntax(node), + in: typeContext, + kind: .subscriptSetter, + name: name) + } + } catch { + self.log.debug("Failed to import: \(node.qualifiedNameForDebug); \(error)") + } + } + + private func importAccessor( + from node: DeclSyntax, + in typeContext: ImportedNominalType?, + kind: SwiftAPIKind, + name: String + ) throws { + let signature: SwiftFunctionSignature + + switch node.as(DeclSyntaxEnum.self) { + case .variableDecl(let varNode): + signature = try SwiftFunctionSignature( + varNode, + isSet: kind == .setter, + enclosingType: typeContext?.swiftType, + lookupContext: translator.lookupContext) + case .subscriptDecl(let subscriptNode): + signature = try SwiftFunctionSignature( + subscriptNode, + isSet: kind == .subscriptSetter, + enclosingType: typeContext?.swiftType, + lookupContext: translator.lookupContext) + default: + log.warning("Not supported declaration type \(node.kind) while calling importAccessor!") + return + } + + let imported = ImportedFunc( + module: translator.swiftModuleName, + swiftDecl: node, + name: name, + apiKind: kind, + functionSignature: signature + ) + + log.debug( + "Record imported variable accessor \(kind == .getter || kind == .subscriptGetter ? "getter" : "setter"):\(node.qualifiedNameForDebug)" + ) + if let typeContext { + typeContext.variables.append(imported) + } else { + translator.importedGlobalVariables.append(imported) + } + } + private func synthesizeRawRepresentableConformance( enumDecl node: EnumDeclSyntax, in parent: ImportedNominalType? - ) { + ) { guard let imported = translator.importedNominalType(node, parent: parent) else { return } @@ -304,7 +367,9 @@ final class Swift2JavaVisitor { ), inheritanceType.isRawTypeCompatible { - if !imported.variables.contains(where: { $0.name == "rawValue" && $0.functionSignature.result.type != inheritanceType }) { + if !imported.variables.contains(where: { + $0.name == "rawValue" && $0.functionSignature.result.type != inheritanceType + }) { let decl: DeclSyntax = "public var rawValue: \(raw: inheritanceType.description) { get }" self.visit(decl: decl, in: imported, sourceFilePath: imported.sourceFilePath) } @@ -312,7 +377,11 @@ final class Swift2JavaVisitor { // FIXME: why is this un-used imported.variables.first?.signatureString - if !imported.initializers.contains(where: { $0.functionSignature.parameters.count == 1 && $0.functionSignature.parameters.first?.parameterName == "rawValue" && $0.functionSignature.parameters.first?.type == inheritanceType }) { + if !imported.initializers.contains(where: { + $0.functionSignature.parameters.count == 1 + && $0.functionSignature.parameters.first?.parameterName == "rawValue" + && $0.functionSignature.parameters.first?.type == inheritanceType + }) { let decl: DeclSyntax = "public init?(rawValue: \(raw: inheritanceType))" self.visit(decl: decl, in: imported, sourceFilePath: imported.sourceFilePath) } @@ -330,7 +399,9 @@ extension DeclSyntaxProtocol where Self: WithModifiersSyntax & WithAttributesSyn } guard meetsRequiredAccessLevel else { - log.debug("Skip import '\(self.qualifiedNameForDebug)': not at least \(config.effectiveMinimumInputAccessLevelMode)") + log.debug( + "Skip import '\(self.qualifiedNameForDebug)': not at least \(config.effectiveMinimumInputAccessLevelMode)" + ) return false } guard !attributes.contains(where: { $0.isJava }) else { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift index 1f2fbd365..804769e59 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift @@ -271,31 +271,10 @@ extension SwiftFunctionSignature { lookupContext: SwiftTypeLookupContext ) throws { - // If this is a member of a type, so we will have a self parameter. Figure out the - // type and convention for the self parameter. - if let enclosingType { - var isStatic = false - for modifier in varNode.modifiers { - switch modifier.name.tokenKind { - case .keyword(.static): isStatic = true - case .keyword(.class): throw SwiftFunctionTranslationError.classMethod(modifier.name) - default: break - } - } - - if isStatic { - self.selfParameter = .staticMethod(enclosingType) - } else { - self.selfParameter = .instance( - SwiftParameter( - convention: isSet && !enclosingType.isReferenceType ? .inout : .byValue, - type: enclosingType - ) - ) - } - } else { - self.selfParameter = nil - } + self.selfParameter = try Self.variableSelfParameter( + for: DeclSyntax(varNode), + enclosingType: enclosingType, + isSet: isSet) guard let binding = varNode.bindings.first, varNode.bindings.count == 1 else { throw SwiftFunctionTranslationError.multipleBindings(varNode) @@ -323,7 +302,9 @@ extension SwiftFunctionSignature { self.effectSpecifiers = effectSpecifiers ?? [] if isSet { - self.parameters = [SwiftParameter(convention: .byValue, parameterName: "newValue", type: valueType)] + self.parameters = [ + SwiftParameter(convention: .byValue, parameterName: "newValue", type: valueType) + ] self.result = .void } else { self.parameters = [] @@ -333,6 +314,50 @@ extension SwiftFunctionSignature { self.genericRequirements = [] } + init( + _ subscriptNode: SubscriptDeclSyntax, + isSet: Bool, + enclosingType: SwiftType?, + lookupContext: SwiftTypeLookupContext + ) throws { + self.selfParameter = try Self.variableSelfParameter( + for: DeclSyntax(subscriptNode), + enclosingType: enclosingType, + isSet: isSet) + + let valueType: SwiftType = try SwiftType(subscriptNode.returnClause.type, lookupContext: lookupContext) + var nodeParameters = try subscriptNode.parameterClause.parameters.map { param in + try SwiftParameter(param, lookupContext: lookupContext) + } + + var effectSpecifiers: [SwiftEffectSpecifier]? = nil + switch subscriptNode.accessorBlock?.accessors { + case .getter(let getter): + if let getter = getter.as(AccessorDeclSyntax.self) { + effectSpecifiers = try Self.effectSpecifiers(from: getter) + } + case .accessors(let accessors): + if let getter = accessors.first(where: { $0.accessorSpecifier.tokenKind == .keyword(.get) }) { + effectSpecifiers = try Self.effectSpecifiers(from: getter) + } + default: + break + } + + self.effectSpecifiers = effectSpecifiers ?? [] + + if isSet { + nodeParameters.append(SwiftParameter(convention: .byValue, parameterName: "newValue", type: valueType)) + self.result = .void + } else { + self.result = .init(convention: .direct, type: valueType) + } + + self.parameters = nodeParameters + self.genericParameters = [] + self.genericRequirements = [] + } + private static func effectSpecifiers(from decl: AccessorDeclSyntax) throws -> [SwiftEffectSpecifier] { var effectSpecifiers = [SwiftEffectSpecifier]() if decl.effectSpecifiers?.throwsClause != nil { @@ -343,27 +368,80 @@ extension SwiftFunctionSignature { } return effectSpecifiers } -} -extension VariableDeclSyntax { - struct SupportedAccessorKinds: OptionSet { - var rawValue: UInt8 + private static func variableSelfParameter( + for decl: DeclSyntax, + enclosingType: SwiftType?, + isSet: Bool + ) throws -> SwiftSelfParameter? { + let modifiers: DeclModifierListSyntax? = + switch decl.as(DeclSyntaxEnum.self) { + case .variableDecl(let varDecl): varDecl.modifiers + case .subscriptDecl(let subscriptDecl): subscriptDecl.modifiers + default: nil + } - static var get: Self = .init(rawValue: 1 << 0) - static var set: Self = .init(rawValue: 1 << 1) + guard let modifiers else { + return nil + } + + // If this is a member of a type, so we will have a self parameter. Figure out the + // type and convention for the self parameter. + if let enclosingType { + var isStatic = false + for modifier in modifiers { + switch modifier.name.tokenKind { + case .keyword(.static): isStatic = true + case .keyword(.class): throw SwiftFunctionTranslationError.classMethod(modifier.name) + default: break + } + } + + if isStatic { + return .staticMethod(enclosingType) + } else { + return .instance( + SwiftParameter( + convention: isSet && !enclosingType.isReferenceType ? .inout : .byValue, + type: enclosingType + ) + ) + } + } else { + return nil + } } +} +extension VariableDeclSyntax { /// Determine what operations (i.e. get and/or set) supported in this `VariableDeclSyntax` /// /// - Parameters: /// - binding the pattern binding in this declaration. - func supportedAccessorKinds(binding: PatternBindingSyntax) -> SupportedAccessorKinds { + func supportedAccessorKinds(binding: PatternBindingSyntax) -> AccessorBlockSyntax.SupportedAccessorKinds { if self.bindingSpecifier.tokenKind == .keyword(.let) { return [.get] } if let accessorBlock = binding.accessorBlock { - switch accessorBlock.accessors { + return accessorBlock.supportedAccessorKinds() + } + + return [.get, .set] + } +} + +extension AccessorBlockSyntax { + struct SupportedAccessorKinds: OptionSet { + var rawValue: UInt8 + + static var get: Self = .init(rawValue: 1 << 0) + static var set: Self = .init(rawValue: 1 << 1) + } + + /// Determine what operations (i.e. get and/or set) supported in this `AccessorBlockSyntax` + func supportedAccessorKinds() -> SupportedAccessorKinds { + switch self.accessors { case .getter: return [.get] case .accessors(let accessors): @@ -379,9 +457,6 @@ extension VariableDeclSyntax { } return [.get] } - } - - return [.get, .set] } } diff --git a/Sources/JExtractSwiftLib/ThunkNameRegistry.swift b/Sources/JExtractSwiftLib/ThunkNameRegistry.swift index 3369ec629..da2e95c1b 100644 --- a/Sources/JExtractSwiftLib/ThunkNameRegistry.swift +++ b/Sources/JExtractSwiftLib/ThunkNameRegistry.swift @@ -32,9 +32,9 @@ package struct ThunkNameRegistry { let suffix: String switch decl.apiKind { - case .getter: + case .getter, .subscriptGetter: suffix = "$get" - case .setter: + case .setter, .subscriptSetter: suffix = "$set" default: suffix = decl.functionSignature.parameters diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 7e8c66d3d..849b7f01f 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -83,7 +83,7 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Ownership modifiers: `inout`, `borrowing`, `consuming` | ❌ | ❌ | | Default parameter values: `func p(name: String = "")` | ❌ | ❌ | | Operators: `+`, `-`, user defined | ❌ | ❌ | -| Subscripts: `subscript()` | ❌ | ❌ | +| Subscripts: `subscript()` | ✅ | ✅ | | Equatable | ❌ | ❌ | | Pointers: `UnsafeRawPointer`, UnsafeBufferPointer (?) | 🟡 | ❌ | | Nested types: `struct Hello { struct World {} }` | ❌ | ✅ | diff --git a/Tests/JExtractSwiftTests/FFM/FFMSubscriptsTests.swift b/Tests/JExtractSwiftTests/FFM/FFMSubscriptsTests.swift new file mode 100644 index 000000000..89029dd70 --- /dev/null +++ b/Tests/JExtractSwiftTests/FFM/FFMSubscriptsTests.swift @@ -0,0 +1,212 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import Testing + +import JExtractSwiftLib +import Testing + +@Suite +struct FFMSubscriptsTests { + private let noParamsSubscriptSource = """ + public struct MyStruct { + private var testVariable: Double = 0 + + public subscript() -> Double { + get { return testVariable } + set { testVariable = newValue } + } + } + """ + + private let subscriptWithParamsSource = """ + public struct MyStruct { + private var testVariable: [Int32] = [] + + public subscript(index: Int32) -> Int32 { + get { return testVariable[Int(index)] } + set { testVariable[Int(index)] = newValue } + } + } + """ + + @Test("Test generation of JavaClass for subscript with no parameters") + func generatesJavaClassForNoParams() throws { + try assertOutput(input: noParamsSubscriptSource, .ffm, .java, expectedChunks: [ + """ + private static class swiftjava_SwiftModule_MyStruct_subscript$get { + private static final FunctionDescriptor DESC = FunctionDescriptor.of( + /* -> */SwiftValueLayout.SWIFT_DOUBLE, + /* self: */SwiftValueLayout.SWIFT_POINTER + ); + """, + """ + private static final MemorySegment ADDR = + SwiftModule.findOrThrow("swiftjava_SwiftModule_MyStruct_subscript$get"); + """, + """ + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static double call(java.lang.foreign.MemorySegment self) { + try { + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall(self); + } + return (double) HANDLE.invokeExact(self); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + """, + """ + public double getSubscript() { + $ensureAlive(); + return swiftjava_SwiftModule_MyStruct_subscript$get.call(this.$memorySegment()); + """, + ]) + try assertOutput(input: noParamsSubscriptSource, .ffm, .java, expectedChunks: [ + """ + private static class swiftjava_SwiftModule_MyStruct_subscript$set { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* newValue: */SwiftValueLayout.SWIFT_DOUBLE, + /* self: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + SwiftModule.findOrThrow("swiftjava_SwiftModule_MyStruct_subscript$set"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static void call(double newValue, java.lang.foreign.MemorySegment self) { + try { + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall(newValue, self); + } + HANDLE.invokeExact(newValue, self); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + """, + """ + public void setSubscript(double newValue) { + $ensureAlive(); + swiftjava_SwiftModule_MyStruct_subscript$set.call(newValue, this.$memorySegment()); + """ + ]) + } + + @Test("Test generation of JavaClass for subscript with parameters") + func generatesJavaClassForParameters() throws { + try assertOutput(input: subscriptWithParamsSource, .ffm, .java, expectedChunks: [ + """ + private static class swiftjava_SwiftModule_MyStruct_subscript$get { + private static final FunctionDescriptor DESC = FunctionDescriptor.of( + /* -> */SwiftValueLayout.SWIFT_INT32, + /* index: */SwiftValueLayout.SWIFT_INT32, + /* self: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + SwiftModule.findOrThrow("swiftjava_SwiftModule_MyStruct_subscript$get"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static int call(int index, java.lang.foreign.MemorySegment self) { + try { + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall(index, self); + } + return (int) HANDLE.invokeExact(index, self); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + """, + """ + public int getSubscript(int index) { + $ensureAlive(); + return swiftjava_SwiftModule_MyStruct_subscript$get.call(index, this.$memorySegment()); + """, + ]) + try assertOutput(input: subscriptWithParamsSource, .ffm, .java, expectedChunks: [ + """ + private static class swiftjava_SwiftModule_MyStruct_subscript$set { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* index: */SwiftValueLayout.SWIFT_INT32, + /* newValue: */SwiftValueLayout.SWIFT_INT32, + /* self: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + SwiftModule.findOrThrow("swiftjava_SwiftModule_MyStruct_subscript$set"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static void call(int index, int newValue, java.lang.foreign.MemorySegment self) { + try { + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall(index, newValue, self); + } + HANDLE.invokeExact(index, newValue, self); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + """, + """ + public void setSubscript(int index, int newValue) { + $ensureAlive(); + swiftjava_SwiftModule_MyStruct_subscript$set.call(index, newValue, this.$memorySegment()); + """, + ]) + } + + @Test("Test generation of Swift thunks for subscript without parameters") + func subscriptWithoutParamsMethodSwiftThunk() throws { + try assertOutput( + input: noParamsSubscriptSource, + .ffm, + .swift, + expectedChunks: [ + """ + @_cdecl("swiftjava_SwiftModule_MyStruct_subscript$get") + public func swiftjava_SwiftModule_MyStruct_subscript$get(_ self: UnsafeRawPointer) -> Double { + return self.assumingMemoryBound(to: MyStruct.self).pointee[] + } + """, + """ + @_cdecl("swiftjava_SwiftModule_MyStruct_subscript$set") + public func swiftjava_SwiftModule_MyStruct_subscript$set(_ newValue: Double, _ self: UnsafeMutableRawPointer) { + self.assumingMemoryBound(to: MyStruct.self).pointee[] = newValue + } + """ + ] + ) + } + + @Test("Test generation of Swift thunks for subscript with parameters") + func subscriptWithParamsMethodSwiftThunk() throws { + try assertOutput( + input: subscriptWithParamsSource, + .ffm, + .swift, + expectedChunks: [ + """ + @_cdecl("swiftjava_SwiftModule_MyStruct_subscript$get") + public func swiftjava_SwiftModule_MyStruct_subscript$get(_ index: Int32, _ self: UnsafeRawPointer) -> Int32 { + return self.assumingMemoryBound(to: MyStruct.self).pointee[index] + } + """, + """ + @_cdecl("swiftjava_SwiftModule_MyStruct_subscript$set") + public func swiftjava_SwiftModule_MyStruct_subscript$set(_ index: Int32, _ newValue: Int32, _ self: UnsafeMutableRawPointer) { + self.assumingMemoryBound(to: MyStruct.self).pointee[index] = newValue + } + """ + ] + ) + } +} diff --git a/Tests/JExtractSwiftTests/JNI/JNISubscriptsTests.swift b/Tests/JExtractSwiftTests/JNI/JNISubscriptsTests.swift new file mode 100644 index 000000000..0f7b131d0 --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNISubscriptsTests.swift @@ -0,0 +1,155 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import Testing + +@Suite +struct JNISubscriptsTests { + private let noParamsSubscriptSource = """ + public struct MyStruct { + private var testVariable: Double = 0 + + public subscript() -> Double { + get { return testVariable } + set { testVariable = newValue } + } + } + """ + + private let subscriptWithParamsSource = """ + public struct MyStruct { + private var testVariable: [Int32] = [] + + public subscript(index: Int32) -> Int32 { + get { return testVariable[Int(index)] } + set { testVariable[Int(index)] = newValue } + } + } + """ + + @Test("Test generation of JavaClass for subscript with no parameters") + func generatesJavaClassForNoParams() throws { + try assertOutput(input: noParamsSubscriptSource, .jni, .java, expectedChunks: [ + """ + public double getSubscript() { + return MyStruct.$getSubscript(this.$memoryAddress()); + """, + """ + private static native double $getSubscript(long self); + """, + """ + public void setSubscript(double newValue) { + MyStruct.$setSubscript(newValue, this.$memoryAddress()); + """, + """ + private static native void $setSubscript(double newValue, long self); + """ + ]) + try assertOutput( + input: noParamsSubscriptSource, .jni, .java, + expectedChunks: [ + """ + private static native void $destroy(long selfPointer); + """ + ]) + } + + @Test("Test generation of JavaClass for subscript with parameters") + func generatesJavaClassForParameters() throws { + try assertOutput(input: subscriptWithParamsSource, .jni, .java, expectedChunks: [ + """ + public int getSubscript(int index) { + return MyStruct.$getSubscript(index, this.$memoryAddress()); + + """, + """ + private static native int $getSubscript(int index, long self); + """, + """ + public void setSubscript(int index, int newValue) { + MyStruct.$setSubscript(index, newValue, this.$memoryAddress()); + """, + """ + private static native void $setSubscript(int index, int newValue, long self); + """ + ]) + } + + @Test("Test generation of Swift thunks for subscript without parameters") + func subscriptWithoutParamsMethodSwiftThunk() throws { + try assertOutput( + input: noParamsSubscriptSource, + .jni, + .swift, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyStruct__00024getSubscript__J") + func Java_com_example_swift_MyStruct__00024getSubscript__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jdouble { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment)) + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { + fatalError("self memory address was null in call to \\(#function)!") + } + return self$.pointee[].getJNIValue(in: environment) + """, + """ + @_cdecl("Java_com_example_swift_MyStruct__00024setSubscript__DJ") + func Java_com_example_swift_MyStruct__00024setSubscript__DJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jdouble, self: jlong) { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment)) + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { + fatalError("self memory address was null in call to \\(#function)!") + } + self$.pointee[] = Double(fromJNI: newValue, in: environment) + """ + ] + ) + } + + @Test("Test generation of Swift thunks for subscript with parameters") + func subscriptWithParamsMethodSwiftThunk() throws { + try assertOutput( + input: subscriptWithParamsSource, + .jni, + .swift, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyStruct__00024getSubscript__IJ") + func Java_com_example_swift_MyStruct__00024getSubscript__IJ(environment: UnsafeMutablePointer!, thisClass: jclass, index: jint, self: jlong) -> jint { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment)) + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { + fatalError("self memory address was null in call to \\(#function)!") + } + return self$.pointee[Int32(fromJNI: index, in: environment)].getJNIValue(in: environment) + """, + """ + @_cdecl("Java_com_example_swift_MyStruct__00024setSubscript__IIJ") + func Java_com_example_swift_MyStruct__00024setSubscript__IIJ(environment: UnsafeMutablePointer!, thisClass: jclass, index: jint, newValue: jint, self: jlong) { + assert(self != 0, "self memory address was null") + let selfBits$ = Int(Int64(fromJNI: self, in: environment)) + let self$ = UnsafeMutablePointer(bitPattern: selfBits$) + guard let self$ else { + fatalError("self memory address was null in call to \\(#function)!") + } + self$.pointee[Int32(fromJNI: index, in: environment)] = Int32(fromJNI: newValue, in: environment) + """ + ] + ) + } +} From c0c6fd593f68d14d7084a0a5935e02331991193d Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Mon, 1 Dec 2025 11:56:29 +0100 Subject: [PATCH 133/141] Use thread environment instead for JNI methods (#465) --- Sources/SwiftJava/AnyJavaObject.swift | 2 +- .../SwiftJava/JavaObject+MethodCalls.swift | 50 ++++++++++++------- Sources/SwiftJava/JavaObjectHolder.swift | 2 + Tests/SwiftJavaTests/BasicRuntimeTests.swift | 11 ++++ 4 files changed, 46 insertions(+), 19 deletions(-) diff --git a/Sources/SwiftJava/AnyJavaObject.swift b/Sources/SwiftJava/AnyJavaObject.swift index fe77bdbd4..33a83159c 100644 --- a/Sources/SwiftJava/AnyJavaObject.swift +++ b/Sources/SwiftJava/AnyJavaObject.swift @@ -66,7 +66,7 @@ extension AnyJavaObject { javaHolder.object } - /// Retrieve the environment in which this Java object resides. + /// Retrieve the environment in which this Java object was created. public var javaEnvironment: JNIEnvironment { javaHolder.environment } diff --git a/Sources/SwiftJava/JavaObject+MethodCalls.swift b/Sources/SwiftJava/JavaObject+MethodCalls.swift index 4880f7503..0626be23a 100644 --- a/Sources/SwiftJava/JavaObject+MethodCalls.swift +++ b/Sources/SwiftJava/JavaObject+MethodCalls.swift @@ -104,7 +104,8 @@ extension AnyJavaObject { resultType: Result.Type ) throws -> jmethodID { // Retrieve the Java class instance from the object. - let environment = javaEnvironment + let environment = try JavaVirtualMachine.shared().environment() + let thisClass = try environment.translatingJNIExceptions { environment.interface.GetObjectClass(environment, javaThis) }! @@ -115,7 +116,7 @@ extension AnyJavaObject { methodName: methodName, parameterTypes: repeat each parameterTypes, resultType: Result.javaType, - in: javaEnvironment + in: environment ) } } @@ -126,7 +127,8 @@ extension AnyJavaObject { parameterTypes: repeat (each Param).Type ) throws -> jmethodID { // Retrieve the Java class instance from the object. - let environment = javaEnvironment + let environment = try JavaVirtualMachine.shared().environment() + let thisClass = try environment.translatingJNIExceptions { environment.interface.GetObjectClass(environment, javaThis) }! @@ -137,7 +139,7 @@ extension AnyJavaObject { methodName: methodName, parameterTypes: repeat each parameterTypes, resultType: .void, - in: javaEnvironment + in: environment ) } } @@ -167,8 +169,10 @@ extension AnyJavaObject { method: jmethodID, args: repeat each Param ) throws -> Result { + let environment = try JavaVirtualMachine.shared().environment() + return try Self.javaMethodCall( - in: javaEnvironment, + in: environment, this: javaThis, method: method, args: repeat each args @@ -229,8 +233,10 @@ extension AnyJavaObject { method: jmethodID, args: repeat each Param ) throws { + let environment = try JavaVirtualMachine.shared().environment() + try Self.javaMethodCall( - in: javaEnvironment, + in: environment, this: javaThis, method: method, args: repeat each args @@ -276,7 +282,7 @@ extension AnyJavaObject { private func getJNIFieldID(_ fieldName: String, fieldType: FieldType.Type) -> jfieldID? where FieldType: ~Copyable { let this = javaThis - let environment = javaEnvironment + let environment = try! JavaVirtualMachine.shared().environment() // Retrieve the Java class instance from the object. let thisClass = environment.interface.GetObjectClass(environment, this)! @@ -289,15 +295,19 @@ extension AnyJavaObject { fieldType fieldType: FieldType.Type ) -> FieldType where FieldType: ~Copyable { get { + let environment = try! JavaVirtualMachine.shared().environment() + let fieldID = getJNIFieldID(fieldName, fieldType: fieldType)! - let jniMethod = FieldType.jniFieldGet(in: javaEnvironment) - return FieldType(fromJNI: jniMethod(javaEnvironment, javaThis, fieldID), in: javaEnvironment) + let jniMethod = FieldType.jniFieldGet(in: environment) + return FieldType(fromJNI: jniMethod(environment, javaThis, fieldID), in: environment) } nonmutating set { + let environment = try! JavaVirtualMachine.shared().environment() + let fieldID = getJNIFieldID(fieldName, fieldType: fieldType)! - let jniMethod = FieldType.jniFieldSet(in: javaEnvironment) - jniMethod(javaEnvironment, javaThis, fieldID, newValue.getJNIValue(in: javaEnvironment)) + let jniMethod = FieldType.jniFieldSet(in: environment) + jniMethod(environment, javaThis, fieldID, newValue.getJNIValue(in: environment)) } } } @@ -311,7 +321,7 @@ extension JavaClass { resultType: Result.Type ) throws -> Result { let thisClass = javaThis - let environment = javaEnvironment + let environment = try! JavaVirtualMachine.shared().environment() // Compute the method signature so we can find the right method, then look up the // method within the class. @@ -345,7 +355,7 @@ extension JavaClass { arguments: repeat each Param ) throws { let thisClass = javaThis - let environment = javaEnvironment + let environment = try JavaVirtualMachine.shared().environment() // Compute the method signature so we can find the right method, then look up the // method within the class. @@ -372,7 +382,7 @@ extension JavaClass { /// Retrieve the JNI field ID for a field with the given name and type. private func getJNIStaticFieldID(_ fieldName: String, fieldType: FieldType.Type) -> jfieldID? { - let environment = javaEnvironment + let environment = try! JavaVirtualMachine.shared().environment() return environment.interface.GetStaticFieldID(environment, javaThis, fieldName, FieldType.jniMangling) } @@ -382,15 +392,19 @@ extension JavaClass { fieldType fieldType: FieldType.Type ) -> FieldType { get { + let environment = try! JavaVirtualMachine.shared().environment() + let fieldID = getJNIStaticFieldID(fieldName, fieldType: fieldType)! - let jniMethod = FieldType.jniStaticFieldGet(in: javaEnvironment) - return FieldType(fromJNI: jniMethod(javaEnvironment, javaThis, fieldID), in: javaEnvironment) + let jniMethod = FieldType.jniStaticFieldGet(in: environment) + return FieldType(fromJNI: jniMethod(environment, javaThis, fieldID), in: environment) } set { + let environment = try! JavaVirtualMachine.shared().environment() + let fieldID = getJNIStaticFieldID(fieldName, fieldType: fieldType)! - let jniMethod = FieldType.jniStaticFieldSet(in: javaEnvironment) - jniMethod(javaEnvironment, javaThis, fieldID, newValue.getJNIValue(in: javaEnvironment)) + let jniMethod = FieldType.jniStaticFieldSet(in: environment) + jniMethod(environment, javaThis, fieldID, newValue.getJNIValue(in: environment)) } } } diff --git a/Sources/SwiftJava/JavaObjectHolder.swift b/Sources/SwiftJava/JavaObjectHolder.swift index 5930da59b..b5e888351 100644 --- a/Sources/SwiftJava/JavaObjectHolder.swift +++ b/Sources/SwiftJava/JavaObjectHolder.swift @@ -32,6 +32,8 @@ public final class JavaObjectHolder { /// in Swift and the Java virtual machine is free to move or deallocate it. func forget() { if let object { + let environment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(environment, object) self.object = nil } diff --git a/Tests/SwiftJavaTests/BasicRuntimeTests.swift b/Tests/SwiftJavaTests/BasicRuntimeTests.swift index b6c18bee5..292c2a688 100644 --- a/Tests/SwiftJavaTests/BasicRuntimeTests.swift +++ b/Tests/SwiftJavaTests/BasicRuntimeTests.swift @@ -82,6 +82,17 @@ class BasicRuntimeTests: XCTestCase { let nullString = String(fromJNI: nil, in: environment) XCTAssertEqual(nullString, "") } + + func testCrossThreadAccess() async throws { + let environment = try jvm.environment() + let url = try URL("https://swift.org", environment: environment) + let string = await Task.detached { + // This should be called on a different thread + url.toString() + }.value + + XCTAssertEqual(string, "https://swift.org") + } } @JavaClass("org.swift.javakit.Nonexistent") From 2ed85e25ed3831c4c9fdb00e3c92fc4b62255885 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Mon, 1 Dec 2025 12:21:21 +0100 Subject: [PATCH 134/141] [jextract] Add async legacy mode (#462) --- .../java/com/example/swift/AsyncTest.java | 33 +-- .../com/example/swift/MySwiftClassTest.java | 4 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 71 +++--- ...wift2JavaGenerator+NativeTranslation.swift | 14 +- .../JavaTypes/JavaType+JDK.swift | 5 + .../JavaTypes/JavaType+SwiftKit.swift | 5 + .../JExtract/JExtractAsyncFuncMode.swift | 2 +- ...ltCaches.swift => JNIMethodIDCaches.swift} | 30 +++ .../core/SimpleCompletableFuture.java | 223 ++++++++++++++++++ .../core/SimpleCompletableFutureTest.java | 185 +++++++++++++++ .../JNI/JNIAsyncTests.swift | 92 ++++++-- 11 files changed, 597 insertions(+), 67 deletions(-) rename Sources/SwiftJavaRuntimeSupport/{DefaultCaches.swift => JNIMethodIDCaches.swift} (70%) create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/SimpleCompletableFuture.java create mode 100644 SwiftKitCore/src/test/java/org/swift/swiftkit/core/SimpleCompletableFutureTest.java diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java index 5fe7c1310..400844fdc 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/AsyncTest.java @@ -26,31 +26,32 @@ import java.util.OptionalLong; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; import static org.junit.jupiter.api.Assertions.*; public class AsyncTest { @Test - void asyncSum() { - CompletableFuture future = MySwiftLibrary.asyncSum(10, 12); + void asyncSum() throws Exception { + Future future = MySwiftLibrary.asyncSum(10, 12); - Long result = future.join(); + Long result = future.get(); assertEquals(22, result); } @Test - void asyncSleep() { - CompletableFuture future = MySwiftLibrary.asyncSleep(); - future.join(); + void asyncSleep() throws Exception { + Future future = MySwiftLibrary.asyncSleep(); + future.get(); } @Test - void asyncCopy() { + void asyncCopy() throws Exception { try (var arena = SwiftArena.ofConfined()) { MySwiftClass obj = MySwiftClass.init(10, 5, arena); - CompletableFuture future = MySwiftLibrary.asyncCopy(obj, arena); + Future future = MySwiftLibrary.asyncCopy(obj, arena); - MySwiftClass result = future.join(); + MySwiftClass result = future.get(); assertEquals(10, result.getX()); assertEquals(5, result.getY()); @@ -59,7 +60,7 @@ void asyncCopy() { @Test void asyncThrows() { - CompletableFuture future = MySwiftLibrary.asyncThrows(); + Future future = MySwiftLibrary.asyncThrows(); ExecutionException ex = assertThrows(ExecutionException.class, future::get); @@ -70,14 +71,14 @@ void asyncThrows() { } @Test - void asyncOptional() { - CompletableFuture future = MySwiftLibrary.asyncOptional(42); - assertEquals(OptionalLong.of(42), future.join()); + void asyncOptional() throws Exception { + Future future = MySwiftLibrary.asyncOptional(42); + assertEquals(OptionalLong.of(42), future.get()); } @Test - void asyncString() { - CompletableFuture future = MySwiftLibrary.asyncString("hey"); - assertEquals("hey", future.join()); + void asyncString() throws Exception { + Future future = MySwiftLibrary.asyncString("hey"); + assertEquals("hey", future.get()); } } \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java index a58386299..860f1641c 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -165,10 +165,10 @@ void addXWithJavaLong() { } @Test - void getAsyncVariable() { + void getAsyncVariable() throws Exception { try (var arena = SwiftArena.ofConfined()) { MySwiftClass c1 = MySwiftClass.init(20, 10, arena); - assertEquals(42, c1.getGetAsync().join()); + assertEquals(42, c1.getGetAsync().get()); } } } \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index c8e4bbf34..6a2771c30 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -501,40 +501,55 @@ extension JNISwift2JavaGenerator { originalFunctionSignature: SwiftFunctionSignature, mode: JExtractAsyncFuncMode ) { + // Update translated function + let nativeFutureType: JavaType + let translatedFutureType: JavaType + let completeMethodID: String + let completeExceptionallyMethodID: String + switch mode { case .completableFuture: - // Update translated function - - let nativeFutureType = JavaType.completableFuture(nativeFunctionSignature.result.javaType) + nativeFutureType = .completableFuture(nativeFunctionSignature.result.javaType) + translatedFutureType = .completableFuture(translatedFunctionSignature.resultType.javaType) + completeMethodID = "_JNIMethodIDCache.CompletableFuture.complete" + completeExceptionallyMethodID = "_JNIMethodIDCache.CompletableFuture.completeExceptionally" + + case .legacyFuture: + nativeFutureType = .simpleCompletableFuture(nativeFunctionSignature.result.javaType) + translatedFutureType = .future(translatedFunctionSignature.resultType.javaType) + completeMethodID = "_JNIMethodIDCache.SimpleCompletableFuture.complete" + completeExceptionallyMethodID = "_JNIMethodIDCache.SimpleCompletableFuture.completeExceptionally" + } - let futureOutParameter = OutParameter( - name: "$future", - type: nativeFutureType, - allocation: .new - ) + let futureOutParameter = OutParameter( + name: "future$", + type: nativeFutureType, + allocation: .new + ) - let result = translatedFunctionSignature.resultType - translatedFunctionSignature.resultType = TranslatedResult( - javaType: .completableFuture(translatedFunctionSignature.resultType.javaType), - annotations: result.annotations, - outParameters: result.outParameters + [futureOutParameter], - conversion: .aggregate(variable: nil, [ - .print(.placeholder), // Make the downcall - .method(.constant("$future"), function: "thenApply", arguments: [ - .lambda(args: ["futureResult$"], body: .replacingPlaceholder(result.conversion, placeholder: "futureResult$")) - ]) + let result = translatedFunctionSignature.resultType + translatedFunctionSignature.resultType = TranslatedResult( + javaType: translatedFutureType, + annotations: result.annotations, + outParameters: result.outParameters + [futureOutParameter], + conversion: .aggregate(variable: nil, [ + .print(.placeholder), // Make the downcall + .method(.constant("future$"), function: "thenApply", arguments: [ + .lambda(args: ["futureResult$"], body: .replacingPlaceholder(result.conversion, placeholder: "futureResult$")) ]) - ) + ]) + ) - // Update native function - nativeFunctionSignature.result.conversion = .asyncCompleteFuture( - swiftFunctionResultType: originalFunctionSignature.result.type, - nativeFunctionSignature: nativeFunctionSignature, - isThrowing: originalFunctionSignature.isThrowing - ) - nativeFunctionSignature.result.javaType = .void - nativeFunctionSignature.result.outParameters.append(.init(name: "result_future", type: nativeFutureType)) - } + // Update native function + nativeFunctionSignature.result.conversion = .asyncCompleteFuture( + swiftFunctionResultType: originalFunctionSignature.result.type, + nativeFunctionSignature: nativeFunctionSignature, + isThrowing: originalFunctionSignature.isThrowing, + completeMethodID: completeMethodID, + completeExceptionallyMethodID: completeExceptionallyMethodID + ) + nativeFunctionSignature.result.javaType = .void + nativeFunctionSignature.result.outParameters.append(.init(name: "result_future", type: nativeFutureType)) } func translateProtocolParameter( diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 1994dce04..24e469067 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -689,7 +689,9 @@ extension JNISwift2JavaGenerator { indirect case asyncCompleteFuture( swiftFunctionResultType: SwiftType, nativeFunctionSignature: NativeFunctionSignature, - isThrowing: Bool + isThrowing: Bool, + completeMethodID: String, + completeExceptionallyMethodID: String ) /// `{ (args) -> return body }` @@ -927,7 +929,9 @@ extension JNISwift2JavaGenerator { case .asyncCompleteFuture( let swiftFunctionResultType, let nativeFunctionSignature, - let isThrowing + let isThrowing, + let completeMethodID, + let completeExceptionallyMethodID ): var globalRefs: [String] = ["globalFuture"] @@ -954,7 +958,7 @@ extension JNISwift2JavaGenerator { printer.print("environment = try! JavaVirtualMachine.shared().environment()") let inner = nativeFunctionSignature.result.conversion.render(&printer, "swiftResult$") if swiftFunctionResultType.isVoid { - printer.print("environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)])") + printer.print("environment.interface.CallBooleanMethodA(environment, globalFuture, \(completeMethodID), [jvalue(l: nil)])") } else { let result: String if nativeFunctionSignature.result.javaType.requiresBoxing { @@ -964,7 +968,7 @@ extension JNISwift2JavaGenerator { result = inner } - printer.print("environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: \(result))])") + printer.print("environment.interface.CallBooleanMethodA(environment, globalFuture, \(completeMethodID), [jvalue(l: \(result))])") } } @@ -986,7 +990,7 @@ extension JNISwift2JavaGenerator { """ let catchEnvironment = try! JavaVirtualMachine.shared().environment() let exception = catchEnvironment.interface.NewObjectA(catchEnvironment, _JNIMethodIDCache.Exception.class, _JNIMethodIDCache.Exception.constructWithMessage, [String(describing: error).getJValue(in: catchEnvironment)]) - catchEnvironment.interface.CallBooleanMethodA(catchEnvironment, globalFuture, _JNIMethodIDCache.CompletableFuture.completeExceptionally, [jvalue(l: exception)]) + catchEnvironment.interface.CallBooleanMethodA(catchEnvironment, globalFuture, \(completeExceptionallyMethodID), [jvalue(l: exception)]) """ ) } diff --git a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift index 5e5a72688..511bf8de6 100644 --- a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift +++ b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift @@ -44,4 +44,9 @@ extension JavaType { static func completableFuture(_ T: JavaType) -> JavaType { .class(package: "java.util.concurrent", name: "CompletableFuture", typeParameters: [T.boxedType]) } + + /// The description of the type java.util.concurrent.Future + static func future(_ T: JavaType) -> JavaType { + .class(package: "java.util.concurrent", name: "Future", typeParameters: [T.boxedType]) + } } diff --git a/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift index 2ab9c0a22..7319b2942 100644 --- a/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift +++ b/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift @@ -85,4 +85,9 @@ extension JavaType { } } + /// The description of the type org.swift.swiftkit.core.SimpleCompletableFuture + static func simpleCompletableFuture(_ T: JavaType) -> JavaType { + .class(package: "org.swift.swiftkit.core", name: "SimpleCompletableFuture", typeParameters: [T.boxedType]) + } + } diff --git a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncFuncMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncFuncMode.swift index 221649c52..d7fd84623 100644 --- a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncFuncMode.swift +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractAsyncFuncMode.swift @@ -23,7 +23,7 @@ public enum JExtractAsyncFuncMode: String, Codable { /// Android 23 and below. /// /// - Note: Prefer using the `completableFuture` mode instead, if possible. -// case future + case legacyFuture } extension JExtractAsyncFuncMode { diff --git a/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift b/Sources/SwiftJavaRuntimeSupport/JNIMethodIDCaches.swift similarity index 70% rename from Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift rename to Sources/SwiftJavaRuntimeSupport/JNIMethodIDCaches.swift index 1c3079bc3..56fe0351a 100644 --- a/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift +++ b/Sources/SwiftJavaRuntimeSupport/JNIMethodIDCaches.swift @@ -47,6 +47,36 @@ extension _JNIMethodIDCache { } } + public enum SimpleCompletableFuture { + private static let completeMethod = Method( + name: "complete", + signature: "(Ljava/lang/Object;)Z" + ) + + private static let completeExceptionallyMethod = Method( + name: "completeExceptionally", + signature: "(Ljava/lang/Throwable;)Z" + ) + + private static let cache = _JNIMethodIDCache( + environment: try! JavaVirtualMachine.shared().environment(), + className: "org/swift/swiftkit/core/SimpleCompletableFuture", + methods: [completeMethod, completeExceptionallyMethod] + ) + + public static var `class`: jclass { + cache.javaClass + } + + public static var complete: jmethodID { + cache.methods[completeMethod]! + } + + public static var completeExceptionally: jmethodID { + cache.methods[completeExceptionallyMethod]! + } + } + public enum Exception { private static let messageConstructor = Method(name: "", signature: "(Ljava/lang/String;)V") diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SimpleCompletableFuture.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SimpleCompletableFuture.java new file mode 100644 index 000000000..b92527236 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SimpleCompletableFuture.java @@ -0,0 +1,223 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core; + +import java.util.Deque; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; + +/** + * A simple completable {@link Future} for platforms that do not support {@link java.util.concurrent.CompletableFuture}, + * e.g. before Java 8, and/or before Android 23. + *

+ * Prefer using the {@link CompletableFuture} for bridging Swift asynchronous functions, i.e. use the {@code completableFuture} + * mode in {@code swift-java jextract}. + * + * @param The result type + */ +public final class SimpleCompletableFuture implements Future { + // Marker object used to indicate the Future has not yet been completed. + private static final Object PENDING = new Object(); + private static final Object NULL = new Object(); + private final AtomicReference result = new AtomicReference<>(PENDING); + + private final Deque callbacks = new ConcurrentLinkedDeque<>(); + + /** + * Wrapper type we use to indicate that a recorded result was a failure (recorded using {@link SimpleCompletableFuture#completeExceptionally(Throwable)}. + * Since no-one else can instantiate this type, we know for sure that a recorded CompletedExceptionally indicates a failure. + */ + static final class CompletedExceptionally { + private final Throwable exception; + + private CompletedExceptionally(Throwable exception) { + this.exception = exception; + } + } + + /** + * Returns a new future that, when this stage completes + * normally, is executed with this stage's result as the argument + * to the supplied function. + * + *

This method is analogous to + * {@link java.util.Optional#map Optional.map} and + * {@link java.util.stream.Stream#map Stream.map}. + * + * @return the new Future + */ + public Future thenApply(Function fn) { + SimpleCompletableFuture newFuture = new SimpleCompletableFuture<>(); + addCallback(() -> { + Object observed = this.result.get(); + if (observed instanceof CompletedExceptionally) { + newFuture.completeExceptionally(((CompletedExceptionally) observed).exception); + } else { + try { + // We're guaranteed that an observed result is of type T. + // noinspection unchecked + U newResult = fn.apply(observed == NULL ? null : (T) observed); + newFuture.complete(newResult); + } catch (Throwable t) { + newFuture.completeExceptionally(t); + } + } + }); + return newFuture; + } + + /** + * If not already completed, sets the value returned by {@link #get()} and + * related methods to the given value. + * + * @param value the result value + * @return {@code true} if this invocation caused this CompletableFuture + * to transition to a completed state, else {@code false} + */ + public boolean complete(T value) { + if (result.compareAndSet(PENDING, value == null ? NULL : value)) { + synchronized (result) { + result.notifyAll(); + } + runCallbacks(); + return true; + } + + return false; + } + + /** + * If not already completed, causes invocations of {@link #get()} + * and related methods to throw the given exception. + * + * @param ex the exception + * @return {@code true} if this invocation caused this CompletableFuture + * to transition to a completed state, else {@code false} + */ + public boolean completeExceptionally(Throwable ex) { + if (result.compareAndSet(PENDING, new CompletedExceptionally(ex))) { + synchronized (result) { + result.notifyAll(); + } + runCallbacks(); + return true; + } + + return false; + } + + private void runCallbacks() { + // This is a pretty naive implementation; even if we enter this by racing a thenApply, + // with a completion; we're using a concurrent deque so we won't happen to trigger a callback twice. + Runnable callback; + while ((callback = callbacks.pollFirst()) != null) { + callback.run(); + } + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + // TODO: If we're representing a Swift Task computation with this future, + // we could trigger a Task.cancel() from here + return false; + } + + @Override + public boolean isCancelled() { + return false; + } + + @Override + public boolean isDone() { + return this.result.get() != PENDING; + } + + @Override + public T get() throws InterruptedException, ExecutionException { + Object observed; + // If PENDING check fails immediately, we have no need to take the result lock at all. + while ((observed = result.get()) == PENDING) { + synchronized (result) { + if (result.get() == PENDING) { + result.wait(); + } + } + } + + return getReturn(observed); + } + + @Override + public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + Object observed; + + // Fast path: are we already completed and don't need to do any waiting? + if ((observed = result.get()) != PENDING) { + return get(); + } + + long nanos = unit.toNanos(timeout); + synchronized (result) { + if (!isDone()) { + if (nanos <= 0) { + throw new TimeoutException(); + } + long deadline = System.nanoTime() + nanos; + while (!isDone()) { + nanos = deadline - System.nanoTime(); + if (nanos <= 0L) { + throw new TimeoutException(); + } + result.wait(nanos / 1000000, (int) (nanos % 1000000)); + } + } + } + + // Seems we broke out of the wait loop, let's trigger the 'get()' implementation + observed = result.get(); + if (observed == PENDING) { + throw new ExecutionException("Unexpectedly finished wait-loop while future was not completed, this is a bug.", null); + } + return getReturn(observed); + } + + private T getReturn(Object observed) throws ExecutionException { + if (observed instanceof CompletedExceptionally) { + // We observed a failure, unwrap and throw it + Throwable exception = ((CompletedExceptionally) observed).exception; + if (exception instanceof CancellationException) { + throw (CancellationException) exception; + } + throw new ExecutionException(exception); + } else if (observed == NULL) { + return null; + } else { + // We're guaranteed that we only allowed registering completions of type `T` + // noinspection unchecked + return (T) observed; + } + } + + private void addCallback(Runnable action) { + callbacks.add(action); + if (isDone()) { + // This may race, but we don't care since triggering the callbacks is going to be at-most-once + // by means of using the concurrent deque as our list of callbacks. + runCallbacks(); + } + } + +} \ No newline at end of file diff --git a/SwiftKitCore/src/test/java/org/swift/swiftkit/core/SimpleCompletableFutureTest.java b/SwiftKitCore/src/test/java/org/swift/swiftkit/core/SimpleCompletableFutureTest.java new file mode 100644 index 000000000..b4bb98b3a --- /dev/null +++ b/SwiftKitCore/src/test/java/org/swift/swiftkit/core/SimpleCompletableFutureTest.java @@ -0,0 +1,185 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package org.swift.swiftkit.core; + +import org.junit.jupiter.api.Test; + +import java.util.Objects; +import java.util.concurrent.*; +import static org.junit.jupiter.api.Assertions.*; + +public class SimpleCompletableFutureTest { + + @Test + void testCompleteAndGet() throws ExecutionException, InterruptedException { + SimpleCompletableFuture future = new SimpleCompletableFuture<>(); + assertFalse(future.isDone()); + assertTrue(future.complete("test")); + assertTrue(future.isDone()); + assertEquals("test", future.get()); + } + + @Test + void testCompleteWithNullAndGet() throws ExecutionException, InterruptedException { + SimpleCompletableFuture future = new SimpleCompletableFuture<>(); + assertFalse(future.isDone()); + assertTrue(future.complete(null)); + assertTrue(future.isDone()); + assertNull(future.get()); + } + + @Test + void testCompleteExceptionallyAndGet() { + SimpleCompletableFuture future = new SimpleCompletableFuture<>(); + RuntimeException ex = new RuntimeException("Test Exception"); + assertTrue(future.completeExceptionally(ex)); + assertTrue(future.isDone()); + + ExecutionException thrown = assertThrows(ExecutionException.class, future::get); + assertEquals(ex, thrown.getCause()); + } + + @Test + void testGetWithTimeout_timesOut() { + SimpleCompletableFuture future = new SimpleCompletableFuture<>(); + assertThrows(TimeoutException.class, () -> future.get(10, TimeUnit.MILLISECONDS)); + } + + @Test + void testGetWithTimeout_completesInTime() throws ExecutionException, InterruptedException, TimeoutException { + SimpleCompletableFuture future = new SimpleCompletableFuture<>(); + future.complete("fast"); + assertEquals("fast", future.get(10, TimeUnit.MILLISECONDS)); + } + + @Test + void testGetWithTimeout_completesInTimeAfterWait() throws Exception { + SimpleCompletableFuture future = new SimpleCompletableFuture<>(); + Thread t = new Thread(() -> { + try { + Thread.sleep(50); + } catch (InterruptedException e) { + // ignore + } + future.complete("late"); + }); + t.start(); + assertEquals("late", future.get(200, TimeUnit.MILLISECONDS)); + } + + @Test + void testThenApply() throws ExecutionException, InterruptedException { + SimpleCompletableFuture future = new SimpleCompletableFuture<>(); + Future mapped = future.thenApply(String::length); + + future.complete("hello"); + + assertEquals(5, mapped.get()); + } + + @Test + void testThenApplyOnCompletedFuture() throws ExecutionException, InterruptedException { + SimpleCompletableFuture future = new SimpleCompletableFuture<>(); + future.complete("done"); + + Future mapped = future.thenApply(String::length); + + assertEquals(4, mapped.get()); + } + + @Test + void testThenApplyWithNull() throws ExecutionException, InterruptedException { + SimpleCompletableFuture future = new SimpleCompletableFuture<>(); + Future mapped = future.thenApply(Objects::isNull); + + future.complete(null); + + assertTrue(mapped.get()); + } + + @Test + void testThenApplyExceptionally() { + SimpleCompletableFuture future = new SimpleCompletableFuture<>(); + RuntimeException ex = new RuntimeException("Initial Exception"); + Future mapped = future.thenApply(String::length); + + future.completeExceptionally(ex); + + ExecutionException thrown = assertThrows(ExecutionException.class, mapped::get); + assertEquals(ex, thrown.getCause()); + } + + @Test + void testThenApplyTransformationThrows() { + SimpleCompletableFuture future = new SimpleCompletableFuture<>(); + RuntimeException ex = new RuntimeException("Transformation Exception"); + Future mapped = future.thenApply(s -> { + throw ex; + }); + + future.complete("hello"); + + ExecutionException thrown = assertThrows(ExecutionException.class, mapped::get); + assertEquals(ex, thrown.getCause()); + } + + @Test + void testCompleteTwice() throws ExecutionException, InterruptedException { + SimpleCompletableFuture future = new SimpleCompletableFuture<>(); + + assertTrue(future.complete("first")); + assertFalse(future.complete("second")); + + assertEquals("first", future.get()); + } + + @Test + void testCompleteThenCompleteExceptionally() throws ExecutionException, InterruptedException { + SimpleCompletableFuture future = new SimpleCompletableFuture<>(); + + assertTrue(future.complete("first")); + assertFalse(future.completeExceptionally(new RuntimeException("second"))); + + assertEquals("first", future.get()); + } + + @Test + void testCompleteExceptionallyThenComplete() { + SimpleCompletableFuture future = new SimpleCompletableFuture<>(); + RuntimeException ex = new RuntimeException("first"); + + assertTrue(future.completeExceptionally(ex)); + assertFalse(future.complete("second")); + + ExecutionException thrown = assertThrows(ExecutionException.class, future::get); + assertEquals(ex, thrown.getCause()); + } + + @Test + void testIsDone() { + SimpleCompletableFuture future = new SimpleCompletableFuture<>(); + assertFalse(future.isDone()); + future.complete("done"); + assertTrue(future.isDone()); + } + + @Test + void testIsDoneExceptionally() { + SimpleCompletableFuture future = new SimpleCompletableFuture<>(); + assertFalse(future.isDone()); + future.completeExceptionally(new RuntimeException()); + assertTrue(future.isDone()); + } +} diff --git a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift index 1930e601a..f246bd0f7 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift @@ -14,6 +14,7 @@ import JExtractSwiftLib import Testing +import SwiftJavaConfigurationShared @Suite struct JNIAsyncTests { @@ -33,9 +34,9 @@ struct JNIAsyncTests { * } */ public static java.util.concurrent.CompletableFuture asyncVoid() { - java.util.concurrent.CompletableFuture $future = new java.util.concurrent.CompletableFuture(); - SwiftModule.$asyncVoid($future); - return $future.thenApply((futureResult$) -> { + java.util.concurrent.CompletableFuture future$ = new java.util.concurrent.CompletableFuture(); + SwiftModule.$asyncVoid(future$); + return future$.thenApply((futureResult$) -> { return futureResult$; } ); @@ -107,9 +108,9 @@ struct JNIAsyncTests { * } */ public static java.util.concurrent.CompletableFuture async() { - java.util.concurrent.CompletableFuture $future = new java.util.concurrent.CompletableFuture(); - SwiftModule.$async($future); - return $future.thenApply((futureResult$) -> { + java.util.concurrent.CompletableFuture future$ = new java.util.concurrent.CompletableFuture(); + SwiftModule.$async(future$); + return future$.thenApply((futureResult$) -> { return futureResult$; } ); @@ -195,9 +196,9 @@ struct JNIAsyncTests { * } */ public static java.util.concurrent.CompletableFuture async(long i) { - java.util.concurrent.CompletableFuture $future = new java.util.concurrent.CompletableFuture(); - SwiftModule.$async(i, $future); - return $future.thenApply((futureResult$) -> { + java.util.concurrent.CompletableFuture future$ = new java.util.concurrent.CompletableFuture(); + SwiftModule.$async(i, future$); + return future$.thenApply((futureResult$) -> { return futureResult$; } ); @@ -276,9 +277,9 @@ struct JNIAsyncTests { * } */ public static java.util.concurrent.CompletableFuture async(MyClass c, SwiftArena swiftArena$) { - java.util.concurrent.CompletableFuture $future = new java.util.concurrent.CompletableFuture(); - SwiftModule.$async(c.$memoryAddress(), $future); - return $future.thenApply((futureResult$) -> { + java.util.concurrent.CompletableFuture future$ = new java.util.concurrent.CompletableFuture(); + SwiftModule.$async(c.$memoryAddress(), future$); + return future$.thenApply((futureResult$) -> { return MyClass.wrapMemoryAddressUnsafe(futureResult$, swiftArena$); } ); @@ -365,9 +366,9 @@ struct JNIAsyncTests { expectedChunks: [ """ public static java.util.concurrent.CompletableFuture async(java.lang.String s) { - java.util.concurrent.CompletableFuture $future = new java.util.concurrent.CompletableFuture(); - SwiftModule.$async(s, $future); - return $future.thenApply((futureResult$) -> { + java.util.concurrent.CompletableFuture future$ = new java.util.concurrent.CompletableFuture(); + SwiftModule.$async(s, future$); + return future$.thenApply((futureResult$) -> { return futureResult$; } ); @@ -408,4 +409,65 @@ struct JNIAsyncTests { ] ) } + + @Test("Import: (MyClass) async -> MyClass (Java, LegacyFuture)") + func legacyFuture_asyncMyClassToMyClass_java() throws { + var config = Configuration() + config.asyncFuncMode = .legacyFuture + + try assertOutput( + input: """ + class MyClass { } + + public func async(c: MyClass) async -> MyClass + """, + config: config, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public static java.util.concurrent.Future async(MyClass c, SwiftArena swiftArena$) { + org.swift.swiftkit.core.SimpleCompletableFuture future$ = new org.swift.swiftkit.core.SimpleCompletableFuture(); + SwiftModule.$async(c.$memoryAddress(), future$); + return future$.thenApply((futureResult$) -> { + return MyClass.wrapMemoryAddressUnsafe(futureResult$, swiftArena$); + } + ); + } + """, + """ + private static native void $async(long c, org.swift.swiftkit.core.SimpleCompletableFuture result_future); + """, + ] + ) + } + + @Test("Import: (MyClass) async -> MyClass (Swift, LegacyFuture)") + func legacyFuture_asyncMyClassToMyClass_swift() throws { + var config = Configuration() + config.asyncFuncMode = .legacyFuture + + try assertOutput( + input: """ + class MyClass { } + + public func async(c: MyClass) async -> MyClass + """, + config: config, + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024async__JLorg_swift_swiftkit_core_SimpleCompletableFuture_2") + func Java_com_example_swift_SwiftModule__00024async__JLorg_swift_swiftkit_core_SimpleCompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, c: jlong, result_future: jobject?) { + ... + var task: Task? = nil + ... + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.SimpleCompletableFuture.complete, [jvalue(l: boxedResult$)]) + ... + } + """ + ] + ) + } } From eea0638c5573c89bf67698a7e131be04970042c8 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Mon, 1 Dec 2025 13:22:18 +0100 Subject: [PATCH 135/141] link log on android (#467) --- Package.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Package.swift b/Package.swift index b4c202082..d877ae696 100644 --- a/Package.swift +++ b/Package.swift @@ -386,6 +386,9 @@ let package = Package( swiftSettings: [ .swiftLanguageMode(.v5), .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"], .when(platforms: [.macOS, .linux, .windows])) + ], + linkerSettings: [ + .linkedLibrary("log", .when(platforms: [.android])) ] ), From 67c20f76d083e18a95abfd7c9a2c830ba350cb7d Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Mon, 1 Dec 2025 15:21:23 +0100 Subject: [PATCH 136/141] Fix JNI caching for native threads (#464) --- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 16 +- Sources/SwiftJava/generated/JavaThread.swift | 229 ++++++++++++++++++ Sources/SwiftJava/swift-java.config | 1 + Sources/SwiftJavaRuntimeSupport/JNI.swift | 31 +++ .../JNIMethodIDCaches.swift | 3 - .../_JNIBoxedConversions.swift | 8 - .../_JNIMethodIDCache.swift | 31 ++- 7 files changed, 302 insertions(+), 17 deletions(-) create mode 100644 Sources/SwiftJava/generated/JavaThread.swift create mode 100644 Sources/SwiftJavaRuntimeSupport/JNI.swift diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index d3b477326..6628be333 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -119,6 +119,8 @@ extension JNISwift2JavaGenerator { private func printGlobalSwiftThunkSources(_ printer: inout CodePrinter) throws { printHeader(&printer) + printJNIOnLoad(&printer) + for decl in analysis.importedGlobalFuncs { printSwiftFunctionThunk(&printer, decl) printer.println() @@ -130,6 +132,18 @@ extension JNISwift2JavaGenerator { } } + private func printJNIOnLoad(_ printer: inout CodePrinter) { + printer.print( + """ + @_cdecl("JNI_OnLoad") + func JNI_OnLoad(javaVM: JavaVMPointer, reserved: UnsafeMutableRawPointer) -> jint { + SwiftJavaRuntimeSupport._JNI_OnLoad(javaVM, reserved) + return JNI_VERSION_1_6 + } + """ + ) + } + private func printNominalTypeThunks(_ printer: inout CodePrinter, _ type: ImportedNominalType) throws { printHeader(&printer) @@ -222,7 +236,7 @@ extension JNISwift2JavaGenerator { let methodSignature = MethodSignature(resultType: .void, parameterTypes: enumCase.parameterConversions.map(\.native.javaType)) let methods = #"[.init(name: "", signature: "\#(methodSignature.mangledName)")]"# - return #"_JNIMethodIDCache(environment: try! JavaVirtualMachine.shared().environment(), className: "\#(nativeParametersClassName)", methods: \#(methods))"# + return #"_JNIMethodIDCache(className: "\#(nativeParametersClassName)", methods: \#(methods))"# } private func printEnumGetAsCaseThunk( diff --git a/Sources/SwiftJava/generated/JavaThread.swift b/Sources/SwiftJava/generated/JavaThread.swift new file mode 100644 index 000000000..c71e933b4 --- /dev/null +++ b/Sources/SwiftJava/generated/JavaThread.swift @@ -0,0 +1,229 @@ +// Auto-generated by Java-to-Swift wrapper generator. +import CSwiftJavaJNI + +@JavaClass("java.lang.Thread") +open class JavaThread: JavaObject { + @JavaMethod + @_nonoverride public convenience init(_ arg0: String, environment: JNIEnvironment? = nil) + + @JavaMethod + @_nonoverride public convenience init(environment: JNIEnvironment? = nil) + + @JavaMethod + open func getName() -> String + + @JavaMethod + open func run() + + @JavaMethod + open func interrupt() + + @JavaMethod + open override func toString() -> String + + @JavaMethod + open override func clone() throws -> JavaObject! + + @JavaMethod + open func join(_ arg0: Int64, _ arg1: Int32) throws + + @JavaMethod + open func join() throws + + @JavaMethod + open func join(_ arg0: Int64) throws + + @JavaMethod + open func setContextClassLoader(_ arg0: JavaClassLoader?) + + @JavaMethod + open func setPriority(_ arg0: Int32) + + @JavaMethod + open func setDaemon(_ arg0: Bool) + + @JavaMethod + open func start() + + @JavaMethod + open func getPriority() -> Int32 + + @JavaMethod + open func isDaemon() -> Bool + + @JavaMethod + open func getContextClassLoader() -> JavaClassLoader! + + @JavaMethod + open func isVirtual() -> Bool + + @JavaMethod + open func isAlive() -> Bool + + @JavaMethod + open func threadId() -> Int64 + + @JavaMethod + open func getUncaughtExceptionHandler() -> JavaThread.UncaughtExceptionHandler! + + @JavaMethod + open func stop() + + @JavaMethod + open func isInterrupted() -> Bool + + @JavaMethod + open func setName(_ arg0: String) + + @JavaMethod + open func checkAccess() + + @JavaMethod + open func getId() -> Int64 + + @JavaMethod + open func setUncaughtExceptionHandler(_ arg0: JavaThread.UncaughtExceptionHandler?) +} +extension JavaThread { + @JavaInterface("java.lang.Thread$Builder") + public struct Builder { + @JavaMethod + public func name(_ arg0: String) -> JavaThread.Builder! + + @JavaMethod + public func name(_ arg0: String, _ arg1: Int64) -> JavaThread.Builder! + + @JavaMethod + public func uncaughtExceptionHandler(_ arg0: JavaThread.UncaughtExceptionHandler?) -> JavaThread.Builder! + + @JavaMethod + public func inheritInheritableThreadLocals(_ arg0: Bool) -> JavaThread.Builder! + } +} +extension JavaThread.Builder { + @JavaInterface("java.lang.Thread$Builder$OfPlatform", extends: JavaThread.Builder.self) + public struct OfPlatform { + @JavaMethod + public func name(_ arg0: String, _ arg1: Int64) -> JavaThread.Builder! + + @JavaMethod + public func name(_ arg0: String, _ arg1: Int64) -> JavaThread.Builder.OfPlatform! + + @JavaMethod + public func name(_ arg0: String) -> JavaThread.Builder! + + @JavaMethod + public func name(_ arg0: String) -> JavaThread.Builder.OfPlatform! + + @JavaMethod + public func priority(_ arg0: Int32) -> JavaThread.Builder.OfPlatform! + + @JavaMethod + public func daemon() -> JavaThread.Builder.OfPlatform! + + @JavaMethod + public func daemon(_ arg0: Bool) -> JavaThread.Builder.OfPlatform! + + @JavaMethod + public func uncaughtExceptionHandler(_ arg0: JavaThread.UncaughtExceptionHandler?) -> JavaThread.Builder.OfPlatform! + + @JavaMethod + public func uncaughtExceptionHandler(_ arg0: JavaThread.UncaughtExceptionHandler?) -> JavaThread.Builder! + + @JavaMethod + public func stackSize(_ arg0: Int64) -> JavaThread.Builder.OfPlatform! + + @JavaMethod + public func inheritInheritableThreadLocals(_ arg0: Bool) -> JavaThread.Builder.OfPlatform! + + @JavaMethod + public func inheritInheritableThreadLocals(_ arg0: Bool) -> JavaThread.Builder! + } +} +extension JavaThread.Builder { + @JavaInterface("java.lang.Thread$Builder$OfVirtual", extends: JavaThread.Builder.self) + public struct OfVirtual { + @JavaMethod + public func name(_ arg0: String, _ arg1: Int64) -> JavaThread.Builder! + + @JavaMethod + public func name(_ arg0: String, _ arg1: Int64) -> JavaThread.Builder.OfVirtual! + + @JavaMethod + public func name(_ arg0: String) -> JavaThread.Builder! + + @JavaMethod + public func name(_ arg0: String) -> JavaThread.Builder.OfVirtual! + + @JavaMethod + public func uncaughtExceptionHandler(_ arg0: JavaThread.UncaughtExceptionHandler?) -> JavaThread.Builder! + + @JavaMethod + public func uncaughtExceptionHandler(_ arg0: JavaThread.UncaughtExceptionHandler?) -> JavaThread.Builder.OfVirtual! + + @JavaMethod + public func inheritInheritableThreadLocals(_ arg0: Bool) -> JavaThread.Builder.OfVirtual! + + @JavaMethod + public func inheritInheritableThreadLocals(_ arg0: Bool) -> JavaThread.Builder! + } +} +extension JavaThread { + @JavaInterface("java.lang.Thread$UncaughtExceptionHandler") + public struct UncaughtExceptionHandler { + @JavaMethod + public func uncaughtException(_ arg0: JavaThread?, _ arg1: Throwable?) + } +} +extension JavaClass { + @JavaStaticField(isFinal: true) + public var MIN_PRIORITY: Int32 + + @JavaStaticField(isFinal: true) + public var NORM_PRIORITY: Int32 + + @JavaStaticField(isFinal: true) + public var MAX_PRIORITY: Int32 + + @JavaStaticMethod + public func currentThread() -> JavaThread! + + @JavaStaticMethod + public func onSpinWait() + + @JavaStaticMethod + public func holdsLock(_ arg0: JavaObject?) -> Bool + + @JavaStaticMethod + public func interrupted() -> Bool + + @JavaStaticMethod + public func activeCount() -> Int32 + + @JavaStaticMethod + public func enumerate(_ arg0: [JavaThread?]) -> Int32 + + @JavaStaticMethod + public func yield() + + @JavaStaticMethod + public func sleep(_ arg0: Int64) throws + + @JavaStaticMethod + public func sleep(_ arg0: Int64, _ arg1: Int32) throws + + @JavaStaticMethod + public func ofPlatform() -> JavaThread.Builder.OfPlatform! + + @JavaStaticMethod + public func ofVirtual() -> JavaThread.Builder.OfVirtual! + + @JavaStaticMethod + public func dumpStack() + + @JavaStaticMethod + public func setDefaultUncaughtExceptionHandler(_ arg0: JavaThread.UncaughtExceptionHandler?) + + @JavaStaticMethod + public func getDefaultUncaughtExceptionHandler() -> JavaThread.UncaughtExceptionHandler! +} diff --git a/Sources/SwiftJava/swift-java.config b/Sources/SwiftJava/swift-java.config index d07ff1620..d43096a73 100644 --- a/Sources/SwiftJava/swift-java.config +++ b/Sources/SwiftJava/swift-java.config @@ -23,6 +23,7 @@ "java.lang.Void" : "JavaVoid", "java.lang.CharSequence": "CharSequence", "java.lang.Appendable": "Appendable", + "java.lang.Thread": "JavaThread", "java.util.Optional": "JavaOptional", "java.util.OptionalDouble": "JavaOptionalDouble", "java.util.OptionalInt": "JavaOptionalInt", diff --git a/Sources/SwiftJavaRuntimeSupport/JNI.swift b/Sources/SwiftJavaRuntimeSupport/JNI.swift new file mode 100644 index 000000000..b6f0117df --- /dev/null +++ b/Sources/SwiftJavaRuntimeSupport/JNI.swift @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftJava +import CSwiftJavaJNI + +final class JNI { + static var shared: JNI! + + let applicationClassLoader: JavaClassLoader + + init(fromVM javaVM: JavaVirtualMachine) { + self.applicationClassLoader = try! JavaClass(environment: javaVM.environment()).currentThread().getContextClassLoader() + } +} + +// Called by generated code, and not automatically by Java. +public func _JNI_OnLoad(_ javaVM: JavaVMPointer, _ reserved: UnsafeMutableRawPointer) { + JNI.shared = JNI(fromVM: JavaVirtualMachine(adoptingJVM: javaVM)) +} diff --git a/Sources/SwiftJavaRuntimeSupport/JNIMethodIDCaches.swift b/Sources/SwiftJavaRuntimeSupport/JNIMethodIDCaches.swift index 56fe0351a..16a9c899e 100644 --- a/Sources/SwiftJavaRuntimeSupport/JNIMethodIDCaches.swift +++ b/Sources/SwiftJavaRuntimeSupport/JNIMethodIDCaches.swift @@ -27,7 +27,6 @@ extension _JNIMethodIDCache { ) private static let cache = _JNIMethodIDCache( - environment: try! JavaVirtualMachine.shared().environment(), className: "java/util/concurrent/CompletableFuture", methods: [completeMethod, completeExceptionallyMethod] ) @@ -59,7 +58,6 @@ extension _JNIMethodIDCache { ) private static let cache = _JNIMethodIDCache( - environment: try! JavaVirtualMachine.shared().environment(), className: "org/swift/swiftkit/core/SimpleCompletableFuture", methods: [completeMethod, completeExceptionallyMethod] ) @@ -81,7 +79,6 @@ extension _JNIMethodIDCache { private static let messageConstructor = Method(name: "", signature: "(Ljava/lang/String;)V") private static let cache = _JNIMethodIDCache( - environment: try! JavaVirtualMachine.shared().environment(), className: "java/lang/Exception", methods: [messageConstructor] ) diff --git a/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift b/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift index 68d98ffcd..ad4572caa 100644 --- a/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift +++ b/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift @@ -26,47 +26,39 @@ public enum _JNIBoxedConversions { private static let doubleMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(D)Ljava/lang/Double;", isStatic: true) private static let booleanCache = _JNIMethodIDCache( - environment: try! JavaVirtualMachine.shared().environment(), className: "java/lang/Boolean", methods: [booleanMethod] ) private static let byteCache = _JNIMethodIDCache( - environment: try! JavaVirtualMachine.shared().environment(), className: "java/lang/Byte", methods: [byteMethod] ) private static let charCache = _JNIMethodIDCache( - environment: try! JavaVirtualMachine.shared().environment(), className: "java/lang/Character", methods: [charMethod] ) private static let shortCache = _JNIMethodIDCache( - environment: try! JavaVirtualMachine.shared().environment(), className: "java/lang/Short", methods: [shortMethod] ) private static let intCache = _JNIMethodIDCache( - environment: try! JavaVirtualMachine.shared().environment(), className: "java/lang/Integer", methods: [intMethod] ) private static let longCache = _JNIMethodIDCache( - environment: try! JavaVirtualMachine.shared().environment(), className: "java/lang/Long", methods: [longMethod] ) private static let floatCache = _JNIMethodIDCache( - environment: try! JavaVirtualMachine.shared().environment(), className: "java/lang/Float", methods: [floatMethod] ) private static let doubleCache = _JNIMethodIDCache( - environment: try! JavaVirtualMachine.shared().environment(), className: "java/lang/Double", methods: [doubleMethod] ) diff --git a/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift b/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift index dd7eb5d13..fbadf1916 100644 --- a/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift +++ b/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift @@ -39,11 +39,33 @@ public final class _JNIMethodIDCache: Sendable { self._class! } - public init(environment: UnsafeMutablePointer!, className: String, methods: [Method]) { - guard let clazz = environment.interface.FindClass(environment, className) else { - fatalError("Class \(className) could not be found!") + /// An optional reference to a java object holder + /// if we cached this class through the class loader + /// This is to make sure that the underlying reference remains valid + nonisolated(unsafe) private let javaObjectHolder: JavaObjectHolder? + + public init(className: String, methods: [Method]) { + let environment = try! JavaVirtualMachine.shared().environment() + + let clazz: jobject + if let jniClass = environment.interface.FindClass(environment, className) { + clazz = environment.interface.NewGlobalRef(environment, jniClass)! + self.javaObjectHolder = nil + } else { + // Clear any ClassNotFound exceptions from FindClass + environment.interface.ExceptionClear(environment) + + if let javaClass = try? JNI.shared.applicationClassLoader.loadClass( + className.replacingOccurrences(of: "/", with: ".") + ) { + clazz = javaClass.javaThis + self.javaObjectHolder = javaClass.javaHolder + } else { + fatalError("Class \(className) could not be found!") + } } - self._class = environment.interface.NewGlobalRef(environment, clazz)! + + self._class = clazz self.methods = methods.reduce(into: [:]) { (result, method) in if method.isStatic { if let methodID = environment.interface.GetStaticMethodID(environment, clazz, method.name, method.signature) { @@ -61,7 +83,6 @@ public final class _JNIMethodIDCache: Sendable { } } - public subscript(_ method: Method) -> jmethodID? { methods[method] } From 3be8df2d8d47f45cb8e14805de3685814adaba82 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 2 Dec 2025 15:35:58 +0100 Subject: [PATCH 137/141] jextract: add support for implementing Swift protocols in Java (#449) Co-authored-by: Konrad `ktoso` Malawski --- .../JExtractSwiftPlugin.swift | 199 +++++++++- Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift | 4 +- .../JavaDependencySampleApp/ci-validate.sh | 11 +- Samples/JavaKitSampleApp/ci-validate.sh | 10 +- Samples/JavaProbablyPrime/ci-validate.sh | 10 +- .../MySwiftLibrary/CallbackProtcol.swift | 77 ++++ .../Sources/MySwiftLibrary/Storage.swift | 36 ++ .../Sources/MySwiftLibrary/swift-java.config | 1 + .../SwiftJavaExtractJNISampleApp/build.gradle | 10 +- .../ci-validate.sh | 41 ++- .../example/swift/ProtocolCallbacksTest.java | 117 ++++++ .../java/com/example/swift/ProtocolTest.java | 30 ++ .../Convenience/JavaType+Extensions.swift | 2 +- .../Convenience/String+Extensions.swift | 2 +- .../Convenience/SwiftSyntax+Extensions.swift | 2 +- Sources/JExtractSwiftLib/ImportedDecls.swift | 9 + ...Generator+InterfaceWrapperGeneration.swift | 342 ++++++++++++++++++ ...t2JavaGenerator+JavaBindingsPrinting.swift | 63 +++- ...ISwift2JavaGenerator+JavaTranslation.swift | 59 +-- ...wift2JavaGenerator+NativeTranslation.swift | 133 +++++-- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 226 +++++++++++- .../JNI/JNISwift2JavaGenerator.swift | 8 + .../JavaTypes/JavaType+JDK.swift | 6 + .../SwiftTypes/SwiftKnownTypeDecls.swift | 14 + .../SwiftTypes/SwiftType.swift | 6 +- .../Constructor+Utilities.swift | 5 - .../JavaLangReflect/HasJavaModifiers.swift | 47 +++ .../JavaLangReflect/Method+Utilities.swift | 22 -- .../SwiftJava/JVM/JavaVirtualMachine.swift | 2 +- Sources/SwiftJava/Macros.swift | 5 +- Sources/SwiftJava/String+Extensions.swift | 10 +- .../Configuration.swift | 12 +- .../Documentation.docc/index.md | 2 +- Sources/SwiftJavaMacros/JavaMethodMacro.swift | 18 +- .../JNIMethodIDCaches.swift | 29 ++ .../generated/JavaJNISwiftInstance.swift | 21 ++ .../Commands/ConfigureCommand.swift | 115 +++--- .../Commands/JExtractCommand.swift | 18 +- .../Commands/WrapJavaCommand.swift | 102 +++++- Sources/SwiftJavaTool/CommonOptions.swift | 5 +- .../SwiftJavaBaseAsyncParsableCommand.swift | 14 +- .../JavaClassTranslator.swift | 14 + Sources/SwiftJavaToolLib/StringExtras.swift | 5 + .../JExtractSwiftTests/JNI/JNIEnumTests.swift | 10 +- .../JNI/JNIProtocolTests.swift | 237 ++++++++---- .../MemoryManagementModeTests.swift | 2 +- .../WrapJavaTests/GenericsWrapJavaTests.swift | 41 ++- 47 files changed, 1871 insertions(+), 283 deletions(-) create mode 100644 Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/CallbackProtcol.swift create mode 100644 Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Storage.swift create mode 100644 Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolCallbacksTest.java create mode 100644 Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+InterfaceWrapperGeneration.swift create mode 100644 Sources/JavaStdlib/JavaLangReflect/HasJavaModifiers.swift create mode 100644 Sources/SwiftJavaRuntimeSupport/generated/JavaJNISwiftInstance.swift diff --git a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift index 8d0be4559..2808b6d31 100644 --- a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift +++ b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift @@ -25,9 +25,12 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] { let toolURL = try context.tool(named: "SwiftJavaTool").url - + + var commands: [Command] = [] + guard let sourceModule = target.sourceModule else { return [] } + // Note: Target doesn't have a directoryURL counterpart to directory, // so we cannot eliminate this deprecation warning. for dependency in target.dependencies { @@ -80,7 +83,7 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { let (moduleName, configFile) = moduleAndConfigFile return [ "--depends-on", - "\(configFile.path(percentEncoded: false))" + "\(moduleName)=\(configFile.path(percentEncoded: false))" ] } arguments += dependentConfigFilesArguments @@ -123,15 +126,165 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { print("[swift-java-plugin] Output swift files:\n - \(outputSwiftFiles.map({$0.absoluteString}).joined(separator: "\n - "))") - return [ + var jextractOutputFiles = outputSwiftFiles + + // If the developer has enabled java callbacks in the configuration (default is false) + // and we are running in JNI mode, we will run additional phases in this build plugin + // to generate Swift wrappers using wrap-java that can be used to callback to Java. + let shouldRunJavaCallbacksPhases = + if let configuration, + configuration.enableJavaCallbacks == true, + configuration.effectiveMode == .jni { + true + } else { + false + } + + // Extract list of all sources + let javaSourcesListFileName = "jextract-generated-sources.txt" + let javaSourcesFile = outputJavaDirectory.appending(path: javaSourcesListFileName) + if shouldRunJavaCallbacksPhases { + arguments += [ + "--generated-java-sources-list-file-output", javaSourcesListFileName + ] + jextractOutputFiles += [javaSourcesFile] + } + + commands += [ .buildCommand( displayName: "Generate Java wrappers for Swift types", executable: toolURL, arguments: arguments, inputFiles: [ configFile ] + swiftFiles, - outputFiles: outputSwiftFiles + outputFiles: jextractOutputFiles + ) + ] + + // If we do not need Java callbacks, we can skip the remaining steps. + guard shouldRunJavaCallbacksPhases else { + return commands + } + + // The URL of the compiled Java sources + let javaCompiledClassesURL = context.pluginWorkDirectoryURL + .appending(path: "compiled-java-output") + + // Build SwiftKitCore and get the classpath + // as the jextracted sources will depend on that + + guard let swiftJavaDirectory = findSwiftJavaDirectory(for: target) else { + fatalError("Unable to find the path to the swift-java sources, please file an issue.") + } + log("Found swift-java at \(swiftJavaDirectory)") + + let swiftKitCoreClassPath = swiftJavaDirectory.appending(path: "SwiftKitCore/build/classes/java/main") + + // We need to use a different gradle home, because + // this plugin might be run from inside another gradle task + // and that would cause conflicts. + let gradleUserHome = context.pluginWorkDirectoryURL.appending(path: "gradle-user-home") + + let GradleUserHome = "GRADLE_USER_HOME" + let gradleUserHomePath = gradleUserHome.path(percentEncoded: false) + log("Prepare command: :SwiftKitCore:build in \(GradleUserHome)=\(gradleUserHomePath)") + var gradlewEnvironment = ProcessInfo.processInfo.environment + gradlewEnvironment[GradleUserHome] = gradleUserHomePath + log("Forward environment: \(gradlewEnvironment)") + + let gradleExecutable = findExecutable(name: "gradle") ?? // try using installed 'gradle' if available in PATH + swiftJavaDirectory.appending(path: "gradlew") // fallback to calling ./gradlew if gradle is not installed + log("Detected 'gradle' executable (or gradlew fallback): \(gradleExecutable)") + + commands += [ + .buildCommand( + displayName: "Build SwiftKitCore using Gradle (Java)", + executable: gradleExecutable, + arguments: [ + ":SwiftKitCore:build", + "--project-dir", swiftJavaDirectory.path(percentEncoded: false), + "--gradle-user-home", gradleUserHomePath, + "--configure-on-demand", + "--no-daemon" + ], + environment: gradlewEnvironment, + inputFiles: [swiftJavaDirectory], + outputFiles: [swiftKitCoreClassPath] + ) + ] + + // Compile the jextracted sources + let javaHome = URL(filePath: findJavaHome()) + + commands += [ + .buildCommand( + displayName: "Build extracted Java sources", + executable: javaHome + .appending(path: "bin") + .appending(path: self.javacName), + arguments: [ + "@\(javaSourcesFile.path(percentEncoded: false))", + "-d", javaCompiledClassesURL.path(percentEncoded: false), + "-parameters", + "-classpath", swiftKitCoreClassPath.path(percentEncoded: false) + ], + inputFiles: [javaSourcesFile, swiftKitCoreClassPath], + outputFiles: [javaCompiledClassesURL] + ) + ] + + // Run `configure` to extract a swift-java config to use for wrap-java + let swiftJavaConfigURL = context.pluginWorkDirectoryURL.appending(path: "swift-java.config") + + commands += [ + .buildCommand( + displayName: "Output swift-java.config that contains all extracted Java sources", + executable: toolURL, + arguments: [ + "configure", + "--output-directory", context.pluginWorkDirectoryURL.path(percentEncoded: false), + "--cp", javaCompiledClassesURL.path(percentEncoded: false), + "--swift-module", sourceModule.name, + "--swift-type-prefix", "Java" + ], + inputFiles: [javaCompiledClassesURL], + outputFiles: [swiftJavaConfigURL] ) ] + + let singleSwiftFileOutputName = "WrapJavaGenerated.swift" + + // In the end we can run wrap-java on the previous inputs + var wrapJavaArguments = [ + "wrap-java", + "--swift-module", sourceModule.name, + "--output-directory", outputSwiftDirectory.path(percentEncoded: false), + "--config", swiftJavaConfigURL.path(percentEncoded: false), + "--cp", swiftKitCoreClassPath.path(percentEncoded: false), + "--single-swift-file-output", singleSwiftFileOutputName + ] + + // Add any dependent config files as arguments + wrapJavaArguments += dependentConfigFilesArguments + + commands += [ + .buildCommand( + displayName: "Wrap compiled Java sources using wrap-java", + executable: toolURL, + arguments: wrapJavaArguments, + inputFiles: [swiftJavaConfigURL, swiftKitCoreClassPath], + outputFiles: [outputSwiftDirectory.appending(path: singleSwiftFileOutputName)] + ) + ] + + return commands + } + + var javacName: String { +#if os(Windows) + "javac.exe" +#else + "javac" +#endif } /// Find the manifest files from other swift-java executions in any targets @@ -181,5 +334,43 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { return dependentConfigFiles } + + private func findSwiftJavaDirectory(for target: any Target) -> URL? { + for dependency in target.dependencies { + switch dependency { + case .target(let target): + continue + + case .product(let product): + guard let swiftJava = product.sourceModules.first(where: { $0.name == "SwiftJava" }) else { + return nil + } + + // We are inside Sources/SwiftJava + return swiftJava.directoryURL.deletingLastPathComponent().deletingLastPathComponent() + + @unknown default: + continue + } + } + + return nil + } } +func findExecutable(name: String) -> URL? { + let fileManager = FileManager.default + + guard let path = ProcessInfo.processInfo.environment["PATH"] else { + return nil + } + + for path in path.split(separator: ":") { + let fullURL = URL(fileURLWithPath: String(path)).appendingPathComponent(name) + if fileManager.isExecutableFile(atPath: fullURL.path) { + return fullURL + } + } + + return nil +} \ No newline at end of file diff --git a/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift index 7908932d1..a578091f1 100644 --- a/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift +++ b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift @@ -171,8 +171,6 @@ struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { arguments += javaStdlibModules.flatMap { ["--depends-on", $0] } if !outputSwiftFiles.isEmpty { - arguments += [ configFile.path(percentEncoded: false) ] - let displayName = "Wrapping \(classes.count) Java classes in Swift target '\(sourceModule.name)'" log("Prepared: \(displayName)") commands += [ @@ -266,4 +264,4 @@ func getExtractedJavaStdlibModules() -> [String] { } return url.lastPathComponent }.sorted() -} \ No newline at end of file +} diff --git a/Samples/JavaDependencySampleApp/ci-validate.sh b/Samples/JavaDependencySampleApp/ci-validate.sh index feeb87675..1c3e2d552 100755 --- a/Samples/JavaDependencySampleApp/ci-validate.sh +++ b/Samples/JavaDependencySampleApp/ci-validate.sh @@ -3,9 +3,16 @@ set -e set -x +# WORKAROUND: prebuilts broken on Swift 6.2.1 and Linux and tests using macros https://github.com/swiftlang/swift-java/issues/418 +if [ "$(uname)" = "Darwin" ]; then + DISABLE_EXPERIMENTAL_PREBUILTS='' +else + DISABLE_EXPERIMENTAL_PREBUILTS='--disable-experimental-prebuilts' +fi + # invoke resolve as part of a build run swift build \ - --disable-experimental-prebuilts \ + $DISABLE_EXPERIMENTAL_PREBUILTS \ --disable-sandbox # explicitly invoke resolve without explicit path or dependency @@ -13,7 +20,7 @@ swift build \ # FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 swift run \ - --disable-experimental-prebuilts \ + $DISABLE_EXPERIMENTAL_PREBUILTS \ swift-java resolve \ Sources/JavaCommonsCSV/swift-java.config \ --swift-module JavaCommonsCSV \ diff --git a/Samples/JavaKitSampleApp/ci-validate.sh b/Samples/JavaKitSampleApp/ci-validate.sh index 297f5c885..327baadf9 100755 --- a/Samples/JavaKitSampleApp/ci-validate.sh +++ b/Samples/JavaKitSampleApp/ci-validate.sh @@ -3,8 +3,14 @@ set -e set -x -swift build \ - --disable-experimental-prebuilts # FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 +# WORKAROUND: prebuilts broken on Swift 6.2.1 and Linux and tests using macros https://github.com/swiftlang/swift-java/issues/418 +if [ "$(uname)" = "Darwin" ]; then + DISABLE_EXPERIMENTAL_PREBUILTS='' +else + DISABLE_EXPERIMENTAL_PREBUILTS='--disable-experimental-prebuilts' +fi + +swift build $DISABLE_EXPERIMENTAL_PREBUILTS "$JAVA_HOME/bin/java" \ -cp .build/plugins/outputs/javakitsampleapp/JavaKitExample/destination/JavaCompilerPlugin/Java \ diff --git a/Samples/JavaProbablyPrime/ci-validate.sh b/Samples/JavaProbablyPrime/ci-validate.sh index dc6249969..202dcbabe 100755 --- a/Samples/JavaProbablyPrime/ci-validate.sh +++ b/Samples/JavaProbablyPrime/ci-validate.sh @@ -3,7 +3,13 @@ set -e set -x -# FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 +# WORKAROUND: prebuilts broken on Swift 6.2.1 and Linux and tests using macros https://github.com/swiftlang/swift-java/issues/418 +if [ "$(uname)" = "Darwin" ]; then + DISABLE_EXPERIMENTAL_PREBUILTS='' +else + DISABLE_EXPERIMENTAL_PREBUILTS='--disable-experimental-prebuilts' +fi + swift run \ - --disable-experimental-prebuilts \ + $DISABLE_EXPERIMENTAL_PREBUILTS \ JavaProbablyPrime 1337 \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/CallbackProtcol.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/CallbackProtcol.swift new file mode 100644 index 000000000..985771bef --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/CallbackProtcol.swift @@ -0,0 +1,77 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftJava + +public protocol CallbackProtocol { + func withBool(_ input: Bool) -> Bool + func withInt8(_ input: Int8) -> Int8 + func withUInt16(_ input: UInt16) -> UInt16 + func withInt16(_ input: Int16) -> Int16 + func withInt32(_ input: Int32) -> Int32 + func withInt64(_ input: Int64) -> Int64 + func withFloat(_ input: Float) -> Float + func withDouble(_ input: Double) -> Double + func withString(_ input: String) -> String + func withVoid() + func withObject(_ input: MySwiftClass) -> MySwiftClass + func withOptionalInt64(_ input: Int64?) -> Int64? + func withOptionalObject(_ input: MySwiftClass?) -> Optional +} + +public struct CallbackOutput { + public let bool: Bool + public let int8: Int8 + public let uint16: UInt16 + public let int16: Int16 + public let int32: Int32 + public let int64: Int64 + public let _float: Float + public let _double: Double + public let string: String + public let object: MySwiftClass + public let optionalInt64: Int64? + public let optionalObject: MySwiftClass? +} + +public func outputCallbacks( + _ callbacks: some CallbackProtocol, + bool: Bool, + int8: Int8, + uint16: UInt16, + int16: Int16, + int32: Int32, + int64: Int64, + _float: Float, + _double: Double, + string: String, + object: MySwiftClass, + optionalInt64: Int64?, + optionalObject: MySwiftClass? +) -> CallbackOutput { + return CallbackOutput( + bool: callbacks.withBool(bool), + int8: callbacks.withInt8(int8), + uint16: callbacks.withUInt16(uint16), + int16: callbacks.withInt16(int16), + int32: callbacks.withInt32(int32), + int64: callbacks.withInt64(int64), + _float: callbacks.withFloat(_float), + _double: callbacks.withDouble(_double), + string: callbacks.withString(string), + object: callbacks.withObject(object), + optionalInt64: callbacks.withOptionalInt64(optionalInt64), + optionalObject: callbacks.withOptionalObject(optionalObject) + ) +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Storage.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Storage.swift new file mode 100644 index 000000000..488a78cc1 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Storage.swift @@ -0,0 +1,36 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftJava + +public class StorageItem { + public let value: Int64 + + public init(value: Int64) { + self.value = value + } +} + +public protocol Storage { + func load() -> StorageItem + func save(_ item: StorageItem) +} + +public func saveWithStorage(_ item: StorageItem, s: any Storage) { + s.save(item); +} + +public func loadWithStorage(s: any Storage) -> StorageItem { + return s.load(); +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config index 3d6a12012..52143b7f7 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config @@ -1,5 +1,6 @@ { "javaPackage": "com.example.swift", "mode": "jni", + "enableJavaCallbacks": true, "logLevel": "debug" } diff --git a/Samples/SwiftJavaExtractJNISampleApp/build.gradle b/Samples/SwiftJavaExtractJNISampleApp/build.gradle index a8ce51d27..7bb64c554 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/build.gradle +++ b/Samples/SwiftJavaExtractJNISampleApp/build.gradle @@ -101,7 +101,7 @@ def jextract = tasks.register("jextract", Exec) { } // FIXME: disable prebuilts until swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 - def cmdArgs = ["build", "--disable-experimental-prebuilts"] + def cmdArgs = ["build", "--disable-experimental-prebuilts", "--disable-sandbox"] // Check if the 'swiftSdk' project property was passed if (project.hasProperty('swiftSdk')) { @@ -209,3 +209,11 @@ jmh { "-Djextract.trace.downcalls=false" ] } + +task printGradleHome { + doLast { + println "Gradle Home: ${gradle.gradleHomeDir}" + println "Gradle Version: ${gradle.gradleVersion}" + println "Gradle User Home: ${gradle.gradleUserHomeDir}" + } +} \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh b/Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh index ff5c32c80..d62a09102 100755 --- a/Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh +++ b/Samples/SwiftJavaExtractJNISampleApp/ci-validate.sh @@ -3,7 +3,44 @@ set -x set -e -swift build --disable-experimental-prebuilts # FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 +# WORKAROUND: prebuilts broken on Swift 6.2.1 and Linux and tests using macros https://github.com/swiftlang/swift-java/issues/418 +if [ "$(uname)" = "Darwin" ]; then + DISABLE_EXPERIMENTAL_PREBUILTS='' +else + DISABLE_EXPERIMENTAL_PREBUILTS='--disable-experimental-prebuilts' +fi + +if [[ "$(uname)" == "Darwin" && -n "$GITHUB_ACTION" ]]; then + # WORKAROUND: GitHub Actions on macOS issue with downloading gradle wrapper + # We seem to be hitting a problem when the swiftpm plugin, needs to execute gradle wrapper in a new gradle_user_home. + # Normally, this would just download gradle again and kick off a build, this seems to timeout *specifically* on + # github actions runners. + # + # It is not a sandbox problem, becuase the ./gradlew is run without sandboxing as we already execute + # the entire swift build with '--disable-sandbox' for other reasons. + # + # We cannot use the same gradle user home as the default one since we might make gradle think we're + # building the same project concurrently, which we kind of are, however only a limited subset in order + # to trigger wrap-java with those dependencies. + # + # TODO: this may use some further improvements so normal usage does not incur another wrapper download. + + ./gradlew -h # prime ~/.gradle/wrapper/dists/... + + # Worst part of workaround here; we make sure to pre-load the resolved gradle wrapper downloaded distribution + # to the "known" location the plugin will use for its local builds, which are done in order to compile SwiftKitCore. + # This build is only necessary in order to drive wrap-java on sources generated during the build itself + # which enables the "Implement Swift protocols in Java" feature of jextract/jni mode. + GRADLE_USER_HOME="$(pwd)/.build/plugins/outputs/swiftjavaextractjnisampleapp/MySwiftLibrary/destination/JExtractSwiftPlugin/gradle-user-home" + if [ -d "$HOME/.gradle" ] ; then + echo "COPY $HOME/.gradle to $GRADLE_USER_HOME" + mkdir -p "$GRADLE_USER_HOME" + cp -r "$HOME/.gradle/"* "$GRADLE_USER_HOME/" || true + fi +fi + +# FIXME: disable prebuilts until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418 +swift build $DISABLE_EXPERIMENTAL_PREBUILTS --disable-sandbox ./gradlew run -./gradlew test \ No newline at end of file +./gradlew test diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolCallbacksTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolCallbacksTest.java new file mode 100644 index 000000000..e79fd4a3b --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolCallbacksTest.java @@ -0,0 +1,117 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.SwiftArena; +import org.swift.swiftkit.core.annotations.Unsigned; + +import java.util.Optional; +import java.util.OptionalLong; + +import static org.junit.jupiter.api.Assertions.*; + +public class ProtocolCallbacksTest { + static class JavaCallbacks implements CallbackProtocol { + @Override + public boolean withBool(boolean input) { + return input; + } + + @Override + public byte withInt8(byte input) { + return input; + } + + @Override + public @Unsigned char withUInt16(char input) { + return input; + } + + @Override + public short withInt16(short input) { + return input; + } + + @Override + public int withInt32(int input) { + return input; + } + + @Override + public long withInt64(long input) { + return input; + } + + @Override + public float withFloat(float input) { + return input; + } + + @Override + public double withDouble(double input) { + return input; + } + + @Override + public String withString(String input) { + return input; + } + + @Override + public void withVoid() {} + + @Override + public MySwiftClass withObject(MySwiftClass input) { + return input; + } + + @Override + public OptionalLong withOptionalInt64(OptionalLong input) { + return input; + } + + @Override + public Optional withOptionalObject(Optional input) { + return input; + } + } + + @Test + void primitiveCallbacks() { + try (var arena = SwiftArena.ofConfined()) { + JavaCallbacks callbacks = new JavaCallbacks(); + var object = MySwiftClass.init(5, 3, arena); + var optionalObject = Optional.of(MySwiftClass.init(10, 10, arena)); + var output = MySwiftLibrary.outputCallbacks(callbacks, true, (byte) 1, (char) 16, (short) 16, (int) 32, 64L, 1.34f, 1.34, "Hello from Java!", object, OptionalLong.empty(), optionalObject, arena); + + assertEquals(1, output.getInt8()); + assertEquals(16, output.getUint16()); + assertEquals(16, output.getInt16()); + assertEquals(32, output.getInt32()); + assertEquals(64, output.getInt64()); + assertEquals(1.34f, output.get_float()); + assertEquals(1.34, output.get_double()); + assertEquals("Hello from Java!", output.getString()); + assertFalse(output.getOptionalInt64().isPresent()); + assertEquals(5, output.getObject(arena).getX()); + assertEquals(3, output.getObject(arena).getY()); + + var optionalObjectOutput = output.getOptionalObject(arena); + assertTrue(optionalObjectOutput.isPresent()); + assertEquals(10, optionalObjectOutput.get().getX()); + } + } +} \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolTest.java index c095a42a4..f5d1ffcf7 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolTest.java @@ -72,4 +72,34 @@ void protocolMethod() { assertEquals("ConcreteProtocolAB", proto1.name()); } } + + static class JavaStorage implements Storage { + StorageItem item; + + JavaStorage(StorageItem item) { + this.item = item; + } + + @Override + public StorageItem load() { + return item; + } + + @Override + public void save(StorageItem item) { + this.item = item; + } + } + + @Test + void useStorage() { + try (var arena = SwiftArena.ofConfined()) { + JavaStorage storage = new JavaStorage(null); + MySwiftLibrary.saveWithStorage(StorageItem.init(10, arena), storage); + assertEquals(10, MySwiftLibrary.loadWithStorage(storage, arena).getValue()); + MySwiftLibrary.saveWithStorage(StorageItem.init(7, arena), storage); + MySwiftLibrary.saveWithStorage(StorageItem.init(5, arena), storage); + assertEquals(5, MySwiftLibrary.loadWithStorage(storage, arena).getValue()); + } + } } \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift index 645e5aa48..9f7a19cce 100644 --- a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift @@ -102,7 +102,7 @@ extension JavaType { } } - /// Returns whether this type returns `JavaValue` from JavaKit + /// Returns whether this type returns `JavaValue` from SwiftJava var implementsJavaValue: Bool { return switch self { case .boolean, .byte, .char, .short, .int, .long, .float, .double, .void, .javaLangString: diff --git a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift index 82ce5c1c0..5641c7f23 100644 --- a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift @@ -68,7 +68,7 @@ extension String { /// Looks up self as a SwiftJava wrapped class name and converts it /// into a `JavaType.class` if it exists in `lookupTable`. - func parseJavaClassFromJavaKitName(in lookupTable: [String: String]) -> JavaType? { + func parseJavaClassFromSwiftJavaName(in lookupTable: [String: String]) -> JavaType? { guard let canonicalJavaName = lookupTable[self] else { return nil } diff --git a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift index d3902aa4d..c9f87b6dd 100644 --- a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift @@ -128,7 +128,7 @@ extension WithModifiersSyntax { } extension AttributeListSyntax.Element { - /// Whether this node has `JavaKit` attributes. + /// Whether this node has `SwiftJava` attributes. var isJava: Bool { guard case let .attribute(attr) = self else { // FIXME: Handle #if. diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index b9cc2d497..aa4014d21 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -264,3 +264,12 @@ extension ImportedFunc { } } } + +extension ImportedNominalType: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(self)) + } + public static func == (lhs: ImportedNominalType, rhs: ImportedNominalType) -> Bool { + return lhs === rhs + } +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+InterfaceWrapperGeneration.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+InterfaceWrapperGeneration.swift new file mode 100644 index 000000000..f1e7c6851 --- /dev/null +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+InterfaceWrapperGeneration.swift @@ -0,0 +1,342 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JavaTypes +import SwiftJavaConfigurationShared +import SwiftSyntax + +extension JNISwift2JavaGenerator { + + func generateInterfaceWrappers( + _ types: [ImportedNominalType] + ) -> [ImportedNominalType: JavaInterfaceSwiftWrapper] { + var wrappers = [ImportedNominalType: JavaInterfaceSwiftWrapper]() + + for type in types { + do { + let translator = JavaInterfaceProtocolWrapperGenerator() + wrappers[type] = try translator.generate(for: type) + } catch { + self.logger.warning("Failed to generate protocol wrapper for: '\(type.swiftNominal.qualifiedName)'; \(error)") + } + } + + return wrappers + } + + /// A type that describes a Swift protocol + /// that uses an underlying wrap-java `@JavaInterface` + /// to make callbacks to Java from Swift using protocols. + struct JavaInterfaceSwiftWrapper { + let protocolType: SwiftNominalType + let functions: [Function] + let variables: [Variable] + + var wrapperName: String { + protocolType.nominalTypeDecl.javaInterfaceSwiftProtocolWrapperName + } + + var swiftName: String { + protocolType.nominalTypeDecl.qualifiedName + } + + var javaInterfaceVariableName: String { + protocolType.nominalTypeDecl.javaInterfaceVariableName + } + + var javaInterfaceName: String { + protocolType.nominalTypeDecl.javaInterfaceName + } + + struct Function { + let swiftFunctionName: String + let originalFunctionSignature: SwiftFunctionSignature + let swiftDecl: any DeclSyntaxProtocol + let parameterConversions: [UpcallConversionStep] + let resultConversion: UpcallConversionStep + } + + struct Variable { + let swiftDecl: any DeclSyntaxProtocol + let getter: Function + let setter: Function? + } + } + + + struct JavaInterfaceProtocolWrapperGenerator { + func generate(for type: ImportedNominalType) throws -> JavaInterfaceSwiftWrapper { + let functions = try type.methods.map { method in + try translate(function: method) + } + + // FIXME: Finish support for variables + if !type.variables.isEmpty { + throw JavaTranslationError.protocolVariablesNotSupported + } + + let variables = try Dictionary(grouping: type.variables, by: { $0.swiftDecl.id }).map { (id, funcs) in + precondition(funcs.count > 0 && funcs.count <= 2, "Variables must contain a getter and optionally a setter") + guard let getter = funcs.first(where: { $0.apiKind == .getter }) else { + fatalError("Getter not found for variable with imported funcs: \(funcs)") + } + let setter = funcs.first(where: { $0.apiKind == .setter }) + + return try self.translateVariable(getter: getter, setter: setter) + } + + return JavaInterfaceSwiftWrapper( + protocolType: SwiftNominalType(nominalTypeDecl: type.swiftNominal), + functions: functions, + variables: variables + ) + } + + private func translate(function: ImportedFunc) throws -> JavaInterfaceSwiftWrapper.Function { + let parameters = try function.functionSignature.parameters.map { + try self.translateParameter($0) + } + + let result = try translateResult(function.functionSignature.result, methodName: function.name) + + return JavaInterfaceSwiftWrapper.Function( + swiftFunctionName: function.name, + originalFunctionSignature: function.functionSignature, + swiftDecl: function.swiftDecl, + parameterConversions: parameters, + resultConversion: result + ) + } + + private func translateVariable(getter: ImportedFunc, setter: ImportedFunc?) throws -> JavaInterfaceSwiftWrapper.Variable { + return try JavaInterfaceSwiftWrapper.Variable( + swiftDecl: getter.swiftDecl, // they should be the same + getter: translate(function: getter), + setter: setter.map { try self.translate(function: $0) } + ) + } + + private func translateParameter(_ parameter: SwiftParameter) throws -> UpcallConversionStep { + try self.translateParameter(parameterName: parameter.parameterName!, type: parameter.type) + } + + private func translateParameter(parameterName: String, type: SwiftType) throws -> UpcallConversionStep { + + switch type { + case .nominal(let nominalType): + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + switch knownType { + case .optional: + guard let genericArgs = nominalType.genericArguments, genericArgs.count == 1 else { + throw JavaTranslationError.unsupportedSwiftType(type) + } + return try translateOptionalParameter( + name: parameterName, + wrappedType: genericArgs[0] + ) + + default: + guard knownType.isDirectlyTranslatedToWrapJava else { + throw JavaTranslationError.unsupportedSwiftType(type) + } + + return .placeholder + } + } + + // We assume this is then a JExtracted Swift class + return .toJavaWrapper( + .placeholder, + name: parameterName, + nominalType: nominalType + ) + + case .tuple([]): // void + return .placeholder + + case .optional(let wrappedType): + return try translateOptionalParameter( + name: parameterName, + wrappedType: wrappedType + ) + + case .genericParameter, .function, .metatype, .tuple, .existential, .opaque, .composite, .array: + throw JavaTranslationError.unsupportedSwiftType(type) + } + } + + private func translateOptionalParameter(name: String, wrappedType: SwiftType) throws -> UpcallConversionStep { + let wrappedConversion = try translateParameter(parameterName: name, type: wrappedType) + return .toJavaOptional(.map(.placeholder, body: wrappedConversion)) + } + + private func translateResult(_ result: SwiftResult, methodName: String) throws -> UpcallConversionStep { + try self.translateResult(type: result.type, methodName: methodName) + } + + private func translateResult( + type: SwiftType, + methodName: String, + allowNilForObjects: Bool = false + ) throws -> UpcallConversionStep { + switch type { + case .nominal(let nominalType): + if let knownType = nominalType.nominalTypeDecl.knownTypeKind { + switch knownType { + case .optional: + guard let genericArgs = nominalType.genericArguments, genericArgs.count == 1 else { + throw JavaTranslationError.unsupportedSwiftType(type) + } + return try self.translateOptionalResult( + wrappedType: genericArgs[0], + methodName: methodName + ) + + default: + guard knownType.isDirectlyTranslatedToWrapJava else { + throw JavaTranslationError.unsupportedSwiftType(type) + } + return .placeholder + } + } + + let inner: UpcallConversionStep = !allowNilForObjects ? + .unwrapOptional(.placeholder, message: "Upcall to \(methodName) unexpectedly returned nil") + : .placeholder + + // We assume this is then a JExtracted Swift class + return .toSwiftClass( + inner, + name: "result$", + nominalType: nominalType + ) + + case .tuple([]): // void + return .placeholder + + case .optional(let wrappedType): + return try self.translateOptionalResult(wrappedType: wrappedType, methodName: methodName) + + case .genericParameter, .function, .metatype, .tuple, .existential, .opaque, .composite, .array: + throw JavaTranslationError.unsupportedSwiftType(type) + } + } + + private func translateOptionalResult(wrappedType: SwiftType, methodName: String) throws -> UpcallConversionStep { + // The `fromJavaOptional` will handle the nullability + let wrappedConversion = try translateResult( + type: wrappedType, + methodName: methodName, + allowNilForObjects: true + ) + return .map(.fromJavaOptional(.placeholder), body: wrappedConversion) + } + } +} + + /// Describes how to convert values from and to wrap-java types + enum UpcallConversionStep { + case placeholder + + case constant(String) + + indirect case toJavaWrapper( + UpcallConversionStep, + name: String, + nominalType: SwiftNominalType + ) + + indirect case toSwiftClass( + UpcallConversionStep, + name: String, + nominalType: SwiftNominalType + ) + + indirect case unwrapOptional( + UpcallConversionStep, + message: String + ) + + indirect case toJavaOptional(UpcallConversionStep) + + indirect case fromJavaOptional(UpcallConversionStep) + + indirect case map(UpcallConversionStep, body: UpcallConversionStep) + + /// Returns the conversion string applied to the placeholder. + func render(_ printer: inout CodePrinter, _ placeholder: String) -> String { + switch self { + case .placeholder: + return placeholder + + case .constant(let constant): + return constant + + case .toJavaWrapper(let inner, let name, let nominalType): + let inner = inner.render(&printer, placeholder) + printer.print( + """ + let \(name)Class = try! JavaClass<\(nominalType.nominalTypeDecl.generatedJavaClassMacroName)>(environment: JavaVirtualMachine.shared().environment()) + let \(name)Pointer = UnsafeMutablePointer<\(nominalType.nominalTypeDecl.qualifiedName)>.allocate(capacity: 1) + \(name)Pointer.initialize(to: \(inner)) + """ + ) + + return "\(name)Class.wrapMemoryAddressUnsafe(Int64(Int(bitPattern: \(name)Pointer)))" + + case .toSwiftClass(let inner, let name, let nominalType): + let inner = inner.render(&printer, placeholder) + + // The wrap-java methods will return null + printer.print( + """ + let \(name)MemoryAddress$ = \(inner).as(JavaJNISwiftInstance.self)!.memoryAddress() + let \(name)Pointer = UnsafeMutablePointer<\(nominalType.nominalTypeDecl.qualifiedName)>(bitPattern: Int(\(name)MemoryAddress$))! + """ + ) + + return "\(name)Pointer.pointee" + + case .unwrapOptional(let inner, let message): + let inner = inner.render(&printer, placeholder) + + printer.print( + """ + guard let unwrapped$ = \(inner) else { + fatalError("\(message)") + } + """ + ) + + return "unwrapped$" + + case .toJavaOptional(let inner): + let inner = inner.render(&printer, placeholder) + return "\(inner).toJavaOptional()" + + case .fromJavaOptional(let inner): + let inner = inner.render(&printer, placeholder) + return "Optional(javaOptional: \(inner))" + + case .map(let inner, let body): + let inner = inner.render(&printer, placeholder) + var printer = CodePrinter() + printer.printBraceBlock("\(inner).map") { printer in + let body = body.render(&printer, "$0") + printer.print("return \(body)") + } + return printer.finalize() + } + } +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 1e18cbe8f..c492d439d 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -12,7 +12,9 @@ // //===----------------------------------------------------------------------===// +import Foundation import JavaTypes +import OrderedCollections // MARK: Defaults @@ -40,6 +42,8 @@ extension JNISwift2JavaGenerator { package func writeExportedJavaSources(_ printer: inout CodePrinter) throws { let importedTypes = analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) + var exportedFileNames: OrderedSet = [] + // Each parent type goes into its own file // any nested types are printed inside the body as `static class` for (_, ty) in importedTypes.filter({ _, type in type.parent == nil }) { @@ -52,6 +56,7 @@ extension JNISwift2JavaGenerator { javaPackagePath: javaPackagePath, filename: filename ) { + exportedFileNames.append(outputFile.path(percentEncoded: false)) logger.info("[swift-java] Generated: \(ty.swiftNominal.name.bold).java (at \(outputFile))") } } @@ -65,10 +70,24 @@ extension JNISwift2JavaGenerator { javaPackagePath: javaPackagePath, filename: filename ) { + exportedFileNames.append(outputFile.path(percentEncoded: false)) logger.info("[swift-java] Generated: \(self.swiftModuleName).java (at \(outputFile))") } + + // Write java sources list file + if let generatedJavaSourcesListFileOutput = config.generatedJavaSourcesListFileOutput, !exportedFileNames.isEmpty { + let outputPath = URL(fileURLWithPath: javaOutputDirectory).appending(path: generatedJavaSourcesListFileOutput) + try exportedFileNames.joined(separator: "\n").write( + to: outputPath, + atomically: true, + encoding: .utf8 + ) + logger.info("Generated file at \(outputPath)") + } } + + private func printModule(_ printer: inout CodePrinter) { printHeader(&printer) printPackage(&printer) @@ -113,20 +132,29 @@ extension JNISwift2JavaGenerator { } private func printProtocol(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { - let extends = ["JNISwiftInstance"] - printer.printBraceBlock("public interface \(decl.swiftNominal.name) extends \(extends.joined(separator: ", "))") { printer in + var extends = [String]() + + // If we cannot generate Swift wrappers + // that allows the user to implement the wrapped interface in Java + // then we require only JExtracted types can conform to this. + if !self.interfaceProtocolWrappers.keys.contains(decl) { + extends.append("JNISwiftInstance") + } + let extendsString = extends.isEmpty ? "" : " extends \(extends.joined(separator: ", "))" + + printer.printBraceBlock("public interface \(decl.swiftNominal.name)\(extendsString)") { printer in for initializer in decl.initializers { - printFunctionDowncallMethods(&printer, initializer, signaturesOnly: true) + printFunctionDowncallMethods(&printer, initializer, skipMethodBody: true, skipArenas: true) printer.println() } for method in decl.methods { - printFunctionDowncallMethods(&printer, method, signaturesOnly: true) + printFunctionDowncallMethods(&printer, method, skipMethodBody: true, skipArenas: true) printer.println() } for variable in decl.variables { - printFunctionDowncallMethods(&printer, variable, signaturesOnly: true) + printFunctionDowncallMethods(&printer, variable, skipMethodBody: true, skipArenas: true) printer.println() } } @@ -184,6 +212,10 @@ extension JNISwift2JavaGenerator { public static \(decl.swiftNominal.name) wrapMemoryAddressUnsafe(long selfPointer, SwiftArena swiftArena) { return new \(decl.swiftNominal.name)(selfPointer, swiftArena); } + + public static \(decl.swiftNominal.name) wrapMemoryAddressUnsafe(long selfPointer) { + return new \(decl.swiftNominal.name)(selfPointer, SwiftMemoryManagement.GLOBAL_SWIFT_JAVA_ARENA); + } """ ) @@ -359,10 +391,10 @@ extension JNISwift2JavaGenerator { ["\(conversion.native.javaType) \(value.parameter.name)"] } - printer.print("record $NativeParameters(\(nativeParameters.joined(separator: ", "))) {}") + printer.print("record _NativeParameters(\(nativeParameters.joined(separator: ", "))) {}") } - self.printJavaBindingWrapperMethod(&printer, translatedCase.getAsCaseFunction, signaturesOnly: false) + self.printJavaBindingWrapperMethod(&printer, translatedCase.getAsCaseFunction, skipMethodBody: false, skipArenas: false) printer.println() } } @@ -370,7 +402,8 @@ extension JNISwift2JavaGenerator { private func printFunctionDowncallMethods( _ printer: inout CodePrinter, _ decl: ImportedFunc, - signaturesOnly: Bool = false + skipMethodBody: Bool = false, + skipArenas: Bool = false ) { guard translatedDecl(for: decl) != nil else { // Failed to translate. Skip. @@ -381,7 +414,7 @@ extension JNISwift2JavaGenerator { printJavaBindingWrapperHelperClass(&printer, decl) - printJavaBindingWrapperMethod(&printer, decl, signaturesOnly: signaturesOnly) + printJavaBindingWrapperMethod(&printer, decl, skipMethodBody: skipMethodBody, skipArenas: skipArenas) } /// Print the helper type container for a user-facing Java API. @@ -427,19 +460,21 @@ extension JNISwift2JavaGenerator { private func printJavaBindingWrapperMethod( _ printer: inout CodePrinter, _ decl: ImportedFunc, - signaturesOnly: Bool + skipMethodBody: Bool, + skipArenas: Bool ) { guard let translatedDecl = translatedDecl(for: decl) else { fatalError("Decl was not translated, \(decl)") } - printJavaBindingWrapperMethod(&printer, translatedDecl, importedFunc: decl, signaturesOnly: signaturesOnly) + printJavaBindingWrapperMethod(&printer, translatedDecl, importedFunc: decl, skipMethodBody: skipMethodBody, skipArenas: skipArenas) } private func printJavaBindingWrapperMethod( _ printer: inout CodePrinter, _ translatedDecl: TranslatedFunctionDecl, importedFunc: ImportedFunc? = nil, - signaturesOnly: Bool + skipMethodBody: Bool, + skipArenas: Bool ) { var modifiers = ["public"] if translatedDecl.isStatic { @@ -494,14 +529,14 @@ extension JNISwift2JavaGenerator { printer.println() } - if translatedSignature.requiresSwiftArena { + if translatedSignature.requiresSwiftArena, !skipArenas { parameters.append("SwiftArena swiftArena$") } if let importedFunc { printDeclDocumentation(&printer, importedFunc) } let signature = "\(annotationsStr)\(modifiers.joined(separator: " ")) \(resultType) \(translatedDecl.name)(\(parameters.joined(separator: ", ")))\(throwsClause)" - if signaturesOnly { + if skipMethodBody { printer.print("\(signature);") } else { printer.printBraceBlock(signature) { printer in diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 6a2771c30..553b3e10b 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -16,6 +16,17 @@ import JavaTypes import SwiftJavaConfigurationShared extension JNISwift2JavaGenerator { + var javaTranslator: JavaTranslation { + JavaTranslation( + config: config, + swiftModuleName: swiftModuleName, + javaPackage: self.javaPackage, + javaClassLookupTable: self.javaClassLookupTable, + knownTypes: SwiftKnownTypes(symbolTable: lookupContext.symbolTable), + protocolWrappers: self.interfaceProtocolWrappers + ) + } + func translatedDecl( for decl: ImportedFunc ) -> TranslatedFunctionDecl? { @@ -25,14 +36,7 @@ extension JNISwift2JavaGenerator { let translated: TranslatedFunctionDecl? do { - let translation = JavaTranslation( - config: config, - swiftModuleName: swiftModuleName, - javaPackage: self.javaPackage, - javaClassLookupTable: self.javaClassLookupTable, - knownTypes: SwiftKnownTypes(symbolTable: lookupContext.symbolTable) - ) - translated = try translation.translate(decl) + translated = try self.javaTranslator.translate(decl) } catch { self.logger.debug("Failed to translate: '\(decl.swiftDecl.qualifiedNameForDebug)'; \(error)") translated = nil @@ -56,7 +60,8 @@ extension JNISwift2JavaGenerator { swiftModuleName: swiftModuleName, javaPackage: self.javaPackage, javaClassLookupTable: self.javaClassLookupTable, - knownTypes: SwiftKnownTypes(symbolTable: lookupContext.symbolTable) + knownTypes: SwiftKnownTypes(symbolTable: lookupContext.symbolTable), + protocolWrappers: self.interfaceProtocolWrappers ) translated = try translation.translate(enumCase: decl) } catch { @@ -74,13 +79,15 @@ extension JNISwift2JavaGenerator { let javaPackage: String let javaClassLookupTable: JavaClassLookupTable var knownTypes: SwiftKnownTypes + let protocolWrappers: [ImportedNominalType: JavaInterfaceSwiftWrapper] func translate(enumCase: ImportedEnumCase) throws -> TranslatedEnumCase { let nativeTranslation = NativeJavaTranslation( config: self.config, javaPackage: self.javaPackage, javaClassLookupTable: self.javaClassLookupTable, - knownTypes: self.knownTypes + knownTypes: self.knownTypes, + protocolWrappers: self.protocolWrappers ) let methodName = "" // TODO: Used for closures, replace with better name? @@ -105,7 +112,7 @@ extension JNISwift2JavaGenerator { let caseName = enumCase.name.firstCharacterUppercased let enumName = enumCase.enumType.nominalTypeDecl.name - let nativeParametersType = JavaType.class(package: nil, name: "\(caseName).$NativeParameters") + let nativeParametersType = JavaType.class(package: nil, name: "\(caseName)._NativeParameters") let getAsCaseName = "getAs\(caseName)" // If the case has no parameters, we can skip the native call. let constructRecordConversion = JavaNativeConversionStep.method(.constant("Optional"), function: "of", arguments: [ @@ -168,7 +175,8 @@ extension JNISwift2JavaGenerator { config: self.config, javaPackage: self.javaPackage, javaClassLookupTable: self.javaClassLookupTable, - knownTypes: self.knownTypes + knownTypes: self.knownTypes, + protocolWrappers: self.protocolWrappers ) // Types with no parent will be outputted inside a "module" class. @@ -405,8 +413,8 @@ extension JNISwift2JavaGenerator { } } - if nominalType.isJavaKitWrapper { - guard let javaType = nominalTypeName.parseJavaClassFromJavaKitName(in: self.javaClassLookupTable) else { + if nominalType.isSwiftJavaWrapper { + guard let javaType = nominalTypeName.parseJavaClassFromSwiftJavaName(in: self.javaClassLookupTable) else { throw JavaTranslationError.wrappedJavaClassTranslationNotProvided(swiftType) } @@ -456,7 +464,7 @@ extension JNISwift2JavaGenerator { return try translateProtocolParameter( protocolType: proto, parameterName: parameterName, - javaGenericName: "$T\(parameterPosition)" + javaGenericName: "_T\(parameterPosition)" ) case .genericParameter(let generic): @@ -585,14 +593,14 @@ extension JNISwift2JavaGenerator { } } - // We assume this is a JExtract class. + // We just pass down the jobject return TranslatedParameter( parameter: JavaParameter( name: parameterName, type: .generic(name: javaGenericName, extends: javaProtocolTypes), annotations: [] ), - conversion: .commaSeparated([.valueMemoryAddress(.placeholder), .typeMetadataAddress(.placeholder)]) + conversion: .placeholder ) } @@ -628,8 +636,8 @@ extension JNISwift2JavaGenerator { ) } - if nominalType.isJavaKitWrapper { - guard let javaType = nominalTypeName.parseJavaClassFromJavaKitName(in: self.javaClassLookupTable) else { + if nominalType.isSwiftJavaWrapper { + guard let javaType = nominalTypeName.parseJavaClassFromSwiftJavaName(in: self.javaClassLookupTable) else { throw JavaTranslationError.wrappedJavaClassTranslationNotProvided(swiftType) } @@ -703,7 +711,7 @@ extension JNISwift2JavaGenerator { } } - if nominalType.isJavaKitWrapper { + if nominalType.isSwiftJavaWrapper { throw JavaTranslationError.unsupportedSwiftType(swiftType) } @@ -789,7 +797,7 @@ extension JNISwift2JavaGenerator { } } - guard !nominalType.isJavaKitWrapper else { + guard !nominalType.isSwiftJavaWrapper else { throw JavaTranslationError.unsupportedSwiftType(swiftType) } @@ -836,7 +844,7 @@ extension JNISwift2JavaGenerator { ) } - guard !nominalType.isJavaKitWrapper else { + guard !nominalType.isSwiftJavaWrapper else { throw JavaTranslationError.unsupportedSwiftType(elementType) } @@ -885,7 +893,7 @@ extension JNISwift2JavaGenerator { ) } - guard !nominalType.isJavaKitWrapper else { + guard !nominalType.isSwiftJavaWrapper else { throw JavaTranslationError.unsupportedSwiftType(elementType) } @@ -965,7 +973,7 @@ extension JNISwift2JavaGenerator { /// Function signature of the native function that will be implemented by Swift let nativeFunctionSignature: NativeFunctionSignature - + /// Annotations to include on the Java function declaration var annotations: [JavaAnnotation] { self.translatedFunctionSignature.annotations @@ -1332,5 +1340,8 @@ extension JNISwift2JavaGenerator { /// The user has not supplied a mapping from `SwiftType` to /// a java class. case wrappedJavaClassTranslationNotProvided(SwiftType) + + // FIXME: Remove once we support protocol variables + case protocolVariablesNotSupported } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 24e469067..b744a33b4 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -22,6 +22,7 @@ extension JNISwift2JavaGenerator { let javaPackage: String let javaClassLookupTable: JavaClassLookupTable var knownTypes: SwiftKnownTypes + let protocolWrappers: [ImportedNominalType: JavaInterfaceSwiftWrapper] /// Translates a Swift function into the native JNI method signature. func translate( @@ -113,8 +114,8 @@ extension JNISwift2JavaGenerator { } } - if nominalType.isJavaKitWrapper { - guard let javaType = nominalTypeName.parseJavaClassFromJavaKitName(in: self.javaClassLookupTable) else { + if nominalType.isSwiftJavaWrapper { + guard let javaType = nominalTypeName.parseJavaClassFromSwiftJavaName(in: self.javaClassLookupTable) else { throw JavaTranslationError.wrappedJavaClassTranslationNotProvided(type) } @@ -122,7 +123,7 @@ extension JNISwift2JavaGenerator { parameters: [ JavaParameter(name: parameterName, type: javaType) ], - conversion: .initializeJavaKitWrapper( + conversion: .initializeSwiftJavaWrapper( .unwrapOptional( .placeholder, name: parameterName, @@ -181,14 +182,18 @@ extension JNISwift2JavaGenerator { case .opaque(let proto), .existential(let proto): return try translateProtocolParameter( protocolType: proto, - parameterName: parameterName + methodName: methodName, + parameterName: parameterName, + parentName: parentName ) case .genericParameter: if let concreteTy = type.typeIn(genericParameters: genericParameters, genericRequirements: genericRequirements) { return try translateProtocolParameter( protocolType: concreteTy, - parameterName: parameterName + methodName: methodName, + parameterName: parameterName, + parentName: parentName ) } @@ -207,22 +212,23 @@ extension JNISwift2JavaGenerator { func translateProtocolParameter( protocolType: SwiftType, - parameterName: String + methodName: String, + parameterName: String, + parentName: String? ) throws -> NativeParameter { switch protocolType { case .nominal(let nominalType): - let protocolName = nominalType.nominalTypeDecl.qualifiedName - return try translateProtocolParameter(protocolNames: [protocolName], parameterName: parameterName) + return try translateProtocolParameter(protocolTypes: [nominalType], methodName: methodName, parameterName: parameterName, parentName: parentName) case .composite(let types): - let protocolNames = try types.map { - guard let nominalTypeName = $0.asNominalType?.nominalTypeDecl.qualifiedName else { + let protocolTypes = try types.map { + guard let nominalTypeName = $0.asNominalType else { throw JavaTranslationError.unsupportedSwiftType($0) } return nominalTypeName } - return try translateProtocolParameter(protocolNames: protocolNames, parameterName: parameterName) + return try translateProtocolParameter(protocolTypes: protocolTypes, methodName: methodName, parameterName: parameterName, parentName: parentName) default: throw JavaTranslationError.unsupportedSwiftType(protocolType) @@ -230,18 +236,30 @@ extension JNISwift2JavaGenerator { } private func translateProtocolParameter( - protocolNames: [String], - parameterName: String + protocolTypes: [SwiftNominalType], + methodName: String, + parameterName: String, + parentName: String? ) throws -> NativeParameter { + // We allow Java implementations if we are able to generate the needed + // Swift wrappers for all the protocol types. + let allowsJavaImplementations = protocolTypes.allSatisfy { protocolType in + self.protocolWrappers.contains(where: { $0.value.protocolType == protocolType }) + } + return NativeParameter( parameters: [ - JavaParameter(name: parameterName, type: .long), - JavaParameter(name: "\(parameterName)_typeMetadataAddress", type: .long) + JavaParameter(name: parameterName, type: .javaLangObject) ], - conversion: .extractSwiftProtocolValue( + conversion: .interfaceToSwiftObject( .placeholder, - typeMetadataVariableName: .combinedName(component: "typeMetadataAddress"), - protocolNames: protocolNames + swiftWrapperClassName: JNISwift2JavaGenerator.protocolParameterWrapperClassName( + methodName: methodName, + parameterName: parameterName, + parentName: parentName + ), + protocolTypes: protocolTypes, + allowsJavaImplementations: allowsJavaImplementations ) ) } @@ -276,8 +294,8 @@ extension JNISwift2JavaGenerator { ) } - if nominalType.isJavaKitWrapper { - guard let javaType = nominalTypeName.parseJavaClassFromJavaKitName(in: self.javaClassLookupTable) else { + if nominalType.isSwiftJavaWrapper { + guard let javaType = nominalTypeName.parseJavaClassFromSwiftJavaName(in: self.javaClassLookupTable) else { throw JavaTranslationError.wrappedJavaClassTranslationNotProvided(swiftType) } @@ -285,7 +303,7 @@ extension JNISwift2JavaGenerator { parameters: [ JavaParameter(name: parameterName, type: javaType) ], - conversion: .optionalMap(.initializeJavaKitWrapper(.placeholder, wrapperName: nominalTypeName)) + conversion: .optionalMap(.initializeSwiftJavaWrapper(.placeholder, wrapperName: nominalTypeName)) ) } @@ -359,7 +377,7 @@ extension JNISwift2JavaGenerator { } } - guard !nominalType.isJavaKitWrapper else { + guard !nominalType.isSwiftJavaWrapper else { // TODO: Should be the same as above throw JavaTranslationError.unsupportedSwiftType(swiftType) } @@ -479,7 +497,7 @@ extension JNISwift2JavaGenerator { } } - if nominalType.isJavaKitWrapper { + if nominalType.isSwiftJavaWrapper { throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) } @@ -526,7 +544,7 @@ extension JNISwift2JavaGenerator { ) } - guard !nominalType.isJavaKitWrapper else { + guard !nominalType.isSwiftJavaWrapper else { throw JavaTranslationError.unsupportedSwiftType(.array(elementType)) } @@ -574,7 +592,7 @@ extension JNISwift2JavaGenerator { ) } - guard !nominalType.isJavaKitWrapper else { + guard !nominalType.isSwiftJavaWrapper else { throw JavaTranslationError.unsupportedSwiftType(.array(elementType)) } @@ -645,6 +663,13 @@ extension JNISwift2JavaGenerator { /// `SwiftType(from: value, in: environment)` indirect case initFromJNI(NativeSwiftConversionStep, swiftType: SwiftType) + indirect case interfaceToSwiftObject( + NativeSwiftConversionStep, + swiftWrapperClassName: String, + protocolTypes: [SwiftNominalType], + allowsJavaImplementations: Bool + ) + indirect case extractSwiftProtocolValue( NativeSwiftConversionStep, typeMetadataVariableName: NativeSwiftConversionStep, @@ -668,7 +693,7 @@ extension JNISwift2JavaGenerator { indirect case closureLowering(parameters: [NativeParameter], result: NativeResult) - indirect case initializeJavaKitWrapper(NativeSwiftConversionStep, wrapperName: String) + indirect case initializeSwiftJavaWrapper(NativeSwiftConversionStep, wrapperName: String) indirect case optionalLowering(NativeSwiftConversionStep, discriminatorName: String, valueName: String) @@ -723,6 +748,60 @@ extension JNISwift2JavaGenerator { let inner = inner.render(&printer, placeholder) return "\(swiftType)(fromJNI: \(inner), in: environment)" + case .interfaceToSwiftObject( + let inner, + let swiftWrapperClassName, + let protocolTypes, + let allowsJavaImplementations + ): + let protocolNames = protocolTypes.map { $0.nominalTypeDecl.qualifiedName } + + let inner = inner.render(&printer, placeholder) + let variableName = "\(inner)swiftObject$" + let compositeProtocolName = "(\(protocolNames.joined(separator: " & ")))" + printer.print("let \(variableName): \(compositeProtocolName)") + + func printStandardJExtractBlock(_ printer: inout CodePrinter) { + let pointerVariableName = "\(inner)pointer$" + let typeMetadataVariableName = "\(inner)typeMetadata$" + printer.print( + """ + let \(pointerVariableName) = environment.interface.CallLongMethodA(environment, \(inner), _JNIMethodIDCache.JNISwiftInstance.memoryAddress, []) + let \(typeMetadataVariableName) = environment.interface.CallLongMethodA(environment, \(inner), _JNIMethodIDCache.JNISwiftInstance.typeMetadataAddress, []) + """ + ) + let existentialName = NativeSwiftConversionStep.extractSwiftProtocolValue( + .constant(pointerVariableName), + typeMetadataVariableName: .constant(typeMetadataVariableName), + protocolNames: protocolNames + ).render(&printer, placeholder) + + printer.print("\(variableName) = \(existentialName)") + } + + // If this protocol type supports being implemented by the user + // then we will check whether it is a JNI SwiftInstance type + // or if its a custom class implementing the interface. + if allowsJavaImplementations { + printer.printBraceBlock( + "if environment.interface.IsInstanceOf(environment, \(inner), _JNIMethodIDCache.JNISwiftInstance.class) != 0" + ) { printer in + printStandardJExtractBlock(&printer) + } + printer.printBraceBlock("else") { printer in + let arguments = protocolTypes.map { protocolType in + let nominalTypeDecl = protocolType.nominalTypeDecl + return "\(nominalTypeDecl.javaInterfaceVariableName): \(nominalTypeDecl.javaInterfaceName)(javaThis: \(inner)!, environment: environment)" + } + printer.print("\(variableName) = \(swiftWrapperClassName)(\(arguments.joined(separator: ", ")))") + } + } else { + printStandardJExtractBlock(&printer) + } + + + return variableName + case .extractSwiftProtocolValue(let inner, let typeMetadataVariableName, let protocolNames): let inner = inner.render(&printer, placeholder) let typeMetadataVariableName = typeMetadataVariableName.render(&printer, placeholder) @@ -839,7 +918,7 @@ extension JNISwift2JavaGenerator { return printer.finalize() - case .initializeJavaKitWrapper(let inner, let wrapperName): + case .initializeSwiftJavaWrapper(let inner, let wrapperName): let inner = inner.render(&printer, placeholder) return "\(wrapperName)(javaThis: \(inner), environment: environment)" diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 6628be333..85fdc6404 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -116,6 +116,75 @@ extension JNISwift2JavaGenerator { } } + /// Prints the extension needed to make allow upcalls from Swift to Java for protocols + private func printSwiftInterfaceWrapper( + _ printer: inout CodePrinter, + _ translatedWrapper: JavaInterfaceSwiftWrapper + ) throws { + printer.printBraceBlock("protocol \(translatedWrapper.wrapperName): \(translatedWrapper.swiftName)") { printer in + printer.print("var \(translatedWrapper.javaInterfaceVariableName): \(translatedWrapper.javaInterfaceName) { get }") + } + printer.println() + printer.printBraceBlock("extension \(translatedWrapper.wrapperName)") { printer in + for function in translatedWrapper.functions { + printInterfaceWrapperFunctionImpl(&printer, function, inside: translatedWrapper) + printer.println() + } + + // FIXME: Add support for protocol variables https://github.com/swiftlang/swift-java/issues/457 +// for variable in translatedWrapper.variables { +// printerInterfaceWrapperVariable(&printer, variable, inside: translatedWrapper) +// printer.println() +// } + } + } + + private func printInterfaceWrapperFunctionImpl( + _ printer: inout CodePrinter, + _ function: JavaInterfaceSwiftWrapper.Function, + inside wrapper: JavaInterfaceSwiftWrapper + ) { + printer.printBraceBlock(function.swiftDecl.signatureString) { printer in + let upcallArguments = zip( + function.originalFunctionSignature.parameters, + function.parameterConversions + ).map { param, conversion in + // Wrap-java does not extract parameter names, so no labels + conversion.render(&printer, param.parameterName!) + } + + let javaUpcall = "\(wrapper.javaInterfaceVariableName).\(function.swiftFunctionName)(\(upcallArguments.joined(separator: ", ")))" + + let resultType = function.originalFunctionSignature.result.type + let result = function.resultConversion.render(&printer, javaUpcall) + if resultType.isVoid { + printer.print(result) + } else { + printer.print("return \(result)") + } + } + } + + private func printerInterfaceWrapperVariable( + _ printer: inout CodePrinter, + _ variable: JavaInterfaceSwiftWrapper.Variable, + inside wrapper: JavaInterfaceSwiftWrapper + ) { + // FIXME: Add support for variables. This won't get printed yet + // so we no need to worry about fatalErrors. + printer.printBraceBlock(variable.swiftDecl.signatureString) { printer in + printer.printBraceBlock("get") { printer in + printer.print("fatalError()") + } + + if let setter = variable.setter { + printer.printBraceBlock("set") { printer in + printer.print("fatalError()") + } + } + } + } + private func printGlobalSwiftThunkSources(_ printer: inout CodePrinter) throws { printHeader(&printer) @@ -154,7 +223,7 @@ extension JNISwift2JavaGenerator { case .actor, .class, .enum, .struct: printConcreteTypeThunks(&printer, type) case .protocol: - printProtocolThunks(&printer, type) + try printProtocolThunks(&printer, type) } } @@ -184,13 +253,18 @@ extension JNISwift2JavaGenerator { printer.println() } + printTypeMetadataAddressThunk(&printer, type) printer.println() printDestroyFunctionThunk(&printer, type) } - private func printProtocolThunks(_ printer: inout CodePrinter, _ type: ImportedNominalType) { - let protocolName = type.swiftNominal.name + private func printProtocolThunks(_ printer: inout CodePrinter, _ type: ImportedNominalType) throws { + guard let protocolWrapper = self.interfaceProtocolWrappers[type] else { + return + } + + try printSwiftInterfaceWrapper(&printer, protocolWrapper) } @@ -232,11 +306,19 @@ extension JNISwift2JavaGenerator { } private func renderEnumCaseCacheInit(_ enumCase: TranslatedEnumCase) -> String { - let nativeParametersClassName = "\(javaPackagePath)/\(enumCase.enumName)$\(enumCase.name)$$NativeParameters" + let nativeParametersClassName = "\(enumCase.enumName)$\(enumCase.name)$_NativeParameters" let methodSignature = MethodSignature(resultType: .void, parameterTypes: enumCase.parameterConversions.map(\.native.javaType)) - let methods = #"[.init(name: "", signature: "\#(methodSignature.mangledName)")]"# - return #"_JNIMethodIDCache(className: "\#(nativeParametersClassName)", methods: \#(methods))"# + return renderJNICacheInit(className: nativeParametersClassName, methods: [("", methodSignature)]) + } + + private func renderJNICacheInit(className: String, methods: [(String, MethodSignature)]) -> String { + let fullClassName = "\(javaPackagePath)/\(className)" + let methods = methods.map { name, signature in + #".init(name: "\#(name)", signature: "\#(signature.mangledName)")"# + }.joined(separator: ",\n") + + return #"_JNIMethodIDCache(className: "\#(fullClassName)", methods: [\#(methods)])"# } private func printEnumGetAsCaseThunk( @@ -287,6 +369,8 @@ extension JNISwift2JavaGenerator { return } + printSwiftFunctionHelperClasses(&printer, decl) + printCDecl( &printer, translatedDecl @@ -295,6 +379,96 @@ extension JNISwift2JavaGenerator { } } + + private func printSwiftFunctionHelperClasses( + _ printer: inout CodePrinter, + _ decl: ImportedFunc + ) { + let protocolParameters = decl.functionSignature.parameters.compactMap { parameter in + if let concreteType = parameter.type.typeIn( + genericParameters: decl.functionSignature.genericParameters, + genericRequirements: decl.functionSignature.genericRequirements + ) { + return (parameter, concreteType) + } + + switch parameter.type { + case .opaque(let protocolType), + .existential(let protocolType): + return (parameter, protocolType) + + default: + return nil + } + }.map { parameter, protocolType in + // We flatten any composite types + switch protocolType { + case .composite(let protocols): + return (parameter, protocols) + + default: + return (parameter, [protocolType]) + } + } + + // For each parameter that is a generic or a protocol, + // we generate a Swift class that conforms to all of those. + for (parameter, protocolTypes) in protocolParameters { + let protocolWrappers: [JavaInterfaceSwiftWrapper] = protocolTypes.compactMap { protocolType in + guard let importedType = self.asImportedNominalTypeDecl(protocolType), + let wrapper = self.interfaceProtocolWrappers[importedType] + else { + return nil + } + return wrapper + } + + // Make sure we can generate wrappers for all the protocols + // that the parameter requires + guard protocolWrappers.count == protocolTypes.count else { + // We cannot extract a wrapper for this class + // so it must only be passed in by JExtract instances + continue + } + + guard let parameterName = parameter.parameterName else { + // TODO: Throw + fatalError() + } + let swiftClassName = JNISwift2JavaGenerator.protocolParameterWrapperClassName( + methodName: decl.name, + parameterName: parameterName, + parentName: decl.parentType?.asNominalType?.nominalTypeDecl.qualifiedName ?? swiftModuleName + ) + let implementingProtocols = protocolWrappers.map(\.wrapperName).joined(separator: ", ") + + printer.printBraceBlock("final class \(swiftClassName): \(implementingProtocols)") { printer in + let variables: [(String, String)] = protocolWrappers.map { wrapper in + return (wrapper.javaInterfaceVariableName, wrapper.javaInterfaceName) + } + for (name, type) in variables { + printer.print("let \(name): \(type)") + } + printer.println() + let initializerParameters = variables.map { "\($0): \($1)" }.joined(separator: ", ") + + printer.printBraceBlock("init(\(initializerParameters))") { printer in + for (name, _) in variables { + printer.print("self.\(name) = \(name)") + } + } + } + } + } + + private func asImportedNominalTypeDecl(_ type: SwiftType) -> ImportedNominalType? { + self.analysis.importedTypes.first(where: ( { name, nominalType in + nominalType.swiftType == type + })).map { + $0.value + } + } + private func printFunctionDowncall( _ printer: inout CodePrinter, _ decl: ImportedFunc @@ -562,4 +736,44 @@ extension JNISwift2JavaGenerator { ) return newSelfParamName } + + static func protocolParameterWrapperClassName( + methodName: String, + parameterName: String, + parentName: String? + ) -> String { + let parent = if let parentName { + "\(parentName)_" + } else { + "" + } + return "_\(parent)\(methodName)_\(parameterName)_Wrapper" + } +} + +extension SwiftNominalTypeDeclaration { + private var safeProtocolName: String { + self.qualifiedName.replacingOccurrences(of: ".", with: "_") + } + + /// The name of the corresponding `@JavaInterface` of this type. + var javaInterfaceName: String { + "Java\(safeProtocolName)" + } + + var javaInterfaceSwiftProtocolWrapperName: String { + "SwiftJava\(safeProtocolName)Wrapper" + } + + var javaInterfaceVariableName: String { + "_\(javaInterfaceName.firstCharacterLowercased)Interface" + } + + var generatedJavaClassMacroName: String { + if let parent { + return "\(parent.generatedJavaClassMacroName).Java\(self.name)" + } + + return "Java\(self.name)" + } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index 3b84cfb97..692b66b69 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -41,6 +41,7 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { /// Cached Java translation result. 'nil' indicates failed translation. var translatedDecls: [ImportedFunc: TranslatedFunctionDecl] = [:] var translatedEnumCases: [ImportedEnumCase: TranslatedEnumCase] = [:] + var interfaceProtocolWrappers: [ImportedNominalType: JavaInterfaceSwiftWrapper] = [:] /// Because we need to write empty files for SwiftPM, keep track which files we didn't write yet, /// and write an empty file for those. @@ -78,6 +79,13 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { } else { self.expectedOutputSwiftFiles = [] } + + if translator.config.enableJavaCallbacks ?? false { + // We translate all the protocol wrappers + // as we need them to know what protocols we can allow the user to implement themselves + // in Java. + self.interfaceProtocolWrappers = self.generateInterfaceWrappers(Array(self.analysis.importedTypes.values)) + } } func generate() throws { diff --git a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift index 511bf8de6..e1aabd7fd 100644 --- a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift +++ b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift @@ -40,6 +40,12 @@ extension JavaType { .class(package: "java.lang", name: "Throwable") } + /// The description of the type java.lang.Object. + static var javaLangObject: JavaType { + .class(package: "java.lang", name: "Object") + } + + /// The description of the type java.util.concurrent.CompletableFuture static func completableFuture(_ T: JavaType) -> JavaType { .class(package: "java.util.concurrent", name: "CompletableFuture", typeParameters: [T.boxedType]) diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift index 4a0cb9e8a..363663217 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift @@ -65,4 +65,18 @@ enum SwiftKnownTypeDeclKind: String, Hashable { return false } } + + /// Indicates whether this known type is translated by `wrap-java` + /// into the same type as `jextract`. + /// + /// This means we do not have to perform any mapping when passing + /// this type between jextract and wrap-java + var isDirectlyTranslatedToWrapJava: Bool { + switch self { + case .bool, .int, .uint, .int8, .uint8, .int16, .uint16, .int32, .uint32, .int64, .uint64, .float, .double, .string, .void: + return true + default: + return false + } + } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index b2f8d6eac..aeb88bfba 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -210,9 +210,13 @@ extension SwiftNominalType: CustomStringConvertible { extension SwiftNominalType { // TODO: Better way to detect Java wrapped classes. - var isJavaKitWrapper: Bool { + var isSwiftJavaWrapper: Bool { nominalTypeDecl.name.hasPrefix("Java") } + + var isProtocol: Bool { + nominalTypeDecl.kind == .protocol + } } extension SwiftType { diff --git a/Sources/JavaStdlib/JavaLangReflect/Constructor+Utilities.swift b/Sources/JavaStdlib/JavaLangReflect/Constructor+Utilities.swift index 8f57ffa51..4042ec766 100644 --- a/Sources/JavaStdlib/JavaLangReflect/Constructor+Utilities.swift +++ b/Sources/JavaStdlib/JavaLangReflect/Constructor+Utilities.swift @@ -13,11 +13,6 @@ //===----------------------------------------------------------------------===// extension Constructor { - /// Whether this is a 'public' constructor. - public var isPublic: Bool { - return (getModifiers() & 1) != 0 - } - /// Whether this is a 'native' constructor. public var isNative: Bool { return (getModifiers() & 256) != 0 diff --git a/Sources/JavaStdlib/JavaLangReflect/HasJavaModifiers.swift b/Sources/JavaStdlib/JavaLangReflect/HasJavaModifiers.swift new file mode 100644 index 000000000..fc10edfea --- /dev/null +++ b/Sources/JavaStdlib/JavaLangReflect/HasJavaModifiers.swift @@ -0,0 +1,47 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftJava + +public protocol HasJavaModifiers { + func getModifiers() -> Int32 +} + +extension HasJavaModifiers { + /// Whether the modifiers contain 'public'. + public var isPublic: Bool { + return (getModifiers() & 0x00000001) != 0 + } + + /// Whether the modifiers contain 'private'. + public var isPrivate: Bool { + return (getModifiers() & 0x00000002) != 0 + } + + /// Whether the modifiers contain 'protected'. + public var isProtected: Bool { + return (getModifiers() & 0x00000004) != 0 + } + + /// Whether the modifiers is equivelant to 'package'.. + /// + /// The "default" access level in Java is 'package', it is signified by lack of a different access modifier. + public var isPackage: Bool { + return !isPublic && !isPrivate && !isProtected + } +} + +extension Constructor: HasJavaModifiers {} +extension JavaClass: HasJavaModifiers {} +extension Method: HasJavaModifiers {} diff --git a/Sources/JavaStdlib/JavaLangReflect/Method+Utilities.swift b/Sources/JavaStdlib/JavaLangReflect/Method+Utilities.swift index ecc11b507..00ca3d6bf 100644 --- a/Sources/JavaStdlib/JavaLangReflect/Method+Utilities.swift +++ b/Sources/JavaStdlib/JavaLangReflect/Method+Utilities.swift @@ -13,28 +13,6 @@ //===----------------------------------------------------------------------===// extension Method { - - /// Whether this is a 'public' method. - public var isPublic: Bool { - return (getModifiers() & 0x00000001) != 0 - } - - /// Whether this is a 'private' method. - public var isPrivate: Bool { - return (getModifiers() & 0x00000002) != 0 - } - - /// Whether this is a 'protected' method. - public var isProtected: Bool { - return (getModifiers() & 0x00000004) != 0 - } - - /// Whether this is a 'package' method. - /// - /// The "default" access level in Java is 'package', it is signified by lack of a different access modifier. - public var isPackage: Bool { - return !isPublic && !isPrivate && !isProtected - } /// Whether this is a 'static' method. public var isStatic: Bool { diff --git a/Sources/SwiftJava/JVM/JavaVirtualMachine.swift b/Sources/SwiftJava/JVM/JavaVirtualMachine.swift index 1c7936a34..bb574c8ad 100644 --- a/Sources/SwiftJava/JVM/JavaVirtualMachine.swift +++ b/Sources/SwiftJava/JVM/JavaVirtualMachine.swift @@ -350,4 +350,4 @@ extension JavaVirtualMachine { enum JavaKitError: Error { case classpathEntryNotFound(entry: String, classpath: [String]) } -} \ No newline at end of file +} diff --git a/Sources/SwiftJava/Macros.swift b/Sources/SwiftJava/Macros.swift index eb9c43745..4c0353661 100644 --- a/Sources/SwiftJava/Macros.swift +++ b/Sources/SwiftJava/Macros.swift @@ -141,6 +141,7 @@ public macro JavaStaticField(_ javaFieldName: String? = nil, isFinal: Bool = fal /// returning method signature, and then, convert the result to the expected `T` type on the Swift side. @attached(body) public macro JavaMethod( + _ javaMethodName: String? = nil, typeErasedResult: String? = nil ) = #externalMacro(module: "SwiftJavaMacros", type: "JavaMethodMacro") @@ -154,9 +155,7 @@ public macro JavaMethod( /// func sayHelloBack(_ i: Int32) -> Double /// ``` @attached(body) -public macro JavaStaticMethod( - typeErasedResult: String? = nil -) = #externalMacro(module: "SwiftJavaMacros", type: "JavaMethodMacro") +public macro JavaStaticMethod(_ javaMethodName: String? = nil) = #externalMacro(module: "SwiftJavaMacros", type: "JavaMethodMacro") /// Macro that marks extensions to specify that all of the @JavaMethod /// methods are implementations of Java methods spelled as `native`. diff --git a/Sources/SwiftJava/String+Extensions.swift b/Sources/SwiftJava/String+Extensions.swift index 0af0de107..b87e6a0c6 100644 --- a/Sources/SwiftJava/String+Extensions.swift +++ b/Sources/SwiftJava/String+Extensions.swift @@ -27,10 +27,14 @@ extension String { } extension String { - /// Replace all of the $'s for nested names with "." to turn a Java class - /// name into a Java canonical class name, + /// Convert a Java class name to its canonical name. + /// Replaces `$` with `.` for nested classes but preserves `$` at the start of identifiers. package var javaClassNameToCanonicalName: String { - return replacing("$", with: ".") + self.replacingOccurrences( + of: #"(?<=\w)\$"#, + with: ".", + options: .regularExpression + ) } /// Whether this is the name of an anonymous class. diff --git a/Sources/SwiftJavaConfigurationShared/Configuration.swift b/Sources/SwiftJavaConfigurationShared/Configuration.swift index 661cf9633..37d1e1e79 100644 --- a/Sources/SwiftJavaConfigurationShared/Configuration.swift +++ b/Sources/SwiftJavaConfigurationShared/Configuration.swift @@ -65,6 +65,10 @@ public struct Configuration: Codable { asyncFuncMode ?? .default } + public var enableJavaCallbacks: Bool? // FIXME: default it to false, but that plays not nice with Codable + + public var generatedJavaSourcesListFileOutput: String? + // ==== wrap-java --------------------------------------------------------- /// The Java class path that should be passed along to the swift-java tool. @@ -91,6 +95,8 @@ public struct Configuration: Codable { /// Exclude input Java types by their package prefix or exact match. public var filterExclude: [String]? + public var singleSwiftFileOutput: String? + // ==== dependencies --------------------------------------------------------- // Java dependencies we need to fetch for this target. @@ -162,7 +168,11 @@ public func readConfiguration(sourceDir: String, file: String = #fileID, line: U /// Configuration is expected to be "JSON-with-comments". /// Specifically "//" comments are allowed and will be trimmed before passing the rest of the config into a standard JSON parser. public func readConfiguration(configPath: URL, file: String = #fileID, line: UInt = #line) throws -> Configuration? { - guard let configData = try? Data(contentsOf: configPath) else { + let configData: Data + do { + configData = try Data(contentsOf: configPath) + } catch { + print("Failed to read SwiftJava configuration at '\(configPath.absoluteURL)', error: \(error)") return nil } diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/index.md b/Sources/SwiftJavaDocumentation/Documentation.docc/index.md index 460f396d5..4383727d9 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/index.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/index.md @@ -20,7 +20,7 @@ Reasons why you might want to reach for Swift and Java interoperability include, - Reuse existing libraries which exist in one ecosystem, but don't have a direct equivalent in the other SwiftJava is offering several core libraries which support language interoperability: -- `JavaKit` (Swift -> Java) - JNI-based support library and Swift macros +- `SwiftJava` (Swift -> Java) - JNI-based support library and Swift macros - `SwiftKit` (Java -> Swift) - Support library for Java calling Swift code (either using JNI or FFM) - `swift-java` - command line tool; Supports source generation and also dependency management operations - Build tool integration - SwiftPM Plugin diff --git a/Sources/SwiftJavaMacros/JavaMethodMacro.swift b/Sources/SwiftJavaMacros/JavaMethodMacro.swift index f992ee760..099484528 100644 --- a/Sources/SwiftJavaMacros/JavaMethodMacro.swift +++ b/Sources/SwiftJavaMacros/JavaMethodMacro.swift @@ -50,15 +50,27 @@ extension JavaMethodMacro: BodyMacro { fatalError("not a function: \(declaration)") } + let funcName = + if case .argumentList(let arguments) = node.arguments, + let argument = arguments.first, + argument.label?.text != "typeErasedResult", + let stringLiteral = argument.expression.as(StringLiteralExprSyntax.self), + stringLiteral.segments.count == 1, + case let .stringSegment(funcNameSegment)? = stringLiteral.segments.first + { + funcNameSegment.content.text + } else { + funcDecl.name.text + } + let isStatic = node.attributeName.trimmedDescription == "JavaStaticMethod" - let funcName = funcDecl.name.text let params = funcDecl.signature.parameterClause.parameters let paramNames = params.map { param in param.parameterName?.text ?? "" }.joined(separator: ", ") let genericResultType: String? = if case let .argumentList(arguments) = node.arguments, - let firstElement = arguments.first, - let stringLiteral = firstElement.expression + let element = arguments.first(where: { $0.label?.text == "typeErasedResult" }), + let stringLiteral = element.expression .as(StringLiteralExprSyntax.self), stringLiteral.segments.count == 1, case let .stringSegment(wrapperName)? = stringLiteral.segments.first { diff --git a/Sources/SwiftJavaRuntimeSupport/JNIMethodIDCaches.swift b/Sources/SwiftJavaRuntimeSupport/JNIMethodIDCaches.swift index 16a9c899e..7be79a9fa 100644 --- a/Sources/SwiftJavaRuntimeSupport/JNIMethodIDCaches.swift +++ b/Sources/SwiftJavaRuntimeSupport/JNIMethodIDCaches.swift @@ -91,4 +91,33 @@ extension _JNIMethodIDCache { cache.methods[messageConstructor]! } } + + public enum JNISwiftInstance { + private static let memoryAddressMethod = Method( + name: "$memoryAddress", + signature: "()J" + ) + + private static let typeMetadataAddressMethod = Method( + name: "$typeMetadataAddress", + signature: "()J" + ) + + private static let cache = _JNIMethodIDCache( + className: "org/swift/swiftkit/core/JNISwiftInstance", + methods: [memoryAddressMethod, typeMetadataAddressMethod] + ) + + public static var `class`: jclass { + cache.javaClass + } + + public static var memoryAddress: jmethodID { + cache.methods[memoryAddressMethod]! + } + + public static var typeMetadataAddress: jmethodID { + cache.methods[typeMetadataAddressMethod]! + } + } } diff --git a/Sources/SwiftJavaRuntimeSupport/generated/JavaJNISwiftInstance.swift b/Sources/SwiftJavaRuntimeSupport/generated/JavaJNISwiftInstance.swift new file mode 100644 index 000000000..4040d0bcb --- /dev/null +++ b/Sources/SwiftJavaRuntimeSupport/generated/JavaJNISwiftInstance.swift @@ -0,0 +1,21 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftJava + +@JavaInterface("org.swift.swiftkit.core.JNISwiftInstance") +public struct JavaJNISwiftInstance { + @JavaMethod("$memoryAddress") + public func memoryAddress() -> Int64 +} diff --git a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift index 455cb962d..05bf3b8f3 100644 --- a/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift +++ b/Sources/SwiftJavaTool/Commands/ConfigureCommand.swift @@ -59,8 +59,8 @@ extension SwiftJava { swiftModule } - @Argument(help: "The input file, which is either a swift-java configuration file or (if '-jar' was specified) a Jar file.") - var input: String? + @Option(help: "A prefix that will be added to the names of the Swift types") + var swiftTypePrefix: String? } } @@ -137,14 +137,19 @@ extension SwiftJava.ConfigureCommand { print("[debug][swift-java] Importing classpath entry: \(entry)") if entry.hasSuffix(".jar") { + print("[debug][swift-java] Importing classpath as JAR file: \(entry)") let jarFile = try JarFile(entry, false, environment: environment) try addJavaToSwiftMappings( to: &config, forJar: jarFile, environment: environment ) - } else if FileManager.default.fileExists(atPath: entry) { - log.warning("Currently unable handle directory classpath entries for config generation! Skipping: \(entry)") + } else if FileManager.default.fileExists(atPath: entry), let entryURL = URL(string: entry) { + print("[debug][swift-java] Importing classpath as directory: \(entryURL)") + try addJavaToSwiftMappings( + to: &config, + forDirectory: entryURL + ) } else { log.warning("Classpath entry does not exist, skipping: \(entry)") } @@ -162,62 +167,82 @@ extension SwiftJava.ConfigureCommand { ) } + mutating func addJavaToSwiftMappings( + to configuration: inout Configuration, + forDirectory url: Foundation.URL + ) throws { + let enumerator = FileManager.default.enumerator(atPath: url.path()) + + while let filePath = enumerator?.nextObject() as? String { + try addJavaToSwiftMappings(to: &configuration, fileName: filePath) + } + } + mutating func addJavaToSwiftMappings( to configuration: inout Configuration, forJar jarFile: JarFile, environment: JNIEnvironment ) throws { - let log = Self.log - for entry in jarFile.entries()! { - // We only look at class files in the Jar file. - guard entry.getName().hasSuffix(".class") else { - continue - } + try addJavaToSwiftMappings(to: &configuration, fileName: entry.getName()) + } + } - // Skip some "common" files we know that would be duplicated in every jar - guard !entry.getName().hasPrefix("META-INF") else { - continue - } - guard !entry.getName().hasSuffix("package-info") else { - continue - } - guard !entry.getName().hasSuffix("package-info.class") else { - continue - } + mutating func addJavaToSwiftMappings( + to configuration: inout Configuration, + fileName: String + ) throws { + // We only look at class files + guard fileName.hasSuffix(".class") else { + return + } - // If this is a local class, it cannot be mapped into Swift. - if entry.getName().isLocalJavaClass { - continue - } + // Skip some "common" files we know that would be duplicated in every jar + guard !fileName.hasPrefix("META-INF") else { + return + } + guard !fileName.hasSuffix("package-info") else { + return + } + guard !fileName.hasSuffix("package-info.class") else { + return + } - let javaCanonicalName = String(entry.getName().replacing("/", with: ".") - .dropLast(".class".count)) + // If this is a local class, it cannot be mapped into Swift. + if fileName.isLocalJavaClass { + return + } - guard SwiftJava.shouldImport(javaCanonicalName: javaCanonicalName, commonOptions: self.commonOptions) else { - log.info("Skip importing class: \(javaCanonicalName) due to include/exclude filters") - continue - } + let javaCanonicalName = String(fileName.replacing("/", with: ".") + .dropLast(".class".count)) - if configuration.classes?[javaCanonicalName] != nil { - // We never overwrite an existing class mapping configuration. - // E.g. the user may have configured a custom name for a type. - continue - } + guard SwiftJava.shouldImport(javaCanonicalName: javaCanonicalName, commonOptions: self.commonOptions) else { + log.info("Skip importing class: \(javaCanonicalName) due to include/exclude filters") + return + } - if configuration.classes == nil { - configuration.classes = [:] - } + if configuration.classes?[javaCanonicalName] != nil { + // We never overwrite an existing class mapping configuration. + // E.g. the user may have configured a custom name for a type. + return + } - if let configuredSwiftName = configuration.classes![javaCanonicalName] { - log.info("Java type '\(javaCanonicalName)' already configured as '\(configuredSwiftName)' Swift type.") - } else { - log.info("Configure Java type '\(javaCanonicalName)' as '\(javaCanonicalName.defaultSwiftNameForJavaClass.bold)' Swift type.") - } + if configuration.classes == nil { + configuration.classes = [:] + } - configuration.classes![javaCanonicalName] = - javaCanonicalName.defaultSwiftNameForJavaClass + var swiftName = javaCanonicalName.defaultSwiftNameForJavaClass + if let swiftTypePrefix { + swiftName = "\(swiftTypePrefix)\(swiftName)" } + + if let configuredSwiftName = configuration.classes![javaCanonicalName] { + log.info("Java type '\(javaCanonicalName)' already configured as '\(configuredSwiftName)' Swift type.") + } else { + log.info("Configure Java type '\(javaCanonicalName)' as '\(swiftName.bold)' Swift type.") + } + + configuration.classes![javaCanonicalName] = swiftName } } diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index b5c3a7bb9..775ab6040 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -81,6 +81,12 @@ extension SwiftJava { @Option(help: "The mode to use for extracting asynchronous Swift functions. By default async methods are extracted as Java functions returning CompletableFuture.") var asyncFuncMode: JExtractAsyncFuncMode? + + @Flag(help: "By enabling this mode, JExtract will generate Java code that allows you to implement Swift protocols using Java classes. This feature requires disabling the sandbox mode in SwiftPM. This only works in the 'jni' mode.") + var enableJavaCallbacks: Bool = false + + @Option(help: "If specified, JExtract will output to this file a list of paths to all generated Java source files") + var generatedJavaSourcesListFileOutput: String? } } @@ -96,10 +102,14 @@ extension SwiftJava.JExtractCommand { let writeEmptyFiles = CommandLine.arguments.contains("--write-empty-files") ? true : nil configure(&config.writeEmptyFiles, overrideWith: writeEmptyFiles) + let enableJavaCallbacks = CommandLine.arguments.contains("--enable-java-callbacks") ? true : nil + configure(&config.enableJavaCallbacks, overrideWith: enableJavaCallbacks) + configure(&config.unsignedNumbersMode, overrideWith: self.unsignedNumbersMode) configure(&config.minimumInputAccessLevelMode, overrideWith: self.minimumInputAccessLevelMode) configure(&config.memoryManagementMode, overrideWith: self.memoryManagementMode) configure(&config.asyncFuncMode, overrideWith: self.asyncFuncMode) + configure(&config.generatedJavaSourcesListFileOutput, overrideWith: self.generatedJavaSourcesListFileOutput) try checkModeCompatibility(config: config) @@ -126,11 +136,15 @@ extension SwiftJava.JExtractCommand { case .annotate: () // OK case .wrapGuava: - throw IllegalModeCombinationError("JNI mode does not support '\(JExtractUnsignedIntegerMode.wrapGuava)' Unsigned integer mode! \(Self.helpMessage)") + throw IllegalModeCombinationError("JNI mode does not support '\(JExtractUnsignedIntegerMode.wrapGuava)' Unsigned integer mode! \(Self.helpMessage())") } } else if config.effectiveMode == .ffm { guard config.effectiveMemoryManagementMode == .explicit else { - throw IllegalModeCombinationError("FFM mode does not support '\(self.memoryManagementMode)' memory management mode! \(Self.helpMessage)") + throw IllegalModeCombinationError("FFM mode does not support '\(self.memoryManagementMode ?? .default)' memory management mode! \(Self.helpMessage())") + } + + if let enableJavaCallbacks = config.enableJavaCallbacks, enableJavaCallbacks { + throw IllegalModeCombinationError("FFM mode does not support enabling Java callbacks! \(Self.helpMessage())") } } } diff --git a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift index 51315102a..3e4e482b3 100644 --- a/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift +++ b/Sources/SwiftJavaTool/Commands/WrapJavaCommand.swift @@ -58,8 +58,8 @@ extension SwiftJava { @Option(help: "Match java package directory structure with generated Swift files") var swiftMatchPackageDirectoryStructure: Bool = false - @Argument(help: "Path to .jar file whose Java classes should be wrapped using Swift bindings") - var input: String + @Option(help: "If specified, a single Swift file will be generated containing all the generated code") + var singleSwiftFileOutput: String? } } @@ -69,6 +69,7 @@ extension SwiftJava.WrapJavaCommand { print("self.commonOptions.filterInclude = \(self.commonOptions.filterInclude)") configure(&config.filterInclude, append: self.commonOptions.filterInclude) configure(&config.filterExclude, append: self.commonOptions.filterExclude) + configure(&config.singleSwiftFileOutput, overrideWith: self.singleSwiftFileOutput) // Get base classpath configuration for this target and configuration var classpathSearchDirs = [self.effectiveSwiftModuleURL] @@ -78,7 +79,6 @@ extension SwiftJava.WrapJavaCommand { } else { print("[trace][swift-java] Cache directory: none") } - print("[trace][swift-java] INPUT: \(input)") var classpathEntries = self.configureCommandJVMClasspath( searchDirs: classpathSearchDirs, config: config, log: Self.log) @@ -151,21 +151,41 @@ extension SwiftJava.WrapJavaCommand { .getSystemClassLoader()! var javaClasses: [JavaClass] = [] for (javaClassName, _) in config.classes ?? [:] { + func remove() { + translator.translatedClasses.removeValue(forKey: javaClassName) + } + guard shouldImportJavaClass(javaClassName, config: config) else { + remove() continue } - log.info("Wrapping java type: \(javaClassName)") - guard let javaClass = try classLoader.loadClass(javaClassName) else { log.warning("Could not load Java class '\(javaClassName)', skipping.") + remove() + continue + } + + guard self.shouldExtract(javaClass: javaClass, config: config) else { + log.info("Skip Java type: \(javaClassName) (does not match minimum access level)") + remove() + continue + } + + guard !javaClass.isEnum() else { + log.info("Skip Java type: \(javaClassName) (enums do not currently work)") + remove() continue } + log.info("Wrapping java type: \(javaClassName)") + // Add this class to the list of classes we'll translate. javaClasses.append(javaClass) } + log.info("OK now we go to nested classes") + // Find all of the nested classes for each class, adding them to the list // of classes to be translated if they were already specified. var allClassesToVisit = javaClasses @@ -212,6 +232,16 @@ extension SwiftJava.WrapJavaCommand { return nil } + guard self.shouldExtract(javaClass: nestedClass, config: config) else { + log.info("Skip Java type: \(javaClassName) (does not match minimum access level)") + return nil + } + + guard !nestedClass.isEnum() else { + log.info("Skip Java type: \(javaClassName) (enums do not currently work)") + return nil + } + // Record this as a translated class. let swiftUnqualifiedName = javaClassName.javaClassNameToCanonicalName .defaultSwiftNameForJavaClass @@ -237,9 +267,13 @@ extension SwiftJava.WrapJavaCommand { try translator.validateClassConfiguration() // Translate all of the Java classes into Swift classes. - for javaClass in javaClasses { + + if let singleSwiftFileOutput = config.singleSwiftFileOutput { translator.startNewFile() - let swiftClassDecls = try translator.translateClass(javaClass) + + let swiftClassDecls = try javaClasses.flatMap { + try translator.translateClass($0) + } let importDecls = translator.getImportDecls() let swiftFileText = """ @@ -249,19 +283,53 @@ extension SwiftJava.WrapJavaCommand { """ - var generatedFileOutputDir = self.actualOutputDirectory - if self.swiftMatchPackageDirectoryStructure { - generatedFileOutputDir?.append(path: javaClass.getPackageName().replacing(".", with: "/")) - } - - let swiftFileName = try! translator.getSwiftTypeName(javaClass, preferValueTypes: false) - .swiftName.replacing(".", with: "+") + ".swift" try writeContents( swiftFileText, - outputDirectory: generatedFileOutputDir, - to: swiftFileName, - description: "Java class '\(javaClass.getName())' translation" + outputDirectory: self.actualOutputDirectory, + to: singleSwiftFileOutput, + description: "Java class translation" ) + } else { + for javaClass in javaClasses { + translator.startNewFile() + + let swiftClassDecls = try translator.translateClass(javaClass) + let importDecls = translator.getImportDecls() + + let swiftFileText = """ + // Auto-generated by Java-to-Swift wrapper generator. + \(importDecls.map { $0.description }.joined()) + \(swiftClassDecls.map { $0.description }.joined(separator: "\n")) + + """ + + var generatedFileOutputDir = self.actualOutputDirectory + if self.swiftMatchPackageDirectoryStructure { + generatedFileOutputDir?.append(path: javaClass.getPackageName().replacing(".", with: "/")) + } + + let swiftFileName = try translator.getSwiftTypeName(javaClass, preferValueTypes: false) + .swiftName.replacing(".", with: "+") + ".swift" + try writeContents( + swiftFileText, + outputDirectory: generatedFileOutputDir, + to: swiftFileName, + description: "Java class '\(javaClass.getName())' translation" + ) + } + } + } + + /// Determines whether a method should be extracted for translation. + /// Only look at public and protected methods here. + private func shouldExtract(javaClass: JavaClass, config: Configuration) -> Bool { + switch config.effectiveMinimumInputAccessLevelMode { + case .internal: + return javaClass.isPublic || javaClass.isProtected || javaClass.isPackage + case .package: + return javaClass.isPublic || javaClass.isProtected || javaClass.isPackage + case .public: + return javaClass.isPublic || javaClass.isProtected } } diff --git a/Sources/SwiftJavaTool/CommonOptions.swift b/Sources/SwiftJavaTool/CommonOptions.swift index c313202bc..4627381e0 100644 --- a/Sources/SwiftJavaTool/CommonOptions.swift +++ b/Sources/SwiftJavaTool/CommonOptions.swift @@ -67,6 +67,9 @@ extension SwiftJava { @Option(name: .long, help: "While scanning a classpath, skip types which match the filter prefix") var filterExclude: [String] = [] + + @Option(help: "A path to a custom swift-java.config to use") + var config: String? = nil } struct CommonJVMOptions: ParsableArguments { @@ -145,4 +148,4 @@ extension HasCommonJVMOptions { func makeJVM(classpathEntries: [String]) throws -> JavaVirtualMachine { try JavaVirtualMachine.shared(classpath: classpathEntries) } -} \ No newline at end of file +} diff --git a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift index 9763b4b38..92818e43a 100644 --- a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift +++ b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift @@ -163,14 +163,24 @@ extension SwiftJavaBaseAsyncParsableCommand { func readInitialConfiguration(command: some SwiftJavaBaseAsyncParsableCommand) throws -> Configuration { var earlyConfig: Configuration? - if let moduleBaseDir { + if let configPath = commonOptions.config { + let configURL = URL(filePath: configPath, directoryHint: .notDirectory) + print("[debug][swift-java] Load config from passed in path: \(configURL)") + earlyConfig = try readConfiguration(configPath: configURL) + } else if let moduleBaseDir { print("[debug][swift-java] Load config from module base directory: \(moduleBaseDir.path)") earlyConfig = try readConfiguration(sourceDir: moduleBaseDir.path) } else if let inputSwift = commonOptions.inputSwift { print("[debug][swift-java] Load config from module swift input directory: \(inputSwift)") earlyConfig = try readConfiguration(sourceDir: inputSwift) } - var config = earlyConfig ?? Configuration() + var config: Configuration + if let earlyConfig { + config = earlyConfig + } else { + log.warning("[swift-java] Failed to load initial configuration. Proceeding with empty configuration.") + config = Configuration() + } // override configuration with options from command line config.logLevel = command.logLevel return config diff --git a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift index 0f7aa45d6..ce780f904 100644 --- a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift @@ -228,6 +228,11 @@ struct JavaClassTranslator { continue } + guard method.getName().isValidSwiftFunctionName else { + log.warning("Skipping method \(method.getName()) because it is not a valid Swift function name") + continue + } + addMethod(method, isNative: false) } @@ -604,6 +609,15 @@ extension JavaClassTranslator { } } + // --- Parameter types + for parameter in method.getParameters() { + if let parameterizedType = parameter?.getParameterizedType() { + if parameterizedType.isEqualTo(typeParam.as(Type.self)) { + return true + } + } + } + return false } diff --git a/Sources/SwiftJavaToolLib/StringExtras.swift b/Sources/SwiftJavaToolLib/StringExtras.swift index e69f379c3..c3ac5390f 100644 --- a/Sources/SwiftJavaToolLib/StringExtras.swift +++ b/Sources/SwiftJavaToolLib/StringExtras.swift @@ -35,6 +35,11 @@ extension String { return "`\(self)`" } + /// Returns whether this is a valid Swift function name + var isValidSwiftFunctionName: Bool { + !self.starts(with: "$") + } + /// Replace all occurrences of one character in the string with another. public func replacing(_ character: Character, with replacement: Character) -> String { return replacingOccurrences(of: String(character), with: String(replacement)) diff --git a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift index 38ef87895..2188bcd62 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift @@ -170,17 +170,17 @@ struct JNIEnumTests { """, """ public record First() implements Case { - record $NativeParameters() {} + record _NativeParameters() {} } """, """ public record Second(java.lang.String arg0) implements Case { - record $NativeParameters(java.lang.String arg0) {} + record _NativeParameters(java.lang.String arg0) {} } """, """ public record Third(long x, int y) implements Case { - record $NativeParameters(long x, int y) {} + record _NativeParameters(long x, int y) {} } """ ]) @@ -268,7 +268,7 @@ struct JNIEnumTests { if (getDiscriminator() != Discriminator.SECOND) { return Optional.empty(); } - Second.$NativeParameters $nativeParameters = MyEnum.$getAsSecond(this.$memoryAddress()); + Second._NativeParameters $nativeParameters = MyEnum.$getAsSecond(this.$memoryAddress()); return Optional.of(new Second($nativeParameters.arg0)); } """, @@ -277,7 +277,7 @@ struct JNIEnumTests { if (getDiscriminator() != Discriminator.THIRD) { return Optional.empty(); } - Third.$NativeParameters $nativeParameters = MyEnum.$getAsThird(this.$memoryAddress()); + Third._NativeParameters $nativeParameters = MyEnum.$getAsThird(this.$memoryAddress()); return Optional.of(new Third($nativeParameters.x, $nativeParameters.y)); } """ diff --git a/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift b/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift index c5302ca64..231c4d25d 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift @@ -12,16 +12,22 @@ // //===----------------------------------------------------------------------===// +import SwiftJavaConfigurationShared import JExtractSwiftLib import Testing @Suite struct JNIProtocolTests { + var config: Configuration { + var config = Configuration() + config.enableJavaCallbacks = true + return config + } + let source = """ public protocol SomeProtocol { - var x: Int64 { get set } - public func method() {} + public func withObject(c: SomeClass) -> SomeClass {} } public protocol B {} @@ -37,6 +43,7 @@ struct JNIProtocolTests { func generatesJavaInterface() throws { try assertOutput( input: source, + config: config, .jni, .java, detectChunkByInitialLines: 1, expectedChunks: [ @@ -50,18 +57,13 @@ struct JNIProtocolTests { import org.swift.swiftkit.core.util.*; """, """ - public interface SomeProtocol extends JNISwiftInstance { - ... + public interface SomeProtocol { + ... + public void method(); + ... + public SomeClass withObject(SomeClass c); + ... } - """, - """ - public long getX(); - """, - """ - public void setX(long newValue); - """, - """ - public void method(); """ ]) } @@ -70,6 +72,7 @@ struct JNIProtocolTests { func generatesJavaClassWithExtends() throws { try assertOutput( input: source, + config: config, .jni, .java, detectChunkByInitialLines: 1, expectedChunks: [ @@ -83,16 +86,17 @@ struct JNIProtocolTests { func takeProtocol_java() throws { try assertOutput( input: source, + config: config, .jni, .java, detectChunkByInitialLines: 1, expectedChunks: [ """ - public static <$T0 extends SomeProtocol, $T1 extends SomeProtocol> void takeProtocol($T0 x, $T1 y) { - SwiftModule.$takeProtocol(x.$memoryAddress(), x.$typeMetadataAddress(), y.$memoryAddress(), y.$typeMetadataAddress()); + public static <_T0 extends SomeProtocol, _T1 extends SomeProtocol> void takeProtocol(_T0 x, _T1 y) { + SwiftModule.$takeProtocol(x, y); } """, """ - private static native void $takeProtocol(long x, long x_typeMetadataAddress, long y, long y_typeMetadataAddress); + private static native void $takeProtocol(java.lang.Object x, java.lang.Object y); """ ]) } @@ -101,43 +105,50 @@ struct JNIProtocolTests { func takeProtocol_swift() throws { try assertOutput( input: source, + config: config, .jni, .swift, detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_SwiftModule__00024takeProtocol__JJJJ") - func Java_com_example_swift_SwiftModule__00024takeProtocol__JJJJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, x_typeMetadataAddress: jlong, y: jlong, y_typeMetadataAddress: jlong) { - guard let xTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: x_typeMetadataAddress, in: environment))) else { - fatalError("x_typeMetadataAddress memory address was null") - } - let xDynamicType$: Any.Type = unsafeBitCast(xTypeMetadataPointer$, to: Any.Type.self) - guard let xRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: x, in: environment))) else { - fatalError("x memory address was null") + final class _SwiftModule_takeGeneric_s_Wrapper: SwiftJavaSomeProtocolWrapper { + let _javaSomeProtocolInterface: JavaSomeProtocol + init(_javaSomeProtocolInterface: JavaSomeProtocol) { + self._javaSomeProtocolInterface = _javaSomeProtocolInterface } - #if hasFeature(ImplicitOpenExistentials) - let xExistential$ = xRawPointer$.load(as: xDynamicType$) as! any (SomeProtocol) - #else - func xDoLoad(_ ty: Ty.Type) -> any (SomeProtocol) { - xRawPointer$.load(as: ty) as! any (SomeProtocol) + } + """, + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024takeProtocol__Ljava_lang_Object_2Ljava_lang_Object_2") + func Java_com_example_swift_SwiftModule__00024takeProtocol__Ljava_lang_Object_2Ljava_lang_Object_2(environment: UnsafeMutablePointer!, thisClass: jclass, x: jobject?, y: jobject?) { + let xswiftObject$: (SomeProtocol) + if environment.interface.IsInstanceOf(environment, x, _JNIMethodIDCache.JNISwiftInstance.class) != 0 { + ... + let xpointer$DynamicType$: Any.Type = unsafeBitCast(xpointer$TypeMetadataPointer$, to: Any.Type.self) + guard let xpointer$RawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: xpointer$, in: environment))) else { + fatalError("xpointer$ memory address was null") + } + #if hasFeature(ImplicitOpenExistentials) + let xpointer$Existential$ = xpointer$RawPointer$.load(as: xpointer$DynamicType$) as! any (SomeProtocol) + #else + func xpointer$DoLoad(_ ty: Ty.Type) -> any (SomeProtocol) { + xpointer$RawPointer$.load(as: ty) as! any (SomeProtocol) + } + let xpointer$Existential$ = _openExistential(xpointer$DynamicType$, do: xpointer$DoLoad) + #endif + xswiftObject$ = xpointer$Existential$ } - let xExistential$ = _openExistential(xDynamicType$, do: xDoLoad) - #endif - guard let yTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: y_typeMetadataAddress, in: environment))) else { - fatalError("y_typeMetadataAddress memory address was null") + else { + xswiftObject$ = _SwiftModule_takeProtocol_x_Wrapper(_javaSomeProtocolInterface: JavaSomeProtocol(javaThis: x!, environment: environment)) } - let yDynamicType$: Any.Type = unsafeBitCast(yTypeMetadataPointer$, to: Any.Type.self) - guard let yRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: y, in: environment))) else { - fatalError("y memory address was null") + let yswiftObject$: (SomeProtocol) + if environment.interface.IsInstanceOf(environment, y, _JNIMethodIDCache.JNISwiftInstance.class) != 0 { + ... + yswiftObject$ = ypointer$Existential$ } - #if hasFeature(ImplicitOpenExistentials) - let yExistential$ = yRawPointer$.load(as: yDynamicType$) as! any (SomeProtocol) - #else - func yDoLoad(_ ty: Ty.Type) -> any (SomeProtocol) { - yRawPointer$.load(as: ty) as! any (SomeProtocol) + else { + yswiftObject$ = _SwiftModule_takeProtocol_y_Wrapper(_javaSomeProtocolInterface: JavaSomeProtocol(javaThis: y!, environment: environment)) } - let yExistential$ = _openExistential(yDynamicType$, do: yDoLoad) - #endif - SwiftModule.takeProtocol(x: xExistential$, y: yExistential$) + SwiftModule.takeProtocol(x: xswiftObject$, y: yswiftObject$) } """ ] @@ -148,16 +159,17 @@ struct JNIProtocolTests { func takeGeneric_java() throws { try assertOutput( input: source, + config: config, .jni, .java, detectChunkByInitialLines: 1, expectedChunks: [ """ public static void takeGeneric(S s) { - SwiftModule.$takeGeneric(s.$memoryAddress(), s.$typeMetadataAddress()); + SwiftModule.$takeGeneric(s); } """, """ - private static native void $takeGeneric(long s, long s_typeMetadataAddress); + private static native void $takeGeneric(java.lang.Object s); """ ]) } @@ -166,28 +178,30 @@ struct JNIProtocolTests { func takeGeneric_swift() throws { try assertOutput( input: source, + config: config, .jni, .swift, detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_SwiftModule__00024takeGeneric__JJ") - func Java_com_example_swift_SwiftModule__00024takeGeneric__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, s: jlong, s_typeMetadataAddress: jlong) { - guard let sTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: s_typeMetadataAddress, in: environment))) else { - fatalError("s_typeMetadataAddress memory address was null") + final class _SwiftModule_takeGeneric_s_Wrapper: SwiftJavaSomeProtocolWrapper { + let _javaSomeProtocolInterface: JavaSomeProtocol + init(_javaSomeProtocolInterface: JavaSomeProtocol) { + self._javaSomeProtocolInterface = _javaSomeProtocolInterface } - let sDynamicType$: Any.Type = unsafeBitCast(sTypeMetadataPointer$, to: Any.Type.self) - guard let sRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: s, in: environment))) else { - fatalError("s memory address was null") + } + """, + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024takeGeneric__Ljava_lang_Object_2") + func Java_com_example_swift_SwiftModule__00024takeGeneric__Ljava_lang_Object_2(environment: UnsafeMutablePointer!, thisClass: jclass, s: jobject?) { + let sswiftObject$: (SomeProtocol) + if environment.interface.IsInstanceOf(environment, s, _JNIMethodIDCache.JNISwiftInstance.class) != 0 { + ... + sswiftObject$ = spointer$Existential$ } - #if hasFeature(ImplicitOpenExistentials) - let sExistential$ = sRawPointer$.load(as: sDynamicType$) as! any (SomeProtocol) - #else - func sDoLoad(_ ty: Ty.Type) -> any (SomeProtocol) { - sRawPointer$.load(as: ty) as! any (SomeProtocol) + else { + sswiftObject$ = _SwiftModule_takeGeneric_s_Wrapper(_javaSomeProtocolInterface: JavaSomeProtocol(javaThis: s!, environment: environment)) } - let sExistential$ = _openExistential(sDynamicType$, do: sDoLoad) - #endif - SwiftModule.takeGeneric(s: sExistential$) + SwiftModule.takeGeneric(s: sswiftObject$) } """ ] @@ -198,16 +212,17 @@ struct JNIProtocolTests { func takeComposite_java() throws { try assertOutput( input: source, + config: config, .jni, .java, detectChunkByInitialLines: 1, expectedChunks: [ """ - public static <$T0 extends SomeProtocol & B> void takeComposite($T0 x) { - SwiftModule.$takeComposite(x.$memoryAddress(), x.$typeMetadataAddress()); + public static <_T0 extends SomeProtocol & B> void takeComposite(_T0 x) { + SwiftModule.$takeComposite(x); } """, """ - private static native void $takeComposite(long x, long x_typeMetadataAddress); + private static native void $takeComposite(java.lang.Object x); """ ]) } @@ -216,28 +231,92 @@ struct JNIProtocolTests { func takeComposite_swift() throws { try assertOutput( input: source, + config: config, .jni, .swift, detectChunkByInitialLines: 1, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_SwiftModule__00024takeComposite__JJ") - func Java_com_example_swift_SwiftModule__00024takeComposite__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, x_typeMetadataAddress: jlong) { - guard let xTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: x_typeMetadataAddress, in: environment))) else { - fatalError("x_typeMetadataAddress memory address was null") + final class _SwiftModule_takeComposite_x_Wrapper: SwiftJavaSomeProtocolWrapper, SwiftJavaBWrapper { + let _javaSomeProtocolInterface: JavaSomeProtocol + let _javaBInterface: JavaB + init(_javaSomeProtocolInterface: JavaSomeProtocol, _javaBInterface: JavaB) { + self._javaSomeProtocolInterface = _javaSomeProtocolInterface + self._javaBInterface = _javaBInterface } - let xDynamicType$: Any.Type = unsafeBitCast(xTypeMetadataPointer$, to: Any.Type.self) - guard let xRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: x, in: environment))) else { - fatalError("x memory address was null") + } + """, + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024takeComposite__Ljava_lang_Object_2") + func Java_com_example_swift_SwiftModule__00024takeComposite__Ljava_lang_Object_2(environment: UnsafeMutablePointer!, thisClass: jclass, x: jobject?) { + let xswiftObject$: (SomeProtocol & B) + if environment.interface.IsInstanceOf(environment, x, _JNIMethodIDCache.JNISwiftInstance.class) != 0 { + let xpointer$ = environment.interface.CallLongMethodA(environment, x, _JNIMethodIDCache.JNISwiftInstance.memoryAddress, []) + let xtypeMetadata$ = environment.interface.CallLongMethodA(environment, x, _JNIMethodIDCache.JNISwiftInstance.typeMetadataAddress, []) + guard let xpointer$TypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: xtypeMetadata$, in: environment))) else { + fatalError("xtypeMetadata$ memory address was null") + } + let xpointer$DynamicType$: Any.Type = unsafeBitCast(xpointer$TypeMetadataPointer$, to: Any.Type.self) + guard let xpointer$RawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: xpointer$, in: environment))) else { + fatalError("xpointer$ memory address was null") + } + #if hasFeature(ImplicitOpenExistentials) + let xpointer$Existential$ = xpointer$RawPointer$.load(as: xpointer$DynamicType$) as! any (SomeProtocol & B) + #else + func xpointer$DoLoad(_ ty: Ty.Type) -> any (SomeProtocol & B) { + xpointer$RawPointer$.load(as: ty) as! any (SomeProtocol & B) + } + let xpointer$Existential$ = _openExistential(xpointer$DynamicType$, do: xpointer$DoLoad) + #endif + xswiftObject$ = xpointer$Existential$ } - #if hasFeature(ImplicitOpenExistentials) - let xExistential$ = xRawPointer$.load(as: xDynamicType$) as! any (SomeProtocol & B) - #else - func xDoLoad(_ ty: Ty.Type) -> any (SomeProtocol & B) { - xRawPointer$.load(as: ty) as! any (SomeProtocol & B) + else { + xswiftObject$ = _SwiftModule_takeComposite_x_Wrapper(_javaSomeProtocolInterface: JavaSomeProtocol(javaThis: x!, environment: environment), _javaBInterface: JavaB(javaThis: x!, environment: environment)) } - let xExistential$ = _openExistential(xDynamicType$, do: xDoLoad) - #endif - SwiftModule.takeComposite(x: xExistential$) + SwiftModule.takeComposite(x: xswiftObject$) + } + """ + ] + ) + } + + @Test + func generatesProtocolWrappers() throws { + try assertOutput( + input: source, + config: config, + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + protocol SwiftJavaSomeProtocolWrapper: SomeProtocol { + var _javaSomeProtocolInterface: JavaSomeProtocol { get } + } + """, + """ + extension SwiftJavaSomeProtocolWrapper { + public func method() { + _javaSomeProtocolInterface.method() + } + public func withObject(c: SomeClass) -> SomeClass { + let cClass = try! JavaClass(environment: JavaVirtualMachine.shared().environment()) + let cPointer = UnsafeMutablePointer.allocate(capacity: 1) + cPointer.initialize(to: c) + guard let unwrapped$ = _javaSomeProtocolInterface.withObject(cClass.wrapMemoryAddressUnsafe(Int64(Int(bitPattern: cPointer)))) else { + fatalError("Upcall to withObject unexpectedly returned nil") + } + let result$MemoryAddress$ = unwrapped$.as(JavaJNISwiftInstance.self)!.memoryAddress() + let result$Pointer = UnsafeMutablePointer(bitPattern: Int(result$MemoryAddress$))! + return result$Pointer.pointee + } + } + """, + """ + protocol SwiftJavaBWrapper: B { + var _javaBInterface: JavaB { get } + } + """, + """ + extension SwiftJavaBWrapper { } """ ] diff --git a/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift b/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift index 2228aad88..7e78434d1 100644 --- a/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift +++ b/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift @@ -99,7 +99,7 @@ struct MemoryManagementModeTests { } """, """ - public MyClass f(SwiftArena swiftArena$); + public MyClass f(); """ ] ) diff --git a/Tests/SwiftJavaToolLibTests/WrapJavaTests/GenericsWrapJavaTests.swift b/Tests/SwiftJavaToolLibTests/WrapJavaTests/GenericsWrapJavaTests.swift index cba7b38f5..ceb05df97 100644 --- a/Tests/SwiftJavaToolLibTests/WrapJavaTests/GenericsWrapJavaTests.swift +++ b/Tests/SwiftJavaToolLibTests/WrapJavaTests/GenericsWrapJavaTests.swift @@ -338,6 +338,45 @@ final class GenericsWrapJavaTests: XCTestCase { ) } + func test_wrapJava_genericMethodTypeErasure_customInterface_staticMethods() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + interface MyInterface {} + + final class Public { + public static void useInterface(T myInterface) { } + } + """) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.MyInterface", + "com.example.Public" + ], + classpath: [classpathURL], + expectedChunks: [ + """ + @JavaInterface("com.example.MyInterface") + public struct MyInterface { + """, + """ + @JavaClass("com.example.Public") + open class Public: JavaObject { + """, + """ + extension JavaClass { + """, + """ + @JavaStaticMethod + public func useInterface(_ arg0: T?) + } + """ + ] + ) + } + // TODO: this should be improved some more, we need to generated a `: Map` on the Swift side func test_wrapJava_genericMethodTypeErasure_genericExtendsMap() async throws { let classpathURL = try await compileJava( @@ -370,4 +409,4 @@ final class GenericsWrapJavaTests: XCTestCase { ) } -} \ No newline at end of file +} From a33d709b28bb09b81cce248212f804e21b57f59d Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 3 Dec 2025 12:44:21 +0900 Subject: [PATCH 138/141] test: add tests for @Unsigned in methods in jextract JNI mode (#475) --- .../JExtract/JExtractGenerationMode.swift | 2 +- .../UnsignedNumberTests.swift | 487 +++++++++++------- 2 files changed, 315 insertions(+), 174 deletions(-) diff --git a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift index 1ad331da7..8e11d82b0 100644 --- a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// /// Determines which source generation mode JExtract should be using: JNI or Foreign Function and Memory. -public enum JExtractGenerationMode: String, Codable { +public enum JExtractGenerationMode: String, Sendable, Codable { /// Foreign Value and Memory API case ffm diff --git a/Tests/JExtractSwiftTests/UnsignedNumberTests.swift b/Tests/JExtractSwiftTests/UnsignedNumberTests.swift index 8109714cb..f3f784098 100644 --- a/Tests/JExtractSwiftTests/UnsignedNumberTests.swift +++ b/Tests/JExtractSwiftTests/UnsignedNumberTests.swift @@ -18,251 +18,392 @@ import Testing final class UnsignedNumberTests { - @Test("Import: UInt16 (char)") - func unsignedChar() throws { + @Test( + "Import: UInt16 (char)", + arguments: [ + ( + JExtractGenerationMode.ffm, + /* expected chunks */ + [ + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_unsignedChar__(uint16_t arg) + * } + */ + private static class swiftjava_SwiftModule_unsignedChar__ { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* arg: */SwiftValueLayout.SWIFT_UINT16 + ); + """, + """ + public static void unsignedChar(@Unsigned char arg) { + swiftjava_SwiftModule_unsignedChar__.call(arg); + } + """, + ] + ), + ( + JExtractGenerationMode.jni, + /* expected chunks */ + [ + """ + public static void unsignedChar(@Unsigned char arg) { + SwiftModule.$unsignedChar(arg); + } + private static native void $unsignedChar(char arg); + """, + ] + ) + ]) + func unsignedChar(mode: JExtractGenerationMode, expectedChunks: [String]) throws { try assertOutput( input: "public func unsignedChar(_ arg: UInt16)", - .ffm, .java, + mode, .java, detectChunkByInitialLines: 2, - expectedChunks: [ - """ - /** - * {@snippet lang=c : - * void swiftjava_SwiftModule_unsignedChar__(uint16_t arg) - * } - */ - private static class swiftjava_SwiftModule_unsignedChar__ { - private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( - /* arg: */SwiftValueLayout.SWIFT_UINT16 - ); - """, - """ - public static void unsignedChar(@Unsigned char arg) { - swiftjava_SwiftModule_unsignedChar__.call(arg); - } - """, - ] + expectedChunks: expectedChunks ) } - @Test("Import: UInt32 (wrap)") - func unsignedInt() throws { + @Test( + "Import: UInt32 (wrap)", + arguments: [ + ( + JExtractGenerationMode.ffm, + /* expected chunks */ + [ + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_unsignedInt__(uint32_t arg) + * } + */ + private static class swiftjava_SwiftModule_unsignedInt__ { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* arg: */SwiftValueLayout.SWIFT_UINT32 + ); + """, + """ + public static void unsignedInt(com.google.common.primitives.UnsignedInteger arg) { + swiftjava_SwiftModule_unsignedInt__.call(UnsignedNumbers.toPrimitive(arg)); + } + """, + ] + ), + // JNI mode does not support the "wrap" mode + ]) + func unsignedInt_wrap(mode: JExtractGenerationMode, expectedChunks: [String]) throws { var config = Configuration() config.unsignedNumbersMode = .wrapGuava try assertOutput( input: "public func unsignedInt(_ arg: UInt32)", config: config, - .ffm, .java, + mode, .java, detectChunkByInitialLines: 2, - expectedChunks: [ - """ - /** - * {@snippet lang=c : - * void swiftjava_SwiftModule_unsignedInt__(uint32_t arg) - * } - */ - private static class swiftjava_SwiftModule_unsignedInt__ { - private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( - /* arg: */SwiftValueLayout.SWIFT_UINT32 - ); - """, - """ - public static void unsignedInt(com.google.common.primitives.UnsignedInteger arg) { - swiftjava_SwiftModule_unsignedInt__.call(UnsignedNumbers.toPrimitive(arg)); - } - """, - ] + expectedChunks: expectedChunks ) } - @Test("Import: UInt32 (annotate)") - func unsignedIntAnnotate() throws { + @Test( + "Import: UInt32 (annotate)", + arguments: [ + ( + JExtractGenerationMode.ffm, + /* expected chunks */ + [ + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_unsignedInt__(uint32_t arg) + * } + */ + private static class swiftjava_SwiftModule_unsignedInt__ { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* arg: */SwiftValueLayout.SWIFT_UINT32 + ); + """, + """ + public static void unsignedInt(@Unsigned int arg) { + swiftjava_SwiftModule_unsignedInt__.call(arg); + } + """, + ] + ), + ( + JExtractGenerationMode.jni, + /* expected chunks */ + [ + """ + public static void unsignedInt(@Unsigned int arg) { + SwiftModule.$unsignedInt(arg); + } + private static native void $unsignedInt(int arg); + """, + ] + ) + ]) + func unsignedIntAnnotate(mode: JExtractGenerationMode, expectedChunks: [String]) throws { var config = Configuration() config.unsignedNumbersMode = .annotate try assertOutput( input: "public func unsignedInt(_ arg: UInt32)", config: config, - .ffm, .java, + mode, .java, detectChunkByInitialLines: 2, - expectedChunks: [ - """ - /** - * {@snippet lang=c : - * void swiftjava_SwiftModule_unsignedInt__(uint32_t arg) - * } - */ - private static class swiftjava_SwiftModule_unsignedInt__ { - private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( - /* arg: */SwiftValueLayout.SWIFT_UINT32 - ); - """, - """ - public static void unsignedInt(@Unsigned int arg) { - swiftjava_SwiftModule_unsignedInt__.call(arg); - } - """, - ] + expectedChunks: expectedChunks ) } - @Test("Import: return UInt32 (default)") - func returnUnsignedIntDefault() throws { + @Test( + "Import: return UInt32 (default)", + arguments: [ + ( + JExtractGenerationMode.ffm, + /* expected chunks */ + [ + """ + /** + * {@snippet lang=c : + * uint32_t swiftjava_SwiftModule_returnUnsignedInt(void) + * } + */ + private static class swiftjava_SwiftModule_returnUnsignedInt { + private static final FunctionDescriptor DESC = FunctionDescriptor.of( + /* -> */SwiftValueLayout.SWIFT_UINT32 + ); + """, + """ + @Unsigned + public static int returnUnsignedInt() { + return swiftjava_SwiftModule_returnUnsignedInt.call(); + } + """, + ] + ), + ( + JExtractGenerationMode.jni, + /* expected chunks */ + [ + """ + @Unsigned + public static int returnUnsignedInt() { + return SwiftModule.$returnUnsignedInt(); + } + private static native int $returnUnsignedInt(); + """, + ] + ) + ]) + func returnUnsignedIntDefault(mode: JExtractGenerationMode, expectedChunks: [String]) throws { let config = Configuration() try assertOutput( input: "public func returnUnsignedInt() -> UInt32", config: config, - .ffm, .java, + mode, .java, detectChunkByInitialLines: 2, - expectedChunks: [ - """ - /** - * {@snippet lang=c : - * uint32_t swiftjava_SwiftModule_returnUnsignedInt(void) - * } - */ - private static class swiftjava_SwiftModule_returnUnsignedInt { - private static final FunctionDescriptor DESC = FunctionDescriptor.of( - /* -> */SwiftValueLayout.SWIFT_UINT32 - ); - """, - """ - @Unsigned - public static int returnUnsignedInt() { - return swiftjava_SwiftModule_returnUnsignedInt.call(); - } - """, - ] + expectedChunks: expectedChunks ) } - @Test("Import: return UInt64 (wrap)") - func return_unsignedLongWrap() throws { + @Test( + "Import: return UInt64 (wrap)", + arguments: [ + ( + JExtractGenerationMode.ffm, + /* expected chunks */ + [ + """ + /** + * {@snippet lang=c : + * uint64_t swiftjava_SwiftModule_returnUnsignedLong(void) + * } + */ + private static class swiftjava_SwiftModule_returnUnsignedLong { + private static final FunctionDescriptor DESC = FunctionDescriptor.of( + /* -> */SwiftValueLayout.SWIFT_UINT64 + ); + """, + """ + public static com.google.common.primitives.UnsignedLong returnUnsignedLong() { + return com.google.common.primitives.UnsignedLong.fromLongBits(swiftjava_SwiftModule_returnUnsignedLong.call()); + } + """, + ] + ), + // JNI mode does not support "wrap" mode + ]) + func return_unsignedLongWrap(mode: JExtractGenerationMode, expectedChunks: [String]) throws { var config = Configuration() config.unsignedNumbersMode = .wrapGuava try assertOutput( input: "public func returnUnsignedLong() -> UInt64", config: config, - .ffm, .java, + mode, .java, detectChunkByInitialLines: 2, - expectedChunks: [ - """ - /** - * {@snippet lang=c : - * uint64_t swiftjava_SwiftModule_returnUnsignedLong(void) - * } - */ - private static class swiftjava_SwiftModule_returnUnsignedLong { - private static final FunctionDescriptor DESC = FunctionDescriptor.of( - /* -> */SwiftValueLayout.SWIFT_UINT64 - ); - """, - """ - public static com.google.common.primitives.UnsignedLong returnUnsignedLong() { - return com.google.common.primitives.UnsignedLong.fromLongBits(swiftjava_SwiftModule_returnUnsignedLong.call()); - } - """, - ] + expectedChunks: expectedChunks ) } - @Test("Import: return UInt64 (annotate)") - func return_unsignedLong_annotate() throws { + @Test( + "Import: return UInt64 (annotate)", + arguments: [ + ( + JExtractGenerationMode.ffm, + /* expected chunks */ + [ + """ + /** + * {@snippet lang=c : + * uint64_t swiftjava_SwiftModule_returnUnsignedLong(void) + * } + */ + private static class swiftjava_SwiftModule_returnUnsignedLong { + private static final FunctionDescriptor DESC = FunctionDescriptor.of( + /* -> */SwiftValueLayout.SWIFT_UINT64 + ); + """, + """ + @Unsigned + public static long returnUnsignedLong() { + return swiftjava_SwiftModule_returnUnsignedLong.call(); + } + """, + ] + ), + ( + JExtractGenerationMode.jni, + /* expected chunks */ + [ + """ + @Unsigned + public static long returnUnsignedLong() { + return SwiftModule.$returnUnsignedLong(); + } + private static native long $returnUnsignedLong(); + """, + ] + ) + ]) + func return_unsignedLong_annotate(mode: JExtractGenerationMode, expectedChunks: [String]) throws { var config = Configuration() config.unsignedNumbersMode = .annotate try assertOutput( input: "public func returnUnsignedLong() -> UInt64", config: config, - .ffm, .java, + mode, .java, detectChunkByInitialLines: 2, - expectedChunks: [ - """ - /** - * {@snippet lang=c : - * uint64_t swiftjava_SwiftModule_returnUnsignedLong(void) - * } - */ - private static class swiftjava_SwiftModule_returnUnsignedLong { - private static final FunctionDescriptor DESC = FunctionDescriptor.of( - /* -> */SwiftValueLayout.SWIFT_UINT64 - ); - """, - """ - @Unsigned - public static long returnUnsignedLong() { - return swiftjava_SwiftModule_returnUnsignedLong.call(); - } - """, - ] + expectedChunks: expectedChunks ) } - @Test("Import: take UInt64 (annotate)") - func take_unsignedLong_annotate() throws { + @Test( + "Import: take UInt64 (annotate)", + arguments: [ + ( + JExtractGenerationMode.ffm, + /* expected chunks */ + [ + """ + /** + * {@snippet lang=c : + * void swiftjava_SwiftModule_takeUnsignedLong_arg(uint64_t arg) + * } + */ + private static class swiftjava_SwiftModule_takeUnsignedLong_arg { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* arg: */SwiftValueLayout.SWIFT_UINT64 + ); + """, + """ + public static void takeUnsignedLong(@Unsigned long arg) { + swiftjava_SwiftModule_takeUnsignedLong_arg.call(arg); + } + """, + ] + ), + ( + JExtractGenerationMode.jni, + /* expected chunks */ + [ + """ + public static void takeUnsignedLong(@Unsigned long arg) { + SwiftModule.$takeUnsignedLong(arg); + } + private static native void $takeUnsignedLong(long arg); + """, + ] + ) + ]) + func take_unsignedLong_annotate(mode: JExtractGenerationMode, expectedChunks: [String]) throws { var config = Configuration() config.unsignedNumbersMode = .annotate try assertOutput( input: "public func takeUnsignedLong(arg: UInt64)", config: config, - .ffm, .java, + mode, .java, detectChunkByInitialLines: 2, - expectedChunks: [ - """ - /** - * {@snippet lang=c : - * void swiftjava_SwiftModule_takeUnsignedLong_arg(uint64_t arg) - * } - */ - private static class swiftjava_SwiftModule_takeUnsignedLong_arg { - private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( - /* arg: */SwiftValueLayout.SWIFT_UINT64 - ); - """, - """ - public static void takeUnsignedLong(@Unsigned long arg) { - swiftjava_SwiftModule_takeUnsignedLong_arg.call(arg); - } - """, - ] + expectedChunks: expectedChunks ) } - @Test("Import: take UInt64 return UInt32 (annotate)") - func echo_unsignedLong_annotate() throws { + @Test( + "Import: take UInt64 return UInt32 (annotate)", + arguments: [ + ( + JExtractGenerationMode.ffm, + /* expected chunks */ + [ + """ + /** + * {@snippet lang=c : + * uint32_t swiftjava_SwiftModule_unsignedLong_first_second(uint64_t first, uint32_t second) + * } + */ + private static class swiftjava_SwiftModule_unsignedLong_first_second { + private static final FunctionDescriptor DESC = FunctionDescriptor.of( + /* -> */SwiftValueLayout.SWIFT_UINT32 + /* first: */SwiftValueLayout.SWIFT_UINT64 + /* second: */SwiftValueLayout.SWIFT_UINT32 + ); + """, + """ + @Unsigned + public static int unsignedLong(@Unsigned long first, @Unsigned int second) { + return swiftjava_SwiftModule_unsignedLong_first_second.call(first, second); + } + """, + ] + ), + ( + JExtractGenerationMode.jni, + /* expected chunks */ + [ + """ + @Unsigned + public static int unsignedLong(@Unsigned long first, @Unsigned int second) { + return SwiftModule.$unsignedLong(first, second); + } + private static native int $unsignedLong(long first, int second); + """, + ] + ), + ]) + func echo_unsignedLong_annotate(mode: JExtractGenerationMode, expectedChunks: [String]) throws { var config = Configuration() config.unsignedNumbersMode = .annotate try assertOutput( input: "public func unsignedLong(first: UInt64, second: UInt32) -> UInt32", config: config, - .ffm, .java, + mode, .java, detectChunkByInitialLines: 2, - expectedChunks: [ - """ - /** - * {@snippet lang=c : - * uint32_t swiftjava_SwiftModule_unsignedLong_first_second(uint64_t first, uint32_t second) - * } - */ - private static class swiftjava_SwiftModule_unsignedLong_first_second { - private static final FunctionDescriptor DESC = FunctionDescriptor.of( - /* -> */SwiftValueLayout.SWIFT_UINT32 - /* first: */SwiftValueLayout.SWIFT_UINT64 - /* second: */SwiftValueLayout.SWIFT_UINT32 - ); - """, - """ - @Unsigned - public static int unsignedLong(@Unsigned long first, @Unsigned int second) { - return swiftjava_SwiftModule_unsignedLong_first_second.call(first, second); - } - """, - ] + expectedChunks: expectedChunks ) } } From 670f640522e5094a3f7b94e02859b25253dc6c8d Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 3 Dec 2025 16:53:24 +0900 Subject: [PATCH 139/141] Move some tests ouf of sample app into tests (#476) --- .../MySwiftLibrary/MySwiftLibrary.swift | 18 ++++++ .../com/example/swift/HelloJava2Swift.java | 4 +- .../com/example/swift/WithBufferTest.java | 60 +++++++++++++++++++ 3 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index cdd61c122..9929f888a 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -63,6 +63,24 @@ public func withBuffer(body: (UnsafeRawBufferPointer) -> Void) { body(globalBuffer) } +public func getArray() -> [UInt8] { + return [1, 2, 3] +} + +public func sumAllByteArrayElements(actuallyAnArray: UnsafeRawPointer, count: Int) -> Int { + let bufferPointer = UnsafeRawBufferPointer(start: actuallyAnArray, count: count) + let array = Array(bufferPointer) + return Int(array.reduce(0, { partialResult, element in partialResult + element })) +} + +public func sumAllByteArrayElements(array: [UInt8]) -> Int { + return Int(array.reduce(0, { partialResult, element in partialResult + element })) +} + +public func withArray(body: ([UInt8]) -> Void) { + body([1, 2, 3]) +} + public func globalReceiveSomeDataProtocol(data: some DataProtocol) -> Int { p(Array(data).description) return data.count diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftJavaExtractFFMSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java index cecb12311..a13125478 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java @@ -52,9 +52,7 @@ static void examples() { CallTraces.trace("getGlobalBuffer().byteSize()=" + MySwiftLibrary.getGlobalBuffer().byteSize()); - MySwiftLibrary.withBuffer((buf) -> { - CallTraces.trace("withBuffer{$0.byteSize()}=" + buf.byteSize()); - }); + // Example of using an arena; MyClass.deinit is run at end of scope try (var arena = AllocatingSwiftArena.ofConfined()) { MySwiftClass obj = MySwiftClass.init(2222, 7777, arena); diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java new file mode 100644 index 000000000..54206423c --- /dev/null +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java @@ -0,0 +1,60 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.*; +import org.swift.swiftkit.ffm.*; + +import static org.junit.jupiter.api.Assertions.*; + +import java.lang.foreign.ValueLayout; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.IntStream; + +public class WithBufferTest { + @Test + void test_withBuffer() { + AtomicLong bufferSize = new AtomicLong(); + MySwiftLibrary.withBuffer((buf) -> { + CallTraces.trace("withBuffer{$0.byteSize()}=" + buf.byteSize()); + bufferSize.set(buf.byteSize()); + }); + + assertEquals(124, bufferSize.get()); + } + + @Test + void test_sumAllByteArrayElements_throughMemorySegment() { + byte[] bytes = new byte[124]; + Arrays.fill(bytes, (byte) 1); + + try (var arena = AllocatingSwiftArena.ofConfined()) { + // NOTE: We cannot use MemorySegment.ofArray because that creates a HEAP backed segment and therefore cannot pass into native: + // java.lang.IllegalArgumentException: Heap segment not allowed: MemorySegment{ kind: heap, heapBase: [B@5b6ec132, address: 0x0, byteSize: 124 } + // MemorySegment bytesSegment = MemorySegment.ofArray(bytes); // NO COPY (!) + // MySwiftLibrary.sumAllByteArrayElements(bytesSegment, bytes.length); + + var bytesCopy = arena.allocateFrom(ValueLayout.JAVA_BYTE, bytes); + var swiftSideSum = MySwiftLibrary.sumAllByteArrayElements(bytesCopy, bytes.length); + + System.out.println("swiftSideSum = " + swiftSideSum); + + int javaSideSum = IntStream.range(0, bytes.length).map(i -> bytes[i]).sum(); + assertEquals(javaSideSum, swiftSideSum); + } + } +} From 7519b4c2915566c4c80a3436d6b71a889fd56f10 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Wed, 3 Dec 2025 10:32:10 +0100 Subject: [PATCH 140/141] extract `CustomStringConvertible` as `toString()` and fix extensions (#473) * fix extensions * add runtime test * update docs * add `toString` and `toDebugString` * fix merge --- .../Sources/MySwiftLibrary/MySwiftClass.swift | 12 +++ .../com/example/swift/MySwiftClassTest.java | 16 ++++ ...t2JavaGenerator+JavaBindingsPrinting.swift | 26 +++++ ...ift2JavaGenerator+SwiftThunkPrinting.swift | 44 ++++++++- .../JExtractSwiftLib/Swift2JavaVisitor.swift | 9 +- .../SwiftTypes/SwiftTypeLookupContext.swift | 11 +++ .../Documentation.docc/SupportedFeatures.md | 2 +- .../JNI/JNIExtensionTests.swift | 65 +++++++++++++ .../JNI/JNIToStringTests.swift | 96 +++++++++++++++++++ 9 files changed, 276 insertions(+), 5 deletions(-) create mode 100644 Tests/JExtractSwiftTests/JNI/JNIExtensionTests.swift create mode 100644 Tests/JExtractSwiftTests/JNI/JNIToStringTests.swift diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index e6aed287b..72f2fa357 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -99,3 +99,15 @@ public class MySwiftClass { return self.x + other.longValue() } } + +extension MySwiftClass: CustomStringConvertible { + public var description: String { + "MySwiftClass(x: \(x), y: \(y))" + } +} + +extension MySwiftClass: CustomDebugStringConvertible { + public var debugDescription: String { + "debug: MySwiftClass(x: \(x), y: \(y))" + } +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java index 860f1641c..a1f8a4529 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -171,4 +171,20 @@ void getAsyncVariable() throws Exception { assertEquals(42, c1.getGetAsync().get()); } } + + @Test + void toStringTest() { + try (var arena = SwiftArena.ofConfined()) { + MySwiftClass c1 = MySwiftClass.init(20, 10, arena); + assertEquals("MySwiftClass(x: 20, y: 10)", c1.toString()); + } + } + + @Test + void toDebugStringTest() { + try (var arena = SwiftArena.ofConfined()) { + MySwiftClass c1 = MySwiftClass.init(20, 10, arena); + assertEquals("debug: MySwiftClass(x: 20, y: 10)", c1.toDebugString()); + } + } } \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index c492d439d..78cf7d418 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -260,12 +260,38 @@ extension JNISwift2JavaGenerator { printer.println() } + printToStringMethods(&printer, decl) + printer.println() + printTypeMetadataAddressFunction(&printer, decl) printer.println() printDestroyFunction(&printer, decl) } } + + private func printToStringMethods(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + printer.printBraceBlock("public String toString()") { printer in + printer.print( + """ + return $toString(this.$memoryAddress()); + """ + ) + } + printer.print("private static native java.lang.String $toString(long selfPointer);") + + printer.println() + + printer.printBraceBlock("public String toDebugString()") { printer in + printer.print( + """ + return $toDebugString(this.$memoryAddress()); + """ + ) + } + printer.print("private static native java.lang.String $toDebugString(long selfPointer);") + } + private func printHeader(_ printer: inout CodePrinter) { printer.print( """ diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 85fdc6404..13955430a 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -253,7 +253,7 @@ extension JNISwift2JavaGenerator { printer.println() } - + printToStringMethods(&printer, type) printTypeMetadataAddressThunk(&printer, type) printer.println() printDestroyFunctionThunk(&printer, type) @@ -267,6 +267,48 @@ extension JNISwift2JavaGenerator { try printSwiftInterfaceWrapper(&printer, protocolWrapper) } + private func printToStringMethods(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + let selfPointerParam = JavaParameter(name: "selfPointer", type: .long) + let parentName = type.qualifiedName + + printCDecl( + &printer, + javaMethodName: "$toString", + parentName: type.swiftNominal.qualifiedName, + parameters: [ + selfPointerParam + ], + resultType: .javaLangString + ) { printer in + let selfVar = self.printSelfJLongToUnsafeMutablePointer(&printer, swiftParentName: parentName, selfPointerParam) + + printer.print( + """ + return String(describing: \(selfVar).pointee).getJNIValue(in: environment) + """ + ) + } + + printer.println() + + printCDecl( + &printer, + javaMethodName: "$toDebugString", + parentName: type.swiftNominal.qualifiedName, + parameters: [ + selfPointerParam + ], + resultType: .javaLangString + ) { printer in + let selfVar = self.printSelfJLongToUnsafeMutablePointer(&printer, swiftParentName: parentName, selfPointerParam) + + printer.print( + """ + return String(reflecting: \(selfVar).pointee).getJNIValue(in: environment) + """ + ) + } + } private func printEnumDiscriminator(_ printer: inout CodePrinter, _ type: ImportedNominalType) { let selfPointerParam = JavaParameter(name: "selfPointer", type: .long) diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index ab1ce32fe..cf99d11b6 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift @@ -109,6 +109,12 @@ final class Swift2JavaVisitor { guard let importedNominalType = translator.importedNominalType(node.extendedType) else { return } + + // Add any conforming protocols in the extension + importedNominalType.inheritedTypes += node.inheritanceClause?.inheritedTypes.compactMap { + try? SwiftType($0.type, lookupContext: translator.lookupContext) + } ?? [] + for memberItem in node.memberBlock.members { self.visit(decl: memberItem.decl, in: importedNominalType, sourceFilePath: sourceFilePath) } @@ -374,9 +380,6 @@ final class Swift2JavaVisitor { self.visit(decl: decl, in: imported, sourceFilePath: imported.sourceFilePath) } - // FIXME: why is this un-used - imported.variables.first?.signatureString - if !imported.initializers.contains(where: { $0.functionSignature.parameters.count == 1 && $0.functionSignature.parameters.first?.parameterName == "rawValue" diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift index 33759a2cf..4489b4f65 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift @@ -105,6 +105,17 @@ class SwiftTypeLookupContext { typeDecl = try nominalTypeDeclaration(for: node, sourceFilePath: sourceFilePath) case .protocolDecl(let node): typeDecl = try nominalTypeDeclaration(for: node, sourceFilePath: sourceFilePath) + case .extensionDecl(let node): + // For extensions, we have to perform a unqualified lookup, + // as the extentedType is just the identifier of the type. + + guard case .identifierType(let id) = Syntax(node.extendedType).as(SyntaxEnum.self), + let lookupResult = try unqualifiedLookup(name: Identifier(id.name)!, from: node) + else { + throw TypeLookupError.notType(Syntax(node)) + } + + typeDecl = lookupResult case .typeAliasDecl: fatalError("typealias not implemented") case .associatedTypeDecl: diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 849b7f01f..7161a26c9 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -92,7 +92,7 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Non-escaping closures with primitive arguments/results: `func callMe(maybe: (Int) -> (Double))` | ✅ | ✅ | | Non-escaping closures with object arguments/results: `func callMe(maybe: (JavaObj) -> (JavaObj))` | ❌ | ❌ | | `@escaping` closures: `func callMe(_: @escaping () -> ())` | ❌ | ❌ | -| Swift type extensions: `extension String { func uppercased() }` | 🟡 | 🟡 | +| Swift type extensions: `extension String { func uppercased() }` | ✅ | ✅ | | Swift macros (maybe) | ❌ | ❌ | | Result builders | ❌ | ❌ | | Automatic Reference Counting of class types / lifetime safety | ✅ | ✅ | diff --git a/Tests/JExtractSwiftTests/JNI/JNIExtensionTests.swift b/Tests/JExtractSwiftTests/JNI/JNIExtensionTests.swift new file mode 100644 index 000000000..e6cb10605 --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIExtensionTests.swift @@ -0,0 +1,65 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import Testing + +@Suite +struct JNIExtensionTests { + let interfaceFile = + """ + extension MyStruct { + public var variableInExtension: String { get } + public func methodInExtension() {} + } + + public protocol MyProtocol {} + public struct MyStruct {} + extension MyStruct: MyProtocol {} + """ + + @Test("Import extensions: Java methods") + func import_javaMethods() throws { + try assertOutput( + input: interfaceFile, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public final class MyStruct implements JNISwiftInstance, MyProtocol { + ... + public void methodInExtension() { + ... + } + """ + ]) + } + + @Test("Import extensions: Computed variables") + func import_computedVariables() throws { + try assertOutput( + input: interfaceFile, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public final class MyStruct implements JNISwiftInstance, MyProtocol { + ... + public java.lang.String getVariableInExtension() { + ... + } + """ + ]) + } +} diff --git a/Tests/JExtractSwiftTests/JNI/JNIToStringTests.swift b/Tests/JExtractSwiftTests/JNI/JNIToStringTests.swift new file mode 100644 index 000000000..1059002b6 --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIToStringTests.swift @@ -0,0 +1,96 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import Testing +import SwiftJavaConfigurationShared + +@Suite +struct JNIToStringTests { + let source = + """ + public struct MyType {} + """ + + @Test("JNI toString (Java)") + func toString_java() throws { + try assertOutput( + input: source, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public String toString() { + return $toString(this.$memoryAddress()); + } + """, + """ + private static native java.lang.String $toString(long selfPointer); + """ + ] + ) + } + + @Test("JNI toString (Swift)") + func toString_swift() throws { + try assertOutput( + input: source, + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyType__00024toString__J") + func Java_com_example_swift_MyType__00024toString__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jstring? { + ... + return String(describing: self$.pointee).getJNIValue(in: environment) + } + """, + ] + ) + } + + @Test("JNI toDebugString (Java)") + func toDebugString_java() throws { + try assertOutput( + input: source, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public String toDebugString() { + return $toDebugString(this.$memoryAddress()); + } + """, + ] + ) + } + + @Test("JNI toDebugString (Swift)") + func toDebugString_swift() throws { + try assertOutput( + input: source, + .jni, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyType__00024toDebugString__J") + func Java_com_example_swift_MyType__00024toDebugString__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jstring? { + ... + return String(reflecting: self$.pointee).getJNIValue(in: environment) + } + """, + ] + ) + } +} From c6a56cd61bf11c88b003b8d34cb5ffb98db7a7eb Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Wed, 3 Dec 2025 16:13:25 +0100 Subject: [PATCH 141/141] jextract: fix protocols that return java classes. (#479) * fix protocols * remove comments * comments * cleanup --- Package.swift | 1 + .../MySwiftLibrary/ConcreteProtocolAB.swift | 4 ++ .../Sources/MySwiftLibrary/ProtocolA.swift | 1 + .../example/swift/ProtocolCallbacksTest.java | 4 +- .../java/com/example/swift/ProtocolTest.java | 10 ++++- ...t2JavaGenerator+JavaBindingsPrinting.swift | 39 ++++++++++++------- .../Configuration.swift | 5 ++- .../JNI/JNIProtocolTests.swift | 34 ++++++++++++++-- .../MemoryManagementModeTests.swift | 2 +- 9 files changed, 78 insertions(+), 22 deletions(-) diff --git a/Package.swift b/Package.swift index d877ae696..2c56a2870 100644 --- a/Package.swift +++ b/Package.swift @@ -460,6 +460,7 @@ let package = Package( .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), .product(name: "ArgumentParser", package: "swift-argument-parser"), + .product(name: "OrderedCollections", package: "swift-collections"), "JavaTypes", "SwiftJavaShared", "SwiftJavaConfigurationShared", diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ConcreteProtocolAB.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ConcreteProtocolAB.swift index ed55d0398..d83fbb28d 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ConcreteProtocolAB.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ConcreteProtocolAB.swift @@ -21,6 +21,10 @@ public class ConcreteProtocolAB: ProtocolA, ProtocolB { return "ConcreteProtocolAB" } + public func makeClass() -> MySwiftClass { + return MySwiftClass(x: 10, y: 50) + } + public init(constantA: Int64, constantB: Int64) { self.constantA = constantA self.constantB = constantB diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolA.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolA.swift index d5281b81e..6e19596f9 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolA.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ProtocolA.swift @@ -17,6 +17,7 @@ public protocol ProtocolA { var mutable: Int64 { get set } func name() -> String + func makeClass() -> MySwiftClass } public func takeProtocol(_ proto1: any ProtocolA, _ proto2: some ProtocolA) -> Int64 { diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolCallbacksTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolCallbacksTest.java index e79fd4a3b..b4ebe8532 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolCallbacksTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolCallbacksTest.java @@ -74,7 +74,7 @@ public String withString(String input) { public void withVoid() {} @Override - public MySwiftClass withObject(MySwiftClass input) { + public MySwiftClass withObject(MySwiftClass input, SwiftArena swiftArena$) { return input; } @@ -84,7 +84,7 @@ public OptionalLong withOptionalInt64(OptionalLong input) { } @Override - public Optional withOptionalObject(Optional input) { + public Optional withOptionalObject(Optional input, SwiftArena swiftArena$) { return input; } } diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolTest.java index f5d1ffcf7..b8159b8ad 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ProtocolTest.java @@ -73,6 +73,14 @@ void protocolMethod() { } } + @Test + void protocolClassMethod() { + try (var arena = SwiftArena.ofConfined()) { + ProtocolA proto1 = ConcreteProtocolAB.init(10, 5, arena); + assertEquals(10, proto1.makeClass().getX()); + } + } + static class JavaStorage implements Storage { StorageItem item; @@ -81,7 +89,7 @@ static class JavaStorage implements Storage { } @Override - public StorageItem load() { + public StorageItem load(SwiftArena swiftArena$) { return item; } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 78cf7d418..bfa2ff12d 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -144,17 +144,17 @@ extension JNISwift2JavaGenerator { printer.printBraceBlock("public interface \(decl.swiftNominal.name)\(extendsString)") { printer in for initializer in decl.initializers { - printFunctionDowncallMethods(&printer, initializer, skipMethodBody: true, skipArenas: true) + printFunctionDowncallMethods(&printer, initializer, skipMethodBody: true) printer.println() } for method in decl.methods { - printFunctionDowncallMethods(&printer, method, skipMethodBody: true, skipArenas: true) + printFunctionDowncallMethods(&printer, method, skipMethodBody: true) printer.println() } for variable in decl.variables { - printFunctionDowncallMethods(&printer, variable, skipMethodBody: true, skipArenas: true) + printFunctionDowncallMethods(&printer, variable, skipMethodBody: true) printer.println() } } @@ -420,7 +420,7 @@ extension JNISwift2JavaGenerator { printer.print("record _NativeParameters(\(nativeParameters.joined(separator: ", "))) {}") } - self.printJavaBindingWrapperMethod(&printer, translatedCase.getAsCaseFunction, skipMethodBody: false, skipArenas: false) + self.printJavaBindingWrapperMethod(&printer, translatedCase.getAsCaseFunction, skipMethodBody: false) printer.println() } } @@ -428,8 +428,7 @@ extension JNISwift2JavaGenerator { private func printFunctionDowncallMethods( _ printer: inout CodePrinter, _ decl: ImportedFunc, - skipMethodBody: Bool = false, - skipArenas: Bool = false + skipMethodBody: Bool = false ) { guard translatedDecl(for: decl) != nil else { // Failed to translate. Skip. @@ -440,7 +439,7 @@ extension JNISwift2JavaGenerator { printJavaBindingWrapperHelperClass(&printer, decl) - printJavaBindingWrapperMethod(&printer, decl, skipMethodBody: skipMethodBody, skipArenas: skipArenas) + printJavaBindingWrapperMethod(&printer, decl, skipMethodBody: skipMethodBody) } /// Print the helper type container for a user-facing Java API. @@ -486,21 +485,19 @@ extension JNISwift2JavaGenerator { private func printJavaBindingWrapperMethod( _ printer: inout CodePrinter, _ decl: ImportedFunc, - skipMethodBody: Bool, - skipArenas: Bool + skipMethodBody: Bool ) { guard let translatedDecl = translatedDecl(for: decl) else { fatalError("Decl was not translated, \(decl)") } - printJavaBindingWrapperMethod(&printer, translatedDecl, importedFunc: decl, skipMethodBody: skipMethodBody, skipArenas: skipArenas) + printJavaBindingWrapperMethod(&printer, translatedDecl, importedFunc: decl, skipMethodBody: skipMethodBody) } private func printJavaBindingWrapperMethod( _ printer: inout CodePrinter, _ translatedDecl: TranslatedFunctionDecl, importedFunc: ImportedFunc? = nil, - skipMethodBody: Bool, - skipArenas: Bool + skipMethodBody: Bool ) { var modifiers = ["public"] if translatedDecl.isStatic { @@ -531,14 +528,28 @@ extension JNISwift2JavaGenerator { let parametersStr = parameters.joined(separator: ", ") // Print default global arena variation + // If we have enabled javaCallbacks we must emit default + // arena methods for protocols, as this is what + // Swift will call into, when you call a interface from Swift. + let shouldGenerateGlobalArenaVariation: Bool + let isParentProtocol = importedFunc?.parentType?.asNominalType?.isProtocol ?? false + if config.effectiveMemoryManagementMode.requiresGlobalArena && translatedSignature.requiresSwiftArena { + shouldGenerateGlobalArenaVariation = true + } else if isParentProtocol, translatedSignature.requiresSwiftArena, config.effectiveEnableJavaCallbacks { + shouldGenerateGlobalArenaVariation = true + } else { + shouldGenerateGlobalArenaVariation = false + } + + if shouldGenerateGlobalArenaVariation { if let importedFunc { printDeclDocumentation(&printer, importedFunc) } var modifiers = modifiers // If we are a protocol, we emit this as default method - if importedFunc?.parentType?.asNominalTypeDeclaration?.kind == .protocol { + if isParentProtocol { modifiers.insert("default", at: 1) } @@ -555,7 +566,7 @@ extension JNISwift2JavaGenerator { printer.println() } - if translatedSignature.requiresSwiftArena, !skipArenas { + if translatedSignature.requiresSwiftArena { parameters.append("SwiftArena swiftArena$") } if let importedFunc { diff --git a/Sources/SwiftJavaConfigurationShared/Configuration.swift b/Sources/SwiftJavaConfigurationShared/Configuration.swift index 37d1e1e79..e0c40f1c5 100644 --- a/Sources/SwiftJavaConfigurationShared/Configuration.swift +++ b/Sources/SwiftJavaConfigurationShared/Configuration.swift @@ -65,7 +65,10 @@ public struct Configuration: Codable { asyncFuncMode ?? .default } - public var enableJavaCallbacks: Bool? // FIXME: default it to false, but that plays not nice with Codable + public var enableJavaCallbacks: Bool? + public var effectiveEnableJavaCallbacks: Bool { + enableJavaCallbacks ?? false + } public var generatedJavaSourcesListFileOutput: String? diff --git a/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift b/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift index 231c4d25d..3b47fd100 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift @@ -32,7 +32,9 @@ struct JNIProtocolTests { public protocol B {} - public class SomeClass: SomeProtocol {} + public class SomeClass: SomeProtocol { + public func makeClass() -> SomeClass {} + } public func takeProtocol(x: some SomeProtocol, y: any SomeProtocol) public func takeGeneric(s: S) @@ -61,7 +63,29 @@ struct JNIProtocolTests { ... public void method(); ... - public SomeClass withObject(SomeClass c); + public SomeClass withObject(SomeClass c, SwiftArena swiftArena$); + ... + } + """ + ]) + } + + @Test + func emitsDefault() throws { + try assertOutput( + input: source, + config: config, + .jni, .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public interface SomeProtocol { + ... + public default SomeClass withObject(SomeClass c) { + return withObject(c, SwiftMemoryManagement.GLOBAL_SWIFT_JAVA_ARENA); + } + ... + public SomeClass withObject(SomeClass c, SwiftArena swiftArena$); ... } """ @@ -78,7 +102,11 @@ struct JNIProtocolTests { expectedChunks: [ """ public final class SomeClass implements JNISwiftInstance, SomeProtocol { - """ + ... + public SomeClass makeClass(SwiftArena swiftArena$) { + ... + } + """, ]) } diff --git a/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift b/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift index 7e78434d1..2228aad88 100644 --- a/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift +++ b/Tests/JExtractSwiftTests/MemoryManagementModeTests.swift @@ -99,7 +99,7 @@ struct MemoryManagementModeTests { } """, """ - public MyClass f(); + public MyClass f(SwiftArena swiftArena$); """ ] )