Skip to content

[WIP] JExtractSwiftPlugin #138

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

Closed
wants to merge 14 commits into from
Closed
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
14 changes: 8 additions & 6 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ jobs:
strategy:
fail-fast: true
matrix:
swift_version: ['nightly-main']
# swift_version: ['nightly-main']
swift_version: ['6.0.2']
os_version: ['jammy']
jdk_vendor: ['Corretto']
container:
Expand All @@ -43,8 +44,8 @@ jobs:
if: steps.cache-jdk.outputs.cache-hit != 'true'
run: "bash -xc 'JDK_VENDOR=${{ matrix.jdk_vendor }} ./docker/install_jdk.sh'"
# TODO: not using setup-java since incompatible with the swiftlang/swift base image
- name: Install Untested Nightly Swift
run: "bash -xc './docker/install_untested_nightly_swift.sh'"
# - name: Install Untested Nightly Swift
# run: "bash -xc './docker/install_untested_nightly_swift.sh'"
- name: Cache local Gradle repository
uses: actions/cache@v4
continue-on-error: true
Expand Down Expand Up @@ -79,7 +80,8 @@ jobs:
strategy:
fail-fast: false
matrix:
swift_version: ['nightly-main']
# swift_version: ['nightly-main']
swift_version: ['6.0.2']
os_version: ['jammy']
jdk_vendor: ['Corretto']
container:
Expand All @@ -103,8 +105,8 @@ jobs:
if: steps.cache-jdk.outputs.cache-hit != 'true'
run: "bash -xc 'JDK_VENDOR=${{ matrix.jdk_vendor }} ./docker/install_jdk.sh'"
# TODO: not using setup-java since incompatible with the swiftlang/swift base image
- name: Install Untested Nightly Swift
run: "bash -xc './docker/install_untested_nightly_swift.sh'"
# - name: Install Untested Nightly Swift
# run: "bash -xc './docker/install_untested_nightly_swift.sh'"
- name: Cache local Gradle repository
uses: actions/cache@v4
continue-on-error: true
Expand Down
2 changes: 2 additions & 0 deletions .licenseignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,5 @@ Makefile
gradle/wrapper/gradle-wrapper.properties
gradlew
gradlew.bat
**/gradlew
**/gradlew.bat
17 changes: 17 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,14 @@ let package = Package(
targets: ["JExtractSwift"]
),

// ==== Plugin for wrapping Java classes in Swift
.plugin(
name: "JExtractSwiftPlugin",
targets: [
"JExtractSwiftPlugin"
]
),

// ==== Examples

.library(
Expand Down Expand Up @@ -229,6 +237,7 @@ let package = Package(
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"])
]
),

.plugin(
name: "JavaCompilerPlugin",
capability: .buildTool()
Expand Down Expand Up @@ -331,6 +340,14 @@ let package = Package(
]
),

.plugin(
name: "JExtractSwiftPlugin",
capability: .buildTool(),
dependencies: [
"JExtractSwiftTool"
]
),

.testTarget(
name: "JavaKitTests",
dependencies: ["JavaKit", "JavaKitNetwork"],
Expand Down
36 changes: 36 additions & 0 deletions Plugins/JExtractSwiftPlugin/Configuration.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//===----------------------------------------------------------------------===//
//
// 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

/// Configuration for the JExtractSwift translation tool, provided on a per-target
/// basis.
struct Configuration: Codable {
var javaPackage: String
}

func readConfiguration(sourceDir: String) throws -> Configuration {
let configFile = URL(filePath: sourceDir).appending(path: "JExtractSwift.config")
do {
let configData = try Data(contentsOf: configFile)
return try JSONDecoder().decode(Configuration.self, from: configData)
} catch {
throw ConfigurationError(message: "Failed to parse JExtractSwift configuration at '\(configFile)!'", error: error)
}
}

struct ConfigurationError: Error {
let message: String
let error: any Error
}
85 changes: 85 additions & 0 deletions Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//===----------------------------------------------------------------------===//
//
// 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
import PackagePlugin

@main
struct JExtractSwiftBuildToolPlugin: BuildToolPlugin {
func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] {
guard let sourceModule = target.sourceModule else { return [] }

// Note: Target doesn't have a directoryURL counterpart to directory,
// so we cannot eliminate this deprecation warning.
let sourceDir = target.directory.string

let configuration = try readConfiguration(sourceDir: "\(sourceDir)")

// We use the the usual maven-style structure of "src/[generated|main|test]/java/..."
// that is common in JVM ecosystem
let outputDirectoryJava = context.pluginWorkDirectoryURL
.appending(path: "src")
.appending(path: "generated")
.appending(path: "java")
let outputDirectorySwift = context.pluginWorkDirectoryURL
.appending(path: "src")
.appending(path: "generated")
.appending(path: "Sources")

var arguments: [String] = [
"--swift-module", sourceModule.name,
"--package-name", configuration.javaPackage,
"--output-directory-java", outputDirectoryJava.path(percentEncoded: false),
"--output-directory-swift", outputDirectorySwift.path(percentEncoded: false),
// TODO: "--build-cache-directory", ...
// Since plugins cannot depend on libraries we cannot detect what the output files will be,
// as it depends on the contents of the input files. Therefore we have to implement this as a prebuild plugin.
// We'll have to make up some caching inside the tool so we don't re-parse files which have not changed etc.
]
arguments.append(sourceDir)

return [
.prebuildCommand(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can't work, since we need this to act on swiftinterface files of the module it's acting on. It has to be a "post build" plugin really.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: make this into a command plugin people would have to invoke manually swift package jextract which would generate the bindings.

We could also then swift package jextract jar to produce a jar or even publish it etc.

displayName: "Generate Java wrappers for Swift types",
executable: try context.tool(named: "JExtractSwiftTool").url,
arguments: arguments,
// inputFiles: [ configFile ] + swiftFiles,
// outputFiles: outputJavaFiles
outputFilesDirectory: outputDirectorySwift
)
]
}
}

// Note: the JAVA_HOME environment variable must be set to point to where
// Java is installed, e.g.,
// Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home.
func findJavaHome() -> String {
if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] {
return home
}

// This is a workaround for envs (some IDEs) which have trouble with
// picking up env variables during the build process
let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home"
if let home = try? String(contentsOfFile: path, encoding: .utf8) {
if let lastChar = home.last, lastChar.isNewline {
return String(home.dropLast())
}

return home
}

fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.")
}
8 changes: 8 additions & 0 deletions Samples/JExtractPluginSampleApp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
70 changes: 70 additions & 0 deletions Samples/JExtractPluginSampleApp/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

import class Foundation.FileManager
import class Foundation.ProcessInfo

// Note: the JAVA_HOME environment variable must be set to point to where
// Java is installed, e.g.,
// Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home.
func findJavaHome() -> String {
if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] {
return home
}

// This is a workaround for envs (some IDEs) which have trouble with
// picking up env variables during the build process
let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home"
if let home = try? String(contentsOfFile: path, encoding: .utf8) {
if let lastChar = home.last, lastChar.isNewline {
return String(home.dropLast())
}

return home
}

fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.")
}
let javaHome = findJavaHome()

let javaIncludePath = "\(javaHome)/include"
#if os(Linux)
let javaPlatformIncludePath = "\(javaIncludePath)/linux"
#elseif os(macOS)
let javaPlatformIncludePath = "\(javaIncludePath)/darwin"
#else
// TODO: Handle windows as well
#error("Currently only macOS and Linux platforms are supported, this may change in the future.")
#endif

let package = Package(
name: "JExtractPluginSampleApp",
platforms: [
.macOS(.v10_15),
],
products: [
.library(
name: "JExtractPluginSampleLib",
type: .dynamic,
targets: ["JExtractPluginSampleLib"]
),
],
dependencies: [
.package(name: "swift-java", path: "../../"),
],
targets: [
.target(
name: "JExtractPluginSampleLib",
dependencies: [
],
swiftSettings: [
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"])
],
plugins: [
.plugin(name: "JExtractSwiftPlugin", package: "swift-java"),
]
),
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"javaPackage": "com.example.swift"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

public class MyCoolSwiftClass {
var number: Int
public init(number: Int) {
print("[swift] init(number: \(number))")
self.number = number
}

public func exposedToJava() {
print("[swift] exposedToJava()")
print("[swift] number = \(number)")
}
}
Loading
Loading