-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathbuild.sh
More file actions
executable file
·479 lines (428 loc) · 16.2 KB
/
Copy pathbuild.sh
File metadata and controls
executable file
·479 lines (428 loc) · 16.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
#!/bin/bash
set -euo pipefail
# Builds and packages the SwiftSyntax prebuilt archive in two phases so the
# work can fan out across heterogeneous CI runners:
#
# ./build.sh build Per-TARGET_PLATFORM. Emits staging/<platform>.tar.gz.
# ./build.sh package Combines all per-platform staging tarballs into the
# final release archive with a select()'d BUILD.bazel.
#
# Required env (build): SWIFT_SYNTAX_VERSION, RULES_SWIFT_VERSION, TARGET_PLATFORM
# Required env (package): SWIFT_SYNTAX_VERSION, RULES_SWIFT_VERSION
# Optional env: BUILD_NUMBER, APPLE_SUPPORT_VERSION, RULES_APPLE_VERSION, MACOS_VERSION
#
# Build phase expects apple/swift-syntax checked out at ./swift-syntax.
APPLE_SUPPORT_VERSION="${APPLE_SUPPORT_VERSION:-2.5.4}"
RULES_APPLE_VERSION="${RULES_APPLE_VERSION:-4.2.0}"
MACOS_VERSION="${MACOS_VERSION:-13.0}"
# Workaround for https://github.com/bazelbuild/bazel/pull/27014 on macOS 26+.
export USE_BAZEL_VERSION="${USE_BAZEL_VERSION:-8.4.2}"
mode="${1:-}"
case "$mode" in
build | package) ;;
*)
echo "usage: $0 <build|package>" >&2
exit 64
;;
esac
if [ -z "${SWIFT_SYNTAX_VERSION:-}" ]; then
echo "error: SWIFT_SYNTAX_VERSION is required" >&2
exit 64
fi
if [ -z "${RULES_SWIFT_VERSION:-}" ]; then
echo "error: RULES_SWIFT_VERSION is required" >&2
exit 64
fi
release_tag="$SWIFT_SYNTAX_VERSION"
if [ -n "${BUILD_NUMBER:-}" ]; then
release_tag="$release_tag+$BUILD_NUMBER"
fi
archive_name="swift-syntax-$release_tag"
staging_root="$PWD/staging"
bazel_cpu_for_target() {
case "$1" in
macos-arm64) echo "darwin_arm64" ;;
macos-x86_64) echo "darwin_x86_64" ;;
linux-x86_64) echo "k8" ;;
linux-arm64) echo "aarch64" ;;
*)
echo "error: unsupported TARGET_PLATFORM '$1'" >&2
exit 64
;;
esac
}
os_family_for_target() {
case "$1" in
macos-*) echo "darwin" ;;
linux-*) echo "linux" ;;
*)
echo "error: unsupported TARGET_PLATFORM '$1'" >&2
exit 64
;;
esac
}
# Echoes the @platforms//os constraint name for a platform dir. The archive
# ships one slice per OS, so the generated select()s key on OS alone -- this
# matches any cpu within an OS (e.g. a macos_x86_64 target gets the macOS
# slice), mirroring the single-archive behavior consumers relied on before.
os_constraint_for_platform() {
case "$1" in
macos-*) echo "macos" ;;
linux-*) echo "linux" ;;
*)
echo "error: unsupported platform '$1'" >&2
exit 64
;;
esac
}
do_build() {
if [ -z "${TARGET_PLATFORM:-}" ]; then
echo "error: TARGET_PLATFORM is required for build mode" >&2
exit 64
fi
local cpu os_family platform_dir
cpu=$(bazel_cpu_for_target "$TARGET_PLATFORM")
os_family=$(os_family_for_target "$TARGET_PLATFORM")
platform_dir="$staging_root/$TARGET_PLATFORM"
rm -rf "$platform_dir"
mkdir -p "$platform_dir"
pushd swift-syntax >/dev/null
# Stash the upstream config; idempotent for repeat runs.
[ -f .bazelrc ] && mv .bazelrc .bazelrc.original
[ -f MODULE.bazel ] && mv MODULE.bazel MODULE.bazel.original
cat >./MODULE.bazel <<EOF
module(name = "swift-syntax", version = "$SWIFT_SYNTAX_VERSION", compatibility_level = 1)
bazel_dep(name = "apple_support", version = "$APPLE_SUPPORT_VERSION", repo_name = "build_bazel_apple_support")
bazel_dep(name = "rules_swift", version = "$RULES_SWIFT_VERSION", repo_name = "build_bazel_rules_swift")
bazel_dep(name = "rules_apple", version = "$RULES_APPLE_VERSION", repo_name = "build_bazel_rules_apple")
EOF
# swift-syntax's BUILD.bazel through 603.0.1 omits SwiftIfConfig
# from SwiftSyntaxMacros' deps even though Package.swift lists it
# and the source `public import`s it. Fixed upstream post-603.0.1
# in 9acadd6e; patch in place so the prebuilt build doesn't fail
# SwiftSyntaxMacros. buildozer exits 3 (no change) if the dep is
# already present -- tolerate that under `set -e`.
# TODO: remove this after swift-syntax 604.0.0 ships.
buildozer "add deps :SwiftIfConfig" //:SwiftSyntaxMacros || [ $? -eq 3 ]
local -a build_flags
build_flags=(
"--@build_bazel_rules_swift//swift:copt=-whole-module-optimization"
"--@build_bazel_rules_swift//swift:exec_copt=-whole-module-optimization"
"--compilation_mode=opt"
"--cpu=$cpu"
"--features=swift.emit_swiftinterface"
# Also emit `<module>.private.swiftinterface` so that consumers can import SPI.
# A public `.swiftinterface` strips all `@_spi` declarations.
"--features=swift.emit_private_swiftinterface"
"--features=swift.enable_library_evolution"
)
if [ "$os_family" = "darwin" ]; then
build_flags+=(
"--host_macos_minimum_os=$MACOS_VERSION"
"--macos_minimum_os=$MACOS_VERSION"
)
fi
# Use `bazel query` (load-phase only) for label discovery; cquery would
# analyze the whole //... universe and fail on Apple-toolchain-only
# sibling targets like //:SwiftCompilerPluginTest.ios on Linux. The
# later --output=files cqueries are on specific labels, so they only
# analyze what we actually need.
local query_out
query_out=$(bazel query "filter(_opt, //...)")
if [ -z "$query_out" ]; then
echo "error: query for _opt swift targets returned no results" >&2
exit 1
fi
local -a labels
labels=()
while IFS= read -r line; do
[ -z "$line" ] || labels+=("$line")
done <<<"$query_out"
query_out=$(bazel query "kind(cc_library, //...)")
if [ -z "$query_out" ]; then
echo "error: query for cc_library targets returned no results" >&2
exit 1
fi
local -a c_deps
c_deps=()
while IFS= read -r line; do
[ -z "$line" ] || c_deps+=("$line")
done <<<"$query_out"
# swift-corelibs-foundation isn't compiled with library evolution support,
# so swift_libraries that import Foundation (notably _SwiftSyntaxTestSupport)
# fail under `swift.enable_library_evolution` on Linux. Drop TestSupport
# from the Linux prebuilt; consumers needing it can build from source.
if [ "$os_family" = "linux" ]; then
local -a filtered_labels filtered_c_deps
filtered_labels=()
for label in "${labels[@]}"; do
case "$label" in
*TestSupport*) ;;
*) filtered_labels+=("$label") ;;
esac
done
labels=("${filtered_labels[@]}")
filtered_c_deps=()
for dep in "${c_deps[@]}"; do
case "$dep" in
*TestSupport*) ;;
*) filtered_c_deps+=("$dep") ;;
esac
done
c_deps=("${filtered_c_deps[@]}")
fi
# Build swift_library and cc_library targets separately; combining them
# exposes a transition mismatch where cc_library outputs land under iOS
# configs inherited from upstream's ios_unit_test rules.
bazel build "${labels[@]}" "${build_flags[@]}"
bazel build "${c_deps[@]}" "${build_flags[@]}"
local meta_file="$platform_dir/_targets.tsv"
: >"$meta_file"
local label non_opt_label module_name dependencies
for label in "${labels[@]}"; do
non_opt_label="${label%_opt}"
module_name=$(buildozer "print name" "$non_opt_label")
dependencies=$(buildozer "print deps" "$non_opt_label" | sed 's/^\[//' | sed 's/\]$//')
if [ "$dependencies" = "(missing)" ]; then
dependencies=""
fi
printf 'swift_import\t%s\t%s\n' "$module_name" "$dependencies" >>"$meta_file"
done
# `cquery --output=files` lists files for every config a target appears in,
# including iOS transitions from upstream's ios_unit_test / ios_xctestrun_runner;
# those paths are never produced by our `bazel build` above, so skip them.
local outputs output
outputs=$(bazel cquery "set(${labels[*]})" --output=files "${build_flags[@]}")
outputs+=$'\n'$(bazel cquery "set(${c_deps[*]})" --output=files "${build_flags[@]}")
for output in $outputs; do
[ -f "$output" ] || continue
case "$output" in
# Drop the position-independent variant Linux toolchains emit alongside
# `libX.a`; we only need the static archive for consumer linking.
*.pic.a) ;;
*.swiftinterface | *.a | *.swiftdoc)
cp -R "$output" "$platform_dir/$(basename "$output")"
;;
esac
done
# Same cquery feeds the metadata row and the staged include/ tree.
local hdrs_dir="$platform_dir/_headers"
rm -rf "$hdrs_dir"
local dep name hdr hdr_path target_hdrs_dir
for dep in "${c_deps[@]}"; do
name=$(buildozer "print name" "$dep")
local -a hdrs=()
while IFS= read -r hdr; do
[ -z "$hdr" ] || hdrs+=("$hdr")
done < <(bazel cquery "$dep" --output=jsonproto "${build_flags[@]}" |
jq -rc '.results[0].target.rule.attribute[] | select(.name == "hdrs").stringListValue | .[]?')
printf 'cc_import\t%s\t%s\n' "$name" "${hdrs[*]}" >>"$meta_file"
if [ "${#hdrs[@]}" -gt 0 ]; then
target_hdrs_dir="$hdrs_dir/$name/include"
mkdir -p "$target_hdrs_dir"
for hdr in "${hdrs[@]}"; do
hdr_path="${hdr#//:}"
cp "$hdr_path" "$target_hdrs_dir/$(basename "$hdr_path")"
done
fi
done
popd >/dev/null
# Fail fast if nothing landed; an empty staging tar breaks the package
# phase in confusing ways downstream.
if ! find "$platform_dir" -maxdepth 1 -type f \
\( -name '*.a' -o -name '*.swiftinterface' -o -name '*.swiftdoc' \) \
| grep -q .; then
echo "error: no .a / .swiftinterface / .swiftdoc outputs landed in $platform_dir" >&2
exit 1
fi
# Every module must ship a `.private.swiftinterface` next to its public one.
# Fail here rather than publishing an archive that looks complete but silently
# cannot serve SPI consumers (see swift.emit_private_swiftinterface above).
local public_iface
while IFS= read -r public_iface; do
if [ ! -f "${public_iface%.swiftinterface}.private.swiftinterface" ]; then
echo "error: no .private.swiftinterface alongside $(basename "$public_iface") in $platform_dir;" >&2
echo " swift.emit_private_swiftinterface did not take effect" >&2
exit 1
fi
done < <(find "$platform_dir" -maxdepth 1 -type f \
-name '*.swiftinterface' ! -name '*.private.swiftinterface')
local platform_tar="$staging_root/${TARGET_PLATFORM}.tar.gz"
tar -czf "$platform_tar" -C "$staging_root" "$TARGET_PLATFORM"
echo "build: wrote $platform_tar"
}
# Echoes a Starlark `select({...})` mapping each platform to <platform>/<basename>.
# kind "list" wraps each value in [] (list attrs like swift_import.archives);
# kind "scalar" emits a bare string (single-label attrs like swiftdoc,
# swiftinterface, cc_import.static_library).
emit_select_for_basename() {
local kind="$1" basename="$2"
shift 2
local plat os
printf 'select({\n'
for plat in "$@"; do
os=$(os_constraint_for_platform "$plat")
if [ "$kind" = "list" ]; then
printf ' "@platforms//os:%s": ["%s/%s"],\n' "$os" "$plat" "$basename"
else
printf ' "@platforms//os:%s": "%s/%s",\n' "$os" "$plat" "$basename"
fi
done
printf ' })'
}
do_package() {
if [ ! -d "$staging_root" ]; then
echo "error: $staging_root not found; run './build.sh build' on each TARGET_PLATFORM first" >&2
exit 64
fi
local tar_file platform
for tar_file in "$staging_root"/*.tar.gz; do
[ -e "$tar_file" ] || continue
platform=$(basename "$tar_file" .tar.gz)
if [ ! -d "$staging_root/$platform" ]; then
tar -xzf "$tar_file" -C "$staging_root"
fi
done
local -a platforms
platforms=()
while IFS= read -r dir; do
[ -z "$dir" ] && continue
platforms+=("$(basename "$dir")")
done < <(find "$staging_root" -mindepth 1 -maxdepth 1 -type d | sort)
if [ ${#platforms[@]} -eq 0 ]; then
echo "error: no per-platform staging directories found in $staging_root" >&2
exit 64
fi
# Dep graph is platform-invariant; any platform's manifest works.
local meta_platform=""
for p in "${platforms[@]}"; do
if [[ "$p" == macos-* ]]; then
meta_platform="$p"
break
fi
done
if [ -z "$meta_platform" ]; then
echo "error: no macOS platform found in staging; cannot determine full module list" >&2
exit 64
fi
local meta_file="$staging_root/$meta_platform/_targets.tsv"
if [ ! -f "$meta_file" ]; then
echo "error: missing metadata file $meta_file" >&2
exit 64
fi
rm -rf "$archive_name"
mkdir -p "$archive_name"
cat >"$archive_name/MODULE.bazel" <<EOF
module(
name = "swift-syntax",
version = "$SWIFT_SYNTAX_VERSION",
compatibility_level = 1,
)
bazel_dep(
name = "platforms",
version = "0.0.8",
)
bazel_dep(
name = "rules_cc",
version = "0.2.14",
)
bazel_dep(
name = "rules_swift",
version = "$RULES_SWIFT_VERSION",
max_compatibility_level = 3,
repo_name = "build_bazel_rules_swift",
)
EOF
cat >"$archive_name/BUILD.bazel" <<'EOF'
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_import")
load("@rules_cc//cc:cc_import.bzl", "cc_import")
# Generated by build.sh package; do not edit.
# Rules select() on @platforms//os: the archive ships one slice per OS.
EOF
local kind name deps
while IFS=$'\t' read -r kind name deps; do
case "$kind" in
swift_import)
{
local -a module_platforms=()
for plat in "${platforms[@]}"; do
[ -f "$staging_root/$plat/lib${name}.a" ] && module_platforms+=("$plat")
done
printf '\nswift_import(\n'
printf ' name = "%s",\n' "$name"
printf ' module_name = "%s",\n' "$name"
printf ' archives = '
emit_select_for_basename list "lib${name}.a" "${module_platforms[@]}"
printf ',\n'
printf ' swiftdoc = '
emit_select_for_basename scalar "${name}.swiftdoc" "${module_platforms[@]}"
printf ',\n'
# Point `swiftinterface` at the private interface, not the public one:
# it is a superset that also carries `@_spi` declarations, which SPI
# consumers need. Refuse to fall back to the public interface -- doing so
# would produce an archive that fails only later, in consumer builds.
for plat in "${module_platforms[@]}"; do
if [ ! -f "$staging_root/$plat/${name}.private.swiftinterface" ]; then
echo "error: missing $plat/${name}.private.swiftinterface;" >&2
echo " rebuild $plat with swift.emit_private_swiftinterface" >&2
exit 1
fi
done
printf ' swiftinterface = '
emit_select_for_basename scalar "${name}.private.swiftinterface" "${module_platforms[@]}"
printf ',\n'
if [ -n "$deps" ]; then
# buildozer emits bare, space-separated labels (`:X :Y`); convert
# to Starlark string list (`":X", ":Y"`).
local quoted_deps
# shellcheck disable=SC2086 # intentional word split on $deps
quoted_deps=$(printf '"%s", ' $deps)
quoted_deps=${quoted_deps%, }
printf ' deps = [%s],\n' "$quoted_deps"
fi
printf ' visibility = ["//visibility:public"],\n'
printf ')\n'
printf '\nalias(\n name = "%s_opt",\n actual = ":%s",\n visibility = ["//visibility:public"],\n)\n' "$name" "$name"
} >>"$archive_name/BUILD.bazel"
;;
cc_import)
# Headers are platform-agnostic; merge once at archive root.
local src_hdrs="$staging_root/${platforms[0]}/_headers/$name/include"
if [ -d "$src_hdrs" ]; then
mkdir -p "$archive_name/$name/include"
cp -R "$src_hdrs/." "$archive_name/$name/include/"
fi
{
printf '\ncc_import(\n'
printf ' name = "%s",\n' "$name"
printf ' static_library = '
emit_select_for_basename scalar "lib${name}.a" "${platforms[@]}"
printf ',\n'
if [ -d "$src_hdrs" ]; then
printf ' hdrs = glob(["%s/include/*.h"]),\n' "$name"
fi
printf ' visibility = ["//visibility:public"],\n'
printf ')\n'
} >>"$archive_name/BUILD.bazel"
;;
esac
done <"$meta_file"
local plat
for plat in "${platforms[@]}"; do
mkdir -p "$archive_name/$plat"
find "$staging_root/$plat" -maxdepth 1 -type f \
\( -name '*.a' -o -name '*.swiftinterface' -o -name '*.swiftdoc' \) \
-exec cp {} "$archive_name/$plat/" \;
done
tar -czf "${archive_name}.tar.gz" "$archive_name"
openssl dgst -sha256 -binary "${archive_name}.tar.gz" |
openssl base64 -A |
sed 's/^/sha256-/' >"${archive_name}.tar.gz.sha256"
local sha256_checksum
sha256_checksum=$(cat "${archive_name}.tar.gz.sha256")
echo "package: wrote ${archive_name}.tar.gz (${sha256_checksum}) covering platforms: ${platforms[*]}"
}
case "$mode" in
build) do_build ;;
package) do_package ;;
esac