Skip to content

Commit ed4b942

Browse files
committed
WIP
1 parent 3a91a38 commit ed4b942

9 files changed

Lines changed: 314 additions & 32 deletions

File tree

.github/workflows/nightly.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ permissions:
77
pull-requests: write
88

99
env:
10-
FEATURES: "async,ffi,qlog"
10+
# `rpk` covers the boring 4 vs 5 RPK arms in
11+
# `tokio-quiche/src/settings/config.rs`; see the same comment in
12+
# `stable.yml`.
13+
FEATURES: "async,ffi,qlog,rpk"
1114
RUSTFLAGS: "-D warnings"
1215
RUSTDOCFLAGS: "--cfg docsrs"
1316
RUSTTOOLCHAIN: "nightly"

.github/workflows/stable.yml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ permissions:
77
pull-requests: write
88

99
env:
10-
DEFAULT_OPTIONS: "--features=async,ffi,qlog --workspace"
10+
# `rpk` is included so the boring 4 vs 5 RPK arms in
11+
# `tokio-quiche/src/settings/config.rs` are exercised by CI;
12+
# without it neither arm is compiled and a regression on either
13+
# version would slip through.
14+
DEFAULT_OPTIONS: "--features=async,ffi,qlog,rpk --workspace"
1115
# Used by `quiche_multiarch`, which can't link `boring` for foreign targets.
1216
NO_BORING_OPTIONS: "--features=ffi,qlog --workspace --exclude h3i --exclude tokio-quiche"
1317
RUSTFLAGS: "-D warnings"
@@ -20,6 +24,10 @@ concurrency:
2024
jobs:
2125
quiche:
2226
runs-on: ubuntu-latest
27+
strategy:
28+
fail-fast: false
29+
matrix:
30+
boring_version: ["latest", "4"]
2331
# Only run on "pull_request" event for external PRs. This is to avoid
2432
# duplicate builds for PRs created from internal branches.
2533
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
@@ -38,6 +46,17 @@ jobs:
3846
sudo apt-get update
3947
sudo apt-get install libexpat1-dev libfreetype6-dev libfontconfig1-dev
4048
49+
- name: Maybe pin boring to latest 4.x
50+
if: matrix.boring_version == '4'
51+
run: |
52+
# Pin boring 4. cargo's resolver normally picks the highest match
53+
# (5). Generate the lockfile first, then downgrade to a known-good
54+
# 4.x via `--precise`. `quiche/src/build.rs` and
55+
# `tokio-quiche/build.rs` then read the lockfile and flip
56+
# `cfg(boring_v4)` on.
57+
cargo generate-lockfile
58+
cargo update -p boring --precise 4.22.0
59+
4160
- name: Unused dependency check
4261
uses: bnjbvr/cargo-machete@main
4362

Cargo.toml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ categories = ["network-programming"]
2727
unexpected_cfgs = { level = "warn", check-cfg = [
2828
'cfg(capture_keylogs)',
2929
'cfg(test_invalid_len_compilation_fail)',
30+
'cfg(boring_v4)',
3031
] }
3132

3233
[workspace.metadata.release]
@@ -40,7 +41,20 @@ publish = false
4041
[workspace.dependencies]
4142
anyhow = { version = "1" }
4243
assert_matches = { version = "1" }
43-
boring = { version = "5.1.0" }
44+
# Accept both `boring` 4.x (>= 4.21, where the APIs we use stabilised)
45+
# and `boring` 5.x. The active version is picked by cargo's resolver
46+
# based on the lockfile / downstream constraints; quiche's `build.rs`
47+
# detects which one was selected and emits `cfg(boring_v4)` for the
48+
# legacy 4.x case so source code can switch on it. The `boring_v4`
49+
# cfg is meant to be a transitional flag; new code should live in the
50+
# `not(boring_v4)` arm so the cfg can be retired by simply deleting
51+
# the legacy arms once internal users have moved off 4.x. See
52+
# `quiche/src/build.rs` and the `boringssl-boring-crate` feature in
53+
# `quiche/Cargo.toml`.
54+
#
55+
# `boring-sys` uses `links = "boringssl"`, so only one major version
56+
# can ever be in the dep graph at a time.
57+
boring = { version = ">=4.21, <6" }
4458
buffer-pool = { version = "0.2.1", path = "./buffer-pool" }
4559
bytes = { version = "1.11.1" }
4660
crossbeam = { version = "0.8.1", default-features = false }

quiche/Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,22 @@ workspace = true
2828
default = ["boringssl-boring-crate"]
2929

3030
# Use the BoringSSL library provided by the boring crate.
31+
#
32+
# Both `boring` 4.x (>= 4.21) and 5.x are supported. Cargo's resolver
33+
# picks the version based on the lockfile and downstream constraints;
34+
# `build.rs` detects which major version was selected at compile time
35+
# and emits a `cfg(boring_v4)` flag for the legacy 4.x case. The two
36+
# majors differ in their default TLS curve list (5.x advertises
37+
# post-quantum key shares) and in their Rust API for raw public
38+
# keys; see `tokio-quiche/src/settings/config.rs` for the per-version
39+
# code. `boring_v4` is a transitional cfg meant to be retired once
40+
# internal users have moved off 4.x.
41+
#
42+
# Downstream code that depends on `boring` directly (e.g. to construct
43+
# an `SslContextBuilder` for `Config::with_boring_ssl_ctx_builder`)
44+
# will resolve to the same `boring` version as quiche, since
45+
# `boring-sys` uses `links = "boringssl"` and cargo allows only one
46+
# copy in the dep graph.
3147
boringssl-boring-crate = ["boring", "foreign-types-shared"]
3248

3349
# Allow client connections to provide a custom DCID when initiating a

quiche/src/build.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,83 @@ Cflags: -I${{includedir}}
3030
out_file.write_all(output.as_bytes()).unwrap();
3131
}
3232

33+
/// Returns true if cargo resolved `boring` to a 4.x version.
34+
///
35+
/// Walks up from `OUT_DIR` looking for a `Cargo.lock`, then scans it
36+
/// for the `boring` package. We use `Cargo.lock` rather than shelling
37+
/// out to `cargo metadata` because (a) the lockfile is guaranteed to
38+
/// exist at this point in the build, (b) parsing it is cheap and has
39+
/// no extra dependencies, and (c) it avoids re-entering cargo from a
40+
/// build script.
41+
fn detect_boring_v4() -> bool {
42+
let Some(lockfile) = find_cargo_lock() else {
43+
// No lockfile (shouldn't happen in normal cargo builds, but
44+
// be conservative). Assume 5.x — the default and forward-
45+
// looking version. Downstream can fix this by generating a
46+
// lockfile (`cargo generate-lockfile`).
47+
println!(
48+
"cargo:warning=quiche: Cargo.lock not found; assuming boring 5.x"
49+
);
50+
return false;
51+
};
52+
53+
println!("cargo:rerun-if-changed={}", lockfile.display());
54+
55+
let contents = match std::fs::read_to_string(&lockfile) {
56+
Ok(s) => s,
57+
Err(e) => {
58+
println!(
59+
"cargo:warning=quiche: failed to read {}: {e}; assuming boring 5.x",
60+
lockfile.display(),
61+
);
62+
return false;
63+
},
64+
};
65+
66+
// The lockfile is TOML but a regex-light scan is enough: find a
67+
// `[[package]]` whose `name = "boring"` (not "boring-sys") and
68+
// read its `version`.
69+
let mut in_boring = false;
70+
for line in contents.lines() {
71+
let line = line.trim();
72+
if line == "[[package]]" {
73+
in_boring = false;
74+
continue;
75+
}
76+
if line == "name = \"boring\"" {
77+
in_boring = true;
78+
continue;
79+
}
80+
if in_boring {
81+
if let Some(rest) = line.strip_prefix("version = \"") {
82+
let version = rest.trim_end_matches('"');
83+
let major = version.split('.').next().unwrap_or("");
84+
return major == "4";
85+
}
86+
}
87+
}
88+
89+
// `boring` not present in the lockfile (e.g.
90+
// `boringssl-boring-crate` is off). Doesn't matter what we return
91+
// since the `cfg` won't be observed.
92+
false
93+
}
94+
95+
fn find_cargo_lock() -> Option<std::path::PathBuf> {
96+
// Start from `CARGO_MANIFEST_DIR` and walk up. Cargo guarantees
97+
// the lockfile lives at the workspace root, which is an ancestor
98+
// of the manifest dir.
99+
let manifest_dir =
100+
std::path::PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR")?);
101+
for dir in manifest_dir.ancestors() {
102+
let candidate = dir.join("Cargo.lock");
103+
if candidate.is_file() {
104+
return Some(candidate);
105+
}
106+
}
107+
None
108+
}
109+
33110
fn target_dir_path() -> std::path::PathBuf {
34111
let out_dir = std::env::var("OUT_DIR").unwrap();
35112
let out_dir = std::path::Path::new(&out_dir);
@@ -44,7 +121,18 @@ fn target_dir_path() -> std::path::PathBuf {
44121
}
45122

46123
fn main() {
124+
// Emit `cfg(boring_v4)` if boring version 4.x is detected. This is used to
125+
// pick which APIs to expect and to guide test expectations. (Larger post
126+
// quantum key shares are enabled by default in boring 5.x but not boring
127+
// 4.x.)
128+
//
129+
// The cfg is always registered (even when the backend feature is
130+
// off) so rustc doesn't warn about unknown cfg names.
131+
println!("cargo::rustc-check-cfg=cfg(boring_v4)");
47132
if cfg!(feature = "boringssl-boring-crate") {
133+
if detect_boring_v4() {
134+
println!("cargo:rustc-cfg=boring_v4");
135+
}
48136
println!("cargo:rustc-link-lib=static=ssl");
49137
println!("cargo:rustc-link-lib=static=crypto");
50138
}

0 commit comments

Comments
 (0)