Skip to content

Commit d3a23cb

Browse files
committed
Include LinkageTest to avoid accidentally linking Foundation
This is based on the impl in swiftlang/swift-java-jni-core#7
1 parent 409e0d0 commit d3a23cb

4 files changed

Lines changed: 256 additions & 0 deletions

File tree

.github/workflows/pull_request.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,3 +268,21 @@ jobs:
268268
SWIFT_VERSION: "${{ matrix.swift_version }}"
269269
- name: "Verify sample ${{ matrix.sample_app }}"
270270
run: .github/scripts/validate_sample.sh Samples/${{ matrix.sample_app }}
271+
272+
linkage-test:
273+
name: Linkage test (jammy swift:6.2)
274+
runs-on: ubuntu-latest
275+
strategy:
276+
fail-fast: false
277+
matrix:
278+
jdk_vendor: ['corretto']
279+
container:
280+
image: swift:6.2-jammy
281+
steps:
282+
- uses: actions/checkout@v6
283+
- name: Prepare CI Environment
284+
uses: ./.github/actions/prepare_env
285+
- name: Run linkage test
286+
run: ./scripts/run-linkage-test.sh
287+
env:
288+
JAVA_HOME: ${{ env.JAVA_HOME }}

Tests/LinkageTest/Package.swift

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
// swift-tools-version: 6.1
2+
3+
import Foundation
4+
import PackageDescription
5+
6+
// Note: the JAVA_HOME environment variable must be set to point to where
7+
// Java is installed, e.g.,
8+
// Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home.
9+
func findJavaHome() -> String {
10+
if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] {
11+
return home
12+
}
13+
14+
// This is a workaround for envs (some IDEs) which have trouble with
15+
// picking up env variables during the build process
16+
let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home"
17+
if let home = try? String(contentsOfFile: path, encoding: .utf8) {
18+
if let lastChar = home.last, lastChar.isNewline {
19+
return String(home.dropLast())
20+
}
21+
22+
return home
23+
}
24+
25+
if let home = getJavaHomeFromLibexecJavaHome(),
26+
!home.isEmpty
27+
{
28+
return home
29+
}
30+
31+
if let home = getJavaHomeFromSDKMAN() {
32+
return home
33+
}
34+
35+
if let home = getJavaHomeFromPath() {
36+
return home
37+
}
38+
39+
if ProcessInfo.processInfo.environment["SPI_PROCESSING"] == "1"
40+
&& ProcessInfo.processInfo.environment["SPI_BUILD"] == nil
41+
{
42+
return ""
43+
}
44+
fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.")
45+
}
46+
47+
/// On MacOS we can use the java_home tool as a fallback if we can't find JAVA_HOME environment variable.
48+
func getJavaHomeFromLibexecJavaHome() -> String? {
49+
let task = Process()
50+
task.executableURL = URL(fileURLWithPath: "/usr/libexec/java_home")
51+
52+
guard FileManager.default.fileExists(atPath: task.executableURL!.path) else {
53+
return nil
54+
}
55+
56+
let pipe = Pipe()
57+
task.standardOutput = pipe
58+
task.standardError = pipe
59+
60+
do {
61+
try task.run()
62+
task.waitUntilExit()
63+
64+
let data = pipe.fileHandleForReading.readDataToEndOfFile()
65+
let output = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)
66+
67+
if task.terminationStatus == 0 {
68+
return output
69+
} else {
70+
return nil
71+
}
72+
} catch {
73+
return nil
74+
}
75+
}
76+
77+
func getJavaHomeFromSDKMAN() -> String? {
78+
let home = FileManager.default.homeDirectoryForCurrentUser
79+
.appendingPathComponent(".sdkman/candidates/java/current")
80+
81+
let javaBin = home.appendingPathComponent("bin/java").path
82+
if FileManager.default.isExecutableFile(atPath: javaBin) {
83+
return home.path
84+
}
85+
return nil
86+
}
87+
88+
func getJavaHomeFromPath() -> String? {
89+
let task = Process()
90+
task.executableURL = URL(fileURLWithPath: "/usr/bin/which")
91+
task.arguments = ["java"]
92+
93+
let pipe = Pipe()
94+
task.standardOutput = pipe
95+
96+
do {
97+
try task.run()
98+
task.waitUntilExit()
99+
guard task.terminationStatus == 0 else { return nil }
100+
101+
let data = pipe.fileHandleForReading.readDataToEndOfFile()
102+
guard
103+
let javaPath = String(data: data, encoding: .utf8)?
104+
.trimmingCharacters(in: .whitespacesAndNewlines),
105+
!javaPath.isEmpty
106+
else { return nil }
107+
108+
let resolved = URL(fileURLWithPath: javaPath).resolvingSymlinksInPath()
109+
return
110+
resolved
111+
.deletingLastPathComponent()
112+
.deletingLastPathComponent()
113+
.path
114+
} catch {
115+
return nil
116+
}
117+
}
118+
119+
let javaHome = findJavaHome()
120+
121+
let javaIncludePath = "\(javaHome)/include"
122+
#if os(Linux)
123+
let javaPlatformIncludePath = "\(javaIncludePath)/linux"
124+
#elseif os(macOS)
125+
let javaPlatformIncludePath = "\(javaIncludePath)/darwin"
126+
#elseif os(Windows)
127+
let javaPlatformIncludePath = "\(javaIncludePath)/win32"
128+
#endif
129+
130+
let package = Package(
131+
name: "linkage-test",
132+
dependencies: [
133+
.package(name: "swift-java", path: "../..")
134+
],
135+
targets: [
136+
.executableTarget(
137+
name: "LinkageTest",
138+
dependencies: [
139+
.product(name: "SwiftJava", package: "swift-java")
140+
],
141+
swiftSettings: [
142+
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"])
143+
],
144+
linkerSettings: [
145+
.unsafeFlags(
146+
[
147+
"-L\(javaHome)/lib/server",
148+
"-Xlinker", "-rpath",
149+
"-Xlinker", "\(javaHome)/lib/server",
150+
],
151+
.when(platforms: [.linux, .macOS])
152+
),
153+
.unsafeFlags(
154+
[
155+
"-L\(javaHome)/lib"
156+
],
157+
.when(platforms: [.windows])
158+
),
159+
.linkedLibrary(
160+
"jvm",
161+
.when(platforms: [.linux, .macOS, .windows])
162+
),
163+
]
164+
)
165+
]
166+
)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2024-2025 Apple Inc. and the Swift.org project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import SwiftJava
16+
17+
let _ = try? JavaVirtualMachine.shared()
18+
let _ = SwiftPlatform.debugOrRelease
19+
print("Linkage test passed: SwiftJava imported successfully.")

scripts/run-linkage-test.sh

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#!/bin/bash
2+
##===----------------------------------------------------------------------===##
3+
##
4+
## This source file is part of the Swift.org open source project
5+
##
6+
## Copyright (c) 2026 Apple Inc. and the Swift.org project authors
7+
## Licensed under Apache License v2.0
8+
##
9+
## See LICENSE.txt for license information
10+
## See CONTRIBUTORS.txt for the list of Swift.org project authors
11+
##
12+
## SPDX-License-Identifier: Apache-2.0
13+
##
14+
##===----------------------------------------------------------------------===##
15+
set -eu
16+
17+
# Validate that we're running on Linux
18+
if [[ "$(uname -s)" != "Linux" ]]; then
19+
echo "Error: This script must be run on Linux. Current OS: $(uname -s)" >&2
20+
exit 1
21+
fi
22+
23+
echo "Detected JAVA_HOME=${JAVA_HOME}"
24+
25+
echo "Running on Linux - proceeding with linkage test..."
26+
27+
# Build the linkage test package
28+
echo "Building linkage test package..."
29+
swift build --package-path Tests/LinkageTest
30+
31+
# Construct build path
32+
build_path=$(swift build --package-path Tests/LinkageTest --show-bin-path)
33+
binary_path="$build_path/LinkageTest"
34+
35+
# Verify the binary exists
36+
if [[ ! -f "$binary_path" ]]; then
37+
echo "Error: Built binary not found at $binary_path" >&2
38+
exit 1
39+
fi
40+
41+
echo "Checking linkage for binary: $binary_path"
42+
43+
# Run ldd and check if libFoundation.so is linked
44+
ldd_output=$(ldd "$binary_path")
45+
echo "LDD output:"
46+
echo "$ldd_output"
47+
48+
if echo "$ldd_output" | grep -q "libFoundation.so"; then
49+
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
50+
exit 1
51+
else
52+
echo "Success: Binary is not linked against libFoundation.so - linkage test passed."
53+
fi

0 commit comments

Comments
 (0)