]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
cmake: add `CURL_DROP_UNUSED` option to reduce binary sizes
authorViktor Szakats <commit@vsz.me>
Mon, 19 Jan 2026 11:34:59 +0000 (12:34 +0100)
committerViktor Szakats <commit@vsz.me>
Thu, 22 Jan 2026 16:08:20 +0000 (17:08 +0100)
To enable known linker options dropping unused, dead, code and data from
the executables built.

Useful to reduce binary sizes for curl, libcurl shared lib and apps
linking static libcurl. It's effective on both "unity" and non-unity
builds. Aligning "unity" build sizes with default, non-unity ones.

Supported platforms: Apple, MSVC, llvm/clang and GCC on all tested
platforms: Linux, BSDs, Windows, MSYS2/Cygwin, Android, MS-DOS.

Notes:
- Static libraries grow 20-30% with non-Apple toolchains.
  This effect is controlled by separate, optional compiler flags on
  non-Apple. This patch enables them automatically for public binaries
  (libcurl and curl tool), and leaves them off for internal/test ones.
- MSVC enables this option by default for 'Release' configurations.
  The curl build option has no effect on it.
- Observed effect on VS2010 is negligible. VS2012+ is recommended.
- Works with LTO, Fil-C.
- No observed/conclusive effect on build speed.
- On Windows with clang/gcc (mingw-w64/MSYS2/Cygwin) it also enables
  `-fno-asynchronous-unwind-tables` as a workaround to make
  the toolchain options actually work.
  Ref: https://sourceware.org/bugzilla/show_bug.cgi?id=11539
Thanks-to: Andarwinux
Also:
- GHA: enable in Linux and MinGW jobs to test it. Size changes:

  - linux aws-lc H3:
    curl: 2000000 -> 1937152, libcurl.a: 2065724 -> 2716532 bytes
  - macos clang HTTP-only:
    curl: 1364376 -> 128799 bytes, libcurl.a: unchanged
  - macos llvm MultiSSL:
    curl: 410056 -> 405720, libcurl.dylib: 1350336 -> 1348480 bytes
  - mingw schannel c-ares U:
    curl: 1588736 -> 1507328, libcurl-d.a: 3322040 -> 3884746 bytes
    bld: 34 -> 35MB

- GHA: enable in MSVC and Apple jobs to reduce disk footprint, with no
  obvious downside. Size changes:

  - AppVeyor CI VS2019:
    curl: 2339840 -> 1295872, libcurl-d.dll: 3155968 -> 1900544 bytes
    bld: 161 -> 97MB
  - AppVeyor CI VS2022 clang-cl:
    curl: 2933248 -> 2332160, libcurl-d.lib: 4762688 -> 5511330 bytes
    bld: 133 -> 121MB
  - AppVeyor CI VS2022 HTTP-only:
    curl: 3514368 -> 2177024, libcurl-d.lib: 2538420 -> 3151740 bytes
    bld: 137 -> 83MB
  - GHA intel:
    curl: 2629120 -> 2023424, libcurl-d.lib: 4366652 -> 5350670 bytes
    bld: 86 -> 69MB
  - GHA arm64:
    curl: 2832896 -> 2063872, libcurl-d.lib: 4690616 -> 5597250 bytes
    bld: 82 -> 66MB

Refs:
https://maskray.me/blog/2021-02-28-linker-garbage-collection
https://web.archive.org/web/20110811230637/msdn.microsoft.com/en-us/library/bxwfs976.aspx (VS2010)
https://learn.microsoft.com/cpp/build/reference/opt-optimizations
https://learn.microsoft.com/cpp/build/reference/gy-enable-function-level-linking

Closes #20357

.github/workflows/http3-linux.yml
.github/workflows/linux.yml
.github/workflows/macos.yml
.github/workflows/windows.yml
CMakeLists.txt
appveyor.sh
docs/INSTALL-CMAKE.md
lib/CMakeLists.txt
src/CMakeLists.txt

index a1465f6d4a22d52d462bf90f05054ca531a11956..5a9e1fdd9e3f7b3af5c3282359b46c2ca276476d 100644 (file)
@@ -389,7 +389,7 @@ jobs:
             tflags: '--min=1790'
             generate: >-
               -DOPENSSL_ROOT_DIR=/home/runner/awslc/build -DUSE_NGTCP2=ON -DBUILD_SHARED_LIBS=OFF
-              -DCMAKE_UNITY_BUILD=ON
+              -DCMAKE_UNITY_BUILD=ON -DCURL_DROP_UNUSED=ON
 
           - name: 'boringssl'
             install_steps: skipall
index 52492695c321054ed09ce60fff7975888f11c473..4ed36b6741a8403e471d79043b2e826e7e558ccf 100644 (file)
@@ -121,7 +121,7 @@ jobs:
             tflags: '--min=830 1 to 950'
             LDFLAGS: -Wl,-rpath,/home/runner/mbedtls/lib
             PKG_CONFIG_PATH: /home/runner/mbedtls/lib/pkgconfig
-            generate: -DCURL_USE_MBEDTLS=ON -DENABLE_DEBUG=ON -DCURL_USE_GSSAPI=ON
+            generate: -DCURL_USE_MBEDTLS=ON -DENABLE_DEBUG=ON -DCURL_USE_GSSAPI=ON -DCURL_DROP_UNUSED=ON
 
           - name: 'mbedtls gss valgrind 2'
             install_packages: libnghttp2-dev libidn2-dev libldap-dev libgss-dev valgrind
@@ -163,7 +163,7 @@ jobs:
           - name: 'awslc'
             install_packages: libidn2-dev
             install_steps: awslc
-            generate: -DOPENSSL_ROOT_DIR=/home/runner/awslc -DUSE_ECH=ON -DCMAKE_UNITY_BUILD=OFF
+            generate: -DOPENSSL_ROOT_DIR=/home/runner/awslc -DUSE_ECH=ON -DCMAKE_UNITY_BUILD=OFF -DCURL_DROP_UNUSED=ON
 
           - name: 'boringssl'
             install_steps: boringssl pytest
index 754d9f6a2301f1faae7336cfebabf25def2e1f3a..443828146f860b7f0ebf983e9e4b733a65b0acf3 100644 (file)
@@ -151,7 +151,7 @@ jobs:
             # https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling-for-ios-tvos-visionos-or-watchos
             [ -n "${MATRIX_GENERATOR}" ] && options="-G ${MATRIX_GENERATOR}"
             cmake -B bld -G Ninja -D_CURL_PREFILL=ON \
-              -DCMAKE_UNITY_BUILD=ON -DCURL_WERROR=ON \
+              -DCMAKE_UNITY_BUILD=ON -DCURL_DROP_UNUSED=ON -DCURL_WERROR=ON \
               -DCMAKE_SYSTEM_NAME=iOS \
               -DUSE_APPLE_IDN=ON \
               ${MATRIX_GENERATE} ${options}
@@ -417,7 +417,7 @@ jobs:
               [ "${_chkprefill}" = '_chkprefill' ] && options+=' -D_CURL_PREFILL=OFF'
               cmake -B "bld${_chkprefill}" -G Ninja -D_CURL_PREFILL=ON \
                 -DCMAKE_INSTALL_PREFIX="$HOME"/curl-install \
-                -DCMAKE_UNITY_BUILD=ON -DCURL_WERROR=ON \
+                -DCMAKE_UNITY_BUILD=ON -DCURL_DROP_UNUSED=ON -DCURL_WERROR=ON \
                 -DCMAKE_OSX_SYSROOT="${sysroot}" \
                 -DCMAKE_C_COMPILER_TARGET="$(uname -m | sed 's/arm64/aarch64e/')-apple-darwin$(uname -r)" \
                 ${MATRIX_GENERATE} ${options}
@@ -652,7 +652,7 @@ jobs:
             [ -n "${MATRIX_MACOS_VERSION_MIN}" ] && options+=" -DCMAKE_OSX_DEPLOYMENT_TARGET=${MATRIX_MACOS_VERSION_MIN}"
             # would pick up nghttp2, libidn2, and libssh2
             cmake -B bld -G Ninja -D_CURL_PREFILL=ON \
-              -DCMAKE_UNITY_BUILD=ON -DCURL_WERROR=ON \
+              -DCMAKE_UNITY_BUILD=ON -DCURL_DROP_UNUSED=ON -DCURL_WERROR=ON \
               -DCMAKE_OSX_SYSROOT="${sysroot}" \
               -DCMAKE_C_COMPILER_TARGET="$(uname -m | sed 's/arm64/aarch64e/')-apple-darwin$(uname -r)" \
               -DCMAKE_IGNORE_PREFIX_PATH=/opt/homebrew \
index 94602febdb2d5175e8f3de7d5a6c325a7e1dd786..ed25d58e049b2d79af55b728d064d64c8c796398 100644 (file)
@@ -204,7 +204,7 @@ jobs:
           # MinGW
           - { build: 'autotools', sys: 'mingw64'   , env: 'x86_64'       , tflags: 'skiprun'   , config: '--enable-debug --with-openssl --disable-threaded-resolver --enable-static --without-zlib', install: 'mingw-w64-x86_64-openssl mingw-w64-x86_64-libssh2', name: 'default' }
           - { build: 'autotools', sys: 'mingw64'   , env: 'x86_64'       , tflags: ''          , config: '--enable-debug --with-openssl --enable-windows-unicode --enable-ares --enable-static --disable-shared --enable-ca-native', install: 'mingw-w64-x86_64-c-ares mingw-w64-x86_64-openssl mingw-w64-x86_64-nghttp3 mingw-w64-x86_64-libssh2', name: 'c-ares U' }
-          - { build: 'cmake'    , sys: 'mingw64'   , env: 'x86_64'       , tflags: '--min=1650', config: '-DENABLE_DEBUG=ON  -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DENABLE_ARES=ON', install: 'mingw-w64-x86_64-c-ares mingw-w64-x86_64-libssh2', type: 'Debug', name: 'schannel c-ares U' }
+          - { build: 'cmake'    , sys: 'mingw64'   , env: 'x86_64'       , tflags: '--min=1650', config: '-DENABLE_DEBUG=ON  -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DENABLE_ARES=ON -DCURL_DROP_UNUSED=ON', install: 'mingw-w64-x86_64-c-ares mingw-w64-x86_64-libssh2', type: 'Debug', name: 'schannel c-ares U' }
           # MinGW torture
           - { build: 'cmake'    , sys: 'mingw64'   , env: 'x86_64'       , tflags: '-t --shallow=13 --min=700 1 to 950'   , config: '-DENABLE_DEBUG=ON  -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DENABLE_ARES=ON', install: 'mingw-w64-x86_64-c-ares mingw-w64-x86_64-libssh2', type: 'Debug', name: 'schannel U torture 1' }
           - { build: 'cmake'    , sys: 'mingw64'   , env: 'x86_64'       , tflags: '-t --shallow=13 --min=700 951 to 9999', config: '-DENABLE_DEBUG=ON  -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DENABLE_ARES=ON', install: 'mingw-w64-x86_64-c-ares mingw-w64-x86_64-libssh2', type: 'Debug', name: 'schannel U torture 2' }
@@ -213,7 +213,7 @@ jobs:
           #          Windows. Do not use this component till there is a fix for these.
           # https://github.com/curl/curl-for-win/blob/3951808deb04df9489ee17430f236ed54436f81a/libssh.sh#L6-L8
           - { build: 'cmake'    , sys: 'clang64'   , env: 'clang-x86_64' , tflags: ''          , config: '-DENABLE_DEBUG=ON  -DBUILD_SHARED_LIBS=OFF -DCURL_USE_GNUTLS=ON   -DENABLE_UNICODE=OFF -DUSE_NGTCP2=ON -DCURL_USE_LIBSSH2=OFF -DCURL_USE_LIBSSH=ON', install: 'mingw-w64-clang-x86_64-gnutls mingw-w64-clang-x86_64-nghttp3 mingw-w64-clang-x86_64-ngtcp2 mingw-w64-clang-x86_64-libssh', type: 'Debug', name: 'gnutls libssh' }
-          - { build: 'cmake'    , sys: 'clangarm64', env: 'clang-aarch64', tflags: 'skiprun'   , config: '-DENABLE_DEBUG=OFF -DBUILD_SHARED_LIBS=ON  -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON', install: 'mingw-w64-clang-aarch64-libssh2', type: 'Release', name: 'schannel R', image: 'windows-11-arm' }
+          - { build: 'cmake'    , sys: 'clangarm64', env: 'clang-aarch64', tflags: 'skiprun'   , config: '-DENABLE_DEBUG=OFF -DBUILD_SHARED_LIBS=ON  -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DCURL_DROP_UNUSED=ON', install: 'mingw-w64-clang-aarch64-libssh2', type: 'Release', name: 'schannel R', image: 'windows-11-arm' }
           - { build: 'cmake'    , sys: 'clang64'   , env: 'clang-x86_64' , tflags: 'skiprun'   , config: '-DENABLE_DEBUG=ON  -DBUILD_SHARED_LIBS=OFF -DCURL_USE_OPENSSL=ON  -DENABLE_UNICODE=OFF -DUSE_NGTCP2=ON', install: 'mingw-w64-clang-x86_64-openssl mingw-w64-clang-x86_64-nghttp3 mingw-w64-clang-x86_64-ngtcp2 mingw-w64-clang-x86_64-libssh2', type: 'Release', name: 'openssl', chkprefill: '_chkprefill' }
           - { build: 'cmake'    , sys: 'ucrt64'    , env: 'ucrt-x86_64'  , tflags: 'skiprun'   , config: '-DENABLE_DEBUG=OFF -DBUILD_SHARED_LIBS=ON  -DCURL_USE_OPENSSL=ON', install: 'mingw-w64-ucrt-x86_64-openssl mingw-w64-ucrt-x86_64-libssh2', type: 'Release', test: 'uwp', name: 'schannel' }
           # { build: 'autotools', sys: 'ucrt64'    , env: 'ucrt-x86_64'  , tflags: 'skiprun'   , config: '--without-debug --with-schannel --disable-static', install: 'mingw-w64-ucrt-x86_64-libssh2', type: 'Release', test: 'uwp', name: 'schannel' }
@@ -528,6 +528,7 @@ jobs:
               -DCMAKE_C_COMPILER=gcc \
               -DCMAKE_BUILD_TYPE="${MATRIX_TYPE}" \
               -DCMAKE_UNITY_BUILD=ON -DCMAKE_UNITY_BUILD_BATCH_SIZE=30 \
+              -DCURL_DROP_UNUSED=ON \
               -DCURL_WERROR=ON \
               -DUSE_LIBIDN2=OFF \
               ${MATRIX_CONFIG}
@@ -891,6 +892,7 @@ jobs:
               -DCMAKE_EXE_LINKER_FLAGS="-INCREMENTAL:NO ${ldflags}" \
               -DCMAKE_SHARED_LINKER_FLAGS="-INCREMENTAL:NO ${ldflags}" \
               -DCMAKE_UNITY_BUILD=ON \
+              -DCURL_DROP_UNUSED=ON \
               -DCURL_WERROR=ON \
               -DLIBPSL_INCLUDE_DIR="${MINGW_PREFIX}/include" \
               -DLIBPSL_LIBRARY="${MINGW_PREFIX}/lib/libpsl.dll.a" \
index 20cd74542b4c491c4fb64052ad20c711f8bf2254..e2b93389ee771b0b37acdf42eebf1d5d33277613 100644 (file)
@@ -300,6 +300,40 @@ if(CURL_CODE_COVERAGE)
   endif()
 endif()
 
+set(CURL_CFLAGS "")  # C flags set for libcurl and curl tool (aka public binaries) only
+
+option(CURL_DROP_UNUSED "Drop unused code and data from built binaries" OFF)
+if(CURL_DROP_UNUSED OR TRUE)
+  if(APPLE)
+    if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.13)
+      set_property(DIRECTORY APPEND PROPERTY LINK_OPTIONS "-Wl,-dead_strip")
+    else()
+      set_property(DIRECTORY APPEND PROPERTY LINK_FLAGS "-Wl,-dead_strip")
+    endif()
+  elseif(MSVC)  # Options below are toolchain defaults in Release configurations.
+    # This option does not seem to have an effect with VS2010:
+    if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.13)
+      set_property(DIRECTORY APPEND PROPERTY LINK_OPTIONS "-OPT:REF")
+    else()
+      set_property(DIRECTORY APPEND PROPERTY LINK_FLAGS "-OPT:REF")
+    endif()
+    # Optional, but reduces binary size further, with the cost of larger objects/static libraries:
+    list(APPEND CURL_CFLAGS "-Gy")
+  elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang")
+    if(WIN32)
+      # To make -Wl,--gc-sections work on Windows: https://sourceware.org/bugzilla/show_bug.cgi?id=11539
+      set_property(DIRECTORY APPEND PROPERTY COMPILE_OPTIONS "-fno-asynchronous-unwind-tables")
+    endif()
+    if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.13)
+      set_property(DIRECTORY APPEND PROPERTY LINK_OPTIONS "-Wl,--gc-sections")
+    else()
+      set_property(DIRECTORY APPEND PROPERTY LINK_FLAGS "-Wl,--gc-sections")
+    endif()
+    # Optional, but reduces binary size further, with the cost of larger objects/static libraries:
+    list(APPEND CURL_CFLAGS "-ffunction-sections" "-fdata-sections")
+  endif()
+endif()
+
 # For debug libs and exes, add "-d" postfix
 if(NOT DEFINED CMAKE_DEBUG_POSTFIX)
   set(CMAKE_DEBUG_POSTFIX "-d")
index 0701127c26ed42dad87880791481f1a5b5d5a2d2..e98fe2f613388c1b1bb41933b56d62cdc9bca9f5 100644 (file)
@@ -87,6 +87,7 @@ if [ -n "${CMAKE_GENERATOR:-}" ]; then
     time cmake -G "${CMAKE_GENERATOR}" \
       -DENABLE_DEBUG=ON -DCURL_WERROR=ON \
       -DCURL_STATIC_CRT=ON \
+      -DCURL_DROP_UNUSED=ON \
       -DCURL_USE_SCHANNEL=ON -DCURL_USE_LIBPSL=OFF \
       ${CMAKE_GENERATE:-} \
       ${options} \
index c446832544e00c7386aec85eb7d8fef9eedb3091..63c9f3103ef1b2c76d6774844e5888d3b6ac4994 100644 (file)
@@ -243,6 +243,7 @@ target_link_libraries(my_target PRIVATE CURL::libcurl)
 - `CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX`:  Override default versioned symbol prefix. Default: `<TLS-BACKEND>_` or `MULTISSL_`
 - `CURL_LINT`:                              Run lint checks while building. Default: `OFF`
 - `CURL_LTO`:                               Enable compiler Link Time Optimizations. Default: `OFF`
+- `CURL_DROP_UNUSED`:                       Drop unused code and data from built binaries. Default: `OFF`
 - `CURL_STATIC_CRT`:                        Build libcurl with static CRT with MSVC (`/MT`) (requires UCRT, static libcurl or no curl executable). Default: `OFF`
 - `CURL_TARGET_WINDOWS_VERSION`:            Minimum target Windows version as hex string.
 - `CURL_WERROR`:                            Turn compiler warnings into errors. Default: `OFF`
index 0f842f0cc12d1beb9b34f05d7533be6eef36d215..174f3b745003bcaad0b4667128e67206fd779953 100644 (file)
@@ -111,6 +111,7 @@ if(SHARE_LIB_OBJECT AND CMAKE_VERSION VERSION_GREATER_EQUAL 3.12)
   target_link_libraries(${LIB_OBJECT} PRIVATE ${CURL_LIBS})
   set_target_properties(${LIB_OBJECT} PROPERTIES
     POSITION_INDEPENDENT_CODE ON)
+  set_property(TARGET ${LIB_OBJECT} APPEND PROPERTY COMPILE_OPTIONS "${CURL_CFLAGS}")
   if(CURL_HIDES_PRIVATE_SYMBOLS)
     set_property(TARGET ${LIB_OBJECT} APPEND PROPERTY COMPILE_OPTIONS "${CURL_CFLAG_SYMBOLS_HIDE}")
     set_property(TARGET ${LIB_OBJECT} APPEND PROPERTY COMPILE_DEFINITIONS "CURL_HIDDEN_SYMBOLS")
@@ -152,6 +153,7 @@ if(BUILD_STATIC_LIBS)
     PREFIX "" OUTPUT_NAME "${LIBCURL_OUTPUT_NAME}"
     SUFFIX "${STATIC_LIB_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}"
     INTERFACE_COMPILE_DEFINITIONS "CURL_STATICLIB")
+  set_property(TARGET ${LIB_STATIC} APPEND PROPERTY COMPILE_OPTIONS "${CURL_CFLAGS}")
   if(CURL_HIDES_PRIVATE_SYMBOLS)
     set_property(TARGET ${LIB_STATIC} APPEND PROPERTY COMPILE_OPTIONS "${CURL_CFLAG_SYMBOLS_HIDE}")
     set_property(TARGET ${LIB_STATIC} APPEND PROPERTY COMPILE_DEFINITIONS "CURL_HIDDEN_SYMBOLS")
@@ -213,6 +215,7 @@ if(BUILD_SHARED_LIBS)
     PREFIX "" OUTPUT_NAME "${LIBCURL_OUTPUT_NAME}"
     IMPORT_PREFIX "" IMPORT_SUFFIX "${IMPORT_LIB_SUFFIX}${CMAKE_IMPORT_LIBRARY_SUFFIX}"
     POSITION_INDEPENDENT_CODE ON)
+  set_property(TARGET ${LIB_SHARED} APPEND PROPERTY COMPILE_OPTIONS "${CURL_CFLAGS}")
   if(CURL_HIDES_PRIVATE_SYMBOLS)
     set_property(TARGET ${LIB_SHARED} APPEND PROPERTY COMPILE_OPTIONS "${CURL_CFLAG_SYMBOLS_HIDE}")
     set_property(TARGET ${LIB_SHARED} APPEND PROPERTY COMPILE_DEFINITIONS "CURL_HIDDEN_SYMBOLS")
index 6d3d6e3d4861b9cc1360044048f8252f94e280de..519a3b491e6efcb3a5068c85354b27f1ae298b07 100644 (file)
@@ -105,6 +105,7 @@ set_property(DIRECTORY APPEND PROPERTY INCLUDE_DIRECTORIES
 add_executable(${EXE_NAME} ${CURL_CFILES} ${CURL_HFILES} ${_curl_cfiles_gen} ${_curl_hfiles_gen} ${CURLX_CFILES} ${CURLX_HFILES})
 target_compile_definitions(${EXE_NAME} PRIVATE ${_curl_definitions})
 target_link_libraries(${EXE_NAME} ${LIB_SELECTED_FOR_EXE} ${CURL_LIBS})
+set_property(TARGET ${EXE_NAME} APPEND PROPERTY COMPILE_OPTIONS "${CURL_CFLAGS}")
 
 add_executable(${PROJECT_NAME}::${EXE_NAME} ALIAS ${EXE_NAME})