Repository navigation
Expand file tree
/
Copy pathCompileJavaTool.swift
More file actions
144 lines (125 loc) · 5.77 KB
/
Copy pathCompileJavaTool.swift
File metadata and controls
144 lines (125 loc) · 5.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2026 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 Subprocess
@testable import SwiftJavaToolLib
/// Utility for compiling Java source files using javac in tests.
struct CompileJavaTool {
/// Compiles multiple Java source files together, supporting different packages.
///
/// - Parameter sourceFiles: A dictionary mapping relative file paths
/// (e.g. `"androidx/annotation/RequiresApi.java"`) to their source text.
/// - Returns: The directory containing compiled `.class` files (the classpath root).
static func compileJavaMultiFile(_ sourceFiles: [String: String]) async throws -> Foundation.URL {
let baseDir = FileManager.default.temporaryDirectory
.appendingPathComponent("swift-java-testing-\(UUID().uuidString)")
let srcDir = baseDir.appendingPathComponent("src")
let classesDir = baseDir.appendingPathComponent("classes")
try FileManager.default.createDirectory(at: srcDir, withIntermediateDirectories: true)
try FileManager.default.createDirectory(at: classesDir, withIntermediateDirectories: true)
var filePaths: [String] = []
for (relativePath, source) in sourceFiles {
let fileURL = srcDir.appendingPathComponent(relativePath)
try FileManager.default.createDirectory(
at: fileURL.deletingLastPathComponent(),
withIntermediateDirectories: true
)
try source.write(to: fileURL, atomically: true, encoding: .utf8)
filePaths.append(fileURL.path)
}
var javacArguments: [String] = ["-d", classesDir.path]
javacArguments.append(contentsOf: filePaths)
let javacProcess = try await Subprocess.run(
.path(.init("\(javaHome)" + "/bin/javac")),
arguments: .init(javacArguments),
output: .string(limit: Int.max, encoding: UTF8.self),
error: .string(limit: Int.max, encoding: UTF8.self)
)
guard javacProcess.terminationStatus.isSuccess else {
let outString = javacProcess.standardOutput
let errString = javacProcess.standardError
fatalError(
"javac failed (\(javacProcess.terminationStatus));\nOUT: \(outString)\nERROR: \(errString)"
)
}
print("Compiled java sources to: \(classesDir)")
return classesDir
}
/// Compiles a single Java source file.
///
/// - Parameter sourceText: The Java source code.
/// - Returns: The directory containing compiled `.class` files (the classpath root).
static func compileJava(_ sourceText: String) async throws -> Foundation.URL {
// Java requires public class files to be named after the class
let sourceFile: Foundation.URL
// Allow arbitrary modifiers between `public` and the decl introducer keyword.
// The `-` is allowed because `non-sealed`.
let publicTypeRegex = #"public\s+(?:[\w-]+\s+)*(?:class|record|interface|enum)\s+(\w+)"#
if let match = sourceText.range(of: publicTypeRegex, options: .regularExpression) {
let classNameRange = sourceText[match]
let className = classNameRange.split(separator: " ").last.map(String.init) ?? "tmp_\(UUID().uuidString)"
let dir = FileManager.default.temporaryDirectory
.appendingPathComponent("swift-java-src-\(UUID().uuidString)")
try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
sourceFile = dir.appendingPathComponent("\(className).java")
try sourceText.write(to: sourceFile, atomically: true, encoding: .utf8)
} else {
sourceFile = try TempFile.create(suffix: "java", sourceText)
}
let classesDir = FileManager.default.temporaryDirectory
.appendingPathComponent("swift-java-testing-\(UUID().uuidString)")
try FileManager.default.createDirectory(at: classesDir, withIntermediateDirectories: true)
let javacProcess = try await Subprocess.run(
.path(.init("\(javaHome)" + "/bin/javac")),
arguments: [
"-d", classesDir.path,
sourceFile.path,
],
output: .string(limit: Int.max, encoding: UTF8.self),
error: .string(limit: Int.max, encoding: UTF8.self)
)
guard javacProcess.terminationStatus.isSuccess else {
let outString = javacProcess.standardOutput
let errString = javacProcess.standardError
fatalError(
"javac '\(sourceFile)' failed (\(javacProcess.terminationStatus));\nOUT: \(outString)\nERROR: \(errString)"
)
}
print("Compiled java sources to: \(classesDir)")
return classesDir
}
/// Packages a classes directory into a JAR file.
///
/// - Parameter classesDir: The directory containing compiled `.class` files.
/// - Returns: The URL of the created JAR file.
static func makeJar(classesDir: Foundation.URL) async throws -> Foundation.URL {
let jarFile = classesDir.deletingLastPathComponent()
.appendingPathComponent("test-\(UUID().uuidString).jar")
let jarProcess = try await Subprocess.run(
.path(.init("\(javaHome)" + "/bin/jar")),
arguments: ["cf", jarFile.path, "-C", classesDir.path, "."],
output: .string(limit: Int.max, encoding: UTF8.self),
error: .string(limit: Int.max, encoding: UTF8.self)
)
guard jarProcess.terminationStatus.isSuccess else {
let outString = jarProcess.standardOutput
let errString = jarProcess.standardError
fatalError(
"jar failed (\(jarProcess.terminationStatus));\nOUT: \(outString)\nERROR: \(errString)"
)
}
print("Created JAR: \(jarFile)")
return jarFile
}
}