Skip to content

WIP: Introduce resolve subcommand and further dis-entangle commands #276

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Draft
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.swift-version

.DS_Store
.build
.idea
Expand Down
3 changes: 2 additions & 1 deletion .licenseignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
13 changes: 1 addition & 12 deletions .unacceptablelanguageignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
35 changes: 29 additions & 6 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -85,7 +86,7 @@ let javaIncludePath = "\(javaHome)/include"
let package = Package(
name: "SwiftJava",
platforms: [
.macOS(.v10_15)
.macOS(.v13)
],
products: [
// ==== JavaKit (i.e. calling Java directly Swift utilities)
Expand Down Expand Up @@ -195,6 +196,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")),
],
Expand Down Expand Up @@ -363,7 +368,8 @@ let package = Package(
"JavaTypes",
"JavaKitShared",
"JavaKitConfigurationShared",
"_Subprocess", // using process spawning
// .product(name: "Subprocess", package: "swift-subprocess")
"_Subprocess",
],
swiftSettings: [
.swiftLanguageMode(.v5),
Expand All @@ -379,6 +385,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",
Expand All @@ -387,11 +394,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"),
]
),

Expand Down Expand Up @@ -467,6 +477,15 @@ let package = Package(
]
),

.testTarget(
name: "JavaKitConfigurationSharedTests",
dependencies: ["JavaKitConfigurationShared"],
swiftSettings: [
.swiftLanguageMode(.v5),
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"])
]
),

.testTarget(
name: "JExtractSwiftTests",
dependencies: [
Expand All @@ -480,19 +499,23 @@ let package = Package(

// Experimental Foundation Subprocess Copy
.target(
name: "_CShims",
name: "_SubprocessCShims",
swiftSettings: [
.swiftLanguageMode(.v5)
]
),
.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"),
]
),
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ final class JExtractSwiftCommandPlugin: SwiftJavaPluginProtocol, BuildToolPlugin
]
// 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]
arguments += ["--java-package", package]
}

return arguments
Expand Down
4 changes: 1 addition & 3 deletions Plugins/PluginsShared/SwiftJavaPluginProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
63 changes: 36 additions & 27 deletions Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,24 +88,12 @@ 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.
// TODO: this is not precise and won't work with more advanced Java files, e.g. lambdas etc.
Expand Down Expand Up @@ -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
Expand All @@ -181,39 +166,63 @@ 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
}
}

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 {
Expand Down
8 changes: 8 additions & 0 deletions Samples/JavaDependencySampleApp/ci-validate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
.build/plugins/tools/debug/SwiftJavaTool-tool resolve \
Sources/JavaCommonsCSV/swift-java.config \
--swift-module JavaCommonsCSV \
--output-directory .build/plugins/outputs/javadependencysampleapp/JavaCommonsCSV/destination/SwiftJavaPlugin/
18 changes: 10 additions & 8 deletions Sources/JavaKitConfigurationShared/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -46,11 +46,7 @@ public struct Configuration: Codable {
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
Expand Down Expand Up @@ -80,6 +76,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)
Expand Down Expand Up @@ -154,15 +156,15 @@ 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 []
}

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 {
Expand Down
48 changes: 48 additions & 0 deletions Sources/JavaKitConfigurationShared/GradleDependencyParsing.swift
Original file line number Diff line number Diff line change
@@ -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
}
Loading
Loading