From 3497292c015491e6d8e958d667a967e37f499a21 Mon Sep 17 00:00:00 2001 From: Mauricio Galindo Date: Fri, 17 Apr 2026 10:02:20 -0700 Subject: [PATCH 1/7] Add swift.static_stdlib feature to link Swift runtime statically TODO(#8) in swift/toolchains/swift_toolchain.bzl. Introduces SWIFT_FEATURE_STATIC_STDLIB, which, when enabled resolves the standard library and runtime (including swiftrt.o) from the toolchain's `lib/swift_static/{os}` tree instead of the shared `lib/swift/{os}` tree and drops the shared-library rpath --- swift/internal/feature_names.bzl | 7 +++++++ swift/toolchains/swift_toolchain.bzl | 30 ++++++++++++++++++++++------ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/swift/internal/feature_names.bzl b/swift/internal/feature_names.bzl index 232044502..df2e57f41 100644 --- a/swift/internal/feature_names.bzl +++ b/swift/internal/feature_names.bzl @@ -364,6 +364,13 @@ SWIFT_FEATURE_LLD_GC_WORKAROUND = "swift.lld_gc_workaround" # objects if you know that isn't required. SWIFT_FEATURE_OBJC_LINK_FLAGS = "swift.objc_link_flag" +# If enabled, binaries linked with a Linux Swift toolchain will +# statically link the Swift standard library and runtime by resolving them from +# the toolchain's `lib/swift_static/{os}` directory instead of the shared +# `lib/swift/{os}` directory, and will omit the runtime rpath entry that points +# at the shared library directory. +SWIFT_FEATURE_STATIC_STDLIB = "swift.static_stdlib" + # If enabled, requests the `-enforce-exclusivity=checked` swiftc flag which # enables runtime checking of exclusive memory access on mutation. SWIFT_FEATURE_CHECKED_EXCLUSIVITY = "swift.checked_exclusivity" diff --git a/swift/toolchains/swift_toolchain.bzl b/swift/toolchains/swift_toolchain.bzl index 8dccb2102..f90ae27ea 100644 --- a/swift/toolchains/swift_toolchain.bzl +++ b/swift/toolchains/swift_toolchain.bzl @@ -53,6 +53,7 @@ load("//swift/internal:autolinking.bzl", "autolink_extract_action_configs") load( "//swift/internal:feature_names.bzl", "SWIFT_FEATURE_MODULE_MAP_HOME_IS_CWD", + "SWIFT_FEATURE_STATIC_STDLIB", "SWIFT_FEATURE_USE_AUTOLINK_EXTRACT", "SWIFT_FEATURE_USE_GLOBAL_INDEX_STORE", "SWIFT_FEATURE_USE_MODULE_WRAP", @@ -362,7 +363,8 @@ def _swift_unix_linkopts_cc_info( toolchain_label, toolchain_root, additional_inputs, - additional_rpaths): + additional_rpaths, + static_stdlib): """Returns a `CcInfo` containing flags that should be passed to the linker. The provider returned by this function will be used as an implicit @@ -378,15 +380,19 @@ def _swift_unix_linkopts_cc_info( toolchain_root: The toolchain's root directory. additional_inputs: depset of File objects to add to link actions. additional_rpaths: list of additional RPATH to add at link time. + static_stdlib: If `True`, link the Swift standard library and runtime + statically by sourcing them from `lib/swift_static/{os}` instead of + `lib/swift/{os}` and omitting the runtime rpath entry. Returns: A `CcInfo` provider that will provide linker flags to binaries that depend on Swift targets. """ - # TODO(#8): Support statically linking the Swift runtime. - platform_lib_dir = "{toolchain_root}/lib/swift/{os}".format( + stdlib_subdir = "swift_static" if static_stdlib else "swift" + platform_lib_dir = "{toolchain_root}/lib/{stdlib_subdir}/{os}".format( os = os, + stdlib_subdir = stdlib_subdir, toolchain_root = toolchain_root, ) @@ -398,17 +404,28 @@ def _swift_unix_linkopts_cc_info( linkopts = [ "-pie", "-L{}".format(platform_lib_dir), - "-Wl,-rpath,{}".format(platform_lib_dir), + ] + if not static_stdlib: + linkopts.append("-Wl,-rpath,{}".format(platform_lib_dir)) + linkopts.extend([ "-lm", "-lstdc++", "-lrt", "-ldl", + ]) + if static_stdlib: + # When linking the Swift runtime statically, its archives depend on + # pthread symbols directly rather than picking them up transitively + # from the shared runtime. + linkopts.append("-lpthread") + linkopts.extend([ runtime_object_path, "-static-libgcc", - ] + [ + ]) + linkopts.extend([ "-Wl,-rpath,{}".format(rpath) for rpath in additional_rpaths - ] + ]) return CcInfo( linking_context = cc_common.create_linking_context( @@ -495,6 +512,7 @@ def _swift_toolchain_impl(ctx): toolchain_root, ctx.attr.swift_tools[SwiftToolsInfo].additional_inputs if ctx.attr.swift_tools else [], additional_rpaths, + SWIFT_FEATURE_STATIC_STDLIB in ctx.features, ) # TODO: Remove once we drop bazel 7.x support From d7a425e9066813d2148a77a142b6e16f20e67a5f Mon Sep 17 00:00:00 2001 From: Mauricio G Date: Mon, 20 Apr 2026 10:30:54 -0700 Subject: [PATCH 2/7] Update swift_toolchain.bzl --- swift/toolchains/swift_toolchain.bzl | 7 ------- 1 file changed, 7 deletions(-) diff --git a/swift/toolchains/swift_toolchain.bzl b/swift/toolchains/swift_toolchain.bzl index f90ae27ea..cd90f431a 100644 --- a/swift/toolchains/swift_toolchain.bzl +++ b/swift/toolchains/swift_toolchain.bzl @@ -412,13 +412,6 @@ def _swift_unix_linkopts_cc_info( "-lstdc++", "-lrt", "-ldl", - ]) - if static_stdlib: - # When linking the Swift runtime statically, its archives depend on - # pthread symbols directly rather than picking them up transitively - # from the shared runtime. - linkopts.append("-lpthread") - linkopts.extend([ runtime_object_path, "-static-libgcc", ]) From 1cb6beca220207d952c481bfb307e908e8fe9342 Mon Sep 17 00:00:00 2001 From: Mauricio G Date: Mon, 20 Apr 2026 10:31:29 -0700 Subject: [PATCH 3/7] Update swift_toolchain.bzl --- swift/toolchains/swift_toolchain.bzl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/swift/toolchains/swift_toolchain.bzl b/swift/toolchains/swift_toolchain.bzl index cd90f431a..420531abd 100644 --- a/swift/toolchains/swift_toolchain.bzl +++ b/swift/toolchains/swift_toolchain.bzl @@ -414,11 +414,10 @@ def _swift_unix_linkopts_cc_info( "-ldl", runtime_object_path, "-static-libgcc", - ]) - linkopts.extend([ + ])+ [ "-Wl,-rpath,{}".format(rpath) for rpath in additional_rpaths - ]) + ] return CcInfo( linking_context = cc_common.create_linking_context( From 86bd0181381b51c783a4bf5ac4dc92851cde1f2c Mon Sep 17 00:00:00 2001 From: Mauricio G Date: Mon, 20 Apr 2026 10:31:58 -0700 Subject: [PATCH 4/7] Update swift_toolchain.bzl --- swift/toolchains/swift_toolchain.bzl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swift/toolchains/swift_toolchain.bzl b/swift/toolchains/swift_toolchain.bzl index 420531abd..4ddef49b2 100644 --- a/swift/toolchains/swift_toolchain.bzl +++ b/swift/toolchains/swift_toolchain.bzl @@ -414,7 +414,7 @@ def _swift_unix_linkopts_cc_info( "-ldl", runtime_object_path, "-static-libgcc", - ])+ [ + ] + [ "-Wl,-rpath,{}".format(rpath) for rpath in additional_rpaths ] From 0d9662f246796e78b3a4f8b5bdc7a84637898090 Mon Sep 17 00:00:00 2001 From: Mauricio G Date: Mon, 20 Apr 2026 10:37:00 -0700 Subject: [PATCH 5/7] Update swift_toolchain.bzl --- swift/toolchains/swift_toolchain.bzl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swift/toolchains/swift_toolchain.bzl b/swift/toolchains/swift_toolchain.bzl index 4ddef49b2..7f7863adb 100644 --- a/swift/toolchains/swift_toolchain.bzl +++ b/swift/toolchains/swift_toolchain.bzl @@ -417,7 +417,7 @@ def _swift_unix_linkopts_cc_info( ] + [ "-Wl,-rpath,{}".format(rpath) for rpath in additional_rpaths - ] + ]) return CcInfo( linking_context = cc_common.create_linking_context( From 2aaf1e7ab158213793f26328871ab1d7fb11e353 Mon Sep 17 00:00:00 2001 From: Mauricio Galindo Date: Mon, 20 Apr 2026 10:57:21 -0700 Subject: [PATCH 6/7] Verify the runtime is indeed statically linked --- test/BUILD | 3 ++ test/fixtures/static_stdlib/BUILD | 14 ++++++++ test/fixtures/static_stdlib/main.swift | 1 + test/rules/BUILD | 5 ++- test/rules/static_stdlib_link_test.bzl | 48 ++++++++++++++++++++++++++ test/rules/verify_static_stdlib.sh | 38 ++++++++++++++++++++ test/static_stdlib_tests.bzl | 44 +++++++++++++++++++++++ 7 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/static_stdlib/BUILD create mode 100644 test/fixtures/static_stdlib/main.swift create mode 100644 test/rules/static_stdlib_link_test.bzl create mode 100755 test/rules/verify_static_stdlib.sh create mode 100644 test/static_stdlib_tests.bzl diff --git a/test/BUILD b/test/BUILD index 1ad5fb98d..c4ab8e433 100644 --- a/test/BUILD +++ b/test/BUILD @@ -22,6 +22,7 @@ load(":private_deps_tests.bzl", "private_deps_test_suite") load(":private_swiftinterface_tests.bzl", "private_swiftinterface_test_suite") load(":runtime_deps_tests.bzl", "runtime_deps_test_suite") load(":split_derived_files_tests.bzl", "split_derived_files_test_suite") +load(":static_stdlib_tests.bzl", "static_stdlib_test_suite") load(":swift_binary_linking_tests.bzl", "swift_binary_linking_test_suite") load(":swift_through_non_swift_tests.bzl", "swift_through_non_swift_test_suite") load(":swift_toolchain_tests.bzl", "swift_toolchain_test_suite") @@ -70,6 +71,8 @@ private_deps_test_suite(name = "private_deps") split_derived_files_test_suite(name = "split_derived_files") +static_stdlib_test_suite(name = "static_stdlib") + swift_binary_linking_test_suite(name = "swift_binary_rules") swift_through_non_swift_test_suite(name = "swift_through_non_swift") diff --git a/test/fixtures/static_stdlib/BUILD b/test/fixtures/static_stdlib/BUILD new file mode 100644 index 000000000..5e85319bb --- /dev/null +++ b/test/fixtures/static_stdlib/BUILD @@ -0,0 +1,14 @@ +load("//swift:swift_binary.bzl", "swift_binary") +load("//test/fixtures:common.bzl", "FIXTURE_TAGS") + +package( + default_visibility = ["//test:__subpackages__"], +) + +licenses(["notice"]) + +swift_binary( + name = "bin", + srcs = ["main.swift"], + tags = FIXTURE_TAGS, +) diff --git a/test/fixtures/static_stdlib/main.swift b/test/fixtures/static_stdlib/main.swift new file mode 100644 index 000000000..fad60381e --- /dev/null +++ b/test/fixtures/static_stdlib/main.swift @@ -0,0 +1 @@ +print("hello, static stdlib") diff --git a/test/rules/BUILD b/test/rules/BUILD index 2d366f7c5..f76f8cbcf 100644 --- a/test/rules/BUILD +++ b/test/rules/BUILD @@ -11,4 +11,7 @@ bzl_library( ], ) -exports_files(["swift_shell_runner.sh.template"]) +exports_files([ + "swift_shell_runner.sh.template", + "verify_static_stdlib.sh", +]) diff --git a/test/rules/static_stdlib_link_test.bzl b/test/rules/static_stdlib_link_test.bzl new file mode 100644 index 000000000..855060d9d --- /dev/null +++ b/test/rules/static_stdlib_link_test.bzl @@ -0,0 +1,48 @@ +# Copyright 2026 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 + +"""Transition rule that forwards a `swift_binary` built with `swift.static_stdlib`. + +Used to wrap a `swift_binary` target so that it can be consumed as `data` by an +`sh_test` that inspects the linked ELF for dynamic Swift runtime references. +""" + +def _enable_static_stdlib_impl(settings, _attr): + existing = list(settings["//command_line_option:features"]) + if "swift.static_stdlib" not in existing: + existing.append("swift.static_stdlib") + return {"//command_line_option:features": existing} + +_enable_static_stdlib_transition = transition( + implementation = _enable_static_stdlib_impl, + inputs = ["//command_line_option:features"], + outputs = ["//command_line_option:features"], +) + +def _with_static_stdlib_impl(ctx): + target = ctx.attr.target[0] + return [ + DefaultInfo( + files = target[DefaultInfo].files, + runfiles = target[DefaultInfo].default_runfiles, + ), + ] + +with_static_stdlib = rule( + attrs = { + "target": attr.label( + cfg = _enable_static_stdlib_transition, + mandatory = True, + ), + }, + doc = """\ +Rebuilds `target` with `--features=swift.static_stdlib` added and forwards its +files. Intended to be referenced via `data` on an `sh_test`. +""", + implementation = _with_static_stdlib_impl, +) diff --git a/test/rules/verify_static_stdlib.sh b/test/rules/verify_static_stdlib.sh new file mode 100755 index 000000000..ed12345d9 --- /dev/null +++ b/test/rules/verify_static_stdlib.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# Copyright 2026 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 + +set -euo pipefail + +binary="${1:?usage: verify_static_stdlib.sh }" + +if [[ ! -x "$binary" ]]; then + echo "verify_static_stdlib: binary '$binary' not found or not executable" >&2 + exit 1 +fi + +if ! command -v readelf >/dev/null 2>&1; then + echo "verify_static_stdlib: readelf not available; cannot verify" >&2 + exit 1 +fi + +needed=$(readelf -d "$binary" | awk '/\(NEEDED\)/ {print $NF}' | tr -d '[]') + +swift_dyn=$(printf '%s\n' "$needed" | grep -E '^libswift(Core|_Concurrency|_StringProcessing|_RegexParser|Glibc|Dispatch|Foundation)' || true) + +if [[ -n "$swift_dyn" ]]; then + echo "verify_static_stdlib: Swift runtime was dynamically linked — expected static." >&2 + echo "Dynamic swift NEEDED entries:" >&2 + printf ' %s\n' $swift_dyn >&2 + echo >&2 + echo "Full NEEDED list:" >&2 + printf ' %s\n' $needed >&2 + exit 1 +fi + +echo "verify_static_stdlib: OK — no dynamic Swift runtime NEEDED entries in $binary" diff --git a/test/static_stdlib_tests.bzl b/test/static_stdlib_tests.bzl new file mode 100644 index 000000000..8ffb499c3 --- /dev/null +++ b/test/static_stdlib_tests.bzl @@ -0,0 +1,44 @@ +# Copyright 2026 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 + +"""Tests that `--features=swift.static_stdlib` actually links the Swift runtime statically.""" + +load("//test/rules:static_stdlib_link_test.bzl", "with_static_stdlib") + +def static_stdlib_test_suite(name, tags = []): + """Verifies that binaries built with `swift.static_stdlib` are statically linked. + + Produces one `sh_test` (Linux-only) that inspects the linked ELF with + `readelf -d` and asserts no Swift runtime libraries (`libswiftCore`, + `libswift_Concurrency`, etc.) appear as `NEEDED` entries. + + Args: + name: The base name for targets created by this macro. + tags: Additional tags to apply to each target. + """ + all_tags = [name] + tags + + with_static_stdlib( + name = "{}_bin".format(name), + tags = all_tags + ["manual"], + target = "//test/fixtures/static_stdlib:bin", + ) + + native.sh_test( + name = "{}_not_dynamically_linked".format(name), + srcs = ["//test/rules:verify_static_stdlib.sh"], + args = ["$(rootpath :{}_bin)".format(name)], + data = [":{}_bin".format(name)], + tags = all_tags, + target_compatible_with = ["@platforms//os:linux"], + ) + + native.test_suite( + name = name, + tags = all_tags, + ) From a2db4368bfbd6893d7ac5fd8eb74b3e4b5a55641 Mon Sep 17 00:00:00 2001 From: Mauricio Galindo Date: Mon, 20 Apr 2026 11:04:35 -0700 Subject: [PATCH 7/7] Load sh_test from @rules_shell for Bazel 9 compatibility `native.sh_test` was removed in Bazel 9; load it explicitly from `@rules_shell//shell:sh_test.bzl` (already a dependency in MODULE.bazel at rules_shell 0.3.0) so the CI presubmit can load `test/BUILD`. --- test/static_stdlib_tests.bzl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/static_stdlib_tests.bzl b/test/static_stdlib_tests.bzl index 8ffb499c3..cca105495 100644 --- a/test/static_stdlib_tests.bzl +++ b/test/static_stdlib_tests.bzl @@ -8,6 +8,7 @@ """Tests that `--features=swift.static_stdlib` actually links the Swift runtime statically.""" +load("@rules_shell//shell:sh_test.bzl", "sh_test") load("//test/rules:static_stdlib_link_test.bzl", "with_static_stdlib") def static_stdlib_test_suite(name, tags = []): @@ -29,7 +30,7 @@ def static_stdlib_test_suite(name, tags = []): target = "//test/fixtures/static_stdlib:bin", ) - native.sh_test( + sh_test( name = "{}_not_dynamically_linked".format(name), srcs = ["//test/rules:verify_static_stdlib.sh"], args = ["$(rootpath :{}_bin)".format(name)],