Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,8 @@ let package = Package(
.process("Resources")
],
swiftSettings: [
.swiftLanguageMode(.v5)
.swiftLanguageMode(.v5),
.enableUpcomingFeature("BareSlashRegexLiterals"),
],
plugins: [
.plugin(name: "_StaticBuildConfigPlugin")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ extension FFMSwift2JavaGenerator {
.sorted(by: { $0.qualifiedName < $1.qualifiedName })

let inputFileName = "\(group.key)".split(separator: "/").last ?? "__Unknown.swift"
let filename = "\(inputFileName)".replacing(".swift", with: "+SwiftJava.swift")
let filename = "\(inputFileName)".replacing(/\.swift(interface)?/, with: "+SwiftJava.swift")

// Print file header before all type thunks
printer.print(
Expand Down
8 changes: 5 additions & 3 deletions Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,12 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator {
guard let fileName = input.path.split(separator: PATH_SEPARATOR).last else {
return nil
}
guard fileName.hasSuffix(".swift") else {
return nil
if fileName.hasSuffix(".swift") {
return String(fileName.replacing(".swift", with: "+SwiftJava.swift"))
} else if fileName.hasSuffix(".swiftinterface") {
return String(fileName.replacing(".swiftinterface", with: "+SwiftJava.swift"))
}
return String(fileName.replacing(".swift", with: "+SwiftJava.swift"))
return nil
}
)
// Also include filtered-out files so SwiftPM gets the empty outputs it expects
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ extension JNISwift2JavaGenerator {
.sorted(by: { $0.qualifiedName < $1.qualifiedName })

let inputFileName = "\(group.key)".split(separator: "/").last ?? "__Unknown.swift"
let filename = "\(inputFileName)".replacing(".swift", with: "+SwiftJava.swift")
let filename = "\(inputFileName)".replacing(/\.swift(interface)?/, with: "+SwiftJava.swift")

for ty in importedTypesForThisFile {
logger.info("Printing Swift thunks for type: \(ty.effectiveJavaName.bold)")
Expand Down
8 changes: 5 additions & 3 deletions Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,12 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator {
guard let fileName = input.path.split(separator: PATH_SEPARATOR).last else {
return nil
}
guard fileName.hasSuffix(".swift") else {
return nil
if fileName.hasSuffix(".swift") {
return String(fileName.replacing(".swift", with: "+SwiftJava.swift"))
} else if fileName.hasSuffix(".swiftinterface") {
return String(fileName.replacing(".swiftinterface", with: "+SwiftJava.swift"))
}
return String(fileName.replacing(".swift", with: "+SwiftJava.swift"))
return nil
}
)
// Also include filtered-out files so SwiftPM gets the empty outputs it expects
Expand Down
9 changes: 8 additions & 1 deletion Sources/JExtractSwiftLib/Swift2Java.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,14 @@ public struct SwiftToJava {
let inputPaths = inputSwift.split(separator: ",").map { URL(string: String($0))! }
log.info("Input paths = \(inputPaths)")

let allFiles = collectAllFiles(suffix: ".swift", in: inputPaths, log: translator.log)
var allFiles: OrderedSet<URL> = []
for path in inputPaths {
if path.isDirectory {
allFiles.formUnion(collectAllFiles(suffix: ".swift", in: [path], log: translator.log))
} else {
allFiles.append(path)
}
}

let hasFilters =
!(config.swiftFilterInclude ?? []).isEmpty || !(config.swiftFilterExclude ?? []).isEmpty
Expand Down
18 changes: 1 addition & 17 deletions Tests/JExtractSwiftTests/IfConfigTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ struct IfConfigTests {
@Test
func overrideWithStaticBuildConfigurationFile() throws {
try withTemporaryFile(
suffix: "json",
extension: "json",
contents: """
{
"attributes": [],
Expand Down Expand Up @@ -159,19 +159,3 @@ struct IfConfigTests {
)
}
}

private func withTemporaryFile(
suffix: String,
contents: String = "",
in tempDirectory: URL = FileManager.default.temporaryDirectory,
_ perform: (URL) throws -> Void
) throws {
let tempFileName = "tmp_\(UUID().uuidString).\(suffix)"
let tempFileURL = tempDirectory.appendingPathComponent(tempFileName)

try contents.write(to: tempFileURL, atomically: true, encoding: .utf8)
defer {
try? FileManager.default.removeItem(at: tempFileURL)
}
try perform(tempFileURL)
}
107 changes: 107 additions & 0 deletions Tests/JExtractSwiftTests/InputSwiftTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2026 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 JExtractSwiftLib
import SwiftJavaConfigurationShared
import Testing

@Suite
struct InputSwiftTests {
let fileManager = FileManager.default

@Test(arguments: [JExtractGenerationMode.jni, .ffm])
func loadSwiftinterface(mode: JExtractGenerationMode) throws {
let tempDirectory: URL = fileManager.temporaryDirectory
.appending(path: "loadSwiftinterface-\(UUID())")
let outSwiftURL = tempDirectory.appending(path: "swift")
let outJavaURL = tempDirectory.appending(path: "java")

try withTemporaryFile(
fileName: "MyDependent",
extension: "swiftinterface",
contents: """
public struct Foo {}
""",
in: tempDirectory
) { swiftInterfaceURL in
var config = Configuration()
config.mode = mode
config.javaPackage = "com.example"
config.inputSwiftDirectory = swiftInterfaceURL.absoluteURL.path()
config.swiftModule = "MySwift"
config.outputSwiftDirectory = outSwiftURL.absoluteURL.path()
config.outputJavaDirectory = outJavaURL.absoluteURL.path()

try SwiftToJava(config: config, dependentConfigs: [])
.run()
}

let javaPackageRoot =
outJavaURL
.appending(path: "com")
.appending(path: "example")
let expectedSources: [URL] = [
outSwiftURL.appending(path: "MySwiftModule+SwiftJava.swift"),
outSwiftURL.appending(path: "MyDependent+SwiftJava.swift"),
javaPackageRoot.appending(path: "Foo.java"),
javaPackageRoot.appending(path: "MySwift.java"),
]

for expectedSource in expectedSources {
#expect(fileManager.fileExists(atPath: expectedSource.path()))
}
}

@Test(arguments: [JExtractGenerationMode.jni, .ffm])
func loadEmptySwiftinterface(mode: JExtractGenerationMode) throws {
let tempDirectory: URL = fileManager.temporaryDirectory
.appending(path: "loadEmptySwiftinterface-\(UUID())")
let outSwiftURL = tempDirectory.appending(path: "swift")
let outJavaURL = tempDirectory.appending(path: "java")

try withTemporaryFile(
fileName: "MyDependent",
extension: "swiftinterface",
contents: "",
in: tempDirectory
) { swiftInterfaceURL in
var config = Configuration()
config.mode = mode
config.writeEmptyFiles = true
config.javaPackage = "com.example"
config.inputSwiftDirectory = swiftInterfaceURL.absoluteURL.path()
config.swiftModule = "MySwift"
config.outputSwiftDirectory = outSwiftURL.absoluteURL.path()
config.outputJavaDirectory = outJavaURL.absoluteURL.path()

try SwiftToJava(config: config, dependentConfigs: [])
.run()
}

let javaPackageRoot =
outJavaURL
.appending(path: "com")
.appending(path: "example")
let expectedSources: [URL] = [
outSwiftURL.appending(path: "MySwiftModule+SwiftJava.swift"),
outSwiftURL.appending(path: "MyDependent+SwiftJava.swift"),
javaPackageRoot.appending(path: "MySwift.java"),
]

for expectedSource in expectedSources {
#expect(fileManager.fileExists(atPath: expectedSource.path()))
}
}
}
33 changes: 33 additions & 0 deletions Tests/JExtractSwiftTests/Utils.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2026 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

func withTemporaryFile(
fileName: String = "tmp_\(UUID().uuidString)",
extension: String,
contents: String = "",
in tempDirectory: URL = FileManager.default.temporaryDirectory,
_ perform: (URL) throws -> Void
) throws {
let tempFileName = "\(fileName).\(`extension`)"
let tempFileURL = tempDirectory.appendingPathComponent(tempFileName)

try FileManager.default.createDirectory(at: tempDirectory, withIntermediateDirectories: true)
try contents.write(to: tempFileURL, atomically: true, encoding: .utf8)
defer {
try? FileManager.default.removeItem(at: tempFileURL)
}
try perform(tempFileURL)
}