Skip to content

Commit 0e8e5a6

Browse files
committed
Make bootstrapping from NOTHING possible by having JavaKit include gradle wrapper
1 parent c54fef9 commit 0e8e5a6

File tree

8 files changed

+119
-30
lines changed

8 files changed

+119
-30
lines changed

JavaKit/build.gradle

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,35 @@ tasks.test {
4343
}
4444
}
4545

46+
// Copy the gradle wrapper we're using into the resulting jar's resources.
47+
// We'll use it to bootstrap dependencies (and gradle!) if there is none yet.
48+
tasks.processResources {
49+
from('gradlew') {
50+
into 'gradle/'
51+
}
52+
from('gradlew.bat') {
53+
into 'gradle/'
54+
}
55+
from('../gradle/wrapper/gradle-wrapper.jar') {
56+
into 'gradle/wrapper/'
57+
}
58+
from('../gradle/wrapper/gradle-wrapper.properties') {
59+
into 'gradle/wrapper/'
60+
}
61+
}
62+
63+
//task fatJar(type: Jar) {
64+
// archiveBaseName = 'java-kit-fat-jar'
65+
// duplicatesStrategy = DuplicatesStrategy.EXCLUDE
66+
// from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
67+
// with jar
68+
//}
69+
4670
// Task necessary to bootstrap
4771
task printRuntimeClasspath {
4872
def runtimeClasspath = sourceSets.main.runtimeClasspath
4973
inputs.files(runtimeClasspath)
5074
doLast {
5175
println("CLASSPATH:${runtimeClasspath.asPath}")
5276
}
53-
}
77+
}

JavaKit/src/main/java/org/swift/javakit/dependencies/DependencyResolver.java

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public class DependencyResolver {
5151
public static String resolveDependenciesToClasspath(String projectBaseDirectoryString, String[] dependencies) throws IOException {
5252
try {
5353
simpleLog("Fetch dependencies: " + Arrays.toString(dependencies));
54+
simpleLog("Classpath: " + System.getProperty("java.class.path"));
5455
var projectBasePath = new File(projectBaseDirectoryString).toPath();
5556

5657
File projectDir = Files.createTempDirectory("java-swift-dependencies").toFile();
@@ -118,7 +119,7 @@ private static String resolveDependenciesWithSubprocess(File gradleProjectDir) t
118119
stderrFile.deleteOnExit();
119120

120121
try {
121-
ProcessBuilder gradleBuilder = new ProcessBuilder("gradle", ":printRuntimeClasspath");
122+
ProcessBuilder gradleBuilder = new ProcessBuilder("./gradlew", ":printRuntimeClasspath");
122123
gradleBuilder.directory(gradleProjectDir);
123124
gradleBuilder.redirectOutput(stdoutFile);
124125
gradleBuilder.redirectError(stderrFile);
@@ -172,7 +173,7 @@ public static boolean hasDependencyResolverDependenciesLoaded() {
172173
*
173174
* @return classpath which was resolved for the dependencies
174175
*/
175-
private static String resolveDependenciesUsingAPI(File projectDir, String[] dependencies) throws FileNotFoundException {
176+
private static String resolveDependenciesUsingAPI(File projectDir, String[] dependencies) throws IOException {
176177
printBuildFiles(projectDir, dependencies);
177178

178179
var connection = GradleConnector.newConnector()
@@ -196,7 +197,7 @@ private static String resolveDependenciesUsingAPI(File projectDir, String[] depe
196197

197198
// remove output directories of the project we used for the dependency resolution
198199
var classpath = Arrays.stream(classpathString
199-
.split(":"))
200+
.split(":"))
200201
.filter(s -> !s.startsWith(projectDir.getAbsolutePath()))
201202
.collect(Collectors.joining(":"));
202203

@@ -221,7 +222,8 @@ private static boolean hasDependencyResolverDependenciesLoaded(ClassLoader class
221222
}
222223
}
223224

224-
private static void printBuildFiles(File projectDir, String[] dependencies) throws FileNotFoundException {
225+
private static void printBuildFiles(File projectDir, String[] dependencies) throws IOException {
226+
// === build.gradle
225227
File buildFile = new File(projectDir, "build.gradle");
226228
try (PrintWriter writer = new PrintWriter(buildFile)) {
227229
writer.println("plugins { id 'java-library' }");
@@ -244,12 +246,48 @@ private static void printBuildFiles(File projectDir, String[] dependencies) thro
244246
""");
245247
}
246248

249+
// === settings.gradle
247250
File settingsFile = new File(projectDir, "settings.gradle.kts");
248251
try (PrintWriter writer = new PrintWriter(settingsFile)) {
249252
writer.println("""
250253
rootProject.name = "swift-java-resolve-temp-project"
251254
""");
252255
}
256+
257+
// === gradle wrapper files, so we can even download gradle when necessary to bootstrap
258+
File gradlew = new File(projectDir, "gradlew");
259+
writeResourceToFile("/gradle/gradlew", gradlew);
260+
gradlew.setExecutable(true);
261+
262+
File gradlewBat = new File(projectDir, "gradlew.bat");
263+
writeResourceToFile("/gradle/gradlew.bat", gradlewBat);
264+
gradlew.setExecutable(true);
265+
266+
File gradleDir = new File(projectDir, "gradle");
267+
File gradleWrapperDir = new File(gradleDir, "wrapper");
268+
gradleWrapperDir.mkdirs();
269+
270+
File gradleWrapperJar = new File(gradleWrapperDir, "gradle-wrapper.jar");
271+
writeResourceToFile("/gradle/wrapper/gradle-wrapper.jar", gradleWrapperJar);
272+
File gradleWrapperProps = new File(gradleWrapperDir, "gradle-wrapper.properties");
273+
writeResourceToFile("/gradle/wrapper/gradle-wrapper.properties", gradleWrapperProps);
274+
}
275+
276+
private static void writeResourceToFile(String resource, File target) throws IOException {
277+
try (PrintWriter writer = new PrintWriter(target)) {
278+
try (InputStream inputStream = DependencyResolver.class.getResourceAsStream(resource)) {
279+
if (inputStream == null) {
280+
throw new FileNotFoundException("Not found: gradlew wrapper in resources!");
281+
}
282+
try (var os = new BufferedOutputStream(new FileOutputStream(target))) {
283+
byte[] buffer = new byte[8192]; // Buffer size of 8 KB
284+
int bytesRead;
285+
while ((bytesRead = inputStream.read(buffer)) != -1) {
286+
os.write(buffer, 0, bytesRead);
287+
}
288+
}
289+
}
290+
}
253291
}
254292

255293
private static void simpleLog(String message) {

Samples/JavaDependencySampleApp/Sources/JavaDependencySample/main.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ import Foundation
2020
// Import the commons-csv library wrapper:
2121
import JavaCommonsCSV
2222

23+
print("")
24+
print("")
25+
print("-----------------------------------------------------------------------")
26+
print("Start Sample app...")
27+
2328
// Make sure we have the classpath loaded
2429
// TODO: this is more complex than that, need to account for dependencies of our module
2530
let currentDir = FileManager.default.currentDirectoryPath

Sources/Java2Swift/JavaToSwift+FetchDependencies.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,8 @@ extension JavaToSwift {
3333
}
3434

3535
func fetchDependenciesCachedClasspath() -> [String]? {
36-
guard let cachedClasspathURL = URL(string: "file://" + FileManager.default.currentDirectoryPath + "/" + JavaKitDependencyResolverClasspathCacheFilePath) else {
37-
return []
38-
}
36+
let cachedClasspathURL = URL(
37+
fileURLWithPath: FileManager.default.currentDirectoryPath + "/" + JavaKitDependencyResolverClasspathCacheFilePath)
3938

4039
guard FileManager.default.fileExists(atPath: cachedClasspathURL.path) else {
4140
return []

Sources/Java2Swift/JavaToSwift.swift

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -255,20 +255,23 @@ struct JavaToSwift: ParsableCommand {
255255
}
256256

257257
// Add extra classpath entries which are specific to building the JavaKit project and samples
258-
let classpathBuildJavaKitEntries = [
259-
"JavaKit/build/classes/java/main",
260-
"../../JavaKit/build/classes/java/main",
261-
].filter {
262-
FileManager.default.fileExists(atPath: $0)
263-
}
258+
let classpathBuildJavaKitEntries = [ // FIXME: THIS IS A TRICK UNTIL WE FIGURE OUT HOW TO BOOTSTRAP THIS PART
259+
FileManager.default.currentDirectoryPath,
260+
FileManager.default.currentDirectoryPath + "/.build",
261+
FileManager.default.currentDirectoryPath + "/JavaKit/build/libs",
262+
URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
263+
.deletingLastPathComponent()
264+
.deletingLastPathComponent().absoluteURL.path + "/JavaKit/build/libs/JavaKit-1.0-SNAPSHOT.jar"
265+
]
264266
classpathEntries += classpathBuildJavaKitEntries
265-
267+
266268
// Bring up the Java VM.
267269
// TODO: print only in verbose mode
268-
let jvm = try JavaVirtualMachine.shared(classpath: classpathEntries)
269270
let classpath = classpathEntries.joined(separator: ":")
270271
print("[debug][swift-java] Initialize JVM with classpath: \(classpath)")
271272

273+
let jvm = try JavaVirtualMachine.shared(classpath: classpathEntries)
274+
272275
// FIXME: we should resolve dependencies here perhaps
273276
// if let dependencies = config.dependencies {
274277
// print("[info][swift-java] Resolve dependencies...")
@@ -318,6 +321,8 @@ struct JavaToSwift: ParsableCommand {
318321
fatalError("Fetching dependencies must effective cache directory! Specify --output-directory or --cache-directory")
319322
}
320323

324+
print("[debug][swift-java] Base classpath to fetch dependencies: \(classpathOptionEntries)")
325+
321326
let dependencyClasspath = try fetchDependencies(
322327
moduleName: moduleName,
323328
dependencies: dependencies,

Sources/JavaKit/JavaKitVM/JavaVirtualMachine.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,7 @@ public final class JavaVirtualMachine: @unchecked Sendable {
7373
for path in classpath {
7474
if !fileManager.fileExists(atPath: path) {
7575
// FIXME: this should be configurable, a classpath missing a directory isn't reason to blow up
76-
print("[warning][swift-java][JavaVirtualMachine] Missing classpath element: \(path)") // TODO: stderr
77-
// throw JavaKitError.classpathEntryNotFound(entry: path, classpath: classpath)
76+
print("[warning][swift-java][JavaVirtualMachine] Missing classpath element: \(URL(fileURLWithPath: path).absoluteString)") // TODO: stderr
7877
}
7978
}
8079
let colonSeparatedClassPath = classpath.joined(separator: ":")
@@ -250,6 +249,7 @@ extension JavaVirtualMachine {
250249
// wrapper.
251250
let javaVirtualMachine = JavaVirtualMachine(adoptingJVM: jvm!)
252251
sharedJVMPointer = javaVirtualMachine
252+
print("WAS EXISTING")
253253
return javaVirtualMachine
254254
}
255255

@@ -261,6 +261,7 @@ extension JavaVirtualMachine {
261261
// Create a new instance of the JVM.
262262
let javaVirtualMachine: JavaVirtualMachine
263263
do {
264+
print("CREATE")
264265
javaVirtualMachine = try JavaVirtualMachine(
265266
classpath: classpath,
266267
vmOptions: vmOptions,

Sources/JavaKitConfigurationShared/Configuration.swift

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,18 @@ public struct JavaDependencyDescriptor: Hashable, Codable {
7171
let container = try decoder.singleValueContainer()
7272
let string = try container.decode(String.self)
7373
let parts = string.split(separator: ":")
74+
75+
if parts.count == 1 && string.hasPrefix(":") {
76+
self.groupID = ""
77+
self.artifactID = ":" + String(parts.first!)
78+
self.version = ""
79+
return
80+
}
81+
7482
guard parts.count == 3 else {
75-
throw JavaDependencyDescriptorError(message: "Illegal dependency, did not match: `groupID:artifactID:version`")
83+
throw JavaDependencyDescriptorError(message: "Illegal dependency, did not match: `groupID:artifactID:version`, parts: '\(parts)'")
7684
}
85+
7786
self.groupID = String(parts[0])
7887
self.artifactID = String(parts[1])
7988
self.version = String(parts[2])
@@ -96,14 +105,18 @@ public struct JavaDependencyDescriptor: Hashable, Codable {
96105
public func readConfiguration(sourceDir: String, file: String = #fileID, line: UInt = #line) throws -> Configuration {
97106
// Workaround since filePath is macOS 13
98107
let sourcePath =
99-
if sourceDir.hasPrefix("file://") { sourceDir } else { "file://" + sourceDir }
100-
let configFile = URL(string: sourcePath)!.appendingPathComponent("swift-java.config", isDirectory: false)
108+
if sourceDir.hasPrefix("file://") { sourceDir } else { "file://" + sourceDir }
109+
let configPath = URL(string: sourcePath)!.appendingPathComponent("swift-java.config", isDirectory: false)
110+
111+
return try readConfiguration(configPath: configPath, file: file, line: line)
112+
}
101113

114+
public func readConfiguration(configPath: URL, file: String = #fileID, line: UInt = #line) throws -> Configuration {
102115
do {
103-
let configData = try Data(contentsOf: configFile)
116+
let configData = try Data(contentsOf: configPath)
104117
return try JSONDecoder().decode(Configuration.self, from: configData)
105118
} catch {
106-
throw ConfigurationError(message: "Failed to parse SwiftJava configuration at '\(configFile)'!", error: error,
119+
throw ConfigurationError(message: "Failed to parse SwiftJava configuration at '\(configPath)'!", error: error,
107120
file: file, line: line)
108121
}
109122
}
@@ -125,6 +138,9 @@ public func findSwiftJavaClasspaths(in basePath: String = FileManager.default.cu
125138
print("[debug][swift-java] Constructing classpath with entries from: \(fileURL.relativePath)")
126139
if let contents = try? String(contentsOf: fileURL) {
127140
let entries = contents.split(separator: ":").map(String.init)
141+
for entry in entries {
142+
print("[debug][swift-java] Classpath += \(entry)")
143+
}
128144
classpathEntries += entries
129145
}
130146
}
Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
{
2-
"dependencies": [
3-
"dev.gradleplugins:gradle-api:8.10.1"
4-
],
5-
"classes": {
6-
"org.swift.javakit.dependencies.DependencyResolver": "DependencyResolver"
7-
}
8-
}
2+
"dependencies": [
3+
":JavaKit",
4+
]
5+
,
6+
"__classes": {
7+
"org.swift.javakit.dependencies.DependencyResolver": "DependencyResolver"
8+
}
9+
}

0 commit comments

Comments
 (0)