Skip to content

Commit d5ade5e

Browse files
authored
Update LinkageTest to check all modules we provide (#816)
1 parent ba6de8c commit d5ade5e

5 files changed

Lines changed: 229 additions & 11 deletions

File tree

‎.github/workflows/pull_request.yml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,11 @@ jobs:
342342
image: swift:6.3-noble
343343
steps:
344344
- uses: actions/checkout@v7
345+
- name: Install jq
346+
shell: bash
347+
run: |
348+
apt-get update -qq
349+
apt-get install -qq -y jq
345350
- name: Prepare CI Environment
346351
uses: ./.github/actions/prepare_env
347352
- name: Cache Tests/LinkageTest/.build

‎Tests/LinkageTest/Package.swift‎

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,26 @@
33
import Foundation
44
import PackageDescription
55

6+
// Every runtime product exported from swift-java should be included here.
7+
// This is to verify none of the modules would accidentally pull in Foundation.
8+
// `scripts/run-linkage-test.sh` enforces set equality with the root package.
9+
let runtimeLibraryProducts: [String] = [
10+
// RUNTIME_LIBRARY_PRODUCTS:START
11+
"JavaIO",
12+
"JavaLangReflect",
13+
"JavaNet",
14+
"JavaUtil",
15+
"JavaUtilFunction",
16+
"JavaUtilJar",
17+
"SwiftJava",
18+
"SwiftRuntimeFunctions",
19+
// RUNTIME_LIBRARY_PRODUCTS:END
20+
]
21+
22+
let runtimeDependencies: [Target.Dependency] = runtimeLibraryProducts.map {
23+
.product(name: $0, package: "swift-java")
24+
}
25+
626
let package = Package(
727
name: "linkage-test",
828
platforms: [
@@ -14,15 +34,11 @@ let package = Package(
1434
targets: [
1535
.executableTarget(
1636
name: "LinkageTest",
17-
dependencies: [
18-
.product(name: "SwiftJava", package: "swift-java")
19-
]
37+
dependencies: runtimeDependencies
2038
),
2139
.executableTarget(
2240
name: "JExtractLinkageTest",
23-
dependencies: [
24-
.product(name: "SwiftJava", package: "swift-java")
25-
],
41+
dependencies: runtimeDependencies,
2642
exclude: [
2743
"swift-java.config"
2844
],

‎Tests/LinkageTest/Sources/JExtractLinkageTest/main.swift‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2024-2025 Apple Inc. and the Swift.org project authors
5+
// Copyright (c) 2024-2026 Apple Inc. and the Swift.org project authors
66
// Licensed under Apache License v2.0
77
//
88
// See LICENSE.txt for license information
@@ -12,7 +12,18 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15+
// See `Package.swift` for the authoritative list.
16+
// IMPORT_RUNTIME_LIBRARY_PRODUCTS:START
17+
import JavaIO
18+
import JavaLangReflect
19+
import JavaNet
20+
import JavaUtil
21+
import JavaUtilFunction
22+
import JavaUtilJar
1523
import SwiftJava
24+
import SwiftRuntimeFunctions
25+
26+
// IMPORT_RUNTIME_LIBRARY_PRODUCTS:END
1627

1728
let _ = try? JavaVirtualMachine.shared()
1829
let _ = SwiftPlatform.debugOrRelease

‎Tests/LinkageTest/Sources/LinkageTest/main.swift‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2024-2025 Apple Inc. and the Swift.org project authors
5+
// Copyright (c) 2024-2026 Apple Inc. and the Swift.org project authors
66
// Licensed under Apache License v2.0
77
//
88
// See LICENSE.txt for license information
@@ -12,7 +12,21 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15+
// Import every runtime library product exported from swift-java. If any of
16+
// these libraries accidentally does `import Foundation` we'll catch it via `ldd`.
17+
//
18+
// See `Package.swift` for the authoritative list.
19+
// IMPORT_RUNTIME_LIBRARY_PRODUCTS:START
20+
import JavaIO
21+
import JavaLangReflect
22+
import JavaNet
23+
import JavaUtil
24+
import JavaUtilFunction
25+
import JavaUtilJar
1526
import SwiftJava
27+
import SwiftRuntimeFunctions
28+
29+
// IMPORT_RUNTIME_LIBRARY_PRODUCTS:END
1630

1731
let _ = try? JavaVirtualMachine.shared()
1832
let _ = SwiftPlatform.debugOrRelease

‎scripts/run-linkage-test.sh‎

Lines changed: 175 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,185 @@
1414
##===----------------------------------------------------------------------===##
1515
set -eu
1616

17-
# Validate that we're running on Linux
18-
if [[ "$(uname -s)" != "Linux" ]]; then
17+
DRY_RUN=false
18+
for arg in "$@"; do
19+
case "$arg" in
20+
--dry-run)
21+
DRY_RUN=true
22+
;;
23+
-h|--help)
24+
cat <<'EOF'
25+
Usage: run-linkage-test.sh [--dry-run]
26+
27+
Verifies that the linkage test package and its source files cover every
28+
runtime library product exported from swift-java, then (on Linux) builds
29+
those executables and confirms none of them link libFoundation.so.
30+
31+
--dry-run Only run the imports-in-sync check.
32+
Skips the Linux-only build + ldd steps.
33+
EOF
34+
exit 0
35+
;;
36+
*)
37+
echo "Error: unknown argument: $arg" >&2
38+
echo "Run with --help for usage." >&2
39+
exit 1
40+
;;
41+
esac
42+
done
43+
44+
# Validate that we're running on Linux (unless we're only doing the
45+
# imports-in-sync check).
46+
if ! $DRY_RUN && [[ "$(uname -s)" != "Linux" ]]; then
1947
echo "Error: This script must be run on Linux. Current OS: $(uname -s)" >&2
48+
echo "Hint: pass --dry-run to run the imports-in-sync check on any OS." >&2
2049
exit 1
2150
fi
2251

23-
echo "Detected JAVA_HOME=${JAVA_HOME}"
52+
if ! $DRY_RUN; then
53+
echo "Detected JAVA_HOME=${JAVA_HOME}"
54+
fi
55+
56+
# Verify that Tests/LinkageTest/Package.swift and tests include all the runtime libraries.
57+
# This is because if any of them accidentally pulls Foundation, that'd be bad.
58+
#
59+
# We inspect:
60+
# - any library product whose name starts with "Java"
61+
# - "SwiftJava"
62+
# - "SwiftRuntimeFunctions"
63+
# Anything else (tooling, docs, samples, `*Static` linkage variants) is
64+
# ignored on purpose.
65+
#
66+
# The expected list is derived from `swift package describe --type json` on
67+
# the root package so it stays honest as products are added or renamed.
68+
verify_linkage_test_covers_runtime_products() {
69+
local linkage_pkg="Tests/LinkageTest/Package.swift"
70+
71+
if [[ ! -f "Package.swift" || ! -f "$linkage_pkg" ]]; then
72+
echo "Error: cannot find Package.swift or $linkage_pkg from $(pwd)" >&2
73+
exit 1
74+
fi
75+
76+
if ! command -v jq >/dev/null 2>&1; then
77+
echo "Error: 'jq' is required to parse 'swift package describe --type json'." >&2
78+
echo "Install it with your package manager (e.g. 'apt-get install -y jq') and re-run." >&2
79+
exit 1
80+
fi
81+
82+
local expected
83+
expected=$(
84+
swift package --disable-sandbox describe --type json \
85+
| jq -r '
86+
.products[]
87+
| select(.type.library != null)
88+
| .name
89+
| select(startswith("Java") or . == "SwiftJava" or . == "SwiftRuntimeFunctions")
90+
' \
91+
| sort -u
92+
)
93+
94+
if [[ -z "$expected" ]]; then
95+
echo "Error: could not extract runtime library products from 'swift package describe'." >&2
96+
echo "The parser in run-linkage-test.sh likely needs updating." >&2
97+
exit 1
98+
fi
99+
100+
# Actual list, between the markers in Tests/LinkageTest/Package.swift.
101+
local actual
102+
actual=$(
103+
awk '
104+
/RUNTIME_LIBRARY_PRODUCTS:START/ { collecting = 1; next }
105+
/RUNTIME_LIBRARY_PRODUCTS:END/ { collecting = 0 }
106+
collecting && match($0, /"[^"]+"/) {
107+
name = substr($0, RSTART + 1, RLENGTH - 2)
108+
print name
109+
}
110+
' "$linkage_pkg" | sort -u
111+
)
112+
113+
if [[ -z "$actual" ]]; then
114+
echo "Error: no entries found between RUNTIME_LIBRARY_PRODUCTS:START/END in $linkage_pkg." >&2
115+
echo "The linkage test would silently under-cover the runtime libraries." >&2
116+
exit 1
117+
fi
118+
119+
local missing extra
120+
missing=$(comm -23 <(printf '%s\n' "$expected") <(printf '%s\n' "$actual"))
121+
extra=$(comm -13 <(printf '%s\n' "$expected") <(printf '%s\n' "$actual"))
122+
123+
if [[ -n "$missing" || -n "$extra" ]]; then
124+
echo "Error: $linkage_pkg RUNTIME_LIBRARY_PRODUCTS list is out of sync with the root package." >&2
125+
if [[ -n "$missing" ]]; then
126+
echo " Missing from linkage test (add these):" >&2
127+
printf '%s\n' "$missing" | sed 's/^/ /' >&2
128+
fi
129+
if [[ -n "$extra" ]]; then
130+
echo " Present in linkage test but not a runtime library product:" >&2
131+
printf '%s\n' "$extra" | sed 's/^/ /' >&2
132+
fi
133+
echo "Update the RUNTIME_LIBRARY_PRODUCTS list and re-run." >&2
134+
exit 1
135+
fi
136+
137+
# Every linkage-test source file that imports the runtime products must also stay in sync.
138+
# Sources delimit their `import` block with `IMPORT_RUNTIME_LIBRARY_PRODUCTS:START` / `:END`.
139+
local import_source
140+
for import_source in \
141+
Tests/LinkageTest/Sources/LinkageTest/main.swift \
142+
Tests/LinkageTest/Sources/JExtractLinkageTest/main.swift
143+
do
144+
if [[ ! -f "$import_source" ]]; then
145+
echo "Error: expected linkage-test source $import_source not found." >&2
146+
exit 1
147+
fi
148+
149+
local imported
150+
imported=$(
151+
awk '
152+
/IMPORT_RUNTIME_LIBRARY_PRODUCTS:START/ { collecting = 1; next }
153+
/IMPORT_RUNTIME_LIBRARY_PRODUCTS:END/ { collecting = 0 }
154+
collecting && match($0, /^[[:space:]]*import[[:space:]]+[A-Za-z_][A-Za-z0-9_]*/) {
155+
line = substr($0, RSTART, RLENGTH)
156+
sub(/^[[:space:]]*import[[:space:]]+/, "", line)
157+
print line
158+
}
159+
' "$import_source" | sort -u
160+
)
161+
162+
if [[ -z "$imported" ]]; then
163+
echo "Error: no imports found between IMPORT_RUNTIME_LIBRARY_PRODUCTS:START/END in $import_source." >&2
164+
exit 1
165+
fi
166+
167+
local src_missing src_extra
168+
src_missing=$(comm -23 <(printf '%s\n' "$expected") <(printf '%s\n' "$imported"))
169+
src_extra=$(comm -13 <(printf '%s\n' "$expected") <(printf '%s\n' "$imported"))
170+
171+
if [[ -n "$src_missing" || -n "$src_extra" ]]; then
172+
echo "Error: imports in $import_source are out of sync with the root package." >&2
173+
if [[ -n "$src_missing" ]]; then
174+
echo " Missing imports (add these):" >&2
175+
printf '%s\n' "$src_missing" | sed 's/^/ import /' >&2
176+
fi
177+
if [[ -n "$src_extra" ]]; then
178+
echo " Imports not backed by a runtime library product:" >&2
179+
printf '%s\n' "$src_extra" | sed 's/^/ import /' >&2
180+
fi
181+
echo "Update the import block between the IMPORT_RUNTIME_LIBRARY_PRODUCTS markers and re-run." >&2
182+
exit 1
183+
fi
184+
done
185+
186+
echo "Linkage test covers all runtime library products:"
187+
printf '%s\n' "$expected" | sed 's/^/ /'
188+
}
189+
190+
verify_linkage_test_covers_runtime_products
191+
192+
if $DRY_RUN; then
193+
echo "--dry-run: imports-in-sync check passed; skipping Linux build + ldd."
194+
exit 0
195+
fi
24196

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

0 commit comments

Comments
 (0)