From 23188f4b8e82c2d84c9186075a4af03003e77292 Mon Sep 17 00:00:00 2001 From: ns-ylambert Date: Mon, 28 Sep 2026 12:34:49 +0200 Subject: [PATCH 1/5] ci: cover windows-static-ssl feature on x86_64-pc-windows-msvc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `windows-static-ssl` feature (curl+OpenSSL on Windows via vcpkg) has never been exercised in CI since it was introduced in #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. --- .github/workflows/main.yml | 16 ++++++++++++++-- ci/run.sh | 16 +++++++++++++++- systest/Cargo.toml | 1 + vcpkg.json | 13 +++++++++++++ 4 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 vcpkg.json diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 680d47194..ec1247a60 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - build: [x86_64, i686, x86_64-musl, mingw, system-curl, x86_64-beta, x86_64-nightly, macos, win64, win32] + build: [x86_64, i686, x86_64-musl, mingw, system-curl, x86_64-beta, x86_64-nightly, macos, win64-static-ssl, win64, win32] include: - build: x86_64 os: ubuntu-latest @@ -67,6 +67,11 @@ jobs: os: windows-latest rust: stable target: x86_64-pc-windows-msvc + - build: win64-static-ssl + os: windows-latest + rust: stable + target: x86_64-pc-windows-msvc + static_ssl: windows steps: - uses: actions/checkout@v1 with: @@ -75,7 +80,14 @@ jobs: run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }} shell: bash - run: rustup target add ${{ matrix.target }} - - run: TARGET=${{ matrix.target }} ./ci/run.sh + - name: Install vcpkg dependencies (for windows-static-ssl) + if: matrix.static_ssl == 'windows' + shell: bash + run: | + vcpkg install --triplet=x64-windows-static-md \ + --x-install-root="$VCPKG_INSTALLATION_ROOT/installed" + echo "VCPKG_ROOT=$VCPKG_INSTALLATION_ROOT" >> "$GITHUB_ENV" + - run: TARGET=${{ matrix.target }} STATIC_SSL=${{ matrix.static_ssl }} ./ci/run.sh if: matrix.os != 'ubuntu-latest' name: Run non-docker tests shell: bash diff --git a/ci/run.sh b/ci/run.sh index 0abfb5fd6..6e2afaa8a 100755 --- a/ci/run.sh +++ b/ci/run.sh @@ -16,6 +16,16 @@ if [ "$TARGET" = "x86_64-unknown-linux-musl" ]; then features="--features static-ssl" fi +# `windows-static-ssl` (curl + OpenSSL on Windows) needs vcpkg-installed +# OpenSSL and `VCPKG_ROOT` set for vcpkg-rs's discovery. +if [ "$STATIC_SSL" = "windows" ]; then + features="--features windows-static-ssl --no-default-features" + # Enable the Windows native CA store for the test suite so doctests that + # make HTTPS requests can verify server certificates. Not baked into + # build.rs to avoid forcing the default on library consumers. + export CFLAGS="-DCURL_CA_NATIVE" +fi + cargo test --target $TARGET --no-run $features # First test with no extra protocols enabled. cargo test --target $TARGET --no-run --features static-curl $features @@ -28,7 +38,11 @@ cargo test --target $TARGET --no-run --features static-curl $features # Compiling on i686-windows requires nasm to be installed (other platforms # have pre-compiled object files), which is just slightly too much # inconvenience for me. -if [ "$TARGET" != "x86_64-pc-windows-gnu" ] && [ "$TARGET" != "i686-pc-windows-msvc" ] +# +# Note also that rustls silently wins over windows-static-ssl in curl-sys's +# build.rs else-if, so we skip this on the win64-static-ssl entry and keep +# actually exercising windows-static-ssl in the tests below. +if [ "$TARGET" != "x86_64-pc-windows-gnu" ] && [ "$TARGET" != "i686-pc-windows-msvc" ] && [ "$STATIC_SSL" != "windows" ] then cargo test --target $TARGET --no-run --features rustls,static-curl $features fi diff --git a/systest/Cargo.toml b/systest/Cargo.toml index a59386ed0..d77e5a62b 100644 --- a/systest/Cargo.toml +++ b/systest/Cargo.toml @@ -16,3 +16,4 @@ ctest2 = "0.4" [features] static-ssl = ['curl-sys/static-ssl'] +windows-static-ssl = ['curl-sys/windows-static-ssl'] diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 000000000..9b403b126 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,13 @@ +{ + "builtin-baseline": "960d40ee7351cca3fc648e9d38993b59f01dfd2b", + "dependencies": [ + "openssl" + ], + "overrides": [ + { + "name": "openssl", + "version": "3.6.4", + "port-version": 1 + } + ] +} From 0e1ad6c2bc393974b4d4b49238174f1e79c36e46 Mon Sep 17 00:00:00 2001 From: ns-ylambert Date: Mon, 28 Sep 2026 12:43:05 +0200 Subject: [PATCH 2/5] ci: cache vcpkg OpenSSL build via files backend + actions/cache 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. --- .github/workflows/main.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ec1247a60..e00b16ada 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -80,9 +80,24 @@ jobs: run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }} shell: bash - run: rustup target add ${{ matrix.target }} + - name: Compute vcpkg cache entries + if: matrix.static_ssl == 'windows' + id: vcpkg-cache + shell: bash + run: | + echo "path=${{ runner.temp }}/vcpkg-cache" >> "$GITHUB_OUTPUT" + echo "key=vcpkg-${{ matrix.target }}-${{ hashFiles('vcpkg.json') }}-$ImageVersion" >> "$GITHUB_OUTPUT" + - name: Restore vcpkg binary cache (files backend) + if: matrix.static_ssl == 'windows' + uses: actions/cache@v5 + with: + path: ${{ steps.vcpkg-cache.outputs.path }} + key: ${{ steps.vcpkg-cache.outputs.key }} - name: Install vcpkg dependencies (for windows-static-ssl) if: matrix.static_ssl == 'windows' shell: bash + env: + VCPKG_BINARY_SOURCES: "clear;files,${{ steps.vcpkg-cache.outputs.path }},readwrite" run: | vcpkg install --triplet=x64-windows-static-md \ --x-install-root="$VCPKG_INSTALLATION_ROOT/installed" From 95f9266b0de7bd402fa8a4d845b022857b2c3f7e Mon Sep 17 00:00:00 2001 From: ns-ylambert Date: Mon, 21 Sep 2026 10:32:52 +0200 Subject: [PATCH 3/5] curl-sys: thread vcpkg include paths into cc-rs on windows-static-ssl `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 #466 (2022-09-30, commit b62fa94e0cb1) 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`. --- curl-sys/build.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/curl-sys/build.rs b/curl-sys/build.rs index 7d028d410..d0788e0c2 100644 --- a/curl-sys/build.rs +++ b/curl-sys/build.rs @@ -369,8 +369,17 @@ fn main() { // ./vcpkg.exe integrate install // ./vcpkg.exe install openssl:x64-windows-static-md // ``` + // `find_package` emits `rustc-link-search` + `rustc-link-lib` + // for the linker, but the returned `Library`'s `include_paths` + // still have to be threaded into `cc-rs` explicitly. #[cfg(target_env = "msvc")] - vcpkg::Config::new().find_package("openssl").ok(); + if let Ok(lib) = vcpkg::Config::new().find_package("openssl") { + for path in &lib.include_paths { + cfg.include(path); + } + } + // OpenSSL static libs reference MessageBoxA (OPENSSL_showfatal). + println!("cargo:rustc-link-lib=user32"); #[cfg(target_env = "msvc")] vcpkg::Config::new().find_package("zlib").ok(); } else { From 31163c9c4b1c8071603281100b0efa8e4eda7b78 Mon Sep 17 00:00:00 2001 From: ns-ylambert Date: Thu, 17 Sep 2026 12:29:19 +0200 Subject: [PATCH 4/5] curl-sys: consult OPENSSL_DIR in the windows-static-ssl branch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 `_OPENSSL_DIR` first, then plain `OPENSSL_DIR`, matching openssl-sys's `build/main.rs::env` convention. When either is set, push `/include` onto cc-rs's include list and emit `cargo:rustc-link-search=native=/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. --- curl-sys/build.rs | 59 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/curl-sys/build.rs b/curl-sys/build.rs index d0788e0c2..bf62c8d08 100644 --- a/curl-sys/build.rs +++ b/curl-sys/build.rs @@ -360,22 +360,35 @@ fn main() { if windows { cfg.define("USE_OPENSSL", None) .file("curl/lib/vtls/openssl.c"); - // We need both openssl and zlib - // Those can be installed with - // ```shell - // git clone https://github.com/microsoft/vcpkg - // cd vcpkg - // ./bootstrap-vcpkg.bat -disableMetrics - // ./vcpkg.exe integrate install - // ./vcpkg.exe install openssl:x64-windows-static-md - // ``` - // `find_package` emits `rustc-link-search` + `rustc-link-lib` - // for the linker, but the returned `Library`'s `include_paths` - // still have to be threaded into `cc-rs` explicitly. - #[cfg(target_env = "msvc")] - if let Ok(lib) = vcpkg::Config::new().find_package("openssl") { - for path in &lib.include_paths { - cfg.include(path); + // Probe `_OPENSSL_DIR` / `OPENSSL_DIR` before falling + // back to vcpkg. Matches openssl-sys's env-lookup convention so + // a single env var can point both `openssl-sys` and `curl-sys` + // at the same OpenSSL install. + if let Some(dir) = openssl_dir_from_env(&target) { + cfg.include(dir.join("include")); + println!( + "cargo:rustc-link-search=native={}", + dir.join("lib").display() + ); + println!("cargo:rustc-link-lib=libssl"); + println!("cargo:rustc-link-lib=libcrypto"); + } else { + // vcpkg openssl fallback. Install with: + // ```shell + // git clone https://github.com/microsoft/vcpkg + // cd vcpkg + // ./bootstrap-vcpkg.bat -disableMetrics + // ./vcpkg.exe integrate install + // ./vcpkg.exe install openssl:x64-windows-static-md + // ``` + // `find_package` emits `rustc-link-search` + `rustc-link-lib` + // for the linker, but the returned `Library`'s `include_paths` + // still have to be threaded into `cc-rs` explicitly. + #[cfg(target_env = "msvc")] + if let Ok(lib) = vcpkg::Config::new().find_package("openssl") { + for path in &lib.include_paths { + cfg.include(path); + } } } // OpenSSL static libs reference MessageBoxA (OPENSSL_showfatal). @@ -538,6 +551,20 @@ fn main() { } } +/// Probe `_OPENSSL_DIR` first, then plain `OPENSSL_DIR`, matching +/// openssl-sys's env-lookup convention (`build/main.rs::env`). Always emits +/// `rerun-if-env-changed` for both names so the build is invalidated when +/// the env changes, even when neither is set at build time. +fn openssl_dir_from_env(target: &str) -> Option { + let prefix = target.to_uppercase().replace('-', "_"); + let prefixed = format!("{prefix}_OPENSSL_DIR"); + println!("cargo:rerun-if-env-changed={prefixed}"); + println!("cargo:rerun-if-env-changed=OPENSSL_DIR"); + env::var_os(&prefixed) + .or_else(|| env::var_os("OPENSSL_DIR")) + .map(PathBuf::from) +} + #[cfg(not(target_env = "msvc"))] fn try_vcpkg() -> bool { false From d9f3aaa6350360d9dfaafd383495bce24a8ec26a Mon Sep 17 00:00:00 2001 From: ns-ylambert Date: Mon, 28 Sep 2026 18:27:23 +0200 Subject: [PATCH 5/5] curl-rust: reject ssl + windows-static-ssl at rustc time 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. --- src/lib.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 2965e2bed..14211bdeb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,6 +56,13 @@ #![deny(missing_docs, missing_debug_implementations)] #![doc(html_root_url = "https://docs.rs/curl/0.4")] +// `ssl` (Schannel on Windows) and `windows-static-ssl` (OpenSSL) are mutually +// exclusive TLS backends for libcurl. +// Gated on `not(doc)` so that `cargo doc --all-features` still generates a +// union of feature-conditional items. +#[cfg(all(feature = "ssl", feature = "windows-static-ssl", not(doc)))] +compile_error!("`ssl` and `windows-static-ssl` are mutually exclusive TLS backends"); + use std::ffi::CStr; use std::str; use std::sync::Once;