diff --git a/MODULE.bazel b/MODULE.bazel index 24260271eca..8db349f7346 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -228,13 +228,8 @@ use_repo( # auto-registers `xcode_swift_toolchain` (host Xcode + OS-provided Swift # runtime), which is not hermetic. # -# The version is pinned as a literal rather than read from -# `unified/swift-syntax-rs/.swift-version` via `swift_version_file`: the latter -# makes the extension `module_ctx.read` a `//unified/...` label, which fails to -# resolve when this repo is consumed as a dependency module (`@@ql+`) whose -# `unified/swift-syntax-rs` package is not loadable in that context. Keep this -# in sync with `unified/swift-syntax-rs/.swift-version` (used by the `cargo` -# build) and the `swift-syntax` release in `swift/Package.swift`. +# This and the `swift-syntax` version above are the only pins: the extractor is +# built solely by Bazel, so there is no second Swift build to keep in step. swift = use_extension("@rules_swift//swift:extensions.bzl", "swift") swift.toolchain( name = "swift_toolchain", diff --git a/unified/AGENTS.md b/unified/AGENTS.md index 8ac0e4a00e0..ddc12f85277 100644 --- a/unified/AGENTS.md +++ b/unified/AGENTS.md @@ -4,9 +4,10 @@ This is a CodeQL extractor that maps a language's parse tree onto a shared AST using the `yeast` desugaring engine. Swift, the only language so far, is parsed by Apple's swift-syntax rather than by tree-sitter. -Build and test with Bazel, whose Swift toolchain is hermetic on Linux, so -nothing needs to be installed locally. The extractor links `swift-syntax`, so a -`cargo` build additionally needs a local Swift toolchain. +Everything is built and tested with Bazel, whose Swift toolchain is hermetic on +Linux, so nothing needs to be installed locally. `cargo build`/`cargo test` do +not work here: the extractor links a Swift FFI shim that only Bazel builds. +(`cargo check` still works, so rust-analyzer is unaffected.) ## Building - To build the extractor pack, run `scripts/create-extractor-pack.sh`. diff --git a/unified/swift-syntax-rs/.gitignore b/unified/swift-syntax-rs/.gitignore index 0b649686540..ea8c4bf7f35 100644 --- a/unified/swift-syntax-rs/.gitignore +++ b/unified/swift-syntax-rs/.gitignore @@ -1,2 +1 @@ /target -/swift/.build diff --git a/unified/swift-syntax-rs/.swift-version b/unified/swift-syntax-rs/.swift-version deleted file mode 100644 index 91e4a9f2622..00000000000 --- a/unified/swift-syntax-rs/.swift-version +++ /dev/null @@ -1 +0,0 @@ -6.3.2 diff --git a/unified/swift-syntax-rs/Cargo.toml b/unified/swift-syntax-rs/Cargo.toml index 6957fb0e50e..79319fb7bc9 100644 --- a/unified/swift-syntax-rs/Cargo.toml +++ b/unified/swift-syntax-rs/Cargo.toml @@ -1,3 +1,6 @@ +# `BUILD.bazel` is the build. This file feeds `misc/bazel/3rdparty`, which +# resolves third-party crate versions from it, and `cargo check`; `cargo +# build`/`test` cannot link the Swift shim. [package] name = "swift-syntax-rs" description = "Rust wrapper around the swift-syntax package for parsing Swift source" diff --git a/unified/swift-syntax-rs/README.md b/unified/swift-syntax-rs/README.md index 7d15c6f2c23..1529519143a 100644 --- a/unified/swift-syntax-rs/README.md +++ b/unified/swift-syntax-rs/README.md @@ -4,8 +4,11 @@ A Rust wrapper around the [swift-syntax](https://github.com/swiftlang/swift-synt package, allowing Swift source code to be parsed from Rust. Parsing is delegated to a small Swift shim (in [`swift/`](swift/)) that links -against `SwiftSyntax`/`SwiftParser` and exposes a tiny C ABI. The Rust crate -builds that shim (via `build.rs`) and provides safe bindings on top of it. +against `SwiftSyntax`/`SwiftParser` and exposes a tiny C ABI. This crate provides +safe bindings on top of it. + +Bazel builds both halves; there is no `cargo` build (see +[Building & testing](#building--testing)). ## Output format @@ -122,47 +125,12 @@ an unparenthesised `a *** b + c` is a single flat sequence whose structure cannot be determined without knowing `***`'s precedence, so it is left flat in its entirety. -## Prerequisites - -The build does not depend on any particular version manager. You need: - -- **Rust** — pinned to `1.88` by the repo-root [`rust-toolchain.toml`](../../rust-toolchain.toml), - which `rustup` picks up automatically. -- **Swift** — pinned to the version in [`.swift-version`](.swift-version) - (currently `6.3.2`), used to build `swift-syntax` `603.0.2`. Install it any way - you like — [swift.org](https://www.swift.org/install/) or - [swiftly](https://www.swift.org/swiftly/) (which reads `.swift-version`), or a - system package. Just make sure `swift` is on your `PATH` (or point `build.rs` - at it with the `SWIFT` environment variable). - -On Debian/Ubuntu the Swift runtime also needs `libncurses6` (and related libs) -available on the system. - ## Building & testing -With `cargo` and `swift` on `PATH`: - -```sh -cargo build -cargo test -``` - -If your `swift`/`swiftc` are not on `PATH`, point the build at them explicitly: - -```sh -SWIFT=/path/to/swift SWIFTC=/path/to/swiftc cargo build -``` - -The first build compiles `swift-syntax` and can take several minutes. - -## Building with Bazel (CI) - -CI builds this crate hermetically with Bazel. A Swift toolchain is downloaded -from swift.org by the official `rules_swift` standalone toolchain extension -(wired up in the repo-root `MODULE.bazel`), `swift-syntax` is pulled from the -Bazel Central Registry, and the FFI shim is compiled as a `swift_library` that -the Rust targets link against. `build.rs` is not used under Bazel; it only -builds the Swift shim for the local `cargo` workflow. +Everything is built by Bazel, which downloads a Swift toolchain from swift.org +via the official `rules_swift` standalone toolchain extension (wired up in the +repo-root `MODULE.bazel`) and pulls `swift-syntax` from the Bazel Central +Registry. Nothing has to be installed locally on Linux: ```sh bazel build //unified/swift-syntax-rs:swift-syntax-parse @@ -170,6 +138,13 @@ bazel test //unified/swift-syntax-rs:swift_syntax_rs_test bazel run //unified/swift-syntax-rs:swift-syntax-parse < some.swift ``` +The first build compiles `swift-syntax` and can take several minutes. + +`cargo build`/`cargo test` do **not** work: the Swift shim is compiled by a +`swift_library` in [`BUILD.bazel`](BUILD.bazel), so a `cargo` link finds no +`ssr_*` symbols. `cargo check` does work — it does not link — which is all +rust-analyzer needs. + Requirements: - **`clang`** must be installed on the runner. `rules_swift` requires the Bazel @@ -186,20 +161,12 @@ Requirements: [`xcode_transition.bzl`](xcode_transition.bzl)), so other targets on macOS keep using Bazel's default CC toolchain. -The Swift compiler version is kept in sync across three places: the -[`.swift-version`](.swift-version) file (read by the local `cargo`/`swift build` -and by [swiftly](https://www.swift.org/swiftly/)), the literal `swift_version` -pinned on `swift.toolchain(...)` in the root `MODULE.bazel` (the hermetic -swift.org **Linux** Bazel toolchain), and the `swift-syntax` release in -`swift/Package.swift`. On **macOS** the version is *not* pinned by the Bazel -build: `rules_swift` auto-registers the host `xcode_swift_toolchain`, which uses -whichever Swift ships with the installed Xcode. So the pin governs Linux (and -local) builds, while the macOS compiler version depends on the host Xcode. - -(The Bazel toolchain pins a literal rather than reading `.swift-version` via -`swift_version_file`, because the latter makes the module extension read a -`//unified/...` label, which fails when this repo is consumed as a dependency -module.) +Versions are pinned in the root `MODULE.bazel` and nowhere else: the +`swift_version` literal on `swift.toolchain(...)` selects the hermetic swift.org +**Linux** toolchain, and `bazel_dep(name = "swift-syntax", ...)` selects the +`swift-syntax` release. On **macOS** the compiler version is *not* pinned: +`rules_swift` auto-registers the host `xcode_swift_toolchain`, so it follows +whichever Swift ships with the installed Xcode. ## Usage @@ -213,7 +180,7 @@ println!("{json}"); CLI (reads a file argument or stdin, prints the syntax tree as JSON): ```sh -echo 'let x = 1' | cargo run --bin swift-syntax-parse +echo 'let x = 1' | bazel run //unified/swift-syntax-rs:swift-syntax-parse ``` ## Converting to a yeast AST @@ -221,14 +188,12 @@ echo 'let x = 1' | cargo run --bin swift-syntax-parse The JSON tree is consumed by the CodeQL extractor, which converts it into a [`yeast::Ast`](../../shared/yeast) — the in-memory format its rewrite rules operate on. That adapter is a pure-Rust module living in the extractor -(`unified/extractor/src/languages/swift/adapter.rs`), so the extractor never -needs the Swift toolchain: it consumes the JSON produced out-of-process by this -crate's `parse_to_json` / the `swift-syntax-parse` binary. +(`unified/extractor/src/languages/swift/adapter.rs`); the extractor links this +crate and calls `parse_to_json` in-process. ## Layout -- `swift/` — Swift package exposing the `ssr_parse_json` / `ssr_string_free` C ABI. -- `build.rs` — builds the Swift package and emits link/rpath flags (local `cargo` only). -- `BUILD.bazel` — Bazel targets for the hermetic CI build (swift_library + rust targets). +- `swift/` — Swift sources exposing the `ssr_parse_json` / `ssr_string_free` C ABI. +- `BUILD.bazel` — the build (swift_library + rust targets). - `src/lib.rs` — safe Rust bindings (`parse_to_json`). - `src/main.rs` — demo CLI. diff --git a/unified/swift-syntax-rs/build.rs b/unified/swift-syntax-rs/build.rs deleted file mode 100644 index 6a58ddb4d8d..00000000000 --- a/unified/swift-syntax-rs/build.rs +++ /dev/null @@ -1,120 +0,0 @@ -use std::env; -use std::path::PathBuf; -use std::process::Command; - -fn main() { - let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); - let swift_dir = manifest_dir.join("swift"); - - // Emitting any `rerun-if-changed` disables Cargo's default whole-package - // scan, so we must list every input to the `swift build` below. Watch the - // package manifests, the pinned dependency lockfile, the pinned compiler - // version, and the entire Swift sources tree (a directory is scanned - // recursively). `.build/` is intentionally *not* watched (it is this - // build's output; watching it would cause perpetual rebuilds). - for input in [ - swift_dir.join("Package.swift"), - swift_dir.join("Package.resolved"), - swift_dir.join("Sources"), - manifest_dir.join(".swift-version"), - ] { - println!("cargo:rerun-if-changed={}", input.display()); - } - println!("cargo:rerun-if-env-changed=SWIFT"); - println!("cargo:rerun-if-env-changed=SWIFTC"); - - // Build the Swift FFI package as a release dynamic library. - // - // Degrade gracefully when there is no runnable Swift toolchain. This crate - // is a workspace member, so a plain `cargo check`/`fmt`/`clippy` at the repo - // root runs this build script; if `swift build` cannot even be spawned we - // emit a warning and skip the link directives rather than panicking, so - // those Swift-free workflows keep working. Only `cargo build`/`cargo test` - // then fail — at link time, which is fair: they genuinely need Swift (and CI - // builds go through Bazel anyway). A Swift toolchain that *is* present but - // whose build fails is still surfaced as a hard error below. - let mut command = Command::new(swift_bin()); - command - .args(["build", "-c", "release"]) - .current_dir(&swift_dir); - apply_bare_repository_workaround(&mut command); - let status = match command.status() { - Ok(status) => status, - Err(e) => { - println!( - "cargo:warning=skipping the Swift FFI build: failed to run `{swift} build`: {e}. \ - Install a Swift toolchain (see https://www.swift.org/install/, e.g. via swiftly) \ - and ensure `swift` is on PATH, or set the `SWIFT` environment variable, to build \ - or test this crate. `cargo check`/`fmt`/`clippy` work without it. The pinned \ - version is in `.swift-version`.", - swift = swift_bin(), - ); - return; - } - }; - assert!(status.success(), "`swift build` failed"); - - // Link against the freshly built dynamic library. - let build_dir = swift_dir.join(".build/release"); - println!("cargo:rustc-link-search=native={}", build_dir.display()); - println!("cargo:rustc-link-lib=dylib=SwiftSyntaxFFI"); - println!("cargo:rustc-link-arg=-Wl,-rpath,{}", build_dir.display()); - - // The executable also needs to find the Swift runtime libraries at run time. - if let Some(runtime) = swift_runtime_dir() { - println!("cargo:rustc-link-search=native={}", runtime.display()); - println!("cargo:rustc-link-arg=-Wl,-rpath,{}", runtime.display()); - } -} - -/// Query the active Swift toolchain for the directory containing its runtime -/// shared libraries (e.g. `libswiftCore.so`). -fn swift_runtime_dir() -> Option { - let output = Command::new(swiftc_bin()) - .arg("-print-target-info") - .output() - .ok()?; - if !output.status.success() { - return None; - } - let info = String::from_utf8_lossy(&output.stdout); - - // Extract the value of `"runtimeResourcePath": "..."` without pulling in a - // JSON dependency. - let key = "\"runtimeResourcePath\""; - let start = info.find(key)?; - let rest = &info[start + key.len()..]; - let colon = rest.find(':')?; - let after = &rest[colon + 1..]; - let open = after.find('"')?; - let value_start = &after[open + 1..]; - let close = value_start.find('"')?; - let resource_path = &value_start[..close]; - - Some(PathBuf::from(resource_path).join(if cfg!(target_os = "macos") { "macosx" } else { "linux" })) -} - -/// The `swift` driver to invoke: `$SWIFT` if set, otherwise `swift` from `PATH`. -/// This keeps the build tool-agnostic — any Swift install works; no particular -/// version manager is required. -fn swift_bin() -> String { - env::var("SWIFT").unwrap_or_else(|_| "swift".to_string()) -} - -/// The `swiftc` compiler to invoke: `$SWIFTC` if set, otherwise `swiftc` from -/// `PATH`. -fn swiftc_bin() -> String { - env::var("SWIFTC").unwrap_or_else(|_| "swiftc".to_string()) -} - -/// Some environments (notably GitHub Codespaces) inject -/// `GIT_CONFIG_KEY_0=safe.bareRepository` / `GIT_CONFIG_VALUE_0=explicit`, which -/// breaks the cached bare git repositories `swift build` uses. When exactly that -/// key is present, relax it to `all` for the `swift build` subprocess only -/// (rather than unconditionally, which could clobber an unrelated -/// `GIT_CONFIG_VALUE_0`). -fn apply_bare_repository_workaround(command: &mut Command) { - if env::var("GIT_CONFIG_KEY_0").as_deref() == Ok("safe.bareRepository") { - command.env("GIT_CONFIG_VALUE_0", "all"); - } -} diff --git a/unified/swift-syntax-rs/swift/Package.resolved b/unified/swift-syntax-rs/swift/Package.resolved deleted file mode 100644 index dafd46e961b..00000000000 --- a/unified/swift-syntax-rs/swift/Package.resolved +++ /dev/null @@ -1,15 +0,0 @@ -{ - "originHash" : "169957f8fbd882866f8f9f1c68cde813dca3560577939a8ffec366e3d77548e4", - "pins" : [ - { - "identity" : "swift-syntax", - "kind" : "remoteSourceControl", - "location" : "https://github.com/swiftlang/swift-syntax.git", - "state" : { - "revision" : "79e4b74a295b6eb74a8b585e3a39d29e70c1dbd1", - "version" : "603.0.2" - } - } - ], - "version" : 3 -} diff --git a/unified/swift-syntax-rs/swift/Package.swift b/unified/swift-syntax-rs/swift/Package.swift deleted file mode 100644 index 37fa5a1eed7..00000000000 --- a/unified/swift-syntax-rs/swift/Package.swift +++ /dev/null @@ -1,36 +0,0 @@ -// swift-tools-version:6.0 -import PackageDescription - -let package = Package( - name: "SwiftSyntaxFFI", - platforms: [ - // swift-syntax 603 requires macOS 10.15; declare it explicitly - // rather than relying on the swift-tools-version default (10.13). - .macOS(.v10_15), - ], - products: [ - // Dynamic library so the produced .so records its dependency on the - // Swift runtime libraries, keeping the Rust link step simple. - .library( - name: "SwiftSyntaxFFI", - type: .dynamic, - targets: ["SwiftSyntaxFFI"] - ) - ], - dependencies: [ - .package( - url: "https://github.com/swiftlang/swift-syntax.git", - exact: "603.0.2" - ) - ], - targets: [ - .target( - name: "SwiftSyntaxFFI", - dependencies: [ - .product(name: "SwiftSyntax", package: "swift-syntax"), - .product(name: "SwiftParser", package: "swift-syntax"), - .product(name: "SwiftOperators", package: "swift-syntax"), - ] - ) - ] -)