Skip to content

Commit 371f4e2

Browse files
committed
gradle build now causes proper emission of the swift/java sources
1 parent 98594a9 commit 371f4e2

File tree

23 files changed

+403
-106
lines changed

23 files changed

+403
-106
lines changed

.github/workflows/pull_request.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ jobs:
88
soundness:
99
uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main
1010
with:
11+
# Not API stable package (yet)
1112
api_breakage_check_enabled: false
1213
# FIXME: Something is off with the format task and it gets "stuck", need to investigate
1314
format_check_enabled: false

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ javakit-generate: generate-JavaKit generate-JavaKitReflection generate-JavaKitJa
9494

9595
clean:
9696
rm -rf .build; \
97+
rm -rf build; \
98+
rm -rf Samples/JExtractPluginSampleApp/.build; \
99+
rm -rf Samples/JExtractPluginSampleApp/build; \
97100
rm -rf Samples/SwiftKitExampleApp/src/generated/java/*
98101

99102
format:

Package.swift

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,13 @@ let package = Package(
133133
"JExtractSwiftPlugin"
134134
]
135135
),
136-
136+
.plugin(
137+
name: "JExtractSwiftCommandPlugin",
138+
targets: [
139+
"JExtractSwiftCommandPlugin"
140+
]
141+
),
142+
137143
// ==== Examples
138144

139145
.library(
@@ -347,6 +353,16 @@ let package = Package(
347353
"JExtractSwiftTool"
348354
]
349355
),
356+
.plugin(
357+
name: "JExtractSwiftCommandPlugin",
358+
capability: .command(
359+
intent: .custom(verb: "jextract", description: "Extract Java accessors from Swift module"),
360+
permissions: [
361+
]),
362+
dependencies: [
363+
"JExtractSwiftTool"
364+
]
365+
),
350366

351367
.testTarget(
352368
name: "JavaKitTests",
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
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 Foundation
16+
import PackagePlugin
17+
18+
@main
19+
final class JExtractSwiftCommandPlugin: BuildToolPlugin, CommandPlugin {
20+
21+
var verbose: Bool = false
22+
23+
/// Build the target before attempting to extract from it.
24+
/// This avoids trying to extract from broken sources.
25+
///
26+
/// 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.
27+
var buildInputs: Bool = true
28+
29+
/// Build the target once swift-java sources have been generated.
30+
/// This helps verify that the generated output is correct, and won't miscompile on the next build.
31+
var buildOutputs: Bool = true
32+
33+
func createBuildCommands(context: PackagePlugin.PluginContext, target: any PackagePlugin.Target) async throws -> [PackagePlugin.Command] {
34+
// FIXME: This is not a build plugin but SwiftPM forces us to impleme the protocol anyway? rdar://139556637
35+
return []
36+
}
37+
38+
func performCommand(context: PluginContext, arguments: [String]) throws {
39+
self.verbose = arguments.contains("-v") || arguments.contains("--verbose")
40+
41+
let selectedTargets: [String] =
42+
if let last = arguments.lastIndex(where: { $0.starts(with: "-")}),
43+
last < arguments.endIndex {
44+
Array(arguments[..<last])
45+
} else {
46+
[]
47+
}
48+
49+
for target in context.package.targets {
50+
guard let configPath = getSwiftJavaConfig(target: target) else {
51+
log("Skipping target '\(target.name), has no 'swift-java.config' file")
52+
continue
53+
}
54+
55+
do {
56+
print("[swift-java] Extracting Java wrappers from target: '\(target.name)'...")
57+
try performCommand(context: context, target: target, arguments: arguments)
58+
} catch {
59+
print("[swift-java] error: Failed to extract from target '\(target.name)': \(error)")
60+
}
61+
}
62+
}
63+
64+
/// Perform the command on a specific target.
65+
func performCommand(context: PluginContext, target: Target, arguments: [String]) throws {
66+
// Make sure the target can builds properly
67+
try self.packageManager.build(.target(target.name), parameters: .init())
68+
69+
guard let sourceModule = target.sourceModule else { return }
70+
71+
if self.buildInputs {
72+
log("Pre-building target '\(target.name)' before extracting sources...")
73+
try self.packageManager.build(.target(target.name), parameters: .init())
74+
}
75+
76+
if self.buildOutputs {
77+
log("Post-building target '\(target.name)' to verify generated sources...")
78+
try self.packageManager.build(.target(target.name), parameters: .init())
79+
}
80+
81+
// Note: Target doesn't have a directoryURL counterpart to directory,
82+
// so we cannot eliminate this deprecation warning.
83+
let sourceDir = target.directory.string
84+
85+
let configuration = try readConfiguration(sourceDir: "\(sourceDir)")
86+
87+
// We use the the usual maven-style structure of "src/[generated|main|test]/java/..."
88+
// that is common in JVM ecosystem
89+
let outputDirectoryJava = context.pluginWorkDirectoryURL
90+
.appending(path: "src")
91+
.appending(path: "generated")
92+
.appending(path: "java")
93+
let outputDirectorySwift = context.pluginWorkDirectoryURL
94+
.appending(path: "src")
95+
.appending(path: "generated")
96+
.appending(path: "Sources")
97+
98+
var arguments: [String] = [
99+
"--swift-module", sourceModule.name,
100+
"--package-name", configuration.javaPackage,
101+
"--output-directory-java", outputDirectoryJava.path(percentEncoded: false),
102+
"--output-directory-swift", outputDirectorySwift.path(percentEncoded: false),
103+
// TODO: "--build-cache-directory", ...
104+
// Since plugins cannot depend on libraries we cannot detect what the output files will be,
105+
// as it depends on the contents of the input files. Therefore we have to implement this as a prebuild plugin.
106+
// We'll have to make up some caching inside the tool so we don't re-parse files which have not changed etc.
107+
]
108+
arguments.append(sourceDir)
109+
110+
try runExtract(context: context, target: target, arguments: arguments)
111+
}
112+
113+
func runExtract(context: PluginContext, target: Target, arguments: [String]) throws {
114+
let process = Process()
115+
process.executableURL = try context.tool(named: "JExtractSwiftTool").url
116+
process.arguments = arguments
117+
118+
do {
119+
log("Execute: \(process.executableURL) \(arguments)")
120+
121+
try process.run()
122+
process.waitUntilExit()
123+
124+
assert(process.terminationStatus == 0, "Process failed with exit code: \(process.terminationStatus)")
125+
} catch {
126+
print("[swift-java][command] Failed to extract Java sources for target: '\(target.name); Error: \(error)")
127+
}
128+
}
129+
130+
func log(_ message: @autoclosure () -> String) {
131+
if self.verbose {
132+
print("[swift-java] \(message())")
133+
}
134+
}
135+
}

Plugins/JExtractSwiftPlugin/Configuration.swift renamed to Plugins/JExtractSwiftCommandPlugin/PluginsShared/Configuration.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ struct Configuration: Codable {
2121
}
2222

2323
func readConfiguration(sourceDir: String) throws -> Configuration {
24-
let configFile = URL(filePath: sourceDir).appending(path: "JExtractSwift.config")
24+
let configFile = URL(filePath: sourceDir).appending(path: "swift-java.config")
2525
do {
2626
let configData = try Data(contentsOf: configFile)
2727
return try JSONDecoder().decode(Configuration.self, from: configData)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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 Foundation
16+
import PackagePlugin
17+
18+
// Note: the JAVA_HOME environment variable must be set to point to where
19+
// Java is installed, e.g.,
20+
// Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home.
21+
func findJavaHome() -> String {
22+
if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] {
23+
return home
24+
}
25+
26+
// This is a workaround for envs (some IDEs) which have trouble with
27+
// picking up env variables during the build process
28+
let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home"
29+
if let home = try? String(contentsOfFile: path, encoding: .utf8) {
30+
if let lastChar = home.last, lastChar.isNewline {
31+
return String(home.dropLast())
32+
}
33+
34+
return home
35+
}
36+
37+
fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.")
38+
}
39+
40+
func getSwiftJavaConfig(target: Target) -> String? {
41+
let configPath = URL(fileURLWithPath: target.directory.string).appending(component: "swift-java.config").path()
42+
43+
if FileManager.default.fileExists(atPath: configPath) {
44+
return configPath
45+
} else {
46+
return nil
47+
}
48+
}

Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ struct JExtractSwiftBuildToolPlugin: BuildToolPlugin {
2424
// so we cannot eliminate this deprecation warning.
2525
let sourceDir = target.directory.string
2626

27+
let toolURL = try context.tool(named: "JExtractSwiftTool").url
2728
let configuration = try readConfiguration(sourceDir: "\(sourceDir)")
2829

2930
// We use the the usual maven-style structure of "src/[generated|main|test]/java/..."
@@ -52,7 +53,7 @@ struct JExtractSwiftBuildToolPlugin: BuildToolPlugin {
5253
return [
5354
.prebuildCommand(
5455
displayName: "Generate Java wrappers for Swift types",
55-
executable: try context.tool(named: "JExtractSwiftTool").url,
56+
executable: toolURL,
5657
arguments: arguments,
5758
// inputFiles: [ configFile ] + swiftFiles,
5859
// outputFiles: outputJavaFiles
@@ -62,24 +63,3 @@ struct JExtractSwiftBuildToolPlugin: BuildToolPlugin {
6263
}
6364
}
6465

65-
// Note: the JAVA_HOME environment variable must be set to point to where
66-
// Java is installed, e.g.,
67-
// Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home.
68-
func findJavaHome() -> String {
69-
if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] {
70-
return home
71-
}
72-
73-
// This is a workaround for envs (some IDEs) which have trouble with
74-
// picking up env variables during the build process
75-
let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home"
76-
if let home = try? String(contentsOfFile: path, encoding: .utf8) {
77-
if let lastChar = home.last, lastChar.isNewline {
78-
return String(home.dropLast())
79-
}
80-
81-
return home
82-
}
83-
84-
fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.")
85-
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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 Foundation
16+
17+
/// Configuration for the JExtractSwift translation tool, provided on a per-target
18+
/// basis.
19+
struct Configuration: Codable {
20+
var javaPackage: String
21+
}
22+
23+
func readConfiguration(sourceDir: String) throws -> Configuration {
24+
let configFile = URL(filePath: sourceDir).appending(path: "swift-java.config")
25+
do {
26+
let configData = try Data(contentsOf: configFile)
27+
return try JSONDecoder().decode(Configuration.self, from: configData)
28+
} catch {
29+
throw ConfigurationError(message: "Failed to parse JExtractSwift configuration at '\(configFile)!'", error: error)
30+
}
31+
}
32+
33+
struct ConfigurationError: Error {
34+
let message: String
35+
let error: any Error
36+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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 Foundation
16+
17+
// Note: the JAVA_HOME environment variable must be set to point to where
18+
// Java is installed, e.g.,
19+
// Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home.
20+
func findJavaHome() -> String {
21+
if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] {
22+
return home
23+
}
24+
25+
// This is a workaround for envs (some IDEs) which have trouble with
26+
// picking up env variables during the build process
27+
let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home"
28+
if let home = try? String(contentsOfFile: path, encoding: .utf8) {
29+
if let lastChar = home.last, lastChar.isNewline {
30+
return String(home.dropLast())
31+
}
32+
33+
return home
34+
}
35+
36+
fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.")
37+
}

0 commit comments

Comments
 (0)