Skip to content

Commit 5f85438

Browse files
committed
Add JavaResolver in SwiftJavaToolLib to resolve for ResolveCommand
1 parent afea9f8 commit 5f85438

File tree

3 files changed

+336
-297
lines changed

3 files changed

+336
-297
lines changed

Sources/SwiftJavaTool/Commands/ResolveCommand.swift

Lines changed: 4 additions & 276 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,205 +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 workDir = URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
121-
.appendingPathComponent(".build")
122-
123-
let dependenciesClasspath = await resolveDependencies(dependencies: dependencies, repositories: repositories)
124-
let classpathEntries = dependenciesClasspath.split(separator: ":")
125-
126-
print("[info][swift-java] Resolved classpath for \(deps.count) dependencies of '\(swiftModule)', classpath entries: \(classpathEntries.count), ", terminator: "")
127-
print("done.".green)
128-
129-
for entry in classpathEntries {
130-
print("[info][swift-java] Classpath entry: \(entry)")
131-
}
132-
133-
return ResolvedDependencyClasspath(for: dependencies, classpath: dependenciesClasspath)
134-
}
135-
136-
137-
/// Resolves maven-style dependencies from swift-java.config under temporary project directory.
138-
///
139-
/// - Parameter dependencies: maven-style dependencies to resolve
140-
/// - Parameter repositories: repositories used to resolve dependencies
141-
/// - Returns: Colon-separated classpath
142-
func resolveDependencies(workDir: URL, dependencies: [JavaDependencyDescriptor]) async -> String {
143-
print("Create directory: \(workDir.absoluteString)")
144-
145-
let resolverDir: URL
146-
do {
147-
resolverDir = try createTemporaryDirectory(in: workDir)
148-
} catch {
149-
fatalError("Unable to create temp directory at: \(workDir.absoluteString)! \(error)")
150-
}
151-
defer {
152-
try? FileManager.default.removeItem(at: resolverDir)
153-
}
154-
155-
// We try! because it's easier to track down errors like this than when we bubble up the errors,
156-
// and don't get great diagnostics or backtraces due to how swiftpm plugin tools are executed.
157-
158-
try! copyGradlew(to: resolverDir)
159-
160-
try! printGradleProject(directory: resolverDir, dependencies: dependencies, repositories: repositories)
161-
162-
if #available(macOS 15, *) {
163-
let process = try! await _Subprocess.run(
164-
.path(FilePath(resolverDir.appendingPathComponent("gradlew").path)),
165-
arguments: [
166-
"--no-daemon",
167-
"--rerun-tasks",
168-
"\(printRuntimeClasspathTaskName)",
169-
],
170-
workingDirectory: Optional(FilePath(resolverDir.path)),
171-
// TODO: we could move to stream processing the outputs
172-
output: .string(limit: Int.max, encoding: UTF8.self), // Don't limit output, we know it will be reasonable size
173-
error: .string(limit: Int.max, encoding: UTF8.self) // Don't limit output, we know it will be reasonable size
174-
)
175-
176-
let outString = process.standardOutput ?? ""
177-
let errString = process.standardError ?? ""
178-
179-
let classpathOutput: String
180-
if let found = outString.split(separator: "\n").first(where: { $0.hasPrefix(self.SwiftJavaClasspathPrefix) }) {
181-
classpathOutput = String(found)
182-
} else if let found = errString.split(separator: "\n").first(where: { $0.hasPrefix(self.SwiftJavaClasspathPrefix) }) {
183-
classpathOutput = String(found)
184-
} else {
185-
let suggestDisablingSandbox = "It may be that the Sandbox has prevented dependency fetching, please re-run with '--disable-sandbox'."
186-
fatalError("Gradle output had no SWIFT_JAVA_CLASSPATH! \(suggestDisablingSandbox). \n" +
187-
"Command was: \(CommandLine.arguments.joined(separator: " ").bold)\n" +
188-
"Output was: <<<\(outString)>>>;\n" +
189-
"Err was: <<<\(errString)>>>")
190-
}
191-
192-
return String(classpathOutput.dropFirst(SwiftJavaClasspathPrefix.count))
193-
} else {
194-
// Subprocess is unavailable
195-
fatalError("Subprocess is unavailable yet required to execute `gradlew` subprocess. Please update to macOS 15+")
196-
}
197-
}
198-
199-
/// Creates Gradle project files (build.gradle, settings.gradle.kts) in temporary directory.
200-
func printGradleProject(directory: URL, dependencies: [JavaDependencyDescriptor], repositories: [JavaRepositoryDescriptor]) throws {
201-
let buildGradle = directory
202-
.appendingPathComponent("build.gradle", isDirectory: false)
203-
204-
let buildGradleText =
205-
"""
206-
plugins { id 'java-library' }
207-
repositories {
208-
\(repositories.compactMap({ $0.renderGradleRepository() }).joined(separator: "\n"))
209-
}
210-
211-
dependencies {
212-
\(dependencies.map({ dep in "implementation(\"\(dep.descriptionGradleStyle)\")" }).joined(separator: ",\n"))
213-
}
214-
215-
tasks.register("printRuntimeClasspath") {
216-
def runtimeClasspath = sourceSets.main.runtimeClasspath
217-
inputs.files(runtimeClasspath)
218-
doLast {
219-
println("\(SwiftJavaClasspathPrefix)${runtimeClasspath.asPath}")
220-
}
221-
}
222-
"""
223-
try buildGradleText.write(to: buildGradle, atomically: true, encoding: .utf8)
224-
225-
let settingsGradle = directory
226-
.appendingPathComponent("settings.gradle.kts", isDirectory: false)
227-
let settingsGradleText =
228-
"""
229-
rootProject.name = "swift-java-resolve-temp-project"
230-
"""
231-
try settingsGradleText.write(to: settingsGradle, atomically: true, encoding: .utf8)
232-
}
233-
234-
/// Creates {MySwiftModule}.swift.classpath in the --output-directory.
235-
///
236-
/// - Parameters:
237-
/// - swiftModule: Swift module name for classpath filename (--swift-module value)
238-
/// - outputDirectory: Directory path for classpath file (--output-directory value)
239-
/// - resolvedClasspath: Complete dependency classpath information
240-
///
241-
mutating func writeSwiftJavaClasspathFile(
242-
swiftModule: String,
243-
outputDirectory: String,
244-
resolvedClasspath: ResolvedDependencyClasspath) throws {
245-
// Convert the artifact name to a module name
246-
// e.g. reactive-streams -> ReactiveStreams
247-
248-
// The file contents are just plain
249-
let contents = resolvedClasspath.classpath
250-
251-
let filename = "\(swiftModule).swift-java.classpath"
252-
print("[debug][swift-java] Write resolved dependencies to: \(outputDirectory)/\(filename)")
253-
254-
// Write the file
255-
try writeContents(
256-
contents,
257-
outputDirectory: URL(fileURLWithPath: outputDirectory),
258-
to: filename,
259-
description: "swift-java.classpath file for module \(swiftModule)"
56+
outputDirectory: commonOptions.outputDirectory
26057
)
26158
}
26259

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

Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift

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

0 commit comments

Comments
 (0)