mirror of
https://github.com/github/codeql.git
synced 2026-07-30 23:13:01 +02:00
unified: Make Bazel the only supported build
Nothing builds this extractor with `cargo` any more: the tests, the extractor pack and the corpus regeneration all go through Bazel. The `cargo` half of the build is now dead weight, and it is not free -- it is a second, independent way of compiling the same Swift code, kept in step by hand. `build.rs` shelled out to `swift build` on the SwiftPM package in `swift/`, scraped `swiftc -print-target-info` for the runtime directory, and emitted the resulting link and rpath flags. Bazel does none of that: it compiles `SwiftSyntaxFFI.swift` directly against `@swift-syntax`, resolved from the Bazel Central Registry. `Package.swift` and `Package.resolved` existed only for that `swift build`. Deleting them collapses three version pins into one. `MODULE.bazel` pinned the toolchain, `.swift-version` pinned it again for `swiftly` and `build.rs`, and `Package.swift` pinned the `swift-syntax` release a second time -- with a comment asking that they be kept in sync and nothing enforcing it. Two Swift builds that could silently diverge become one that cannot. `cargo check` still works, since checking does not link, so rust-analyzer keeps working across the crate -- completion, go-to-definition and inline diagnostics included. What no longer works is `cargo build`/`cargo test`, and hence the editor's "run test" lens; use `bazel test //unified/extractor:all_tests`. The Cargo manifests stay because `misc/bazel/3rdparty` resolves third-party crate versions from them. `scripts/create-extractor-pack.sh` becomes a wrapper around the `codeql_pack` installer that already existed, rather than a parallel cargo-based assembly of the same pack. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 297ff885-7c13-4202-a9d0-bc0b17f68d8e
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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`.
|
||||
|
||||
1
unified/swift-syntax-rs/.gitignore
vendored
1
unified/swift-syntax-rs/.gitignore
vendored
@@ -1,2 +1 @@
|
||||
/target
|
||||
/swift/.build
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
6.3.2
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<PathBuf> {
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"),
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
Reference in New Issue
Block a user