Skip to content

Commit f4ba708

Browse files
committed
jextract prebuil plugin; without swift interfaces; triggered by gradle
1 parent cd2f979 commit f4ba708

File tree

16 files changed

+492
-137
lines changed

16 files changed

+492
-137
lines changed

Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,39 +19,41 @@ import PackagePlugin
1919
struct JExtractSwiftBuildToolPlugin: BuildToolPlugin {
2020
func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] {
2121
guard let sourceModule = target.sourceModule else { return [] }
22-
22+
2323
// Note: Target doesn't have a directoryURL counterpart to directory,
2424
// so we cannot eliminate this deprecation warning.
2525
let sourceDir = target.directory.string
26-
26+
2727
let configuration = try readConfiguration(sourceDir: "\(sourceDir)")
28-
28+
2929
// We use the the usual maven-style structure of "src/[generated|main|test]/java/..."
3030
// that is common in JVM ecosystem
31-
let outputDirectory = context.pluginWorkDirectoryURL
31+
let outputDirectoryJava = context.pluginWorkDirectoryURL
3232
.appending(path: "src")
3333
.appending(path: "generated")
3434
.appending(path: "java")
35-
35+
let outputDirectorySwift = context.pluginWorkDirectoryURL
36+
3637
var arguments: [String] = [
3738
"--swift-module", sourceModule.name,
3839
"--package-name", configuration.javaPackage,
39-
"--output-directory", outputDirectory.path(percentEncoded: false),
40+
"--output-directory-java", outputDirectoryJava.path(percentEncoded: false),
41+
"--output-directory-swift", outputDirectorySwift.path(percentEncoded: false),
4042
// TODO: "--build-cache-directory", ...
4143
// Since plugins cannot depend on libraries we cannot detect what the output files will be,
4244
// as it depends on the contents of the input files. Therefore we have to implement this as a prebuild plugin.
4345
// We'll have to make up some caching inside the tool so we don't re-parse files which have not changed etc.
4446
]
4547
arguments.append(sourceDir)
46-
48+
4749
return [
4850
.prebuildCommand(
4951
displayName: "Generate Java wrappers for Swift types",
5052
executable: try context.tool(named: "JExtractSwiftTool").url,
5153
arguments: arguments,
5254
// inputFiles: [ configFile ] + swiftFiles,
5355
// outputFiles: outputJavaFiles
54-
outputFilesDirectory: outputDirectory
56+
outputFilesDirectory: outputDirectorySwift
5557
)
5658
]
5759
}
@@ -64,17 +66,17 @@ func findJavaHome() -> String {
6466
if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] {
6567
return home
6668
}
67-
69+
6870
// This is a workaround for envs (some IDEs) which have trouble with
6971
// picking up env variables during the build process
7072
let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home"
7173
if let home = try? String(contentsOfFile: path, encoding: .utf8) {
7274
if let lastChar = home.last, lastChar.isNewline {
7375
return String(home.dropLast())
7476
}
75-
77+
7678
return home
7779
}
78-
80+
7981
fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.")
8082
}

Samples/JExtractPluginSampleApp/Package.swift

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,19 @@ let package = Package(
4444
platforms: [
4545
.macOS(.v10_15),
4646
],
47-
dependencies: [
47+
products: [
48+
.library(
49+
name: "JExtractPluginSampleLib",
50+
type: .dynamic,
51+
targets: ["JExtractPluginSampleLib"]
52+
),
53+
],
54+
dependencies: [
4855
.package(name: "swift-java", path: "../../"),
4956
],
5057
targets: [
51-
.executableTarget(
52-
name: "JExtractPluginSample",
58+
.target(
59+
name: "JExtractPluginSampleLib",
5360
dependencies: [
5461
],
5562
swiftSettings: [

Samples/JExtractPluginSampleApp/Sources/JExtractPluginSample/main.swift renamed to Samples/JExtractPluginSampleApp/Sources/JExtractPluginSampleLib/MyCoolSwiftClass.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15-
public class MyCoolLibrary {
15+
public class MyCoolSwiftClass {
16+
public init(number: Int) {}
1617
public func exposedToJava() { }
1718
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import org.swift.swiftkit.gradle.BuildUtils
16+
17+
plugins {
18+
id("build-logic.java-application-conventions")
19+
// id("me.champeau.jmh") version "0.7.2"
20+
}
21+
22+
group = "org.swift.swiftkit"
23+
version = "1.0-SNAPSHOT"
24+
25+
repositories {
26+
mavenCentral()
27+
}
28+
29+
java {
30+
toolchain {
31+
languageVersion.set(JavaLanguageVersion.of(22))
32+
}
33+
}
34+
35+
36+
def jextract = tasks.register("swift-build", Exec) {
37+
description = "Builds swift sources, including swift-java source generation"
38+
39+
// monitor project Swift sources:
40+
inputs.dir(layout.projectDirectory.dir("Sources/"))
41+
42+
// monitor root java-swift sources (including source generator libs which may be changing):
43+
inputs.dir("$rootDir/Sources")
44+
45+
// the swift-java produced Java sources are located here:
46+
outputs.dir(layout.buildDirectory.dir("../.build/plugins/outputs/jextractpluginsampleapp/JExtractPluginSampleLib/destination/JExtractSwiftPlugin/src/generated/java"))
47+
48+
workingDir = rootDir
49+
commandLine "swift"
50+
args "build"
51+
}
52+
53+
// Add the java-swift generated Java sources
54+
sourceSets {
55+
main {
56+
java {
57+
srcDir(jextract)
58+
}
59+
}
60+
}
61+
62+
dependencies {
63+
implementation(project(':SwiftKit'))
64+
65+
testImplementation(platform("org.junit:junit-bom:5.10.0"))
66+
testImplementation("org.junit.jupiter:junit-jupiter")
67+
}
68+
69+
tasks.named('test', Test) {
70+
useJUnitPlatform()
71+
}
72+
73+
application {
74+
mainClass = "com.example.swift.HelloJava2Swift"
75+
76+
// In order to silence:
77+
// WARNING: A restricted method in java.lang.foreign.SymbolLookup has been called
78+
// WARNING: java.lang.foreign.SymbolLookup::libraryLookup has been called by org.example.swift.JavaKitExample in an unnamed module
79+
// WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
80+
// WARNING: Restricted methods will be blocked in a future release unless native access is enabled
81+
// FIXME: Find out the proper solution to this
82+
applicationDefaultJvmArgs = [
83+
"--enable-native-access=ALL-UNNAMED",
84+
85+
// Include the library paths where our dylibs are that we want to load and call
86+
"-Djava.library.path=" + BuildUtils.javaLibraryPaths(rootDir).join(":"),
87+
88+
// Enable tracing downcalls (to Swift)
89+
"-Djextract.trace.downcalls=true"
90+
]
91+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.example.swift;
2+
3+
import org.swift.swiftkit.SwiftKit;
4+
5+
public class JExtractPluginSampleMain {
6+
public static void main() {
7+
System.out.println("java.library.path = " + SwiftKit.getJavaLibraryPath());
8+
System.out.println("jextract.trace.downcalls = " + SwiftKit.getJextractTraceDowncalls());
9+
10+
var o = new MyCoolSwiftClass(12);
11+
o.exposedToJava();
12+
}
13+
}

Samples/SwiftKitSampleApp/build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ sourceSets {
5656
}
5757
}
5858

59+
tasks.build {
60+
dependsOn("jextract")
61+
}
62+
5963
dependencies {
6064
implementation(project(':SwiftKit'))
6165

Sources/JExtractSwift/Convenience/Collection+Extensions.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,13 @@ extension Collection {
3939
}
4040
}
4141
}
42+
43+
extension Collection where Element == Int {
44+
var sum: Int {
45+
var s = 0
46+
for i in self {
47+
s += i
48+
}
49+
return s
50+
}
51+
}

Sources/JExtractSwift/ImportedDecls.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,15 @@ public typealias JavaPackage = String
2929
public struct ImportedNominalType: ImportedDecl {
3030
public let swiftTypeName: String
3131
public let javaType: JavaType
32-
public var swiftMangledName: String?
3332
public var kind: NominalTypeKind
3433

3534
public var initializers: [ImportedFunc] = []
3635
public var methods: [ImportedFunc] = []
3736
public var variables: [ImportedVariable] = []
3837

39-
public init(swiftTypeName: String, javaType: JavaType, swiftMangledName: String? = nil, kind: NominalTypeKind) {
38+
public init(swiftTypeName: String, javaType: JavaType, kind: NominalTypeKind) {
4039
self.swiftTypeName = swiftTypeName
4140
self.javaType = javaType
42-
self.swiftMangledName = swiftMangledName
4341
self.kind = kind
4442
}
4543

@@ -201,16 +199,22 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible {
201199

202200
public var swiftMangledName: String = ""
203201

204-
public var syntax: String? = nil
202+
public var swiftDecl: any DeclSyntaxProtocol
203+
204+
public var syntax: String? {
205+
"\(self.swiftDecl)"
206+
}
205207

206208
public var isInit: Bool = false
207209

208210
public init(
211+
decl: any DeclSyntaxProtocol,
209212
parentName: TranslatedType?,
210213
identifier: String,
211214
returnType: TranslatedType,
212215
parameters: [ImportedParam]
213216
) {
217+
self.swiftDecl = decl
214218
self.parentName = parentName
215219
self.identifier = identifier
216220
self.returnType = returnType
@@ -287,6 +291,7 @@ public struct ImportedVariable: ImportedDecl, CustomStringConvertible {
287291
case .set:
288292
let newValueParam: FunctionParameterSyntax = "_ newValue: \(self.returnType.cCompatibleSwiftType)"
289293
var funcDecl = ImportedFunc(
294+
decl: self.syntax!,
290295
parentName: self.parentName,
291296
identifier: self.identifier,
292297
returnType: TranslatedType.void,
@@ -296,6 +301,7 @@ public struct ImportedVariable: ImportedDecl, CustomStringConvertible {
296301

297302
case .get:
298303
var funcDecl = ImportedFunc(
304+
decl: self.syntax!,
299305
parentName: self.parentName,
300306
identifier: self.identifier,
301307
returnType: self.returnType,

Sources/JExtractSwift/Swift2Java.swift

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,43 +28,64 @@ public struct SwiftToJava: ParsableCommand {
2828
@Option(help: "The package the generated Java code should be emitted into.")
2929
var packageName: String
3030

31-
@Option(name: .shortAndLong, help: "The directory in which to output the generated Swift files and manifest.")
32-
var outputDirectory: String = ".build/jextract-swift/generated"
31+
@Option(
32+
name: .shortAndLong,
33+
help: "The directory in which to output the generated Swift files and manifest.")
34+
var outputDirectoryJava: String = ".build/jextract-swift/generated"
3335

34-
@Option(name: .long, help: "Name of the Swift module to import (and the swift interface files belong to)")
36+
@Option(help: "Swift output directory")
37+
var outputDirectorySwift: String
38+
39+
@Option(
40+
name: .long,
41+
help: "Name of the Swift module to import (and the swift interface files belong to)")
3542
var swiftModule: String
3643

3744
// TODO: Once we ship this, make this `.warning` by default
3845
@Option(name: .shortAndLong, help: "Configure the level of lots that should be printed")
3946
var logLevel: Logger.Level = .notice
4047

41-
@Argument(help: "The Swift interface files to export to Java.")
42-
var swiftInterfaceFiles: [String]
48+
@Argument(help: "The Swift files or directories to recursively export to Java.")
49+
var input: [String]
4350

4451
public func run() throws {
45-
let interfaceFiles = self.swiftInterfaceFiles.dropFirst()
46-
print("Interface files: \(interfaceFiles)")
52+
let inputPaths = self.input.dropFirst().map { URL(string: $0)! }
53+
print("Input \(inputPaths)")
4754

4855
let translator = Swift2JavaTranslator(
4956
javaPackage: packageName,
5057
swiftModuleName: swiftModule
5158
)
5259
translator.log.logLevel = logLevel
5360

54-
var fileNo = 1
55-
for interfaceFile in interfaceFiles {
56-
print("[\(fileNo)/\(interfaceFiles.count)] Importing module '\(swiftModule)', interface file: \(interfaceFile)")
57-
defer { fileNo += 1 }
61+
var allFiles: [URL] = []
62+
let fileManager = FileManager.default
63+
64+
for path in inputPaths {
65+
if isDirectory(url: path) {
66+
if let enumerator = fileManager.enumerator(at: path, includingPropertiesForKeys: nil) {
67+
for case let fileURL as URL in enumerator {
68+
allFiles.append(fileURL)
69+
}
70+
}
71+
} else if path.isFileURL {
72+
allFiles.append(path)
73+
}
74+
}
75+
76+
for file in allFiles {
77+
print("Importing module '\(swiftModule)', interface file: \(file)")
5878

59-
try translator.analyze(swiftInterfacePath: interfaceFile)
60-
try translator.writeImportedTypesTo(outputDirectory: outputDirectory)
79+
try translator.analyze(file: file.path)
80+
try translator.writeExportedJavaSources(outputDirectory: outputDirectoryJava)
81+
try translator.writeSwiftThunkSources(outputDirectory: outputDirectorySwift)
6182

62-
print("[\(fileNo)/\(interfaceFiles.count)] Imported interface file: \(interfaceFile) " + "done.".green)
83+
print("Imported interface file: \(file) " + "done.".green)
6384
}
6485

65-
try translator.writeModuleTo(outputDirectory: outputDirectory)
86+
try translator.writeExportedJavaModule(outputDirectory: outputDirectoryJava)
6687
print("")
67-
print("Generated Java sources in package '\(packageName)' in: \(outputDirectory)/")
88+
print("Generated Java sources in package '\(packageName)' in: \(outputDirectoryJava)/")
6889
print("Swift module '\(swiftModule)' import: " + "done.".green)
6990
}
7091

@@ -79,3 +100,9 @@ extension Logger.Level: ExpressibleByArgument {
79100

80101
public private(set) static var defaultCompletionKind: CompletionKind = .default
81102
}
103+
104+
func isDirectory(url: URL) -> Bool {
105+
var isDirectory: ObjCBool = false
106+
FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory)
107+
return isDirectory.boolValue
108+
}

0 commit comments

Comments
 (0)