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
18 changes: 18 additions & 0 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -268,3 +268,21 @@ jobs:
SWIFT_VERSION: "${{ matrix.swift_version }}"
- name: "Verify sample ${{ matrix.sample_app }}"
run: .github/scripts/validate_sample.sh Samples/${{ matrix.sample_app }}

linkage-test:
name: Linkage test (jammy swift:6.2)
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
jdk_vendor: ['corretto']
container:
image: swift:6.2-jammy
steps:
- uses: actions/checkout@v6
- name: Prepare CI Environment
uses: ./.github/actions/prepare_env
- name: Run linkage test
run: ./scripts/run-linkage-test.sh
env:
JAVA_HOME: ${{ env.JAVA_HOME }}
166 changes: 166 additions & 0 deletions Tests/LinkageTest/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// swift-tools-version: 6.1

import Foundation
import PackageDescription

// Note: the JAVA_HOME environment variable must be set to point to where
// Java is installed, e.g.,
// Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home.
func findJavaHome() -> String {
if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] {
return home
}

// This is a workaround for envs (some IDEs) which have trouble with
// picking up env variables during the build process
let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home"
if let home = try? String(contentsOfFile: path, encoding: .utf8) {
if let lastChar = home.last, lastChar.isNewline {
return String(home.dropLast())
}

return home
}

if let home = getJavaHomeFromLibexecJavaHome(),
!home.isEmpty
{
return home
}

if let home = getJavaHomeFromSDKMAN() {
return home
}

if let home = getJavaHomeFromPath() {
return home
}

if ProcessInfo.processInfo.environment["SPI_PROCESSING"] == "1"
&& ProcessInfo.processInfo.environment["SPI_BUILD"] == nil
{
return ""
}
fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.")
}

/// On MacOS we can use the java_home tool as a fallback if we can't find JAVA_HOME environment variable.
func getJavaHomeFromLibexecJavaHome() -> String? {
let task = Process()
task.executableURL = URL(fileURLWithPath: "/usr/libexec/java_home")

guard FileManager.default.fileExists(atPath: task.executableURL!.path) else {
return nil
}

let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe

do {
try task.run()
task.waitUntilExit()

let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)

if task.terminationStatus == 0 {
return output
} else {
return nil
}
} catch {
return nil
}
}

func getJavaHomeFromSDKMAN() -> String? {
let home = FileManager.default.homeDirectoryForCurrentUser
.appendingPathComponent(".sdkman/candidates/java/current")

let javaBin = home.appendingPathComponent("bin/java").path
if FileManager.default.isExecutableFile(atPath: javaBin) {
return home.path
}
return nil
}

func getJavaHomeFromPath() -> String? {
let task = Process()
task.executableURL = URL(fileURLWithPath: "/usr/bin/which")
task.arguments = ["java"]

let pipe = Pipe()
task.standardOutput = pipe

do {
try task.run()
task.waitUntilExit()
guard task.terminationStatus == 0 else { return nil }

let data = pipe.fileHandleForReading.readDataToEndOfFile()
guard
let javaPath = String(data: data, encoding: .utf8)?
.trimmingCharacters(in: .whitespacesAndNewlines),
!javaPath.isEmpty
else { return nil }

let resolved = URL(fileURLWithPath: javaPath).resolvingSymlinksInPath()
return
resolved
.deletingLastPathComponent()
.deletingLastPathComponent()
.path
} catch {
return nil
}
}

let javaHome = findJavaHome()

let javaIncludePath = "\(javaHome)/include"
#if os(Linux)
let javaPlatformIncludePath = "\(javaIncludePath)/linux"
#elseif os(macOS)
let javaPlatformIncludePath = "\(javaIncludePath)/darwin"
#elseif os(Windows)
let javaPlatformIncludePath = "\(javaIncludePath)/win32"
#endif

let package = Package(
name: "linkage-test",
dependencies: [
.package(name: "swift-java", path: "../..")
],
targets: [
.executableTarget(
name: "LinkageTest",
dependencies: [
.product(name: "SwiftJava", package: "swift-java")
],
swiftSettings: [
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"])
],
linkerSettings: [
.unsafeFlags(
[
"-L\(javaHome)/lib/server",
"-Xlinker", "-rpath",
"-Xlinker", "\(javaHome)/lib/server",
],
.when(platforms: [.linux, .macOS])
),
.unsafeFlags(
[
"-L\(javaHome)/lib"
],
.when(platforms: [.windows])
),
.linkedLibrary(
"jvm",
.when(platforms: [.linux, .macOS, .windows])
),
]
)
]
)
19 changes: 19 additions & 0 deletions Tests/LinkageTest/Sources/LinkageTest/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024-2025 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 SwiftJava

let _ = try? JavaVirtualMachine.shared()
let _ = SwiftPlatform.debugOrRelease
print("Linkage test passed: SwiftJava imported successfully.")
53 changes: 53 additions & 0 deletions scripts/run-linkage-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/bin/bash
##===----------------------------------------------------------------------===##
##
## 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
##
##===----------------------------------------------------------------------===##
set -eu

# Validate that we're running on Linux
if [[ "$(uname -s)" != "Linux" ]]; then
echo "Error: This script must be run on Linux. Current OS: $(uname -s)" >&2
exit 1
fi

echo "Detected JAVA_HOME=${JAVA_HOME}"

echo "Running on Linux - proceeding with linkage test..."

# Build the linkage test package
echo "Building linkage test package..."
swift build --package-path Tests/LinkageTest

# Construct build path
build_path=$(swift build --package-path Tests/LinkageTest --show-bin-path)
binary_path="$build_path/LinkageTest"

# Verify the binary exists
if [[ ! -f "$binary_path" ]]; then
echo "Error: Built binary not found at $binary_path" >&2
exit 1
fi

echo "Checking linkage for binary: $binary_path"

# Run ldd and check if libFoundation.so is linked
ldd_output=$(ldd "$binary_path")
echo "LDD output:"
echo "$ldd_output"

if echo "$ldd_output" | grep -q "libFoundation.so"; then
echo "Error: Binary is linked against libFoundation.so - this indicates incorrect linkage. Ensure the full Foundation is not linked on Linux when FoundationEssentials is available." >&2
exit 1
else
echo "Success: Binary is not linked against libFoundation.so - linkage test passed."
fi
Loading