Skip to content

Commit 3e2daf2

Browse files
Copilotjketema
authored andcommitted
Support building swift-syntax-rs on macOS
1 parent 58ddeac commit 3e2daf2

6 files changed

Lines changed: 161 additions & 41 deletions

File tree

MODULE.bazel

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ bazel_dep(name = "googletest", version = "1.17.0.bcr.2")
3333
bazel_dep(name = "rules_rust", version = "0.69.0")
3434
bazel_dep(name = "rules_swift", version = "4.0.0-rc4")
3535
bazel_dep(name = "swift-syntax", version = "602.0.0.bcr.2")
36+
37+
# Needed so we can `use_repo` `local_config_xcode` and
38+
# `local_config_apple_cc_toolchains` below (referenced by the per-target
39+
# Xcode-config transition in `unified/swift-syntax-rs/xcode_transition.bzl`).
40+
bazel_dep(name = "apple_support", version = "2.6.1")
3641
bazel_dep(name = "zstd", version = "1.5.7.bcr.1")
3742

3843
bazel_dep(name = "buildifier_prebuilt", version = "6.4.0", dev_dependency = True)
@@ -221,22 +226,11 @@ use_repo(
221226
"swift-resource-dir-macos",
222227
)
223228

224-
# Hermetic Swift toolchain (from swift.org) for building the `swift-syntax`
225-
# based Rust wrapper in `unified/swift-syntax-rs`. This is the official
226-
# `rules_swift` standalone-toolchain extension and is independent of the
227-
# patched prebuilt toolchain wired up via `swift_deps` above.
228-
#
229-
# Bazel cannot auto-select between Linux distributions, so the toolchain is
230-
# registered explicitly for the distribution our CI runs on (ubuntu24.04,
231-
# x86_64). Add further platforms here if CI grows to cover them.
232-
#
233-
# We register the `exec` toolchain (normal host compilation, targeting
234-
# linux/x86_64) rather than the `embedded` one, which targets `os:none`
235-
# (Embedded Swift) and would not match a normal host build.
236-
#
237-
# The Swift version is read from `unified/swift-syntax-rs/.swift-version`, which
238-
# is the single source of truth shared with the local `cargo` build (via
239-
# swiftly / any Swift install) and `swift/Package.swift`.
229+
# Swift toolchain for building `unified/swift-syntax-rs`. On Linux we register
230+
# a hermetic swift.org toolchain as the exec toolchain; on macOS `rules_swift`
231+
# auto-registers `xcode_swift_toolchain` (host Xcode + OS-provided Swift
232+
# runtime), which is not hermetic. Version comes from
233+
# `unified/swift-syntax-rs/.swift-version`.
240234
swift = use_extension("@rules_swift//swift:extensions.bzl", "swift")
241235
swift.toolchain(
242236
name = "swift_toolchain",
@@ -252,6 +246,14 @@ register_toolchains(
252246
"@swift_toolchain//:swift_toolchain_exec_ubuntu24.04",
253247
)
254248

249+
# `apple_support`'s xcode_config and CC toolchains, needed by the Xcode
250+
# transition in `unified/swift-syntax-rs/xcode_transition.bzl`.
251+
xcode_configure = use_extension("@apple_support//xcode:xcode_configure.bzl", "xcode_configure_extension")
252+
use_repo(xcode_configure, "local_config_xcode")
253+
254+
apple_cc_configure = use_extension("@apple_support//crosstool:setup.bzl", "apple_cc_configure_extension")
255+
use_repo(apple_cc_configure, "local_config_apple_cc_toolchains")
256+
255257
node = use_extension("@rules_nodejs//nodejs:extensions.bzl", "node")
256258
node.toolchain(
257259
name = "nodejs",
Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,44 @@
11
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library", "rust_test")
2-
load("@rules_swift//swift:swift.bzl", "swift_library")
2+
load(":xcode_transition.bzl", "xcode_transition_swift_library")
33

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

6-
# The pinned Swift version, shared with the local `cargo` build and
7-
# `swift/Package.swift`. Referenced by the `swift.toolchain` extension in
8-
# //:MODULE.bazel via `swift_version_file`.
6+
# Pinned Swift version, shared with `cargo` and `swift/Package.swift`.
7+
# Referenced by `swift.toolchain(swift_version_file = ...)` in //:MODULE.bazel.
98
exports_files([".swift-version"])
109

11-
# The Swift FFI shim: wraps swift-syntax and exposes a small C ABI
12-
# (`ssr_parse_json` / `ssr_string_free`). Built with the hermetic Swift
13-
# toolchain registered in //:MODULE.bazel; provides a `CcInfo` that the Rust
14-
# targets below link against.
15-
swift_library(
10+
# Targets in this package require a Swift toolchain (Linux or macOS).
11+
# `select()` gives us OR-of-OSes; other platforms get marked incompatible
12+
# so `bazel build/test //...` skips them cleanly.
13+
_SWIFT_SUPPORTED_PLATFORMS = select({
14+
"@platforms//os:linux": [],
15+
"@platforms//os:macos": [],
16+
"//conditions:default": ["@platforms//:incompatible"],
17+
})
18+
19+
# Swift FFI shim: wraps swift-syntax and exposes a small C ABI. The Rust
20+
# targets below link against its `CcInfo`.
21+
xcode_transition_swift_library(
1622
name = "swift_syntax_ffi",
1723
srcs = ["swift/Sources/SwiftSyntaxFFI/SwiftSyntaxFFI.swift"],
1824
module_name = "SwiftSyntaxFFI",
25+
target_compatible_with = _SWIFT_SUPPORTED_PLATFORMS,
1926
deps = [
2027
"@swift-syntax//:SwiftParser",
2128
"@swift-syntax//:SwiftSyntax",
2229
],
2330
)
2431

25-
# Safe Rust bindings on top of the C ABI. `build.rs` is intentionally *not*
26-
# wired up here (no `cargo_build_script`): under Bazel the Swift side is
27-
# provided by `:swift_syntax_ffi`, whereas `build.rs` only exists to build the
28-
# Swift shim in the local `cargo` workflow.
32+
# Safe Rust bindings on top of the C ABI. Under Bazel the Swift side comes
33+
# from `:swift_syntax_ffi`; `build.rs` is only used by the `cargo` workflow.
2934
rust_library(
3035
name = "swift_syntax_rs",
3136
srcs = glob(
3237
["src/**/*.rs"],
3338
exclude = ["src/main.rs"],
3439
),
3540
edition = "2024",
41+
target_compatible_with = _SWIFT_SUPPORTED_PLATFORMS,
3642
deps = [
3743
":swift_syntax_ffi",
3844
],
@@ -41,20 +47,27 @@ rust_library(
4147
rust_binary(
4248
name = "swift-syntax-parse",
4349
srcs = ["src/main.rs"],
44-
# The Swift toolchain propagates a runfiles-relative RPATH to its runtime
45-
# `.so`s via `swift_syntax_ffi`'s `CcInfo`, but (unlike `swift_binary`)
46-
# `rust_binary` does not copy those libraries into runfiles. Add the
47-
# toolchain files as `data` so the runtime is found at execution time.
48-
# (rules_swift does not yet support statically linking the runtime.)
49-
data = ["@swift_toolchain_ubuntu24.04//:files"],
50+
# `rust_binary` doesn't copy the Swift runtime into runfiles the way
51+
# `swift_binary` does. On Linux, ship the standalone toolchain's runtime;
52+
# on macOS the OS provides it at `/usr/lib/swift` (rpath'd by
53+
# `xcode_swift_toolchain`).
54+
data = select({
55+
"@platforms//os:macos": [],
56+
"@platforms//os:linux": ["@swift_toolchain_ubuntu24.04//:files"],
57+
}),
5058
edition = "2024",
59+
target_compatible_with = _SWIFT_SUPPORTED_PLATFORMS,
5160
deps = [":swift_syntax_rs"],
5261
)
5362

5463
rust_test(
5564
name = "swift_syntax_rs_test",
5665
size = "small",
5766
crate = ":swift_syntax_rs",
58-
data = ["@swift_toolchain_ubuntu24.04//:files"],
67+
data = select({
68+
"@platforms//os:macos": [],
69+
"@platforms//os:linux": ["@swift_toolchain_ubuntu24.04//:files"],
70+
}),
5971
edition = "2024",
72+
target_compatible_with = _SWIFT_SUPPORTED_PLATFORMS,
6073
)

unified/swift-syntax-rs/README.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,16 @@ Requirements:
144144
- **`clang`** must be installed on the runner. `rules_swift` requires the Bazel
145145
CC toolchain to use clang; the repo's `.bazelrc` already sets
146146
`--repo_env=CC=clang`, so no extra flags are needed.
147-
- The registered Swift toolchain currently targets **ubuntu24.04 / x86_64**
148-
only (Bazel cannot auto-select between Linux distributions). Add more
149-
platforms in `MODULE.bazel` (`swift.toolchain` + `register_toolchains`) if CI
150-
grows to cover them.
147+
- The registered Swift toolchains cover **ubuntu24.04 / x86_64** and
148+
**macOS / `xcode`** (Apple Silicon and Intel). Bazel selects the toolchain
149+
matching the host. Targets are marked `target_compatible_with` these two
150+
OSes, so on Windows Bazel skips them cleanly.
151+
- **macOS only:** the Swift toolchain comes from the host Xcode installation
152+
(`rules_swift` auto-registers `xcode_swift_toolchain`), which also needs
153+
Xcode's CC toolchain and xcode_config; these are applied to the Swift
154+
target via an incoming-edge Starlark transition (see
155+
[`xcode_transition.bzl`](xcode_transition.bzl)), so other targets on macOS
156+
keep using Bazel's default CC toolchain.
151157

152158
The Swift compiler version is read from [`.swift-version`](.swift-version) by
153159
both the Bazel toolchain (`swift.toolchain(swift_version_file = …)`) and the

unified/swift-syntax-rs/build.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ fn swift_runtime_dir() -> Option<PathBuf> {
7373
let close = value_start.find('"')?;
7474
let resource_path = &value_start[..close];
7575

76-
Some(PathBuf::from(resource_path).join("linux"))
76+
Some(PathBuf::from(resource_path).join(if cfg!(target_os = "macos") { "macosx" } else { "linux" }))
7777
}
7878

7979
/// The `swift` driver to invoke: `$SWIFT` if set, otherwise `swift` from `PATH`.

unified/swift-syntax-rs/swift/Package.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ import PackageDescription
33

44
let package = Package(
55
name: "SwiftSyntaxFFI",
6+
platforms: [
7+
// swift-syntax 602 requires macOS 10.15; declare it explicitly
8+
// rather than relying on the swift-tools-version default (10.13).
9+
.macOS(.v10_15),
10+
],
611
products: [
712
// Dynamic library so the produced .so records its dependency on the
813
// Swift runtime libraries, keeping the Rust link step simple.
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
"""Per-target Xcode configuration for `//unified/swift-syntax-rs` on macOS.
2+
3+
`rules_swift`'s auto-registered `xcode_swift_toolchain` reads
4+
`cc_toolchain.target_gnu_system_name`, which is literally "local" on
5+
Bazel's built-in `local_config_cc`. To make it usable we need:
6+
7+
- `--xcode_version_config=@local_config_xcode//:host_xcodes` — selects
8+
`apple_support`'s xcode_config (matches `rules_swift`'s `system_sdk` keys).
9+
- `--extra_toolchains=@local_config_apple_cc_toolchains//:all` — forces
10+
`apple_support`'s CC toolchain ahead of `local_config_cc`.
11+
12+
Applied via an incoming-edge Starlark transition on the `swift_library`
13+
target only (via the `xcode_transition_swift_library` macro), so downstream
14+
Rust targets and the rest of the repo stay on the default CC toolchain and
15+
the `@local_config_*` repos are not materialized otherwise. No-op off macOS.
16+
"""
17+
18+
load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
19+
load("@rules_swift//swift:swift.bzl", "swift_library")
20+
load("//misc/bazel:os.bzl", "os_select")
21+
22+
_XCODE_VERSION_CONFIG = "//command_line_option:xcode_version_config"
23+
_EXTRA_TOOLCHAINS = "//command_line_option:extra_toolchains"
24+
25+
def _transition_impl(settings, attr):
26+
if attr.os != "macos":
27+
# Preserve inputs so the configuration is identical to the incoming one.
28+
return {
29+
_XCODE_VERSION_CONFIG: settings[_XCODE_VERSION_CONFIG],
30+
_EXTRA_TOOLCHAINS: settings[_EXTRA_TOOLCHAINS],
31+
}
32+
return {
33+
_XCODE_VERSION_CONFIG: "@local_config_xcode//:host_xcodes",
34+
_EXTRA_TOOLCHAINS: (
35+
list(settings[_EXTRA_TOOLCHAINS]) +
36+
["@local_config_apple_cc_toolchains//:all"]
37+
),
38+
}
39+
40+
_xcode_transition = transition(
41+
implementation = _transition_impl,
42+
inputs = [_XCODE_VERSION_CONFIG, _EXTRA_TOOLCHAINS],
43+
outputs = [_XCODE_VERSION_CONFIG, _EXTRA_TOOLCHAINS],
44+
)
45+
46+
def _wrapper_impl(ctx):
47+
src = ctx.attr.actual[0]
48+
# Forward the providers a downstream `rust_*` target reads from `deps`:
49+
# `DefaultInfo`, `CcInfo` (linking info), and `OutputGroupInfo`.
50+
providers = [src[DefaultInfo]]
51+
for p in (CcInfo, OutputGroupInfo):
52+
if p in src:
53+
providers.append(src[p])
54+
return providers
55+
56+
_xcode_transition_swift_library_rule = rule(
57+
implementation = _wrapper_impl,
58+
attrs = {
59+
"actual": attr.label(
60+
mandatory = True,
61+
cfg = _xcode_transition,
62+
providers = [CcInfo],
63+
),
64+
"os": attr.string(mandatory = True),
65+
"_allowlist_function_transition": attr.label(
66+
default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
67+
),
68+
},
69+
)
70+
71+
def xcode_transition_swift_library(name, visibility = None, tags = None, target_compatible_with = None, **kwargs):
72+
"""`swift_library` wrapped in the macOS Xcode-config transition.
73+
74+
Emits a private inner `_impl_<name>` `swift_library` (tagged `manual`)
75+
and a public `name` that applies the transition on macOS and forwards
76+
the inner target's providers. Downstream `rust_*` targets depend on
77+
`name` as usual; only the `swift_library` sub-graph flips toolchain.
78+
"""
79+
inner_name = "_impl_%s" % name
80+
swift_library(
81+
name = inner_name,
82+
visibility = ["//visibility:private"],
83+
tags = (tags or []) + ["manual"],
84+
target_compatible_with = target_compatible_with,
85+
**kwargs
86+
)
87+
_xcode_transition_swift_library_rule(
88+
name = name,
89+
visibility = visibility,
90+
tags = tags,
91+
target_compatible_with = target_compatible_with,
92+
actual = ":" + inner_name,
93+
os = os_select(linux = "linux", macos = "macos", default = "other"),
94+
)

0 commit comments

Comments
 (0)