Skip to content
Merged
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
38 changes: 24 additions & 14 deletions Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,14 @@ private let SwiftJavaConfigFileName = "swift-java.config"
@main
struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {

struct DependentConfigFile {
struct DependencyConfigFile {
let swiftModuleName: String
// The specific URL of a swift-java.config file
let configURL: URL
// Specific path of sources of this module, usually the same directory where
// swift-java.config is but not always. This can be passed as --depends-on
// if swiftpm cannot find the location automatically though module dependency
let sourceDirURL: URL?
}

var pluginName: String = "swift-java"
Expand Down Expand Up @@ -58,7 +63,7 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
let outputJavaDirectory = context.outputJavaDirectory
let outputSwiftDirectory = context.outputSwiftDirectory

let dependentConfigFiles = searchForDependentConfigFiles(in: target)
let dependencyConfigFiles = searchForDependencyConfigFiles(in: target)

var arguments: [String] = [
/*subcommand=*/"jextract",
Expand All @@ -83,13 +88,14 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
arguments += ["--static-build-config", resolvedURL.absoluteURL.path(percentEncoded: false)]
}

let dependentConfigFilesArguments = dependentConfigFiles.flatMap { dependentConfigFile in
[
"--depends-on",
"\(dependentConfigFile.swiftModuleName)=\(dependentConfigFile.configURL.path(percentEncoded: false))",
]
let dependsOnArguments = dependencyConfigFiles.flatMap { dependencyConfigFile -> [String] in
makeDependsOnArgument(
moduleName: dependencyConfigFile.swiftModuleName,
configPath: dependencyConfigFile.configURL.path(percentEncoded: false),
sourcePaths: dependencyConfigFile.sourceDirURL.map { [$0.path(percentEncoded: false)] } ?? []
)
}
arguments += dependentConfigFilesArguments
arguments += dependsOnArguments

let swiftFiles = sourceModule.sourceFiles.map { $0.url }.filter {
$0.pathExtension == "swift"
Expand Down Expand Up @@ -249,7 +255,7 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
"--output-directory", outputSwiftDirectory.path(percentEncoded: false),
"--single-swift-file-output", singleSwiftFileOutputName,
]
javaCallbacksArguments += dependentConfigFilesArguments
javaCallbacksArguments += dependsOnArguments

commands += [
.buildCommand(
Expand All @@ -275,8 +281,8 @@ 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) -> [DependentConfigFile] {
var dependentConfigFiles: [DependentConfigFile] = []
func searchForDependencyConfigFiles(in target: any Target) -> [DependencyConfigFile] {
var dependencyConfigFiles: [DependencyConfigFile] = []

func _searchForConfigFiles(in target: any Target) {
// log("Search for config files in target: \(target.name)")
Expand All @@ -291,8 +297,12 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
.path(percentEncoded: false)

if FileManager.default.fileExists(atPath: dependencyConfigString) {
dependentConfigFiles.append(
DependentConfigFile(swiftModuleName: target.name, configURL: dependencyConfigURL)
dependencyConfigFiles.append(
DependencyConfigFile(
swiftModuleName: target.name,
configURL: dependencyConfigURL,
sourceDirURL: target.sourceModule?.directoryURL,
)
)
}
}
Expand Down Expand Up @@ -322,7 +332,7 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
_searchForConfigFiles(in: dependency)
}

return dependentConfigFiles
return dependencyConfigFiles
}

private func findSwiftJavaDirectory(for target: any Target) -> URL? {
Expand Down
33 changes: 33 additions & 0 deletions Plugins/PluginsShared/DependsOnArgument.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) 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
//
//===----------------------------------------------------------------------===//

/// Build a single `--depends-on` argument pair: `["--depends-on", "<value>"]`.
///
/// Value form: `[<ModuleName>=]<configPath>[,<sourcePath>...]`.
func makeDependsOnArgument(
moduleName: String? = nil,
configPath: String,
sourcePaths: [String] = []
) -> [String] {
var value: String
if let moduleName, !moduleName.isEmpty {
value = "\(moduleName)=\(configPath)"
} else {
value = configPath
}
if !sourcePaths.isEmpty {
value += "," + sourcePaths.joined(separator: ",")
}
return ["--depends-on", value]
}
22 changes: 11 additions & 11 deletions Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ private let SwiftJavaConfigFileName = "swift-java.config"

@main
struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
struct DependentConfigFile {
struct DependencyConfigFile {
let swiftModuleName: String
let configURL: URL
}
Expand Down Expand Up @@ -50,7 +50,7 @@ struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {

/// Find the manifest files from other swift-java executions in any targets
/// this target depends on.
var dependentConfigFiles: [DependentConfigFile] = []
var dependencyConfigFiles: [DependencyConfigFile] = []
func searchForConfigFiles(in target: any Target) {
// log("Search for config files in target: \(target.name)")
let dependencyURL = target.directoryURL
Expand All @@ -64,8 +64,8 @@ struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
.path(percentEncoded: false)

if FileManager.default.fileExists(atPath: dependencyConfigString) {
dependentConfigFiles.append(
DependentConfigFile(swiftModuleName: target.name, configURL: dependencyConfigURL)
dependencyConfigFiles.append(
DependencyConfigFile(swiftModuleName: target.name, configURL: dependencyConfigURL)
)
}
}
Expand Down Expand Up @@ -98,7 +98,7 @@ struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
var arguments: [String] = []
arguments += argumentsSwiftModule(sourceModule: sourceModule)
arguments += argumentsOutputDirectory(context: context)
arguments += argumentsDependedOnConfigs(dependentConfigFiles)
arguments += dependsOnArguments(dependencyConfigFiles)

let classes = config.classes ?? [:]
print("[swift-java-plugin] Classes to wrap (\(classes.count)): \(classes.map(\.key))")
Expand Down Expand Up @@ -230,12 +230,12 @@ extension SwiftJavaBuildToolPlugin {
]
}

func argumentsDependedOnConfigs(_ dependentConfigFiles: [DependentConfigFile]) -> [String] {
dependentConfigFiles.flatMap { dependentConfigFile in
[
"--depends-on",
"\(dependentConfigFile.swiftModuleName)=\(dependentConfigFile.configURL.path(percentEncoded: false))",
]
func dependsOnArguments(_ dependencyConfigFiles: [DependencyConfigFile]) -> [String] {
dependencyConfigFiles.flatMap { dependencyConfigFile in
makeDependsOnArgument(
moduleName: dependencyConfigFile.swiftModuleName,
configPath: dependencyConfigFile.configURL.path(percentEncoded: false)
)
}
}

Expand Down
29 changes: 25 additions & 4 deletions Samples/SwiftJavaExtractJNISampleApp/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,20 @@ let package = Package(
name: "MySwiftLibrary",
type: .dynamic,
targets: ["MySwiftLibrary"]
)

),
.library(
name: "MySwiftDependencyLibrary",
type: .dynamic,
targets: ["MySwiftDependencyLibrary"]
),
],
dependencies: [
.package(name: "swift-java", path: "../../")
],
targets: [
// Separate module to show that we can handle cross module type references (automatic --depends-on)
.target(
name: "MySwiftLibrary",
name: "MySwiftDependencyLibrary",
dependencies: [
.product(name: "SwiftJava", package: "swift-java")
],
Expand All @@ -35,6 +40,22 @@ let package = Package(
plugins: [
.plugin(name: "JExtractSwiftPlugin", package: "swift-java")
]
)
),
.target(
name: "MySwiftLibrary",
dependencies: [
.product(name: "SwiftJava", package: "swift-java"),
"MySwiftDependencyLibrary",
],
exclude: [
"swift-java.config"
],
swiftSettings: [
.swiftLanguageMode(.v5)
],
plugins: [
.plugin(name: "JExtractSwiftPlugin", package: "swift-java")
]
),
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

public struct ValueInDependencyModule {
public let value: Int32

public init(value: Int32) {
self.value = value
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"javaPackage": "com.example.swift.dep",
"mode": "jni",
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//===----------------------------------------------------------------------===//
//
// 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 MySwiftDependencyLibrary

// Show using a type from another module
// This depends on --depends-on being passed correctly
public func consumeValueFromOtherModule(_ v: ValueInDependencyModule) -> Int32 {
v.value + 1
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.swift.swiftkit.core.SwiftArena;

import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
Expand Down Expand Up @@ -85,4 +86,13 @@ void labeledOverloads() {
assertEquals(202, MySwiftLibrary.globalOverloadedB(200));
assertEquals(303, MySwiftLibrary.globalOverloaded(300));
}

@Test
void call_consumeValueFromOtherModule_crossModule() {
try (var arena = SwiftArena.ofConfined()) {
var value = com.example.swift.dep.ValueInDependencyModule.init(41, arena);
int result = MySwiftLibrary.consumeValueFromOtherModule(value);
assertEquals(42, result);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@ extension FFMSwift2JavaGenerator {
return // no need to write any empty files, yay
}

log.info(
"[swift-java] Write empty [\(self.expectedOutputSwiftFileNames.count)] 'expected' files in: \(swiftOutputDirectory)/"
log.debug(
"Write empty [\(self.expectedOutputSwiftFileNames.count)] 'expected' files in: \(swiftOutputDirectory)/"
)

// FIXME(SwiftPM): We'd like to avoid having to write these blank files
for expectedFileName in self.expectedOutputSwiftFileNames {
log.info("Write SwiftPM-'expected' empty file: \(expectedFileName.bold)")
log.trace("Write SwiftPM-'expected' empty file: \(expectedFileName.bold)")

var printer = CodePrinter()
printer.print("// Empty file generated on purpose")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,13 @@ extension JNISwift2JavaGenerator {
return // no need to write any empty files, yay
}

logger.info(
logger.debug(
"Write empty [\(self.expectedOutputSwiftFileNames.count)] 'expected' files in: \(swiftOutputDirectory)/"
)

// FIXME(SwiftPM): We'd like to avoid having to write these blank files
for expectedFileName in self.expectedOutputSwiftFileNames {
logger.info("Write SwiftPM-'expected' empty file: \(expectedFileName.bold)")
logger.trace("Write SwiftPM-'expected' empty file: \(expectedFileName.bold)")

var printer = CodePrinter()
printer.print("// Empty file generated on purpose")
Expand Down
Loading
Loading