Skip to content
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
14 changes: 14 additions & 0 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,24 @@ jobs:
enable_windows_checks: true
windows_swift_versions: '["nightly-main"]'
enable_macos_checks: true
macos_pre_build_command: brew install openjdk
enable_ios_checks: false
enable_linux_checks: true
linux_os_versions: '["amazonlinux2", "bookworm", "noble", "jammy", "rhel-ubi9"]'
linux_exclude_swift_versions: "[{\"swift_version\": \"5.9\"}, {\"swift_version\": \"5.10\"}]"
linux_pre_build_command: |
if command -v apt-get >/dev/null 2>&1 ; then # bookworm, noble, jammy
if command -v sudo &> /dev/null && [ "$EUID" -ne 0 ]; then
sudo apt-get update -y
sudo apt-get install -y default-jdk
else
apt-get update -y
apt-get install -y default-jdk
fi
elif command -v yum >/dev/null 2>&1 ; then # amazonlinux2, rhel-ubi9
yum update -y
yum install -y java-devel
fi
enable_android_sdk_build: true
enable_android_sdk_checks: true
soundness:
Expand Down
51 changes: 37 additions & 14 deletions Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,42 @@ private func symbol<T>(_ handle: DylibType, _ name: String) -> T? {
}
#endif

/// The Java installation folder for the system, found either with `JAVA_HOME` environment or by checking standard locations
func systemJavaHome() -> String? {
// always defer to JAVA_HOME when it is set in the environment
if let javaHome = ProcessInfo.processInfo.environment["JAVA_HOME"] {
return javaHome
}

// if JAVA_HOME is unset, hunt around some standard locations
var defaultJavaPaths: [String] = []

#if os(macOS)
#if arch(x86_64)
defaultJavaPaths += ["/usr/local/opt/java"] // macOS Homebrew x86
#else
defaultJavaPaths += ["/opt/homebrew/opt/java"] // macOS Homebrew
#endif
defaultJavaPaths += ["/System/Library/Frameworks/JavaVM.framework/Home"] // macOS legacy path
#endif

#if os(Linux)
defaultJavaPaths += [
"/usr/lib/jvm/default-java", // Ubuntu/Debian
"/usr/lib/jvm/default", // Arch
"/usr/lib/jvm/java", // rhel-ubi9, amazonlinux2
]
#endif

if let javaHome = defaultJavaPaths.first(where: {
FileManager.default.fileExists(atPath: $0)
}) {
return javaHome
}

return nil
}

/// Located the shared library that includes the `JNI_GetCreatedJavaVMs` and `JNI_CreateJavaVM` entry points to the `JNINativeInterface` function table
private func loadLibJava() throws -> DylibType {
#if os(Android)
Expand All @@ -437,20 +473,7 @@ private func loadLibJava() throws -> DylibType {
}
#endif

guard
let javaHome = ProcessInfo.processInfo.environment["JAVA_HOME"]
?? {
// if JAVA_HOME is unset, look in some standard locations
[
"/opt/homebrew/opt/java", // macOS Homebrew
"/usr/local/opt/java",
"/usr/lib/jvm/default-java", // Ubuntu/Debian
"/usr/lib/jvm/default", // Arch
].first(where: {
FileManager.default.fileExists(atPath: $0)
})
}()
else {
guard let javaHome = systemJavaHome() else {
throw JavaVirtualMachine.VMError.javaHomeNotFound
}

Expand Down
62 changes: 62 additions & 0 deletions Tests/SwiftJavaJNICoreTests/JavaVirtualMachineTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//===----------------------------------------------------------------------===//
//
// 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 Testing

@testable import SwiftJavaJNICore

@Suite
struct JavaVirtualMachineTests {

static var isSupportedPlatform: Bool {
#if os(Android)
// Android tests are not currently run within an .apk and so do not have any ambient JVM
return false
#else
// disable test when we cannot find a system Java
return systemJavaHome() != nil
#endif
}

@Test(.enabled(if: isSupportedPlatform))
func loadJVMAndCallMethod() throws {
// Load or create a shared JVM instance
let jvm = try JavaVirtualMachine.shared()

// Get the JNI environment for the current thread
let env = try jvm.environment()

// Find java.lang.System
let systemClass = env.interface.FindClass(env, "java/lang/System")
#expect(systemClass != nil, "Failed to find java.lang.System")

// Look up the static method currentTimeMillis()J
let methodID = env.interface.GetStaticMethodID(
env,
systemClass,
"currentTimeMillis",
"()J"
)
#expect(methodID != nil, "Failed to find System.currentTimeMillis")

// Call System.currentTimeMillis() — returns jlong
let timeMillis = env.interface.CallStaticLongMethodA(
env,
systemClass,
methodID,
nil
)
#expect(timeMillis > 0, "Expected a positive timestamp, got \(timeMillis)")
}
}
Loading