diff --git a/MODULE.bazel b/MODULE.bazel index ceb82f839..c88cb6a56 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -38,8 +38,6 @@ use_repo( apple_cc_configure = use_extension("@apple_support//crosstool:setup.bzl", "apple_cc_configure_extension") use_repo(apple_cc_configure, "local_config_apple_cc") -register_toolchains("//swift/toolchains:all") - system_sdk = use_extension("//swift:extensions.bzl", "system_sdk") system_sdk.configure_sdks( # NOTE: This doesn't apply to downstream repos @@ -90,6 +88,9 @@ swift.wasm_sdk( swift.android_sdk( toolchain_name = "swift_toolchain", ) +swift.static_linux_sdk( + toolchain_name = "swift_toolchain", +) use_repo( swift, "swift_toolchain", @@ -117,19 +118,25 @@ register_toolchains( ) register_toolchains( - # Swift SDK toolchains for cross-compiling to WebAssembly and Android; - # used by //examples/cross_compilation. As with the embedded toolchains - # above, we register only the host platforms used by CI rather than - # `@swift_toolchain//:all`, because rules_swift cannot yet auto-select a - # Linux distribution and `:all` would make the host/exec toolchain - # ambiguous across distros. A consumer that builds on a single host - # platform can simply register `@swift_toolchain//:all`. + # Swift SDK toolchains for cross-compiling to WebAssembly, Android, and + # Static Linux; used by //examples/cross_compilation. As with the embedded + # toolchains above, we register only the host platforms used by CI rather + # than `@swift_toolchain//:all`, because rules_swift cannot yet auto-select + # a Linux distribution and `:all` would make the host/exec toolchain + # ambiguous across distros. A consumer that builds on a single host platform + # can simply register `@swift_toolchain//:all`. "@swift_toolchain//:cc_toolchain_android_aarch64_ubuntu22.04", "@swift_toolchain//:cc_toolchain_android_aarch64_ubuntu22.04-aarch64", "@swift_toolchain//:cc_toolchain_android_aarch64_xcode", "@swift_toolchain//:cc_toolchain_android_x86_64_ubuntu22.04", "@swift_toolchain//:cc_toolchain_android_x86_64_ubuntu22.04-aarch64", "@swift_toolchain//:cc_toolchain_android_x86_64_xcode", + "@swift_toolchain//:cc_toolchain_static_linux_aarch64_ubuntu22.04", + "@swift_toolchain//:cc_toolchain_static_linux_aarch64_ubuntu22.04-aarch64", + "@swift_toolchain//:cc_toolchain_static_linux_aarch64_xcode", + "@swift_toolchain//:cc_toolchain_static_linux_x86_64_ubuntu22.04", + "@swift_toolchain//:cc_toolchain_static_linux_x86_64_ubuntu22.04-aarch64", + "@swift_toolchain//:cc_toolchain_static_linux_x86_64_xcode", "@swift_toolchain//:cc_toolchain_wasm32_ubuntu22.04", "@swift_toolchain//:cc_toolchain_wasm32_ubuntu22.04-aarch64", "@swift_toolchain//:cc_toolchain_wasm32_xcode", @@ -139,12 +146,23 @@ register_toolchains( "@swift_toolchain//:swift_toolchain_android_x86_64_ubuntu22.04", "@swift_toolchain//:swift_toolchain_android_x86_64_ubuntu22.04-aarch64", "@swift_toolchain//:swift_toolchain_android_x86_64_xcode", + "@swift_toolchain//:swift_toolchain_static_linux_aarch64_ubuntu22.04", + "@swift_toolchain//:swift_toolchain_static_linux_aarch64_ubuntu22.04-aarch64", + "@swift_toolchain//:swift_toolchain_static_linux_aarch64_xcode", + "@swift_toolchain//:swift_toolchain_static_linux_x86_64_ubuntu22.04", + "@swift_toolchain//:swift_toolchain_static_linux_x86_64_ubuntu22.04-aarch64", + "@swift_toolchain//:swift_toolchain_static_linux_x86_64_xcode", "@swift_toolchain//:swift_toolchain_wasm32_ubuntu22.04", "@swift_toolchain//:swift_toolchain_wasm32_ubuntu22.04-aarch64", "@swift_toolchain//:swift_toolchain_wasm32_xcode", dev_dependency = True, ) +# Keep broad built-in Linux toolchains after Static Linux SDK toolchains: +# Static Linux platforms are also `@platforms//os:linux`, and Bazel chooses the +# first compatible toolchain. +register_toolchains("//swift/toolchains:all") + # Dev dependencies bazel_dep(name = "bazel_skylib_gazelle_plugin", version = "1.5.0", dev_dependency = True) bazel_dep(name = "gazelle", version = "0.46.0", dev_dependency = True) diff --git a/doc/standalone_toolchain.md b/doc/standalone_toolchain.md index 413336918..80ebc2da2 100644 --- a/doc/standalone_toolchain.md +++ b/doc/standalone_toolchain.md @@ -146,7 +146,7 @@ bazel run @rules_swift//tools/swift-releases -- list \ main-snapshot-2024-08-01 --platform xcode --platform ubuntu22.04 ``` -## Cross-compiling with Swift SDKs (WebAssembly and Android) +## Cross-compiling with Swift SDKs swift.org publishes "Swift SDK" artifact bundles (the bundles consumed by `swift sdk install`) that let the host compiler cross-compile for platforms @@ -154,7 +154,7 @@ it cannot target by itself. The `swift` extension can download these and define matching Swift and C/C++ toolchains, so that plain `swift_library` and `swift_binary` targets build for those platforms under `--platforms`. -Add the `wasm_sdk` and/or `android_sdk` tags, referencing the `toolchain` +Add the SDK tags you need, referencing the `toolchain` tag by name (the Swift module format is not stable across compiler versions, so the SDK is always downloaded for exactly the toolchain's version): @@ -174,6 +174,10 @@ swift.android_sdk( # api_level = 28, # the default ) +swift.static_linux_sdk( + toolchain_name = "swift_toolchain", +) + register_toolchains( # WebAssembly (wasm32-unknown-wasip1), per host platform you build on. "@swift_toolchain//:swift_toolchain_wasm32_xcode", @@ -183,21 +187,38 @@ register_toolchains( "@swift_toolchain//:cc_toolchain_android_aarch64_xcode", "@swift_toolchain//:swift_toolchain_android_x86_64_xcode", "@swift_toolchain//:cc_toolchain_android_x86_64_xcode", + # Static Linux (musl), per architecture and host platform. + "@swift_toolchain//:swift_toolchain_static_linux_x86_64_xcode", + "@swift_toolchain//:cc_toolchain_static_linux_x86_64_xcode", + "@swift_toolchain//:swift_toolchain_static_linux_aarch64_xcode", + "@swift_toolchain//:cc_toolchain_static_linux_aarch64_xcode", ) ``` -If you build on a single host platform, you can register everything the -extension generates (standalone, embedded, and Swift-SDK toolchains) in one -line instead of listing the matrix: +The `_xcode` suffix in these labels is the existing standalone-toolchain +platform name for the Swift.org macOS `.pkg`; it does not mean the Xcode-bundled +Swift toolchain is used. + +Register Static Linux SDK toolchains before any generic same-architecture Linux +Swift toolchains (for example `//swift/toolchains:all`). Static Linux platforms +still carry `@platforms//os:linux`, so a generic Linux toolchain can also match; +Bazel uses registration order to choose among compatible toolchains. + +If you build on a single host platform and are not targeting Static Linux from +a same-architecture Linux host, you can register everything the extension +generates (standalone, embedded, and Swift-SDK toolchains) in one line instead +of listing the matrix: ```bzl register_toolchains("@swift_toolchain//:all") ``` -Avoid `:all` when you configure multiple Linux distributions, for the same -reason the standalone host toolchains are registered explicitly: rules_swift -cannot yet auto-select a distribution, so `:all` would make the host/exec -toolchain ambiguous across them. +For Static Linux on Linux hosts, prefer explicit registration so the Static +Linux SDK toolchain labels appear before any same-architecture generic Linux +toolchain labels. Also avoid `:all` when you configure multiple Linux +distributions, for the same reason the standalone host toolchains are registered +explicitly: rules_swift cannot yet auto-select a distribution, so `:all` would +make the host/exec toolchain ambiguous across them. Then build with a platform carrying the matching constraints, for example: @@ -217,6 +238,15 @@ platform( "@platforms//os:android", ], ) + +platform( + name = "static-linux-x86_64", + constraint_values = [ + "@platforms//cpu:x86_64", + "@platforms//os:linux", + "@rules_swift//swift/toolchains:static_linux", + ], +) ``` ```sh @@ -253,21 +283,26 @@ Details worth knowing: * The Swift standard library is linked statically from the SDK, matching the behavior of `swiftc` with these SDKs. WebAssembly binaries are - self-contained `wasm32-wasip1` modules (runnable with `wasmtime` et al.). + self-contained `wasm32-wasip1` modules (runnable with `wasmtime` et al.); + Static Linux binaries are fully static `*-swift-linux-musl` executables. * Android binaries link against the NDK's `libc++_shared.so`, which must be packaged with the application. Reference it host-independently as `@//:libcxx_shared_` (e.g. `@swift_toolchain//:libcxx_shared_aarch64`); the alias selects the NDK for the build host automatically. +* Static Linux toolchains require the + `@rules_swift//swift/toolchains:static_linux` constraint in the target + platform. This prevents the musl SDK toolchain from being selected for normal + Linux/glibc platforms. * The `android_sdk` tag downloads the Android NDK (for its sysroot and clang) in addition to the Swift SDK. The NDK is only fetched when an Android target is actually built; WebAssembly-only builds do not download it. The NDK version and checksums can be overridden with the `ndk_version` and `ndk_sha256s` attributes. * As with toolchains, checksums for the SDK bundles are bundled for a - curated list of releases (see - `swift/internal/extensions/swift_sdk_releases.bzl`); for other releases, - pass `sha256` explicitly. + curated list of Swift/SDK version pairs (see + `swift/internal/extensions/swift_sdk_releases.bzl`); when using an + unlisted SDK version, pass `sha256` explicitly. ### Coexistence with `rules_apple` diff --git a/examples/cross_compilation/BUILD.bazel b/examples/cross_compilation/BUILD.bazel index d12e5c659..d7a39a6d2 100644 --- a/examples/cross_compilation/BUILD.bazel +++ b/examples/cross_compilation/BUILD.bazel @@ -4,8 +4,8 @@ load("//swift:swift.bzl", "swift_binary", "swift_library") package(default_visibility = ["//visibility:public"]) # Platforms covered by the Swift SDK toolchains registered by this repository's -# dev `MODULE.bazel` (via the `swift` extension's `wasm_sdk`/`android_sdk` -# tags). Build the targets below with `--platforms` set to one of these. +# dev `MODULE.bazel` (via the `swift` extension's SDK tags). Build the targets +# below with `--platforms` set to one of these. platform( name = "wasm32-wasip1", constraint_values = [ @@ -22,6 +22,15 @@ platform( ], ) +platform( + name = "static-linux-x86_64", + constraint_values = [ + "@platforms//cpu:x86_64", + "@platforms//os:linux", + "//swift/toolchains:static_linux", + ], +) + # A plain library dependency, compiled for whichever platform depends on it. # Both platform-specific entry points below call into it, demonstrating that a # normal `swift_library` is reused unchanged across targets. @@ -58,6 +67,7 @@ transition_binary( name = "Reactor.wasm", binary = ":Reactor", platform = ":wasm32-wasip1", + tags = ["manual"], ) # A static web app embedding the reactor: `index.html` + `Reactor.wasm` in one @@ -99,4 +109,23 @@ transition_binary( name = "libSwiftJNI.so", binary = ":SwiftJNI", platform = ":android-aarch64", + tags = ["manual"], +) + +# --------------------------------------------------------------------------- +# Static Linux: a fully static musl executable, runnable on Linux systems. +# --------------------------------------------------------------------------- + +swift_binary( + name = "HelloStaticLinux", + srcs = ["Sources/HelloStaticLinux/main.swift"], + tags = ["manual"], + deps = [":Greeter"], +) + +transition_binary( + name = "hello_static_linux", + binary = ":HelloStaticLinux", + platform = ":static-linux-x86_64", + tags = ["manual"], ) diff --git a/examples/cross_compilation/README.md b/examples/cross_compilation/README.md index a22b4dd06..e268fc2e7 100644 --- a/examples/cross_compilation/README.md +++ b/examples/cross_compilation/README.md @@ -1,8 +1,9 @@ -# Cross-compilation example (WebAssembly + Android) +# Cross-compilation example (WebAssembly + Android + Static Linux) Builds plain `swift_library` / `swift_binary` targets for non-host platforms using the Swift SDK toolchains registered by this repository's `MODULE.bazel` -(via the `swift` extension's `wasm_sdk` and `android_sdk` tags). See +(via the `swift` extension's `wasm_sdk`, `android_sdk`, and +`static_linux_sdk` tags). See `doc/standalone_toolchain.md` for the toolchain setup. All targets are tagged `manual` because they download the Swift SDK bundles @@ -16,6 +17,7 @@ All targets are tagged `manual` because they download the Swift SDK bundles | `:Reactor.wasm` | `Reactor.wasm` | A WebAssembly **reactor** (`swift_binary(linkshared)`), no `main`, with exported functions | | `:web_app` | `web_app/` | A static site embedding `Reactor.wasm`, driven from JS — see [`web/README.md`](web/README.md) | | `:libSwiftJNI.so` | `libSwiftJNI.so` | An Android **JNI shared library** (`swift_binary(linkshared)`) that calls `:Greeter` | +| `:hello_static_linux` | `hello_static_linux` | A fully static Linux executable targeting `x86_64-swift-linux-musl` | ```sh # WebAssembly reactor (runnable with wasmtime): @@ -30,11 +32,19 @@ python3 -m http.server -d bazel-bin/examples/cross_compilation/web_app 8000 # Android JNI shared library: bazel build //examples/cross_compilation:libSwiftJNI.so + +# Static Linux executable: +bazel build //examples/cross_compilation:hello_static_linux ``` Each `transition_binary` target builds its `swift_binary` under the matching -platform (`:wasm32-wasip1` / `:android-aarch64`); you can equivalently pass -`--platforms=//examples/cross_compilation:wasm32-wasip1` on the command line. +platform (`:wasm32-wasip1` / `:android-aarch64` / +`:static-linux-x86_64`); you can equivalently pass a platform on the command +line, for example: + +* `--platforms=//examples/cross_compilation:wasm32-wasip1` +* `--platforms=//examples/cross_compilation:android-aarch64` +* `--platforms=//examples/cross_compilation:static-linux-x86_64` ## Android app diff --git a/examples/cross_compilation/Sources/Greeter/Greeter.swift b/examples/cross_compilation/Sources/Greeter/Greeter.swift index e0ef934c8..a45df22a3 100644 --- a/examples/cross_compilation/Sources/Greeter/Greeter.swift +++ b/examples/cross_compilation/Sources/Greeter/Greeter.swift @@ -1,7 +1,7 @@ /// A plain `swift_library` used as a normal dependency of the -/// platform-specific entry points (the Android JNI shared library and the -/// WebAssembly reactor). Nothing in here is platform-specific; it is compiled -/// for whichever platform the depending target is built for. +/// platform-specific entry points (the WebAssembly reactor, Android JNI shared +/// library, and Static Linux executable). Nothing in here is platform-specific; +/// it is compiled for whichever platform the depending target is built for. public struct Greeter { private let subject: String diff --git a/examples/cross_compilation/Sources/HelloStaticLinux/main.swift b/examples/cross_compilation/Sources/HelloStaticLinux/main.swift new file mode 100644 index 000000000..d3d59ccf6 --- /dev/null +++ b/examples/cross_compilation/Sources/HelloStaticLinux/main.swift @@ -0,0 +1,3 @@ +import Greeter + +print(Greeter(subject: "Static Linux").greeting()) diff --git a/swift/BUILD b/swift/BUILD index 5765791b1..0e9d8824b 100644 --- a/swift/BUILD +++ b/swift/BUILD @@ -19,6 +19,8 @@ bzl_library( "//swift/internal:repositories", "//swift/internal/extensions:standalone_toolchain", "//swift/internal/extensions:swift_releases", + "//swift/internal/extensions:swift_sdk_releases", + "//swift/internal/extensions:swift_sdks", "//swift/internal/extensions:toolchains", "@bazel_features//:features", ], diff --git a/swift/extensions.bzl b/swift/extensions.bzl index 7249ec678..a2b2f2db1 100644 --- a/swift/extensions.bzl +++ b/swift/extensions.bzl @@ -25,19 +25,23 @@ load( "DEFAULT_ANDROID_NDK_VERSION", "SWIFT_SDK_RELEASES", "android_ndk_download_url", + "static_linux_sdk_download_url", "swift_sdk_download_url", ) load( "//swift/internal/extensions:swift_sdks.bzl", "ANDROID_ARCHS", "ANDROID_NDK_BUILD_FILE_CONTENT", + "STATIC_LINUX_ARCHS", "swift_android_sdk_repository", + "swift_static_linux_sdk_repository", "swift_wasm_sdk_repository", ) load( "//swift/internal/extensions:toolchains.bzl", _android_libcxx_aliases = "android_libcxx_aliases", _android_sdk_toolchains_for_platform = "android_sdk_toolchains_for_platform", + _static_linux_sdk_toolchains_for_platform = "static_linux_sdk_toolchains_for_platform", _toolchains_for_platform = "toolchains_for_platform", _toolchains_repository = "toolchains_repository", _wasm_sdk_toolchains_for_platform = "wasm_sdk_toolchains_for_platform", @@ -78,7 +82,7 @@ def _setup_wasm_sdk(*, tag, toolchain_name, swift_version, platforms): """ sha256 = tag.sha256 if not sha256: - if swift_version not in SWIFT_SDK_RELEASES: + if swift_version not in SWIFT_SDK_RELEASES or "wasm" not in SWIFT_SDK_RELEASES[swift_version]: fail("No known WebAssembly Swift SDK for version `{}`. Please choose one of {}, or provide the SDK's sha256.".format( swift_version, SWIFT_SDK_RELEASES.keys(), @@ -116,7 +120,7 @@ def _setup_android_sdk(*, tag, toolchain_name, swift_version, platforms): """ sha256 = tag.sha256 if not sha256: - if swift_version not in SWIFT_SDK_RELEASES: + if swift_version not in SWIFT_SDK_RELEASES or "android" not in SWIFT_SDK_RELEASES[swift_version]: fail("No known Android Swift SDK for version `{}`. Please choose one of {}, or provide the SDK's sha256.".format( swift_version, SWIFT_SDK_RELEASES.keys(), @@ -173,6 +177,62 @@ def _setup_android_sdk(*, tag, toolchain_name, swift_version, platforms): ) return build_file_content +def _setup_static_linux_sdk(*, tag, toolchain_name, swift_version, platforms): + """Creates the repositories for a `swift.static_linux_sdk` tag. + + Args: + tag: The `static_linux_sdk` tag. + toolchain_name: The name of the `swift.toolchain` tag the SDK extends. + swift_version: The Swift release version of that toolchain. + platforms: The host platforms the toolchain was created for. + + Returns: + BUILD file content with the `toolchain` declarations to add to the + toolchains hub repository. + """ + release = None + if swift_version in SWIFT_SDK_RELEASES: + release = SWIFT_SDK_RELEASES[swift_version].get("static_linux") + + sdk_version = tag.sdk_version + if not sdk_version: + if not release: + fail("No known Static Linux Swift SDK version for Swift `{}`. Please provide sdk_version.".format( + swift_version, + )) + sdk_version = release["version"] + + sha256 = tag.sha256 + if not sha256: + if not release: + fail("No known Static Linux Swift SDK for version `{}`. Please choose one of {}, or provide the SDK's sha256 and sdk_version.".format( + swift_version, + SWIFT_SDK_RELEASES.keys(), + )) + if sdk_version != release["version"]: + fail("No known checksum for Static Linux Swift SDK version `{}` for Swift `{}`. Please provide sha256 when overriding sdk_version.".format( + sdk_version, + swift_version, + )) + sha256 = release["sha256"] + + build_file_content = "" + for platform in platforms: + repository_name = "{}_static_linux_sdk_{}".format(toolchain_name, platform) + swift_static_linux_sdk_repository( + name = repository_name, + sha256 = sha256, + swift_version = swift_version, + toolchain_repo = "{}_{}".format(toolchain_name, platform), + url = static_linux_sdk_download_url(swift_version, sdk_version), + ) + build_file_content += _static_linux_sdk_toolchains_for_platform( + platform = platform, + sdk_repository = repository_name, + archs = STATIC_LINUX_ARCHS, + ) + return build_file_content + def _sdk_tags_by_toolchain_name(tags, kind): """Groups SDK tags by the toolchain they extend, rejecting duplicates.""" tags_by_name = {} @@ -203,6 +263,10 @@ def _standalone_toolchain_impl(module_ctx): root_module.tags.android_sdk, "android_sdk", ) + static_linux_sdk_tags = _sdk_tags_by_toolchain_name( + root_module.tags.static_linux_sdk, + "static_linux_sdk", + ) toolchain_names = [ toolchain.name @@ -211,6 +275,7 @@ def _standalone_toolchain_impl(module_ctx): for kind, tags in ( ("wasm_sdk", wasm_sdk_tags), ("android_sdk", android_sdk_tags), + ("static_linux_sdk", static_linux_sdk_tags), ): for toolchain_name in tags: if toolchain_name not in toolchain_names: @@ -268,6 +333,14 @@ def _standalone_toolchain_impl(module_ctx): platforms = platforms, ) + if toolchain.name in static_linux_sdk_tags: + toolchains_build_file_content += _setup_static_linux_sdk( + tag = static_linux_sdk_tags[toolchain.name], + toolchain_name = toolchain.name, + swift_version = swift_version, + platforms = platforms, + ) + _toolchains_repository( name = toolchain.name, build_file_content = toolchains_build_file_content, @@ -362,6 +435,50 @@ and build with a platform that has the `@platforms//os:android` and """, ) +_static_linux_sdk = tag_class( + attrs = { + "sdk_version": attr.string( + doc = """\ +The Static Linux SDK version in the artifact bundle filename. May be omitted +for Swift/SDK version pairs known to this version of rules_swift. If this +overrides the known SDK version, `sha256` must also be provided. +""", + ), + "sha256": attr.string( + doc = """\ +The expected SHA-256 of the SDK artifact bundle. May be omitted only for +Swift/SDK version pairs known to this version of rules_swift. +""", + ), + "toolchain_name": attr.string( + doc = "The name of the `toolchain` tag to add this Swift SDK to.", + mandatory = True, + ), + }, + doc = """\ +Downloads the Static Linux Swift SDK matching a `toolchain` tag's Swift version +and defines Swift and C++ toolchains targeting `x86_64-swift-linux-musl` and +`aarch64-swift-linux-musl`. + +Register the generated toolchains for the host platforms you build on, e.g.: + +```starlark +register_toolchains( + "@swift_toolchain//:swift_toolchain_static_linux_x86_64_xcode", + "@swift_toolchain//:cc_toolchain_static_linux_x86_64_xcode", +) +``` + +and build with a platform that has `@platforms//os:linux`, +`@platforms//cpu:x86_64` (or `aarch64`), and +`@rules_swift//swift/toolchains:static_linux` constraints. + +On Linux hosts, register the Static Linux SDK toolchains before generic +same-architecture Linux Swift toolchains, since both can match a Static Linux +platform and Bazel chooses by registration order. +""", +) + _toolchain = tag_class(attrs = { "name": attr.string( doc = "Repository name of the generated toolchain", @@ -383,6 +500,7 @@ swift = module_extension( implementation = _standalone_toolchain_impl, tag_classes = { "android_sdk": _android_sdk, + "static_linux_sdk": _static_linux_sdk, "toolchain": _toolchain, "wasm_sdk": _wasm_sdk, }, diff --git a/swift/internal/extensions/swift_sdk_releases.bzl b/swift/internal/extensions/swift_sdk_releases.bzl index dfd87ee59..43626629a 100644 --- a/swift/internal/extensions/swift_sdk_releases.bzl +++ b/swift/internal/extensions/swift_sdk_releases.bzl @@ -13,8 +13,28 @@ https://www.swift.org/api/v1/install/releases.json. """ SWIFT_SDK_RELEASES = { + "6.3": { + "android": "2f2942c4bcea7965a08665206212c66991dabe23725aeec7c4365fc91acad088", + "static_linux": { + "sha256": "d2078b69bdeb5c31202c10e9d8a11d6f66f82938b51a4b75f032ccb35c4c286c", + "version": "0.1.0", + }, + "wasm": "9fa4016ee632c7e9e906608ec3b55cf13dfc4dff44e47574c5af58064dc33fd9", + }, + "6.3.1": { + "android": "8193a4e96538635131a154736c8896fba0e5a1c30e065524f00ed78719bac35a", + "static_linux": { + "sha256": "fac05271c1f7d060bd203240ce5251d5ca902d30ac899f553765dbb3a88b97ad", + "version": "0.1.0", + }, + "wasm": "bd47baa20771f366d8beed7970afaa30742b2210097afd15f85427226d8f4cf2", + }, "6.3.2": { "android": "939e933549d12d28f2e0bf71019d734d309859e9773c572657ce565a81f85d68", + "static_linux": { + "sha256": "3fd798bef6f4408f1ea5a6f94ce4d4052830c4326ab85ebc04f983f01b3da407", + "version": "0.1.0", + }, "wasm": "a61f0584c93283589f8b2f42db05c1f9a182b506c2957271402992655591dd7c", }, } @@ -54,6 +74,28 @@ def swift_sdk_download_url(swift_version, sdk): version = swift_version, ) +def static_linux_sdk_download_url(swift_version, sdk_version): + """Returns the download URL for a Static Linux Swift SDK artifact bundle. + + Args: + swift_version: The Swift release version (e.g. "6.3.2"). + sdk_version: The Static Linux SDK version (e.g. "0.1.0"). + + Returns: + The URL of the `.artifactbundle.tar.gz` for the given release. + """ + if "-snapshot-" in swift_version: + fail("Swift SDKs are only supported for release versions, got `{}`".format( + swift_version, + )) + return ( + "https://download.swift.org/swift-{version}-release/static-sdk/" + + "swift-{version}-RELEASE/swift-{version}-RELEASE_static-linux-{sdk_version}.artifactbundle.tar.gz" + ).format( + sdk_version = sdk_version, + version = swift_version, + ) + def android_ndk_download_url(ndk_version, host_os): """Returns the download URL for an Android NDK release. diff --git a/swift/internal/extensions/swift_sdks.bzl b/swift/internal/extensions/swift_sdks.bzl index 10be77ee7..6694011ca 100644 --- a/swift/internal/extensions/swift_sdks.bzl +++ b/swift/internal/extensions/swift_sdks.bzl @@ -2,8 +2,8 @@ A "Swift SDK" is the artifact bundle published by swift.org for cross-compiling Swift to platforms that the host toolchain cannot target by -itself (currently WebAssembly and Android); they are the bundles that -`swift sdk install` consumes. +itself (currently WebAssembly, Android, and Static Linux); they are the +bundles that `swift sdk install` consumes. Each repository created by these rules pairs one Swift SDK with one standalone host toolchain repository (created by `standalone_toolchain`) and defines: @@ -238,6 +238,29 @@ def _download_sdk_bundle(repository_ctx): )) return bundles[0] +def _relative_metadata_path(value, field, triple): + """Validates a path read from SDK metadata.""" + if type(value) != "string" or not value: + fail("Expected `{}` for `{}` in swift-sdk.json to be a non-empty string.".format( + field, + triple, + )) + if value.startswith("/") or ".." in value.split("/"): + fail("Expected `{}` for `{}` in swift-sdk.json to be a relative path below the SDK directory, got `{}`.".format( + field, + triple, + value, + )) + return value + +def _static_linux_resource_path(target_settings, triple): + resource_path = target_settings.get("swiftStaticResourcesPath") + field = "swiftStaticResourcesPath" + if not resource_path: + resource_path = target_settings.get("swiftResourcesPath") + field = "swiftResourcesPath" + return _relative_metadata_path(resource_path, field, triple) + def _common_attrs(): return { "sha256": attr.string( @@ -373,6 +396,149 @@ toolchain's compiler. implementation = _swift_wasm_sdk_impl, ) +# The architectures the Static Linux Swift SDK provides resources for and that +# `@platforms//cpu` can express. +STATIC_LINUX_ARCHS = ["aarch64", "x86_64"] + +def _swift_static_linux_sdk_impl(repository_ctx): + bundle_dir = _download_sdk_bundle(repository_ctx) + toolchain_repo = repository_ctx.attr.toolchain_repo + + repo_root = "external/" + repository_ctx.name + sdk_dir_relative = "{}/{}/swift-linux-musl".format( + bundle_dir, + bundle_dir.removesuffix(".artifactbundle"), + ) + if not repository_ctx.path(sdk_dir_relative + "/swift-sdk.json").exists: + fail("The Static Linux Swift SDK bundle has an unexpected layout; " + + "missing {}/{}/swift-sdk.json".format(repo_root, sdk_dir_relative)) + swift_sdk_metadata = json.decode( + repository_ctx.read(sdk_dir_relative + "/swift-sdk.json"), + ) + target_triples = swift_sdk_metadata.get("targetTriples") + if type(target_triples) != "dict": + fail("The Static Linux Swift SDK bundle has an unexpected layout; " + + "swift-sdk.json is missing `targetTriples`.") + + build_content = _BUILD_HEADER_TEMPLATE.format( + bundle_dir = bundle_dir, + compiler_inputs = _build_list([ + ":sdk_files", + "@{}//:{}".format(toolchain_repo, _HOST_COMPILER_INPUTS), + ]), + toolchain_repo = toolchain_repo, + ) + + build_content += _CC_TOOLCHAIN_TEMPLATE.format( + ar = "@{}//:usr/bin/llvm-ar".format(toolchain_repo), + clang = "@{}//:usr/bin/clang".format(toolchain_repo), + clang_data = _build_list([ + ":sdk_files", + "@{}//:{}".format(toolchain_repo, _HOST_LINKER_INPUTS), + ]), + ) + + for arch in STATIC_LINUX_ARCHS: + triple = "{}-swift-linux-musl".format(arch) + suffix = "static_linux_{}".format(arch) + target_settings = target_triples.get(triple) + if type(target_settings) != "dict": + fail("The Static Linux Swift SDK bundle does not define target triple `{}`.".format( + triple, + )) + sdkroot_relative = "{}/{}".format( + sdk_dir_relative, + _relative_metadata_path( + target_settings.get("sdkRootPath"), + "sdkRootPath", + triple, + ), + ) + resource_dir_relative = "{}/{}".format( + sdk_dir_relative, + _static_linux_resource_path(target_settings, triple), + ) + sdkroot = "{}/{}".format(repo_root, sdkroot_relative) + if not repository_ctx.path(sdkroot_relative).exists: + fail("The Static Linux Swift SDK bundle has an unexpected layout; " + + "missing " + sdkroot) + resource_dir = "{}/{}".format(repo_root, resource_dir_relative) + if not repository_ctx.path(resource_dir_relative).exists: + fail("The Static Linux Swift SDK bundle has an unexpected layout; " + + "missing " + resource_dir) + linux_static_dir = resource_dir + "/linux-static" + + build_content += _SWIFT_TOOLCHAIN_TEMPLATE.format( + arch = arch, + copts = _build_list([ + "-resource-dir", + resource_dir, + ]), + features = _build_list([ + "swift.module_map_no_private_headers", + "swift.no_embed_debug_module", + "swift.use_autolink_extract", + # The file prefix map would make the worker resolve the Xcode + # developer directory on macOS hosts, which this toolchain + # does not depend on. + "-swift.file_prefix_map", + ]), + linker_inputs = _build_list([":sdk_files"]), + # These are the runtime objects and libraries that `swiftc` adds + # for a Static Linux executable; see + # `swift_static/linux-static/static-executable-args.lnk` in the SDK. + linkopts = _build_list([ + "{}/{}/swiftrt.o".format(linux_static_dir, arch), + "-L{}".format(linux_static_dir), + "-static", + "-lswiftCore", + "-lswift_RegexParser", + "-Wl,-u,pthread_self", + "-Wl,-u,pthread_once", + "-Wl,-u,pthread_key_create", + "-ldispatch", + "-lBlocksRuntime", + "-lpthread", + "-ldl", + "-lc++", + "-lm", + ]), + os = "linux", + sdkroot = sdkroot, + suffix = suffix, + swift_version = repository_ctx.attr.swift_version, + ) + + build_content += _CC_TOOLCHAIN_FOR_TARGET_TEMPLATE.format( + args = _build_list([ + "--target=" + triple, + "--sysroot=" + sdkroot, + "-resource-dir", + resource_dir + "/clang", + ]), + link_args = _build_list([ + "-fuse-ld=lld", + # Pure C++ final links do not see the Swift toolchain's + # linkopts, so the companion CC toolchain must provide the C++ + # runtime itself. + "-lc++", + ]), + suffix = suffix, + triple = triple, + ) + + repository_ctx.file("BUILD.bazel", build_content) + +swift_static_linux_sdk_repository = repository_rule( + attrs = _common_attrs(), + doc = """\ +Downloads the Static Linux Swift SDK artifact bundle and defines Swift and C++ +toolchains that target `{aarch64,x86_64}-swift-linux-musl` using a standalone +host toolchain's compiler. +""", + implementation = _swift_static_linux_sdk_impl, +) + # The architectures the Android Swift SDK provides resources for and that # `@platforms//cpu` can express. (The SDK also supports armv7, which can be # added on demand.) diff --git a/swift/internal/extensions/toolchains.bzl b/swift/internal/extensions/toolchains.bzl index cbd8e92f6..668eb8792 100644 --- a/swift/internal/extensions/toolchains.bzl +++ b/swift/internal/extensions/toolchains.bzl @@ -135,6 +135,36 @@ def android_sdk_toolchains_for_platform(platform, sdk_repository, archs): ) return content +def static_linux_sdk_toolchains_for_platform(platform, sdk_repository, archs): + """Returns `toolchain` declarations for a Static Linux Swift SDK. + + Args: + platform: The host platform name (e.g. "xcode" or "ubuntu22.04") whose + standalone toolchain the SDK is paired with. + sdk_repository: The name of the repository created by + `swift_static_linux_sdk_repository`. + archs: The Static Linux architectures ("aarch64", "x86_64") to declare + toolchains for. + + Returns: + BUILD file content declaring the Swift and C++ toolchains. + """ + content = "" + for arch in archs: + content += _SDK_TOOLCHAIN_PLATFORM.format( + exec_compatible_with = _exec_compatible_with_for_platform(platform), + platform = platform, + sdk_repository = sdk_repository, + target = "static_linux_" + arch, + target_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:" + arch, + "@rules_swift//swift/toolchains:static_linux", + ], + target_suffix = "static_linux_" + arch, + ) + return content + _NDK_HOST_OS_CONSTRAINT = { "darwin": "@platforms//os:macos", "linux": "@platforms//os:linux", diff --git a/swift/toolchains/BUILD b/swift/toolchains/BUILD index df9328c65..cbfcc21c1 100644 --- a/swift/toolchains/BUILD +++ b/swift/toolchains/BUILD @@ -6,6 +6,17 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") licenses(["notice"]) +constraint_setting( + name = "swift_sdk", + visibility = ["//visibility:public"], +) + +constraint_value( + name = "static_linux", + constraint_setting = ":swift_sdk", + visibility = ["//visibility:public"], +) + bzl_library( name = "swift_toolchain", srcs = ["swift_toolchain.bzl"], diff --git a/test/fixtures/toolchains/BUILD b/test/fixtures/toolchains/BUILD index 0c2f2013c..9c61e964f 100644 --- a/test/fixtures/toolchains/BUILD +++ b/test/fixtures/toolchains/BUILD @@ -1,8 +1,25 @@ +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_cc//cc/toolchains:args.bzl", "cc_args") +load("@rules_cc//cc/toolchains:make_variable.bzl", "cc_make_variable") +load("@rules_cc//cc/toolchains:tool.bzl", "cc_tool") +load("@rules_cc//cc/toolchains:tool_map.bzl", "cc_tool_map") +load("@rules_cc//cc/toolchains:toolchain.bzl", "cc_toolchain") load( "//swift/toolchains:swift_toolchain.bzl", "swift_toolchain", ) +exports_files(["fake_tool.sh"]) + +platform( + name = "linux_musl_x86_64", + constraint_values = [ + "@platforms//cpu:x86_64", + "@platforms//os:linux", + "//swift/toolchains:static_linux", + ], +) + swift_toolchain( name = "_toolchain_macos_arm64_with_sdkroot", arch = "arm64", @@ -18,3 +35,105 @@ toolchain( toolchain = ":_toolchain_macos_arm64_with_sdkroot", toolchain_type = "//toolchains:toolchain_type", ) + +cc_tool( + name = "fake_clang", + src = "fake_tool.sh", + tags = ["manual"], +) + +cc_tool_map( + name = "cc_tools_static_linux_x86_64", + tags = ["manual"], + tools = { + "@rules_cc//cc/toolchains/actions:ar_actions": ":fake_clang", + "@rules_cc//cc/toolchains/actions:assembly_actions": ":fake_clang", + "@rules_cc//cc/toolchains/actions:c_compile": ":fake_clang", + "@rules_cc//cc/toolchains/actions:cpp_compile_actions": ":fake_clang", + "@rules_cc//cc/toolchains/actions:link_actions": ":fake_clang", + }, +) + +cc_args( + name = "cc_args_static_linux_x86_64", + actions = [ + "@rules_cc//cc/toolchains/actions:compile_actions", + "@rules_cc//cc/toolchains/actions:link_actions", + ], + args = [ + "--target=x86_64-swift-linux-musl", + "--sysroot=static-sdk-root", + ], +) + +cc_args( + name = "cc_link_args_static_linux_x86_64", + actions = [ + "@rules_cc//cc/toolchains/actions:link_actions", + ], + args = [ + "-lc++", + ], +) + +cc_make_variable( + name = "cc_target_triple_static_linux_x86_64", + value = "x86_64-swift-linux-musl", + variable_name = "CC_TARGET_TRIPLE", +) + +cc_toolchain( + name = "_cc_toolchain_static_linux_x86_64", + args = [ + ":cc_args_static_linux_x86_64", + ":cc_link_args_static_linux_x86_64", + ], + compiler = "clang", + enabled_features = [ + "@rules_cc//cc/toolchains/args/archiver_flags:feature", + "@rules_cc//cc/toolchains/args/libraries_to_link:feature", + "@rules_cc//cc/toolchains/args/link_flags:feature", + ], + make_variables = [ + ":cc_target_triple_static_linux_x86_64", + ], + tool_map = ":cc_tools_static_linux_x86_64", +) + +toolchain( + name = "cc_toolchain_static_linux_x86_64", + toolchain = ":_cc_toolchain_static_linux_x86_64", + toolchain_type = "@bazel_tools//tools/cpp:toolchain_type", +) + +cc_binary( + name = "static_linux_cc_uses_libcxx", + srcs = ["static_linux_cc_uses_libcxx.cc"], + tags = ["manual"], + visibility = ["//test:__pkg__"], +) + +swift_toolchain( + name = "_toolchain_static_linux_x86_64", + arch = "x86_64", + copts = [ + "-resource-dir", + "static-resource-root", + ], + features = [ + "swift.use_autolink_extract", + ], + linker_inputs = [":fake_tool.sh"], + linkopts = ["-static"], + os = "linux", + parsed_version = "6.3.2", + root = ".", + sdkroot = "static-sdk-root", + version_file = ".swift_version", +) + +toolchain( + name = "toolchain_static_linux_x86_64", + toolchain = ":_toolchain_static_linux_x86_64", + toolchain_type = "//toolchains:toolchain_type", +) diff --git a/test/fixtures/toolchains/fake_tool.sh b/test/fixtures/toolchains/fake_tool.sh new file mode 100644 index 000000000..ecdbef95d --- /dev/null +++ b/test/fixtures/toolchains/fake_tool.sh @@ -0,0 +1,2 @@ +#!/bin/sh +exit 1 diff --git a/test/fixtures/toolchains/static_linux_cc_uses_libcxx.cc b/test/fixtures/toolchains/static_linux_cc_uses_libcxx.cc new file mode 100644 index 000000000..c547ad99d --- /dev/null +++ b/test/fixtures/toolchains/static_linux_cc_uses_libcxx.cc @@ -0,0 +1,6 @@ +#include + +int main() { + std::string value = "static-linux"; + return value.empty(); +} diff --git a/test/swift_toolchain_tests.bzl b/test/swift_toolchain_tests.bzl index 4a4d77d9b..cd1067406 100644 --- a/test/swift_toolchain_tests.bzl +++ b/test/swift_toolchain_tests.bzl @@ -11,6 +11,15 @@ toolchain_macos_arm64_with_sdkroot_test = make_action_command_line_test_rule( }, ) +toolchain_static_linux_x86_64_test = make_action_command_line_test_rule( + config_settings = { + "//command_line_option:extra_toolchains": [ + "//test/fixtures/toolchains:cc_toolchain_static_linux_x86_64", + "//test/fixtures/toolchains:toolchain_static_linux_x86_64", + ], + }, +) + def swift_toolchain_test_suite(name, tags = []): """Test suite for swift_toolchain's provider. @@ -29,6 +38,35 @@ def swift_toolchain_test_suite(name, tags = []): target_under_test = "//test/fixtures/basic:first", ) + toolchain_static_linux_x86_64_test( + name = "{}_static_linux_x86_64".format(name), + expected_argv = [ + "-target", + "x86_64-swift-linux-musl", + "-sdk", + "static-sdk-root", + "-resource-dir", + "static-resource-root", + "-Xcc", + "--target=x86_64-swift-linux-musl", + ], + mnemonic = "SwiftCompile", + tags = all_tags, + target_under_test = "//test/fixtures/basic:first", + ) + + toolchain_static_linux_x86_64_test( + name = "{}_static_linux_cc_links_libcxx".format(name), + expected_argv = [ + "--target=x86_64-swift-linux-musl", + "--sysroot=static-sdk-root", + "-lc++", + ], + mnemonic = "CppLink", + tags = all_tags, + target_under_test = "//test/fixtures/toolchains:static_linux_cc_uses_libcxx", + ) + native.test_suite( name = name, tags = all_tags, diff --git a/test/utils_tests.bzl b/test/utils_tests.bzl index e62764335..1b4f59db1 100644 --- a/test/utils_tests.bzl +++ b/test/utils_tests.bzl @@ -11,6 +11,13 @@ load("//swift/internal:utils.bzl", "include_developer_search_paths") # buildifier: disable=bzl-visibility load("//swift/internal/extensions:standalone_toolchain.bzl", "get_download_url") +# buildifier: disable=bzl-visibility +load( + "//swift/internal/extensions:swift_sdk_releases.bzl", + "SWIFT_SDK_RELEASES", + "static_linux_sdk_download_url", +) + def _include_developer_search_paths_test(ctx): env = unittest.begin(ctx) @@ -168,10 +175,32 @@ def _standalone_toolchain_download_url_test(ctx): standalone_toolchain_download_url_test = unittest.make(_standalone_toolchain_download_url_test) +def _static_linux_sdk_release_metadata_test(ctx): + env = unittest.begin(ctx) + + static_linux_release = SWIFT_SDK_RELEASES["6.3.2"]["static_linux"] + asserts.equals( + env, + "3fd798bef6f4408f1ea5a6f94ce4d4052830c4326ab85ebc04f983f01b3da407", + static_linux_release["sha256"], + ) + asserts.equals(env, "0.1.0", static_linux_release["version"]) + + asserts.equals( + env, + "https://download.swift.org/swift-6.3.2-release/static-sdk/swift-6.3.2-RELEASE/swift-6.3.2-RELEASE_static-linux-0.1.0.artifactbundle.tar.gz", + static_linux_sdk_download_url("6.3.2", "0.1.0"), + ) + + return unittest.end(env) + +static_linux_sdk_release_metadata_test = unittest.make(_static_linux_sdk_release_metadata_test) + def utils_test_suite(name): return unittest.suite( name, developer_dirs_linkopts_test, include_developer_search_paths_test, + static_linux_sdk_release_metadata_test, standalone_toolchain_download_url_test, )