Skip to content

build: statically link OpenSSL so released binaries need no runtime deps - #35

Merged
esrrhs merged 2 commits into
masterfrom
fix/static-link-deps-for-distribution
Oct 17, 2026
Merged

esrrhs merged 2 commits into
masterfrom
fix/static-link-deps-for-distribution

Conversation

@esrrhs

@esrrhs esrrhs commented Oct 16, 2026

Copy link
Copy Markdown
Owner

背景

发布的动态库本该"下载即用",实际上不是:

  • macOS:libfakelua.dylib 里硬编码了绝对路径 /opt/homebrew/opt/openssl@3/lib/libssl.3.dylib,换台没装同款 Homebrew openssl 的机器直接 dyld: Library not loaded。
  • Windows:libfakelua.dll 需要 libssl-3-x64.dll、libcrypto-3-x64.dll、libgcc_s_seh-1.dll、libwinpthread-1.dll、libstdc++-6.dll,release 包里一个都没带。
  • Linux:只需 libssl.so.3 / libcrypto.so.3,多数发行版自带,风险最低。

(实测方式:下载 v2.0.0 三个 release 包,otool -L / strings 查依赖,非推测。)

改动

新增 FAKELUA_PREFER_STATIC_DEPS 选项(默认 ON):

  • OPENSSL_USE_STATIC_LIBS=TRUE → OpenSSL 静态链入
  • MinGW:-static-libgcc + -Wl,-Bstatic -lwinpthread -Wl,-Bdynamic

libstdc++ / libc++ 刻意保持动态。原因是 include/fakelua.h 在 ABI 边界暴露了 STL 类型(std::string 按值返回、std::vector<std::pair<...>>),且 ViToString() 是在头文件里 inline 实现的 —— 宿主会自己编译这段代码。如果静态链 libstdc++,宿主和库各有一份,std::string 跨边界时就是两套 allocator,直接踩堆损坏。libgcc / winpthread 是纯 C 级运行时,没有这层 ABI 面,可以安全静态。

另外加了个配置期守卫:万一 OpenSSL 仍解析到动态库(比如某个安装没带 .a),configure 时打 WARNING,避免这个坑悄悄回归。

效果(macOS 实测)

# 改前
/opt/homebrew/opt/openssl@4/lib/libssl.4.dylib
/opt/homebrew/opt/openssl@4/lib/libcrypto.4.dylib
/usr/lib/libc++.1.dylib
/usr/lib/libSystem.B.dylib

# 改后
/usr/lib/libc++.1.dylib
/usr/lib/libSystem.B.dylib

体积 9.2M → 14M(静态 OpenSSL 约 +5MB)。

验证

  • ✅ macOS 本地构建,otool -L 已无 openssl
  • ✅ test_crypto.* + test_http.tls_echo 共 60 个测试全过(含真实 TLS 握手)
  • ⏳ Linux / Windows 靠 CI 验证 —— 这两个平台我本地测不到,正是开这个 PR 的主要目的

⚠️ 需要 CI 重点确认的两点:

  1. Linux:libssl-dev 的 .a 是否带 -fPIC。若不是,链进 .so 时会报 relocation ... can not be used when making a shared object,需要针对 Linux 关掉静态或改用别的方式。
  2. Windows (MSYS2 MINGW64):mingw-w64-x86_64-openssl 提供的 libssl.a 通常是 DLL 的导入库而非静态归档。若如此,Windows 端 libssl-3-x64.dll / libcrypto-3-x64.dll 仍会需要 —— 那就得在打包时把这两个 DLL 一起放进 zip(见后续工作)。

后续(不在此 PR)

  • release.yml 目前是手工 cp 打包,没跑 cmake --install,所以下载的包里没有 lib/cmake/fakelua/,README 教的 find_package(fakelua) 对下载包不成立。
  • Windows 包需补上残留的运行时 DLL。
  • 发新版必须改 include/fakelua.h 的 FAKELUA_VERSION_STRING,否则 release workflow 不触发。

…ributable binaries

The released shared libraries are supposed to be usable as-is, without the
consumer installing anything. They were not:

- macOS: libfakelua.dylib had absolute Homebrew paths baked in
  (/opt/homebrew/opt/openssl@3/lib/libssl.3.dylib), so it only loaded on
  machines with that exact prefix. Any other Mac got
  "dyld: Library not loaded".
- Windows: libfakelua.dll needed libssl-3-x64.dll, libcrypto-3-x64.dll,
  libgcc_s_seh-1.dll, libwinpthread-1.dll and libstdc++-6.dll, none of
  which shipped in the release zip.

Link OpenSSL statically via OPENSSL_USE_STATIC_LIBS, behind a new
FAKELUA_PREFER_STATIC_DEPS option (default ON). On MinGW also statically
link libgcc and winpthread.

libstdc++/libc++ stay dynamic on purpose: include/fakelua.h exposes STL
types across the ABI boundary (std::string returned by value,
std::vector<std::pair<...>>) and ViToString() is implemented inline in that
header, so host and library must share a single copy of libstdc++. Linking
it statically would give each side its own copy and corrupt the heap when a
std::string crosses the boundary.

A configure-time warning fires if OpenSSL resolves to shared libs anyway
(e.g. an OpenSSL install without the .a files), so the regression can't
come back silently.

Verified on macOS: otool -L now reports only /usr/lib/libc++.1.dylib and
/usr/lib/libSystem.B.dylib; test_crypto.* and test_http.tls_echo (60 tests)
all pass.
@codecov-commenter

codecov-commenter commented Oct 16, 2026 •

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 66.66667% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 88.07%. Comparing base (e4582bd) to head (8f0a989).

Files with missing lines Patch % Lines
src/native/crypto/native_crypto.cpp 66.66% 1 Missing ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@            Coverage Diff             @@
##           master      #35      +/-   ##
==========================================
- Coverage   88.07%   88.07%   -0.01%     
==========================================
  Files         124      124              
  Lines       25494    25498       +4     
==========================================
+ Hits        22454    22457       +3     
- Misses       3040     3041       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Static OpenSSL on MinGW broke 8 test_crypto cases (rc4 / blowfish / des).
Those algorithms live in OpenSSL 3's "legacy" provider, a loadable module
found via the MODULESDIR compiled into libcrypto; MSYS2 bakes in the MSYS path
"/mingw64/lib/ossl-modules", which Win32 LoadLibrary cannot resolve. The
shared libcrypto derives the module dir from its own DLL location, so it finds
legacy.dll.

Also warn (instead of silently ignoring) when the legacy provider fails to
load, and extend the static-lib guard to catch MinGW/MSVC import libraries.
@esrrhs

esrrhs commented Oct 16, 2026

Copy link
Copy Markdown
Owner Author

Windows CI 失败原因已定位并修复(8f0a989)。

根因:test_crypto 的 8 个失败(rc4 / blowfish / des / evp_*)不是静态链接本身,而是 OpenSSL 3 的 legacy provider 加载不到。RC4/Blowfish/DES 不在 libcrypto 里,而在可加载模块 ossl-modules/legacy.dll 中,RegisterCryptoLibraryApi() 每次启动都会 OSSL_PROVIDER_load(nullptr, "legacy")。

  • 动态 libcrypto:模块目录按 DLL 自身位置推导 → 能找到 legacy.dll → master 一直绿。
  • 静态 libcrypto.a:只能用编译时写死的 MODULESDIR,而 MSYS2 编 OpenSSL 时写进去的是 MSYS 路径 /mingw64/lib/ossl-modules(已核实 libcrypto.a 里的字符串),Win32 的 LoadLibrary 解析不了 → legacy 静默加载失败 → EVP_EncryptInit_ex 返回 0 → 报 rc4: EVP_EncryptInit_ex (cipher) failed。

我在 macOS 上用 OPENSSL_MODULES=/tmp/nope 复现出一模一样的 8 个失败,确认是同一条路径。Linux 绿是因为它的 MODULESDIR(/usr/lib/x86_64-linux-gnu/ossl-modules)是真路径。

修法:MinGW 上 OpenSSL 保持动态(macOS/Linux 仍静态,libgcc/winpthread 仍静态),并把 legacy 加载失败从静默改成打 WARN。MinGW 发布包仍需随附 libcrypto-3-x64.dll / libssl-3-x64.dll,就是 PR 里已经列好的后续项。

代价说明:想让 Windows 也静态,就得在 configure 期把 ossl-modules 目录(经 cygpath 转 Windows 路径)烘进二进制再 OSSL_PROVIDER_set_default_search_path,但那是编译机绝对路径,且 legacy.dll 自己要链 libcrypto-3-x64.dll(两份 libcrypto 跨堆),我没法本地验证,风险比收益大,所以没做。

@esrrhs
esrrhs merged commit 2c4bf28 into master Oct 17, 2026
9 checks passed
@esrrhs
esrrhs deleted the fix/static-link-deps-for-distribution branch October 17, 2026 00:06
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.

2 participants