Skip to content

Fix and cover the windows-static-ssl feature (curl+OpenSSL on Windows) - #666

Open
ns-ylambert wants to merge 5 commits into
sagebind:mainfrom
ns-ylambert:fix-windows-static-ssl
Open

ns-ylambert wants to merge 5 commits into
sagebind:mainfrom
ns-ylambert:fix-windows-static-ssl

Conversation

@ns-ylambert

@ns-ylambert ns-ylambert commented Oct 1, 2026 •

Copy link
Copy Markdown

Summary

Adds CI coverage for the windows-static-ssl feature (curl+OpenSSL on Windows via vcpkg) and fixes the latent build regressions that only surface once CI actually exercises the feature. windows-static-ssl was introduced in #466 (2022-09-30) but has never been covered by any matrix entry, so a chain of straightforward build.rs problems (missing include paths, no OPENSSL_DIR discovery) went uncaught since inception.

What lands

Four commits, two concerns:

CI coverage (2 commits)

  • ci: cover windows-static-ssl feature on x86_64-pc-windows-msvc: new win64-static-ssl matrix entry, manifest-mode vcpkg.json pinning openssl@3.6.4#1 via builtin-baseline + overrides, ci/run.sh branching on STATIC_SSL=windows, systest/Cargo.toml feature proxy.
  • ci: cache vcpkg OpenSSL build via files backend + actions/cache: persistent vcpkg binary cache keyed by (target × vcpkg.json state × runner image version). Warm cache <30s; cold ~5–7 min.

curl-sys/build.rs fixes (2 commits)

  • curl-sys: thread vcpkg include paths into cc-rs on windows-static-ssl: consume find_package("openssl")'s include_paths and push them onto cc-rs's include list. Without this, cl fails with C1083: Cannot open include file: 'openssl/opensslv.h'. Also links user32, required by OpenSSL's static libs (OPENSSL_showfatal → MessageBoxA).
  • curl-sys: consult OPENSSL_DIR in the windows-static-ssl branch: env-based OpenSSL discovery (<TARGET>_OPENSSL_DIR then OPENSSL_DIR), matching openssl-sys's convention. Falls back to the existing vcpkg probe.

Not in this PR

Wrapper crate level: ssl,windows-static-ssl feature combination should be rejected at rustc-compile time with a clear compile_error! diagnostic (gated on not(doc) so cargo doc --all-features still succeeds). Compound is nonsensical: ssl (Schannel) and windows-static-ssl (OpenSSL) are mutually exclusive TLS backends at the C level. Nevertheless, the else if chain in curl-sys/build.rs already prioritizes windows-static-ssl over ssl, so the guard could be spurious against other crates forcing curl/ssl via its own dep declaration, making a conflict unavoidable if windows-static-ssl is the chosen backend.

Verification

Full CI matrix green including the new win64-static-ssl entry running the end-to-end test suite (compile + execute + doctests) against curl+OpenSSL statically linked on windows-latest. No changes to existing matrix entries' behavior (STATIC_SSL is unset for them; the new ci/run.sh branch is inert).

Compile-check matrix

cargo check --target x86_64-pc-windows-msvc, standalone build. Rows affected by this PR.

Feature combination main this PR CI mode¹
static-curl,windows-static-ssl ❌ C1083 ✅ test-execute
static-curl,windows-static-ssl,protocol-ftp ❌ C1083 ✅ test-execute
static-curl,windows-static-ssl,http2 ❌ C1083 ✅ test-execute
static-curl,windows-static-ssl,protocol-ftp,ntlm ❌ C1083 ✅ test-compile
static-curl,windows-static-ssl,ntlm ❌ C1083 ✅ —
static-curl,windows-static-ssl,ntlm,http2 ❌ C1083 ✅ —
static-curl,windows-static-ssl,spnego,ntlm ❌ C1083 ❌ C2039² —
static-curl,ssl,windows-static-ssl (compound) ❌ C1083 ✅³ —

¹ CI mode = what ci/run.sh does with this combination under STATIC_SSL=windows. test-execute runs the test binaries, test-compile is --no-run, — means this combo isn't directly exercised by CI but compiles when checked locally.

² Orthogonal, fixed with #667.

³ The compound compiles cleanly now. OpenSSL discovery succeeds where it previously failed, so the compound no longer dies at C1083. What it compiles into is governed by the else if chain in curl-sys/build.rs: windows-static-ssl wins, the build is curl+OpenSSL, and the ssl (Schannel) branch's setup is silently skipped. On main, the compound was never machine-rejected as such. Whether that silent skip should instead be an explicit compile_error! is deliberately out of scope here; see "Not in this PR".

Runtime behavior: ci/run.sh sets CFLAGS=-DCURL_CA_NATIVE for the windows-static-ssl matrix entry so curl+OpenSSL consults the Windows native trust store at handshake time. Without it, cert-verifying HTTPS calls in the doctests fail with unable to get local issuer certificate even though the build compiles. CURL_CA_NATIVE is scoped to CI rather than baked into curl-sys/build.rs so library consumers retain full control over CA trust configuration (CURLOPT_CAINFO, CURLSSLOPT_NATIVE_CA, or their own CFLAGS).

Related

Bisect note

The CI-coverage commit lands before the curl-sys/build.rs fixes, so at that intermediate commit win64-static-ssl still fails (C1083). Presented as one series for review because each subsequent commit fixes exactly one visible symptom, which reads naturally. If bisect cleanliness is preferred, the CI-coverage commit can be reordered last.

The `windows-static-ssl` feature (curl+OpenSSL on Windows via vcpkg) has never
been exercised in CI since it was introduced in sagebind#466 (2022-09-30): no matrix
entry enables it.

Add a `win64-static-ssl` matrix entry:

  - `vcpkg.json` pins `openssl@3.6.4#1` via manifest-mode `overrides`, so silent
    port bumps from runner-image refreshes don't bleed in.
  - `vcpkg install --triplet=x64-windows-static-md --x-install-root=...`
    redirects the manifest-mode install to the classic tree, where curl-sys's
    `find_package("openssl")` locates it via `VCPKG_ROOT` (aliased from runner-
    preset `VCPKG_INSTALLATION_ROOT`, which vcpkg-rs doesn't consult directly).
  - `ci/run.sh` branches on `STATIC_SSL=windows` to set
    `--features windows-static-ssl --no-default-features` and skip the `rustls`
    compile-check.
  - `systest/Cargo.toml` proxies `windows-static-ssl` to `curl-sys`.

Existing matrix entries are unaffected. CI run takes an extra ~5–7 min for
vcpkg to compile OpenSSL from source (no binary caching yet). This entry
compiles cleanly only after the follow-up fixes in this series.
Without caching, the `win64-static-ssl` matrix entry rebuilds OpenSSL from
source on every CI run. vcpkg has built-in binary caching that stores compiled
ports keyed by port + triplet + config + transitive-dep hash. Pair its `files`
backend (writing to a local directory) with `actions/cache` to persist that
directory across CI runs.

Cache key composition:
  - `matrix.target` (e.g. x86_64-pc-windows-msvc).
  - `hashFiles('vcpkg.json')`: invalidates on dep-list edits or override
    version bumps.
  - `$ImageVersion`: invalidates on `windows-latest` runner-image refreshes,
    which are the source of MSVC / Windows SDK / CMake / vcpkg-tool version
    bumps that shift vcpkg's ABI hash. Without this input, an image refresh
    would leave the outer key stable while vcpkg's inner cache missed on the
    new ABI, silently rebuilding on every run without re-persisting.

Expected runtime:
  - Cold cache (first run on main, `vcpkg.json` edit, or runner-image refresh):
    ~5-7 min (compile + upload).
  - Warm cache (typical PR run): <30 sec (restore only).

Cache footprint: ~50 MB per vcpkg build; bounded by GHA's 10 GB per-repo budget
and 7-day inactivity eviction.
`vcpkg::Config::find_package("openssl")` returns a `Library` whose
`include_paths` field carries the vcpkg-provided include directory.
The `windows-static-ssl` branch currently calls `.ok()` on that result,
keeping only the side-effect of `find_package` (`cargo:rustc-link-search=`
+ `cargo:rustc-link-lib=` metadata) and discarding the `Library`. As a
result, `cl`'s command line never receives `-I` include paths, and the C
compile of `openssl.c` (and every crypto helper that transitively includes
`openssl/opensslv.h`) fails with:

    fatal error C1083: Cannot open include file: 'openssl/opensslv.h'

Consume the returned `Library` and push each of its `include_paths` into
`cc-rs`'s include list. The `rustc-link-search` / `rustc-link-lib` metadata
is still emitted internally by `find_package`, so linking is unaffected.

Latent since inception. `windows-static-ssl` was introduced in upstream
PR sagebind#466 (2022-09-30, commit b62fa94) with this exact `.ok()` pattern.
Upstream CI (`ci/run.sh`) never exercises the feature and no issues mention
`opensslv.h` or `C1083`, so the gap has stayed uncaught upstream. It would
only "work" for users whose environment already has vcpkg's include directory
on the `INCLUDE` env var via an external mechanism (Developer Command Prompt
setup etc.); this fix makes it work from a clean `cargo build`.
The `windows-static-ssl` branch's OpenSSL discovery is hard-coded to
`vcpkg::Config::new().find_package("openssl").ok()` — no `OPENSSL_DIR`
probe, no include/lib fallback. Users who already have an OpenSSL build
tree available (matched to `openssl-sys`'s standard env-lookup convention)
must install a redundant vcpkg copy just to satisfy this crate.

Probe `<TARGET>_OPENSSL_DIR` first, then plain `OPENSSL_DIR`, matching
openssl-sys's `build/main.rs::env` convention. When either is set, push
`<dir>/include` onto cc-rs's include list and emit
`cargo:rustc-link-search=native=<dir>/lib` + `cargo:rustc-link-lib=libssl`
+ `cargo:rustc-link-lib=libcrypto`. Fall back to the existing vcpkg
openssl probe otherwise. `rerun-if-env-changed` is emitted for both names
either way so the build is invalidated when the env changes.
The two features are mutually exclusive TLS backends for libcurl. One selects
Schannel (via the `ssl` cargo feature on Windows), the other selects OpenSSL
(via `windows-static-ssl`). Upstream's `build.rs` else-if chain checks
`windows-static-ssl` before `ssl`, so with both enabled, the OpenSSL branch
wins and the Schannel branch's setup is dropped on the floor.

The compound fails deterministically upstream with `cl` error:

    fatal error C1083: Cannot open include file: 'openssl/opensslv.h'

But the compound stays nonsensical: `ssl` (Schannel) and `windows-static-ssl`
(OpenSSL) can never coexist at the C level. Further OpenSSL discovery does not
fix the compound and the failure mode simply shifts to a different obscure
`cc-rs` / link error.

Add a `compile_error!` in the wrapper crate's `lib.rs` gated on
`all(feature = "ssl", feature = "windows-static-ssl", not(doc))`. It fires when
`rustc` compiles the wrapper crate with both features enabled and replaces the
deep `cc-rs` / link error with a clear one-line diagnostic. No effect on
individual-feature builds: `ssl` or `windows-static-ssl` compile alone cleanly.
The guard sits at the wrapper level because `cargo doc` pre-compiles deps with
`rustc`, which doesn't set `cfg(doc)`: only the top-level crate that `rustdoc`
processes gets the flag.

Under `cargo doc --all-features` the guard is suppressed via `cfg(doc)` so
`rustdoc` sees the union of feature-conditional items across every TLS backend.
`build.rs`'s else-if picks `rustls` first when all three backend features
coexist, so the C build itself remains coherent and docs generation succeeds.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant