Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions swift/internal/extensions/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@ bzl_library(
visibility = ["//swift:__subpackages__"],
)

bzl_library(
name = "swift_sdk_releases",
srcs = ["swift_sdk_releases.bzl"],
visibility = ["//swift:__subpackages__"],
)

bzl_library(
name = "swift_sdks",
srcs = ["swift_sdks.bzl"],
visibility = ["//swift:__subpackages__"],
)

bzl_library(
name = "toolchains",
srcs = ["toolchains.bzl"],
Expand Down
38 changes: 38 additions & 0 deletions swift/internal/extensions/swift_sdk_releases.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""Swift SDK release version mappings.

Checksums for the official "Swift SDK" artifact bundles published by swift.org
(the bundles installed by `swift sdk install`), used to cross-compile Swift to
platforms not covered by a host toolchain.

The Swift module format is not stable across compiler versions, so a Swift SDK
can only be used with the host toolchain from exactly the same release; the keys
of `SWIFT_SDK_RELEASES` therefore mirror the keys of `SWIFT_RELEASES` in
`swift_releases.bzl`. Checksums are published in
https://www.swift.org/api/v1/install/releases.json.
"""

# Populated by the per-platform extensions (e.g. `wasm_sdk`, `android_sdk`),
# which add their SDK checksums under each supported Swift release.
SWIFT_SDK_RELEASES = {}

def swift_sdk_download_url(swift_version, sdk):
"""Returns the download URL for a Swift SDK artifact bundle.

Args:
swift_version: The Swift release version (e.g. "6.3.2").
sdk: The SDK kind (e.g. "wasm").

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/{sdk}-sdk/" +
"swift-{version}-RELEASE/swift-{version}-RELEASE_{sdk}.artifactbundle.tar.gz"
).format(
sdk = sdk,
version = swift_version,
)
203 changes: 203 additions & 0 deletions swift/internal/extensions/swift_sdks.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
"""Shared building blocks for Swift SDK repository rules.

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 (e.g. `wasm_sdk`,
`android_sdk`) define repository rules that download such a bundle and generate
a `swift_toolchain` + rules_cc `cc_toolchain` for the target; this module holds
the helpers and BUILD-file templates they share.

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
with; the `swift` module extension enforces this by deriving both from the same
`swift.toolchain` tag.
"""

# Files in the host toolchain that compile actions need: the driver/frontend
# binaries, their libraries, and clang's builtin headers (used by the clang
# importer when the Swift SDK's resource directory does not bundle them).
# buildifier: disable=unused-variable
_HOST_COMPILER_INPUTS = "swift_sdk_compiler_inputs"

# Files in the host toolchain that link actions driven by its clang need.
# buildifier: disable=unused-variable
_HOST_LINKER_INPUTS = "swift_sdk_linker_inputs"

# buildifier: disable=unused-variable
_CC_TOOLCHAIN_TEMPLATE = """
cc_tool(
name = "clang",
src = "{clang}",
data = {clang_data},
tags = ["manual"],
)

cc_tool(
name = "ar",
src = "{ar}",
tags = ["manual"],
)

cc_tool_map(
name = "cc_tools",
tags = ["manual"],
tools = {{
"@rules_cc//cc/toolchains/actions:ar_actions": ":ar",
"@rules_cc//cc/toolchains/actions:assembly_actions": ":clang",
"@rules_cc//cc/toolchains/actions:c_compile": ":clang",
"@rules_cc//cc/toolchains/actions:cpp_compile_actions": ":clang",
"@rules_cc//cc/toolchains/actions:link_actions": ":clang",
}},
)
"""

# buildifier: disable=unused-variable
_CC_TOOLCHAIN_FOR_TARGET_TEMPLATE = """
cc_args(
name = "cc_args_{suffix}",
actions = [
"@rules_cc//cc/toolchains/actions:compile_actions",
"@rules_cc//cc/toolchains/actions:link_actions",
],
args = {args},
)

cc_args(
name = "cc_link_args_{suffix}",
actions = [
"@rules_cc//cc/toolchains/actions:link_actions",
],
args = {link_args},
)

cc_make_variable(
name = "cc_target_triple_{suffix}",
value = "{triple}",
variable_name = "CC_TARGET_TRIPLE",
)

cc_toolchain(
name = "cc_toolchain_{suffix}",
args = [
":cc_args_{suffix}",
":cc_link_args_{suffix}",
],
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",
# Needed so `swift_binary(linkshared = True)` links a shared library
# (passes `-shared` for the dynamic_library link action).
"@rules_cc//cc/toolchains/args/shared_flag:feature",
],
make_variables = [
":cc_target_triple_{suffix}",
],
tool_map = ":cc_tools",
)
"""

# buildifier: disable=unused-variable
_SWIFT_TOOLCHAIN_TEMPLATE = """
swift_toolchain(
name = "swift_toolchain_{suffix}",
arch = "{arch}",
copts = {copts},
features = {features},
linker_inputs = {linker_inputs},
linkopts = {linkopts},
os = "{os}",
parsed_version = "{swift_version}",
sdkroot = "{sdkroot}",
swift_tools = ":tools",
version_file = ".swift-version",
)
"""

# buildifier: disable=unused-variable
_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")
load("@rules_cc//cc/toolchains:tool_map.bzl", "cc_tool_map")
load("@rules_cc//cc/toolchains:toolchain.bzl", "cc_toolchain")
load("@rules_swift//swift/toolchains:swift_toolchain.bzl", "swift_toolchain")
load("@rules_swift//swift/toolchains:swift_tools.bzl", "swift_tools")

package(default_visibility = ["//visibility:public"])

filegroup(
name = "sdk_files",
srcs = glob(["{bundle_dir}/**"]),
)

swift_tools(
name = "tools",
swift_driver = "@{toolchain_repo}//:usr/bin/swiftc",
swift_autolink_extract = "@{toolchain_repo}//:usr/bin/swift-autolink-extract",
swift_symbolgraph_extract = "@{toolchain_repo}//:usr/bin/swift-symbolgraph-extract",
additional_inputs = {compiler_inputs},
)
"""

# buildifier: disable=unused-variable
def _build_list(items, indent = " "):
"""Formats a list of strings as a multi-line BUILD file list literal."""
if not items:
return "[]"
lines = ["["]
for item in items:
lines.append("{} \"{}\",".format(indent, item))
lines.append(indent + "]")
return "\n".join(lines)

# buildifier: disable=unused-variable
def _download_sdk_bundle(repository_ctx):
"""Downloads and extracts the Swift SDK artifact bundle for a repository.

Returns:
The name of the top-level `.artifactbundle` directory.
"""
repository_ctx.download_and_extract(
url = repository_ctx.attr.url,
sha256 = repository_ctx.attr.sha256,
)
repository_ctx.file(".swift-version", repository_ctx.attr.swift_version)

bundles = [
entry.basename
for entry in repository_ctx.path(".").readdir()
if entry.basename.endswith(".artifactbundle")
]
if len(bundles) != 1:
fail(("Expected the archive at {} to contain exactly one " +
".artifactbundle directory, found: {}").format(
repository_ctx.attr.url,
bundles,
))
return bundles[0]

# buildifier: disable=unused-variable
def _common_attrs():
return {
"sha256": attr.string(
doc = "The expected SHA-256 of the SDK artifact bundle.",
mandatory = True,
),
"swift_version": attr.string(
doc = "The Swift release version the SDK belongs to.",
mandatory = True,
),
"toolchain_repo": attr.string(
doc = """\
Name of the `standalone_toolchain` repository providing the host tools that
this SDK is paired with.
""",
mandatory = True,
),
"url": attr.string(
doc = "The download URL of the SDK artifact bundle.",
mandatory = True,
),
}
38 changes: 30 additions & 8 deletions swift/internal/extensions/toolchains.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,42 @@ toolchain(

"""

def toolchains_for_platform(platform, toolchain_repository):
# buildifier: disable=unused-variable
_SDK_TOOLCHAIN_PLATFORM = """
# Swift SDK toolchains from repository: `{sdk_repository}`
toolchain(
name = "swift_toolchain_{target}_{platform}",
exec_compatible_with = {exec_compatible_with},
target_compatible_with = {target_compatible_with},
toolchain = "@{sdk_repository}//:swift_toolchain_{target_suffix}",
toolchain_type = "@rules_swift//toolchains:toolchain_type",
visibility = ["//visibility:public"],
)

toolchain(
name = "cc_toolchain_{target}_{platform}",
exec_compatible_with = {exec_compatible_with},
target_compatible_with = {target_compatible_with},
toolchain = "@{sdk_repository}//:cc_toolchain_{target_suffix}",
toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
visibility = ["//visibility:public"],
)
"""

def _exec_compatible_with_for_platform(platform):
# This assumption is baked into the API so we have to go along with it
if platform == "xcode":
exec_compatible_with = [
return [
"@platforms//os:macos",
]
else:
exec_compatible_with = [
"@platforms//os:linux",
"@platforms//cpu:{}".format("aarch64" if "aarch64" in platform else "x86_64"),
]
return [
"@platforms//os:linux",
"@platforms//cpu:{}".format("aarch64" if "aarch64" in platform else "x86_64"),
]

def toolchains_for_platform(platform, toolchain_repository):
return _TOOLCHAIN_PLATFORM.format(
exec_compatible_with = exec_compatible_with,
exec_compatible_with = _exec_compatible_with_for_platform(platform),
platform = platform,
toolchain_repository = toolchain_repository,
)
Expand Down
65 changes: 65 additions & 0 deletions swift/toolchains/swift_toolchain.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,37 @@ def _swift_unix_linkopts_cc_info(
),
)

def _swift_sdk_linkopts_cc_info(
toolchain_label,
linkopts,
linker_inputs):
"""Returns a `CcInfo` with linker flags supplied by the toolchain target.

Used for toolchains whose Swift runtime libraries come from a Swift SDK
rather than from the host toolchain or system; the repository that defines
the toolchain provides the exact search paths and runtime objects to link.

Args:
toolchain_label: The label that owns the propagated linker input.
linkopts: Linker flags from the toolchain's `linkopts` attribute.
linker_inputs: `File`s that link actions using these flags need.

Returns:
A `CcInfo` that propagates the flags to binaries depending on Swift
targets.
"""
return CcInfo(
linking_context = cc_common.create_linking_context(
linker_inputs = depset([
cc_common.create_linker_input(
owner = toolchain_label,
user_link_flags = depset(linkopts),
additional_inputs = depset(linker_inputs),
),
]),
),
)

def _entry_point_linkopts_provider(*, entry_point_name):
"""Returns linkopts to customize the entry point of a binary."""
return struct(
Expand Down Expand Up @@ -509,6 +540,20 @@ def _swift_toolchain_impl(ctx):
)
elif ctx.attr.os == "none":
swift_linkopts_cc_info = CcInfo()
elif ctx.attr.linkopts or ctx.attr.linker_inputs:
Comment thread
AttilaTheFun marked this conversation as resolved.
# A toolchain whose Swift runtime comes from a Swift SDK (e.g.
# WebAssembly or Android) provides the *complete* set of runtime link
# flags via `linkopts`/`linker_inputs`, replacing — not adding to — the
# defaults computed below. Those defaults are specific to a Linux *host*
# toolchain (`-pie`, `-static-libgcc`, `-lrt`, and the host toolchain's
# own `-L`/rpath and `swiftrt.o`); for a cross-compiled SDK target they
# are at best wrong and at worst invalid (wasm-ld rejects `-pie`, and a
# second `swiftrt.o` would conflict with the SDK's own).
swift_linkopts_cc_info = _swift_sdk_linkopts_cc_info(
ctx.label,
ctx.attr.linkopts,
ctx.files.linker_inputs,
)
else:
swift_linkopts_cc_info = _swift_unix_linkopts_cc_info(
ctx.attr.arch,
Expand Down Expand Up @@ -763,6 +808,26 @@ normally.
""",
mandatory = False,
),
"linker_inputs": attr.label_list(
allow_files = True,
doc = """\
Files that must be available to link actions when `linkopts` is set, such as
the Swift runtime libraries of a Swift SDK.
""",
),
"linkopts": attr.string_list(
doc = """\
The *complete* set of linker flags for the Swift runtime when that runtime is
provided by a Swift SDK (for example WebAssembly or Android) rather than by the
host toolchain — typically search paths for, and inputs from, `linker_inputs`,
plus the SDK's runtime objects.

This is not additive: when set, it *replaces* the runtime link flags the
toolchain would otherwise compute, because those are specific to a Linux host
toolchain and do not apply to a cross-compiled SDK target. Leave it unset for an
ordinary host toolchain, which computes its own flags.
""",
),
"sdkroot": attr.string(
doc = """\
The root of a SDK to be used for building the target.
Expand Down
Loading
Loading