Skip to content

Commit 8fe1360

Browse files
committed
Add JavaResolver in SwiftJavaToolLib to resolve for ResolveCommand
1 parent 967ccaf commit 8fe1360

File tree

3 files changed

+326
-288
lines changed

3 files changed

+326
-288
lines changed

Sources/SwiftJavaTool/Commands/ResolveCommand.swift

Lines changed: 4 additions & 266 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,7 @@
1515
import ArgumentParser
1616
import Foundation
1717
import SwiftJavaToolLib
18-
import SwiftJava
19-
import Foundation
20-
import JavaUtilJar
21-
import SwiftJavaToolLib
2218
import SwiftJavaConfigurationShared
23-
import SwiftJavaShared
24-
import _Subprocess
25-
#if canImport(System)
26-
import System
27-
#else
28-
@preconcurrency import SystemPackage
29-
#endif
3019

3120
typealias Configuration = SwiftJavaConfigurationShared.Configuration
3221

@@ -58,195 +47,13 @@ extension SwiftJava {
5847
}
5948

6049
extension SwiftJava.ResolveCommand {
61-
var SwiftJavaClasspathPrefix: String { "SWIFT_JAVA_CLASSPATH:" }
62-
var printRuntimeClasspathTaskName: String { "printRuntimeClasspath" }
6350

6451
mutating func runSwiftJavaCommand(config: inout Configuration) async throws {
65-
var dependenciesToResolve: [JavaDependencyDescriptor] = []
66-
if let input, let inputDependencies = parseDependencyDescriptor(input) {
67-
dependenciesToResolve.append(inputDependencies)
68-
}
69-
if let dependencies = config.dependencies {
70-
dependenciesToResolve += dependencies
71-
}
72-
73-
if dependenciesToResolve.isEmpty {
74-
print("[warn][swift-java] Attempted to 'resolve' dependencies but no dependencies specified in swift-java.config or command input!")
75-
return
76-
}
77-
78-
var configuredRepositories: [JavaRepositoryDescriptor] = []
79-
80-
if let repositories = config.repositories {
81-
configuredRepositories += repositories
82-
}
83-
84-
if !configuredRepositories.contains(where: { $0 == .other("mavenCentral") }) {
85-
// swift-java dependencies are originally located in mavenCentral
86-
configuredRepositories.append(.other("mavenCentral"))
87-
}
88-
89-
let dependenciesClasspath =
90-
try await resolveDependencies(swiftModule: swiftModule, dependencies: dependenciesToResolve, repositories: configuredRepositories)
91-
92-
// FIXME: disentangle the output directory from SwiftJava and then make it a required option in this Command
93-
guard let outputDirectory = self.commonOptions.outputDirectory else {
94-
fatalError("error: Must specify --output-directory in 'resolve' mode! This option will become explicitly required")
95-
}
96-
97-
try writeSwiftJavaClasspathFile(
52+
try await JavaResolver.runResolveCommand(
53+
config: &config,
54+
input: input,
9855
swiftModule: swiftModule,
99-
outputDirectory: outputDirectory,
100-
resolvedClasspath: dependenciesClasspath)
101-
}
102-
103-
104-
/// Resolves Java dependencies from swift-java.config and returns classpath information.
105-
///
106-
/// - Parameters:
107-
/// - swiftModule: module name from --swift-module. e.g.: --swift-module MySwiftModule
108-
/// - dependencies: parsed maven-style dependency descriptors (groupId:artifactId:version)
109-
/// from Sources/MySwiftModule/swift-java.config "dependencies" array.
110-
/// - repositories: repositories used to resolve dependencies
111-
///
112-
/// - Throws:
113-
func resolveDependencies(
114-
swiftModule: String, dependencies: [JavaDependencyDescriptor],
115-
repositories: [JavaRepositoryDescriptor]
116-
) async throws -> ResolvedDependencyClasspath {
117-
let deps = dependencies.map { $0.descriptionGradleStyle }
118-
print("[debug][swift-java] Resolve and fetch dependencies for: \(deps)")
119-
120-
let dependenciesClasspath = await resolveDependencies(dependencies: dependencies, repositories: repositories)
121-
let classpathEntries = dependenciesClasspath.split(separator: ":")
122-
123-
print("[info][swift-java] Resolved classpath for \(deps.count) dependencies of '\(swiftModule)', classpath entries: \(classpathEntries.count), ", terminator: "")
124-
print("done.".green)
125-
126-
for entry in classpathEntries {
127-
print("[info][swift-java] Classpath entry: \(entry)")
128-
}
129-
130-
return ResolvedDependencyClasspath(for: dependencies, classpath: dependenciesClasspath)
131-
}
132-
133-
134-
/// Resolves maven-style dependencies from swift-java.config under temporary project directory.
135-
///
136-
/// - Parameter dependencies: maven-style dependencies to resolve
137-
/// - Parameter repositories: repositories used to resolve dependencies
138-
/// - Returns: Colon-separated classpath
139-
func resolveDependencies(dependencies: [JavaDependencyDescriptor], repositories: [JavaRepositoryDescriptor]) async -> String {
140-
let workDir = URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
141-
.appendingPathComponent(".build")
142-
let resolverDir = try! createTemporaryDirectory(in: workDir)
143-
defer {
144-
try? FileManager.default.removeItem(at: resolverDir)
145-
}
146-
147-
// We try! because it's easier to track down errors like this than when we bubble up the errors,
148-
// and don't get great diagnostics or backtraces due to how swiftpm plugin tools are executed.
149-
150-
try! copyGradlew(to: resolverDir)
151-
152-
try! printGradleProject(directory: resolverDir, dependencies: dependencies, repositories: repositories)
153-
154-
if #available(macOS 15, *) {
155-
let process = try! await _Subprocess.run(
156-
.path(FilePath(resolverDir.appendingPathComponent("gradlew").path)),
157-
arguments: [
158-
"--no-daemon",
159-
"--rerun-tasks",
160-
"\(printRuntimeClasspathTaskName)",
161-
],
162-
workingDirectory: Optional(FilePath(resolverDir.path)),
163-
// TODO: we could move to stream processing the outputs
164-
output: .string(limit: Int.max, encoding: UTF8.self), // Don't limit output, we know it will be reasonable size
165-
error: .string(limit: Int.max, encoding: UTF8.self) // Don't limit output, we know it will be reasonable size
166-
)
167-
168-
let outString = process.standardOutput ?? ""
169-
let errString = process.standardError ?? ""
170-
171-
let classpathOutput: String
172-
if let found = outString.split(separator: "\n").first(where: { $0.hasPrefix(self.SwiftJavaClasspathPrefix) }) {
173-
classpathOutput = String(found)
174-
} else if let found = errString.split(separator: "\n").first(where: { $0.hasPrefix(self.SwiftJavaClasspathPrefix) }) {
175-
classpathOutput = String(found)
176-
} else {
177-
let suggestDisablingSandbox = "It may be that the Sandbox has prevented dependency fetching, please re-run with '--disable-sandbox'."
178-
fatalError("Gradle output had no SWIFT_JAVA_CLASSPATH! \(suggestDisablingSandbox). \n" +
179-
"Output was:<<<\(outString)>>>; Err was:<<<\(errString ?? "<empty>")>>>")
180-
}
181-
182-
return String(classpathOutput.dropFirst(SwiftJavaClasspathPrefix.count))
183-
} else {
184-
// Subprocess is unavailable
185-
fatalError("Subprocess is unavailable yet required to execute `gradlew` subprocess. Please update to macOS 15+")
186-
}
187-
}
188-
189-
/// Creates Gradle project files (build.gradle, settings.gradle.kts) in temporary directory.
190-
func printGradleProject(directory: URL, dependencies: [JavaDependencyDescriptor], repositories: [JavaRepositoryDescriptor]) throws {
191-
let buildGradle = directory
192-
.appendingPathComponent("build.gradle", isDirectory: false)
193-
194-
let buildGradleText =
195-
"""
196-
plugins { id 'java-library' }
197-
repositories {
198-
\(repositories.compactMap({ $0.renderGradleRepository() }).joined(separator: "\n"))
199-
}
200-
201-
dependencies {
202-
\(dependencies.map({ dep in "implementation(\"\(dep.descriptionGradleStyle)\")" }).joined(separator: ",\n"))
203-
}
204-
205-
tasks.register("printRuntimeClasspath") {
206-
def runtimeClasspath = sourceSets.main.runtimeClasspath
207-
inputs.files(runtimeClasspath)
208-
doLast {
209-
println("\(SwiftJavaClasspathPrefix)${runtimeClasspath.asPath}")
210-
}
211-
}
212-
"""
213-
try buildGradleText.write(to: buildGradle, atomically: true, encoding: .utf8)
214-
215-
let settingsGradle = directory
216-
.appendingPathComponent("settings.gradle.kts", isDirectory: false)
217-
let settingsGradleText =
218-
"""
219-
rootProject.name = "swift-java-resolve-temp-project"
220-
"""
221-
try settingsGradleText.write(to: settingsGradle, atomically: true, encoding: .utf8)
222-
}
223-
224-
/// Creates {MySwiftModule}.swift.classpath in the --output-directory.
225-
///
226-
/// - Parameters:
227-
/// - swiftModule: Swift module name for classpath filename (--swift-module value)
228-
/// - outputDirectory: Directory path for classpath file (--output-directory value)
229-
/// - resolvedClasspath: Complete dependency classpath information
230-
///
231-
mutating func writeSwiftJavaClasspathFile(
232-
swiftModule: String,
233-
outputDirectory: String,
234-
resolvedClasspath: ResolvedDependencyClasspath) throws {
235-
// Convert the artifact name to a module name
236-
// e.g. reactive-streams -> ReactiveStreams
237-
238-
// The file contents are just plain
239-
let contents = resolvedClasspath.classpath
240-
241-
let filename = "\(swiftModule).swift-java.classpath"
242-
print("[debug][swift-java] Write resolved dependencies to: \(outputDirectory)/\(filename)")
243-
244-
// Write the file
245-
try writeContents(
246-
contents,
247-
outputDirectory: URL(fileURLWithPath: outputDirectory),
248-
to: filename,
249-
description: "swift-java.classpath file for module \(swiftModule)"
56+
outputDirectory: commonOptions.outputDirectory
25057
)
25158
}
25259

@@ -255,74 +62,5 @@ extension SwiftJava.ResolveCommand {
25562
let camelCased = components.map { $0.capitalized }.joined()
25663
return camelCased
25764
}
258-
259-
// copy gradlew & gradle.bat from root, throws error if there is no gradle setup.
260-
func copyGradlew(to resolverWorkDirectory: URL) throws {
261-
var searchDir = URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
262-
263-
while searchDir.pathComponents.count > 1 {
264-
let gradlewFile = searchDir.appendingPathComponent("gradlew")
265-
let gradlewExists = FileManager.default.fileExists(atPath: gradlewFile.path)
266-
guard gradlewExists else {
267-
searchDir = searchDir.deletingLastPathComponent()
268-
continue
269-
}
270-
271-
let gradlewBatFile = searchDir.appendingPathComponent("gradlew.bat")
272-
let gradlewBatExists = FileManager.default.fileExists(atPath: gradlewFile.path)
273-
274-
let gradleDir = searchDir.appendingPathComponent("gradle")
275-
let gradleDirExists = FileManager.default.fileExists(atPath: gradleDir.path)
276-
guard gradleDirExists else {
277-
searchDir = searchDir.deletingLastPathComponent()
278-
continue
279-
}
280-
281-
// TODO: gradle.bat as well
282-
try? FileManager.default.copyItem(
283-
at: gradlewFile,
284-
to: resolverWorkDirectory.appendingPathComponent("gradlew"))
285-
if gradlewBatExists {
286-
try? FileManager.default.copyItem(
287-
at: gradlewBatFile,
288-
to: resolverWorkDirectory.appendingPathComponent("gradlew.bat"))
289-
}
290-
try? FileManager.default.copyItem(
291-
at: gradleDir,
292-
to: resolverWorkDirectory.appendingPathComponent("gradle"))
293-
return
294-
}
295-
}
296-
297-
func createTemporaryDirectory(in directory: URL) throws -> URL {
298-
let uuid = UUID().uuidString
299-
let resolverDirectoryURL = directory.appendingPathComponent("swift-java-dependencies-\(uuid)")
300-
301-
try FileManager.default.createDirectory(at: resolverDirectoryURL, withIntermediateDirectories: true, attributes: nil)
302-
303-
return resolverDirectoryURL
304-
}
305-
306-
}
307-
308-
struct ResolvedDependencyClasspath: CustomStringConvertible {
309-
/// The dependency identifiers this is the classpath for.
310-
let rootDependencies: [JavaDependencyDescriptor]
311-
312-
/// Plain string representation of a Java classpath
313-
let classpath: String
314-
315-
var classpathEntries: [String] {
316-
classpath.split(separator: ":").map(String.init)
317-
}
318-
319-
init(for rootDependencies: [JavaDependencyDescriptor], classpath: String) {
320-
self.rootDependencies = rootDependencies
321-
self.classpath = classpath
322-
}
323-
324-
var description: String {
325-
"JavaClasspath(for: \(rootDependencies), classpath: \(classpath))"
326-
}
32765
}
32866

Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -69,27 +69,7 @@ extension SwiftJavaBaseAsyncParsableCommand {
6969
outputDirectory: Foundation.URL?,
7070
to filename: String,
7171
description: String) throws {
72-
guard let outputDir = outputDirectory else {
73-
print("// \(filename) - \(description)")
74-
print(contents)
75-
return
76-
}
77-
78-
// If we haven't tried to create the output directory yet, do so now before
79-
// we write any files to it.
80-
// if !createdOutputDirectory {
81-
try FileManager.default.createDirectory(
82-
at: outputDir,
83-
withIntermediateDirectories: true
84-
)
85-
// createdOutputDirectory = true
86-
//}
87-
88-
// Write the file:
89-
let file = outputDir.appendingPathComponent(filename)
90-
print("[trace][swift-java] Writing \(description) to '\(file.path)'... ", terminator: "")
91-
try contents.write(to: file, atomically: true, encoding: .utf8)
92-
print("done.".green)
72+
try JavaResolver.writeContents(contents, outputDirectory: outputDirectory, to: filename, description: description)
9373
}
9474
}
9575

@@ -167,4 +147,4 @@ extension SwiftJavaBaseAsyncParsableCommand {
167147
config.logLevel = command.logLevel
168148
return config
169149
}
170-
}
150+
}

0 commit comments

Comments
 (0)