Skip to content

Attempt to use build-plugin to bootstrap dependency resolver dependency #198

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

Merged
merged 10 commits into from
Dec 11, 2024
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
2 changes: 1 addition & 1 deletion .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ jobs:
- name: Prepare CI Environment
uses: ./.github/actions/prepare_env
- name: Swift Build
run: "swift build --build-tests"
run: "swift build --build-tests --disable-sandbox"
- name: Swift Test
run: "swift test"

Expand Down
4 changes: 3 additions & 1 deletion .licenseignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Package.resolved
README.md
SECURITY.md
scripts/unacceptable-language.txt
.unacceptablelanguageignore
docker/*
**/*.docc/*
**/.gitignore
Expand Down Expand Up @@ -43,4 +44,5 @@ gradlew.bat
**/DO_NOT_EDIT.txt
Plugins/**/_PluginsShared
Plugins/**/0_PLEASE_SYMLINK*
Plugins/PluginsShared/JavaKitConfigurationShared
Plugins/PluginsShared/JavaKitConfigurationShared
Sources/_Subprocess/_nio_locks.swift
17 changes: 17 additions & 0 deletions .unacceptablelanguageignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
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/Subprocess.swift
21 changes: 8 additions & 13 deletions JavaKit/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,13 @@ tasks.processResources {
}
}

//task fatJar(type: Jar) {
// archiveBaseName = 'java-kit-fat-jar'
// duplicatesStrategy = DuplicatesStrategy.EXCLUDE
// from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
// with jar
//}
// Task necessary to bootstrap and EXIT, this is to prevent hangs when used via SwiftPM plugin.
tasks.register('printRuntimeClasspath') {
dependsOn 'jar'

// Task necessary to bootstrap
task printRuntimeClasspath {
def runtimeClasspath = sourceSets.main.runtimeClasspath
inputs.files(runtimeClasspath)
doLast {
println("CLASSPATH:${runtimeClasspath.asPath}")
}
def runtimeClasspath = sourceSets.main.runtimeClasspath
inputs.files(runtimeClasspath)
doLast {
println("SWIFT_JAVA_CLASSPATH:${runtimeClasspath.asPath}")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
@SuppressWarnings("unused")
public class DependencyResolver {

private static final String COMMAND_OUTPUT_LINE_PREFIX_CLASSPATH = "CLASSPATH:";
private static final String COMMAND_OUTPUT_LINE_PREFIX_CLASSPATH = "SWIFT_JAVA_CLASSPATH:";
private static final String CLASSPATH_CACHE_FILENAME = "JavaKitDependencyResolver.swift-java.classpath";

public static String GRADLE_API_DEPENDENCY = "dev.gradleplugins:gradle-api:8.10.1";
Expand Down Expand Up @@ -236,11 +236,11 @@ private static void printBuildFiles(File projectDir, String[] dependencies) thro
writer.println("}");

writer.println("""
task printRuntimeClasspath {
tasks.register("printRuntimeClasspath") {
def runtimeClasspath = sourceSets.main.runtimeClasspath
inputs.files(runtimeClasspath)
doLast {
println("CLASSPATH:${runtimeClasspath.asPath}")
println("SWIFT_JAVA_CLASSPATH:${runtimeClasspath.asPath}")
}
}
""");
Expand Down
49 changes: 47 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ let package = Package(
"JExtractSwiftCommandPlugin"
]
),

// ==== Examples

.library(
Expand All @@ -152,6 +152,9 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/swiftlang/swift-syntax", from: "600.0.1"),
.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"),

// Benchmarking
.package(url: "https://github.com/ordo-one/package-benchmark", .upToNextMajor(from: "1.4.0")),
],
targets: [
Expand Down Expand Up @@ -184,6 +187,11 @@ let package = Package(
.swiftLanguageMode(.v5),
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]),
]
// // FIXME: when the tool is run from plugin it hangs even if sandbox is disabled
// ,
// plugins: [
// "SwiftJavaBootstrapJavaPlugin",
// ]
),

.target(
Expand Down Expand Up @@ -211,7 +219,7 @@ let package = Package(
.unsafeFlags(
[
"-L\(javaHome)/lib"
],
],
.when(platforms: [.windows])),
.linkedLibrary("jvm"),
]
Expand Down Expand Up @@ -378,6 +386,25 @@ let package = Package(
]
),

.executableTarget(
name: "SwiftJavaBootstrapJavaTool",
dependencies: [
"JavaKitConfigurationShared", // for Configuration reading at runtime
"_Subprocess",
],
swiftSettings: [
.swiftLanguageMode(.v5)
]
),

.plugin(
name: "SwiftJavaBootstrapJavaPlugin",
capability: .buildTool(),
dependencies: [
"SwiftJavaBootstrapJavaTool"
]
),

.plugin(
name: "SwiftJavaPlugin",
capability: .buildTool(),
Expand Down Expand Up @@ -442,6 +469,24 @@ let package = Package(
.swiftLanguageMode(.v5),
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"])
]
),

// Experimental Foundation Subprocess Copy
.target(
name: "_CShims",
swiftSettings: [
.swiftLanguageMode(.v5)
]
),
.target(
name: "_Subprocess",
dependencies: [
"_CShims",
.product(name: "SystemPackage", package: "swift-system"),
],
swiftSettings: [
.swiftLanguageMode(.v5)
]
)
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
//===----------------------------------------------------------------------===//
//
// 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

fileprivate let SwiftJavaConfigFileName = "swift-java.config"

@main
struct Java2SwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {

var pluginName: String = "swift-java-bootstrap"
var verbose: Bool = getEnvironmentBool("SWIFT_JAVA_VERBOSE")

func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] {
log("Create build commands for target '\(target.name)'")
guard let sourceModule = target.sourceModule else { return [] }

let executable = try context.tool(named: "SwiftJavaBootstrapJavaTool").url
var commands: [Command] = []

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

// The name of the configuration file JavaKit.config from the target for
// which we are generating Swift wrappers for Java classes.
let configFile = URL(filePath: sourceDir)
.appending(path: SwiftJavaConfigFileName)
let config = try readConfiguration(sourceDir: sourceDir)

log("Config on path: \(configFile.path(percentEncoded: false))")
log("Config was: \(config)")
var javaDependencies = config.dependencies ?? []

/// Find the manifest files from other Java2Swift executions in any targets
/// this target depends on.
var dependentConfigFiles: [(String, URL)] = []
func searchForConfigFiles(in target: any Target) {
// log("Search for config files in target: \(target.name)")
let dependencyURL = URL(filePath: target.directory.string)

// Look for a config file within this target.
let dependencyConfigURL = dependencyURL
.appending(path: SwiftJavaConfigFileName)
let dependencyConfigString = dependencyConfigURL
.path(percentEncoded: false)

if FileManager.default.fileExists(atPath: dependencyConfigString) {
dependentConfigFiles.append((target.name, dependencyConfigURL))
}
}

// Process direct dependencies of this target.
for dependency in target.dependencies {
switch dependency {
case .target(let target):
searchForConfigFiles(in: target)

case .product(let product):
for target in product.targets {
searchForConfigFiles(in: target)
}

@unknown default:
break
}
}

// Process indirect target dependencies.
for dependency in target.recursiveTargetDependencies {
searchForConfigFiles(in: dependency)
}

var arguments: [String] = []
arguments += argumentsModuleName(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))

let classes = config.classes ?? [:]
print("Classes to wrap: \(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.
let outputDirectoryGenerated = self.outputDirectory(context: context, generated: true)
let outputSwiftFiles = classes.map { (javaClassName, swiftName) in
let swiftNestedName = swiftName.replacingOccurrences(of: ".", with: "+")
return outputDirectoryGenerated.appending(path: "\(swiftNestedName).swift")
}

arguments += [
"--cache-directory",
context.pluginWorkDirectoryURL.path(percentEncoded: false)
]

// Find the Java .class files generated from prior plugins.
let compiledClassFiles = sourceModule.pluginGeneratedResources.filter { url in
url.pathExtension == "class"
}

if let firstClassFile = compiledClassFiles.first {
// Keep stripping off parts of the path until we hit the "Java" part.
// That's where the class path starts.
var classpath = firstClassFile
while classpath.lastPathComponent != "Java" {
classpath.deleteLastPathComponent()
}
arguments += ["--classpath", classpath.path()]
}

var fetchDependenciesOutputFiles: [URL] = []
if let dependencies = config.dependencies, !dependencies.isEmpty {
let displayName = "Fetch (Java) dependencies for Swift target \(sourceModule.name)"
log("Prepared: \(displayName)")

let arguments = [
"--fetch", configFile.path(percentEncoded: false),
"--module-name", sourceModule.name,
"--output-directory", outputDirectory(context: context, generated: false).path(percentEncoded: false)
]

log("Command: \(executable) \(arguments.joined(separator: " "))")

fetchDependenciesOutputFiles += [
outputFilePath(context: context, generated: false, filename: "\(sourceModule.name).swift-java.classpath")
]

commands += [
.buildCommand(
displayName: displayName,
executable: executable,
arguments: arguments,
inputFiles: [configFile],
outputFiles: fetchDependenciesOutputFiles
)
]
} else {
log("No dependencies to fetch for target \(sourceModule.name)")
}

return commands
}
}

extension Java2SwiftBuildToolPlugin {
func argumentsModuleName(sourceModule: Target) -> [String] {
return [
"--module-name", sourceModule.name
]
}

func argumentsOutputDirectory(context: PluginContext, generated: Bool = true) -> [String] {
return [
"--output-directory",
outputDirectory(context: context, generated: generated).path(percentEncoded: false)
]
}

func outputDirectory(context: PluginContext, generated: Bool = true) -> URL {
let dir = context.pluginWorkDirectoryURL
if generated {
return dir.appending(path: "generated")
} else {
return dir
}
}

func outputFilePath(context: PluginContext, generated: Bool, filename: String) -> URL {
outputDirectory(context: context, generated: generated).appending(path: filename)
}
}
1 change: 1 addition & 0 deletions Plugins/SwiftJavaBootstrapJavaPlugin/_PluginsShared
6 changes: 4 additions & 2 deletions Samples/JavaDependencySampleApp/ci-validate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
set -e
set -x

cd ../../JavaKit
./gradlew build
# TODO: this is a workaround for build plugins getting stuck running the bootstrap plugin
cd ../../
swift build --product SwiftJavaBootstrapJavaTool
.build/debug/SwiftJavaBootstrapJavaTool --fetch Sources/JavaKitDependencyResolver/swift-java.config --module-name JavaKitDependencyResolver --output-directory .build/plugins/outputs/swift-java/JavaKitDependencyResolver/destination/SwiftJavaBootstrapJavaPlugin

cd -
swift run --disable-sandbox
Loading
Loading