diff --git a/MODULE.bazel b/MODULE.bazel index 6b6a74319..94f1e71b7 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -145,8 +145,6 @@ http_archive( urls = ["https://github.com/MobileNativeFoundation/index-import/releases/download/6.1.0.1/index-import.tar.gz"], ) -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 @@ -219,6 +217,12 @@ register_toolchains( include("//examples/cross_compilation/android_app:android.MODULE.bazel") include("//examples/cross_compilation/wasm:wasm.MODULE.bazel") +include("//examples/cross_compilation/static_linux:static_linux.MODULE.bazel") + +# 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) diff --git a/doc/standalone_toolchain.md b/doc/standalone_toolchain.md index ee7e5d256..413bf70fd 100644 --- a/doc/standalone_toolchain.md +++ b/doc/standalone_toolchain.md @@ -238,6 +238,36 @@ linked statically from the SDK, so the reactor is a self-contained See `examples/cross_compilation/wasm` for an end to end example. +## Building for Static Linux + +The `swift` module extension can also download the Static Linux Swift SDK, +which defines matching Swift and C/C++ toolchains targeting +`x86_64-swift-linux-musl` and `aarch64-swift-linux-musl`. + +Add the `static_linux_sdk` tag, 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): + +```bzl +swift.static_linux_sdk(toolchain_name = "swift_toolchain") + +register_toolchains( + "@swift_toolchain//:swift_toolchain_static_linux_x86_64_xcode", + "@swift_toolchain//:cc_toolchain_static_linux_x86_64_xcode", +) +``` + +Then build with a platform that has `@platforms//os:linux`, +`@platforms//cpu:x86_64` or `@platforms//cpu:aarch64`, and +`@rules_swift//swift/toolchains:static_linux` constraints. + +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. + +See `examples/cross_compilation/static_linux` for an end to end example. + ## Using the extension from a non-root module The extension is intended for the root module — it fails if a non-root diff --git a/examples/cross_compilation/static_linux/BUILD.bazel b/examples/cross_compilation/static_linux/BUILD.bazel new file mode 100644 index 000000000..1a227f2ab --- /dev/null +++ b/examples/cross_compilation/static_linux/BUILD.bazel @@ -0,0 +1,32 @@ +load("//examples/embedded:transition.bzl", "transition_binary") +load("//swift:swift.bzl", "swift_binary", "swift_library") + +platform( + name = "static-linux-x86_64", + constraint_values = [ + "@platforms//cpu:x86_64", + "@platforms//os:linux", + "//swift/toolchains:static_linux", + ], +) + +swift_library( + name = "Greeter", + srcs = ["Sources/Greeter/Greeter.swift"], + module_name = "Greeter", +) + +# This only builds for the Static Linux SDK platform configured by the +# transition below, so the //examples/... wildcard skips it on a host platform. +swift_binary( + name = "HelloStaticLinux", + srcs = ["Sources/HelloStaticLinux/main.swift"], + target_compatible_with = ["//swift/toolchains:static_linux"], + deps = [":Greeter"], +) + +transition_binary( + name = "hello_static_linux", + binary = ":HelloStaticLinux", + platform = ":static-linux-x86_64", +) diff --git a/examples/cross_compilation/static_linux/README.md b/examples/cross_compilation/static_linux/README.md new file mode 100644 index 000000000..073901dd7 --- /dev/null +++ b/examples/cross_compilation/static_linux/README.md @@ -0,0 +1,11 @@ +# Building Swift for Static Linux + +A simple executable that cross-compiles Swift to a fully static musl binary with +the Static Linux Swift SDK. See `doc/standalone_toolchain.md` for the toolchain +setup. + +## Build + +```sh +bazel build //examples/cross_compilation/static_linux:hello_static_linux +``` diff --git a/examples/cross_compilation/static_linux/Sources/Greeter/Greeter.swift b/examples/cross_compilation/static_linux/Sources/Greeter/Greeter.swift new file mode 100644 index 000000000..4993d1ca3 --- /dev/null +++ b/examples/cross_compilation/static_linux/Sources/Greeter/Greeter.swift @@ -0,0 +1,14 @@ +/// A plain `swift_library` used as a normal dependency of the 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 + + public init(subject: String) { + self.subject = subject + } + + public func greeting() -> String { + return "Hello, \(subject)!" + } +} diff --git a/examples/cross_compilation/static_linux/Sources/HelloStaticLinux/main.swift b/examples/cross_compilation/static_linux/Sources/HelloStaticLinux/main.swift new file mode 100644 index 000000000..d3d59ccf6 --- /dev/null +++ b/examples/cross_compilation/static_linux/Sources/HelloStaticLinux/main.swift @@ -0,0 +1,3 @@ +import Greeter + +print(Greeter(subject: "Static Linux").greeting()) diff --git a/examples/cross_compilation/static_linux/static_linux.MODULE.bazel b/examples/cross_compilation/static_linux/static_linux.MODULE.bazel new file mode 100644 index 000000000..70c9f63b3 --- /dev/null +++ b/examples/cross_compilation/static_linux/static_linux.MODULE.bazel @@ -0,0 +1,20 @@ +swift = use_extension("//swift:extensions.bzl", "swift", dev_dependency = True) +swift.static_linux_sdk(toolchain_name = "swift_toolchain") + +register_toolchains( + # Register Static Linux before the broad built-in Linux toolchains in the + # root MODULE.bazel; Static Linux platforms are also @platforms//os:linux. + "@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//: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", + dev_dependency = True, +) diff --git a/swift/BUILD b/swift/BUILD index caa50d2bc..ecf8dcd24 100644 --- a/swift/BUILD +++ b/swift/BUILD @@ -23,6 +23,8 @@ bzl_library( deps = [ "//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", ], ) diff --git a/swift/extensions.bzl b/swift/extensions.bzl index 4e43318d1..daf6dd65c 100644 --- a/swift/extensions.bzl +++ b/swift/extensions.bzl @@ -19,17 +19,21 @@ load("//swift/internal/extensions:swift_releases.bzl", "SWIFT_RELEASES") load( "//swift/internal/extensions:swift_sdk_releases.bzl", "SWIFT_SDK_RELEASES", + "static_linux_sdk_download_url", "swift_sdk_download_url", ) load( "//swift/internal/extensions:swift_sdks.bzl", "ANDROID_ARCHS", + "STATIC_LINUX_ARCHS", "swift_android_sdk_repository", + "swift_static_linux_sdk_repository", "swift_wasm_sdk_repository", ) load( "//swift/internal/extensions:toolchains.bzl", "android_sdk_toolchains_for_platform", + "static_linux_sdk_toolchains_for_platform", "toolchains_for_platform", "toolchains_repository", "wasm_sdk_toolchains_for_platform", @@ -116,6 +120,62 @@ def _setup_wasm_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 = {} @@ -146,23 +206,27 @@ def _standalone_toolchain_impl(module_ctx): root_module.tags.wasm_sdk, "wasm_sdk", ) + static_linux_sdk_tags = _sdk_tags_by_toolchain_name( + root_module.tags.static_linux_sdk, + "static_linux_sdk", + ) toolchain_names = [ toolchain.name for toolchain in root_module.tags.toolchain ] - for toolchain_name in android_sdk_tags: - if toolchain_name not in toolchain_names: - fail("The `android_sdk` tag references unknown toolchain `{}`. Please use the name of a `toolchain` tag: {}".format( - toolchain_name, - toolchain_names, - )) - for toolchain_name in wasm_sdk_tags: - if toolchain_name not in toolchain_names: - fail("The `wasm_sdk` tag references unknown toolchain `{}`. Please use the name of a `toolchain` tag: {}".format( - toolchain_name, - toolchain_names, - )) + for kind, tags in ( + ("android_sdk", android_sdk_tags), + ("wasm_sdk", wasm_sdk_tags), + ("static_linux_sdk", static_linux_sdk_tags), + ): + for toolchain_name in tags: + if toolchain_name not in toolchain_names: + fail("The `{}` tag references unknown toolchain `{}`. Please use the name of a `toolchain` tag: {}".format( + kind, + toolchain_name, + toolchain_names, + )) toolchains_build_file_content = "" for toolchain in root_module.tags.toolchain: @@ -211,6 +275,13 @@ def _standalone_toolchain_impl(module_ctx): swift_version = swift_version, 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, @@ -257,6 +328,53 @@ and defines Swift and C++ toolchains targeting `wasm32-unknown-wasip1`. """, ) +_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. Custom SDK +versions must preserve the standard Static Linux SDK artifact-bundle layout, +including the `swift-sdk.json` metadata; linker args are read from the SDK's +`linux-static/static-executable-args.lnk`. +""", + ), + "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", @@ -278,6 +396,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 ae0a28a50..bb28baddb 100644 --- a/swift/internal/extensions/swift_sdk_releases.bzl +++ b/swift/internal/extensions/swift_sdk_releases.bzl @@ -14,6 +14,10 @@ https://www.swift.org/api/v1/install/releases.json. SWIFT_SDK_RELEASES = { "6.3.2": { "android": "939e933549d12d28f2e0bf71019d734d309859e9773c572657ce565a81f85d68", + "static_linux": { + "sha256": "3fd798bef6f4408f1ea5a6f94ce4d4052830c4326ab85ebc04f983f01b3da407", + "version": "0.1.0", + }, "wasm": "a61f0584c93283589f8b2f42db05c1f9a182b506c2957271402992655591dd7c", }, } @@ -39,3 +43,25 @@ def swift_sdk_download_url(swift_version, sdk): sdk = 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, + ) diff --git a/swift/internal/extensions/swift_sdks.bzl b/swift/internal/extensions/swift_sdks.bzl index 1ccad94d4..54d391070 100644 --- a/swift/internal/extensions/swift_sdks.bzl +++ b/swift/internal/extensions/swift_sdks.bzl @@ -3,12 +3,12 @@ A "Swift SDK" is an artifact bundle published by swift.org for cross-compiling Swift to a platform the host toolchain cannot target by itself (the bundles that `swift sdk install` consumes). The per-platform extensions (`android_sdk`, -`wasm_sdk`) define repository rules that download such a bundle and generate a -`swift_toolchain` for the target; this module holds the helpers and BUILD-file -templates they share. Android's C/C++ compilation and linking go through a -separately registered Android cc toolchain (e.g. `@androidndk//:all`), while the -WebAssembly repository also generates a rules_cc `cc_toolchain` that drives the -paired toolchain's clang. +`wasm_sdk`, `static_linux_sdk`) define repository rules that download such a +bundle and generate a `swift_toolchain` for the target; this module holds the +helpers and BUILD-file templates they share. Android's C/C++ compilation and +linking go through a separately registered Android cc toolchain (e.g. +`@androidndk//:all`), while the WebAssembly and Static Linux repositories also +generate rules_cc `cc_toolchain` targets that drive the paired toolchain's clang. Because the Swift module format is not stable across compiler versions, a Swift SDK must come from exactly the same release as the host toolchain it is paired @@ -52,9 +52,9 @@ swift_tools( ) """ -# The WebAssembly repository also generates a rules_cc cc_toolchain, so its -# BUILD header needs the rules_cc toolchain loads. -_WASM_BUILD_HEADER_TEMPLATE = """\ +# Some SDK repositories also generate a rules_cc cc_toolchain, so their BUILD +# header needs the rules_cc toolchain loads. +_CC_BUILD_HEADER_TEMPLATE = """\ 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") @@ -207,6 +207,79 @@ 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): + return _relative_metadata_path( + target_settings.get("swiftStaticResourcesPath"), + "swiftStaticResourcesPath", + triple, + ) + +def static_linux_linkopts_from_args( + *, + arch, + linux_static_dir, + args, + include_swiftrt = True): + """Returns linkopts using a Static Linux `static-executable-args.lnk` file. + + Args: + arch: The target architecture. + linux_static_dir: The execroot-relative `linux-static` resource + directory. + args: The parsed non-empty lines from `static-executable-args.lnk`. + include_swiftrt: If True, add the `swiftrt.o` path used by the current + Static Linux SDK layout. + + Returns: + Linkopts suitable for a C/C++ linker action. + """ + linkopts = [] + if include_swiftrt: + linkopts.append("{}/{}/swiftrt.o".format(linux_static_dir, arch)) + linkopts.append("-L{}".format(linux_static_dir)) + return linkopts + args + +def _static_linux_linkopts_from_sdk( + repository_ctx, + *, + arch, + linux_static_dir, + linux_static_dir_relative, + repo_root): + swiftrt_relative = "{}/{}/swiftrt.o".format(linux_static_dir_relative, arch) + args_relative = linux_static_dir_relative + "/static-executable-args.lnk" + if not repository_ctx.path(args_relative).exists: + fail("The Static Linux Swift SDK bundle has an unexpected layout; " + + "missing {}/{}".format(repo_root, args_relative)) + + args = [] + for line in repository_ctx.read(args_relative).split("\n"): + arg = line.strip() + if arg: + args.append(arg) + + return static_linux_linkopts_from_args( + arch = arch, + args = args, + include_swiftrt = repository_ctx.path(swiftrt_relative).exists, + linux_static_dir = linux_static_dir, + ) + def _common_attrs(): return { "sha256": attr.string( @@ -347,7 +420,7 @@ def _swift_wasm_sdk_impl(repository_ctx): wasi_sdk = sdk_dir + "/WASI.sdk" resource_dir = sdk_dir + "/swift.xctoolchain/usr/lib/swift_static" - build_content = _WASM_BUILD_HEADER_TEMPLATE.format( + build_content = _CC_BUILD_HEADER_TEMPLATE.format( bundle_dir = bundle_dir, compiler_inputs = _build_list([ ":sdk_files", @@ -434,3 +507,132 @@ 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 = _CC_BUILD_HEADER_TEMPLATE.format( + bundle_dir = bundle_dir, + compiler_inputs = _build_list([ + ":sdk_files", + "@{}//:swift_sdk_compiler_inputs".format(toolchain_repo), + ]), + 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", + "@{}//:swift_sdk_linker_inputs".format(toolchain_repo), + ]), + ) + + 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_relative = resource_dir_relative + "/linux-static" + 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", + ]), + linker_inputs = _build_list([":sdk_files"]), + linkopts = _build_list(_static_linux_linkopts_from_sdk( + repository_ctx, + arch = arch, + linux_static_dir = linux_static_dir, + linux_static_dir_relative = linux_static_dir_relative, + repo_root = repo_root, + )), + 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. + "-static", + "-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, +) diff --git a/swift/internal/extensions/toolchains.bzl b/swift/internal/extensions/toolchains.bzl index cb6916ba7..51a80618c 100644 --- a/swift/internal/extensions/toolchains.bzl +++ b/swift/internal/extensions/toolchains.bzl @@ -55,7 +55,7 @@ toolchain( ) """ -_WASM_SDK_TOOLCHAIN_PLATFORM = """ +_CC_SDK_TOOLCHAIN_PLATFORM = """ # Swift SDK toolchains from repository: `{sdk_repository}` toolchain( name = "swift_toolchain_{target}_{platform}", @@ -66,12 +66,6 @@ toolchain( visibility = ["//visibility:public"], ) -# The paired rules_cc toolchain drives the SDK bundle's own clang for the C -# side of the build (unlike Android, where the cc toolchain comes from the -# separately registered NDK). It only resolves for this wasm target platform, -# and because root-module registrations take precedence in toolchain -# resolution, a consumer who registers their own wasm cc toolchain wins over -# this one automatically. toolchain( name = "cc_toolchain_{target}_{platform}", exec_compatible_with = {exec_compatible_with}, @@ -141,7 +135,7 @@ def wasm_sdk_toolchains_for_platform(platform, sdk_repository): Returns: BUILD file content declaring the Swift and C++ toolchains. """ - return _WASM_SDK_TOOLCHAIN_PLATFORM.format( + return _CC_SDK_TOOLCHAIN_PLATFORM.format( exec_compatible_with = _exec_compatible_with_for_platform(platform), platform = platform, sdk_repository = sdk_repository, @@ -153,6 +147,36 @@ def wasm_sdk_toolchains_for_platform(platform, sdk_repository): target_suffix = "wasm32", ) +def static_linux_sdk_toolchains_for_platform(platform, sdk_repository, archs): + """Returns `toolchain` declarations for a Static Linux Swift SDK. + + Args: + platform: The 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 += _CC_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 + def _toolchains_impl(repository_ctx): repository_ctx.file("BUILD.bazel", repository_ctx.attr.build_file_content) diff --git a/swift/toolchains/BUILD b/swift/toolchains/BUILD index 10964bc03..b92a6d2f9 100644 --- a/swift/toolchains/BUILD +++ b/swift/toolchains/BUILD @@ -11,6 +11,17 @@ load( licenses(["notice"]) +constraint_setting( + name = "swift_sdk", + visibility = ["//visibility:public"], +) + +constraint_value( + name = "static_linux", + constraint_setting = ":swift_sdk", + visibility = ["//visibility:public"], +) + toolchain( name = "linux-swift-toolchain_x86_64", exec_compatible_with = [ diff --git a/test/fixtures/toolchains/BUILD b/test/fixtures/toolchains/BUILD index 0c2f2013c..c1327457e 100644 --- a/test/fixtures/toolchains/BUILD +++ b/test/fixtures/toolchains/BUILD @@ -1,8 +1,18 @@ +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") load( "//swift/toolchains:swift_toolchain.bzl", "swift_toolchain", ) +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 +28,10 @@ toolchain( toolchain = ":_toolchain_macos_arm64_with_sdkroot", toolchain_type = "//toolchains:toolchain_type", ) + +cc_binary( + name = "static_linux_cc_uses_libcxx", + srcs = ["static_linux_cc_uses_libcxx.cc"], + tags = ["manual"], + visibility = ["//test:__pkg__"], +) 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..68424462e 100644 --- a/test/swift_toolchain_tests.bzl +++ b/test/swift_toolchain_tests.bzl @@ -11,6 +11,14 @@ 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:platforms": [ + str(Label("//test/fixtures/toolchains:linux_musl_x86_64")), + ], + }, +) + def swift_toolchain_test_suite(name, tags = []): """Test suite for swift_toolchain's provider. @@ -29,6 +37,36 @@ 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", + "swift-linux-musl/musl-1.2.5.sdk/x86_64", + "-resource-dir", + "swift-linux-musl/musl-1.2.5.sdk/x86_64/usr/lib/swift_static", + "-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", + "swift-linux-musl/musl-1.2.5.sdk/x86_64", + "-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 7a8530667..721aee115 100644 --- a/test/utils_tests.bzl +++ b/test/utils_tests.bzl @@ -12,7 +12,18 @@ load("//swift/internal:utils.bzl", "include_developer_search_paths") load("//swift/internal/extensions:standalone_toolchain.bzl", "get_download_url") # buildifier: disable=bzl-visibility -load("//swift/internal/extensions:swift_sdk_releases.bzl", "swift_sdk_download_url") +load( + "//swift/internal/extensions:swift_sdk_releases.bzl", + "SWIFT_SDK_RELEASES", + "static_linux_sdk_download_url", + "swift_sdk_download_url", +) + +# buildifier: disable=bzl-visibility +load( + "//swift/internal/extensions:swift_sdks.bzl", + "static_linux_linkopts_from_args", +) def _include_developer_search_paths_test(ctx): env = unittest.begin(ctx) @@ -193,11 +204,107 @@ def _swift_sdk_download_url_test(ctx): swift_sdk_download_url_test = unittest.make(_swift_sdk_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 _static_linux_linkopts_from_args_test(ctx): + env = unittest.begin(ctx) + + args = [ + "-static", + "-lswiftCore", + "-lswift_RegexParser", + "-Xlinker", + "-undefined=pthread_self", + "-Xlinker", + "-undefined=pthread_once", + "-Xlinker", + "-undefined=pthread_key_create", + "-ldispatch", + "-lBlocksRuntime", + "-lpthread", + "-ldl", + "-lc++", + "-lm", + ] + + asserts.equals( + env, + [ + "external/static_linux_sdk/swift_static/linux-static/x86_64/swiftrt.o", + "-Lexternal/static_linux_sdk/swift_static/linux-static", + "-static", + "-lswiftCore", + "-lswift_RegexParser", + "-Xlinker", + "-undefined=pthread_self", + "-Xlinker", + "-undefined=pthread_once", + "-Xlinker", + "-undefined=pthread_key_create", + "-ldispatch", + "-lBlocksRuntime", + "-lpthread", + "-ldl", + "-lc++", + "-lm", + ], + static_linux_linkopts_from_args( + arch = "x86_64", + args = args, + linux_static_dir = "external/static_linux_sdk/swift_static/linux-static", + ), + ) + + asserts.equals( + env, + [ + "-Lexternal/static_linux_sdk/swift_static/linux-static", + "-Xlinker", + "--future-linker-arg", + "-Xlinker", + ], + static_linux_linkopts_from_args( + arch = "x86_64", + args = [ + "-Xlinker", + "--future-linker-arg", + "-Xlinker", + ], + include_swiftrt = False, + linux_static_dir = "external/static_linux_sdk/swift_static/linux-static", + ), + ) + + return unittest.end(env) + +static_linux_linkopts_from_args_test = unittest.make(_static_linux_linkopts_from_args_test) + def utils_test_suite(name): return unittest.suite( name, developer_dirs_linkopts_test, include_developer_search_paths_test, + static_linux_linkopts_from_args_test, + static_linux_sdk_release_metadata_test, standalone_toolchain_download_url_test, swift_sdk_download_url_test, )