Skip to content

Commit 922f088

Browse files
committed
WIP
1 parent 3a91a38 commit 922f088

9 files changed

Lines changed: 367 additions & 31 deletions

File tree

.github/workflows/stable.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ concurrency:
2020
jobs:
2121
quiche:
2222
runs-on: ubuntu-latest
23+
# Quiche currently requires backwards compatibility for boring 4. Run tests
24+
# against the latest version as well as version 4.
25+
strategy:
26+
fail-fast: false
27+
matrix:
28+
boring_version: ["latest", "4"]
2329
# Only run on "pull_request" event for external PRs. This is to avoid
2430
# duplicate builds for PRs created from internal branches.
2531
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
@@ -38,6 +44,17 @@ jobs:
3844
sudo apt-get update
3945
sudo apt-get install libexpat1-dev libfreetype6-dev libfontconfig1-dev
4046
47+
- name: Pin boring to latest 4.x
48+
if: matrix.boring_version == '4'
49+
run: |
50+
# Pin boring 4. cargo's resolver normally picks the highest match
51+
# (5). Generate the lockfile first, then downgrade to a known-good
52+
# 4.x via `--precise`. `quiche/src/build.rs` and
53+
# `tokio-quiche/build.rs` then read the lockfile and flip
54+
# `cfg(boring_v4)` on.
55+
cargo generate-lockfile
56+
cargo update -p boring --precise 4.22.0
57+
4158
- name: Unused dependency check
4259
uses: bnjbvr/cargo-machete@main
4360

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: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,88 @@ 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+
///
42+
/// 4.x is the *legacy* branch; `cfg(boring_v4)` is meant to be a
43+
/// transitional flag that we can drop once internal users have all
44+
/// moved to 5.x. New code should live in the `not(boring_v4)` arm so
45+
/// removing the cfg later is just deleting the legacy arm.
46+
fn detect_boring_v4() -> bool {
47+
let Some(lockfile) = find_cargo_lock() else {
48+
// No lockfile (shouldn't happen in normal cargo builds, but
49+
// be conservative). Assume 5.x — the default and forward-
50+
// looking version. Downstream can fix this by generating a
51+
// lockfile (`cargo generate-lockfile`).
52+
println!(
53+
"cargo:warning=quiche: Cargo.lock not found; assuming boring 5.x"
54+
);
55+
return false;
56+
};
57+
58+
println!("cargo:rerun-if-changed={}", lockfile.display());
59+
60+
let contents = match std::fs::read_to_string(&lockfile) {
61+
Ok(s) => s,
62+
Err(e) => {
63+
println!(
64+
"cargo:warning=quiche: failed to read {}: {e}; assuming boring 5.x",
65+
lockfile.display(),
66+
);
67+
return false;
68+
},
69+
};
70+
71+
// The lockfile is TOML but a regex-light scan is enough: find a
72+
// `[[package]]` whose `name = "boring"` (not "boring-sys") and
73+
// read its `version`.
74+
let mut in_boring = false;
75+
for line in contents.lines() {
76+
let line = line.trim();
77+
if line == "[[package]]" {
78+
in_boring = false;
79+
continue;
80+
}
81+
if line == "name = \"boring\"" {
82+
in_boring = true;
83+
continue;
84+
}
85+
if in_boring {
86+
if let Some(rest) = line.strip_prefix("version = \"") {
87+
let version = rest.trim_end_matches('"');
88+
let major = version.split('.').next().unwrap_or("");
89+
return major == "4";
90+
}
91+
}
92+
}
93+
94+
// `boring` not present in the lockfile (e.g.
95+
// `boringssl-boring-crate` is off). Doesn't matter what we return
96+
// since the `cfg` won't be observed.
97+
false
98+
}
99+
100+
fn find_cargo_lock() -> Option<std::path::PathBuf> {
101+
// Start from `CARGO_MANIFEST_DIR` and walk up. Cargo guarantees
102+
// the lockfile lives at the workspace root, which is an ancestor
103+
// of the manifest dir.
104+
let manifest_dir =
105+
std::path::PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR")?);
106+
for dir in manifest_dir.ancestors() {
107+
let candidate = dir.join("Cargo.lock");
108+
if candidate.is_file() {
109+
return Some(candidate);
110+
}
111+
}
112+
None
113+
}
114+
33115
fn target_dir_path() -> std::path::PathBuf {
34116
let out_dir = std::env::var("OUT_DIR").unwrap();
35117
let out_dir = std::path::Path::new(&out_dir);
@@ -44,7 +126,21 @@ fn target_dir_path() -> std::path::PathBuf {
44126
}
45127

46128
fn main() {
129+
// The `boring` workspace dep has a wide version range
130+
// (`>=4.21, <6`) so the resolver can pick either 4.x or 5.x based
131+
// on downstream constraints. The two majors differ in their
132+
// default TLS curve list (5.x advertises post-quantum key shares)
133+
// and in their Rust API for raw public keys, so we need to switch
134+
// on the resolved version at compile time. Detect it here and
135+
// emit `cfg(boring_v4)` for the legacy 4.x case.
136+
//
137+
// The cfg is always registered (even when the backend feature is
138+
// off) so rustc doesn't warn about unknown cfg names.
139+
println!("cargo::rustc-check-cfg=cfg(boring_v4)");
47140
if cfg!(feature = "boringssl-boring-crate") {
141+
if detect_boring_v4() {
142+
println!("cargo:rustc-cfg=boring_v4");
143+
}
48144
println!("cargo:rustc-link-lib=static=ssl");
49145
println!("cargo:rustc-link-lib=static=crypto");
50146
}

quiche/src/lib.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,14 @@
360360
//! code and dependencies:
361361
//!
362362
//! * `boringssl-boring-crate` (default): Use the BoringSSL library provided by
363-
//! the [boring] crate.
363+
//! the [boring] crate. Both `boring` 4.x (>= 4.21) and 5.x are supported;
364+
//! cargo's resolver picks the version based on the lockfile and any
365+
//! constraints from other crates in the dep graph. `build.rs` detects the
366+
//! resolved major version and emits `cfg(boring_v4)` for the legacy 4.x case
367+
//! to switch between per-version code paths. Downstream code that depends on
368+
//! `boring` directly (e.g. to construct an `SslContextBuilder` for
369+
//! [`Config::with_boring_ssl_ctx_builder`]) will resolve to the same `boring`
370+
//! version as quiche.
364371
//!
365372
//! * `pkg-config-meta`: Generate pkg-config metadata file for libquiche.
366373
//!

0 commit comments

Comments
 (0)