Search code examples
c++cmakestatic-librarieslibcurlstatic-linking

Statically build and linking with CMake


I'm trying to wrap my head around statically linking c++ applications using CMake.

I have built libcurl statically:

./buildconf
./configure --disable-shared --with-openssl
make -j$(nproc)
make install

Which produces a static /usr/local/lib/libcurl.a:

$ ldd /usr/local/lib/libcurl.a
        not a dynamic executable

My CMake is configured to build and link statically:

include(CMakePrintHelpers)
cmake_minimum_required(VERSION 3.17)

project(static-build-test)

set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
set(BUILD_SHARED_LIBS OFF)
set(CMAKE_EXE_LINKER_FLAGS "-static")

find_package(CURL REQUIRED)
cmake_print_variables(CURL_LIBRARIES)

add_executable(static-test main.cpp)
target_link_libraries(static-test PRIVATE ${CURL_LIBRARIES})

But my build fails to link with many "undefined reference to" errors:

$ make
Scanning dependencies of target static-test
[ 50%] Building CXX object CMakeFiles/static-test.dir/main.cpp.o
[100%] Linking CXX executable static-test
...
url.c:(.text+0xf6): undefined reference to `idn2_free'
md5.c:(.text+0x6a): undefined reference to `MD5_Init'
openssl.c:(.text+0x29a): undefined reference to `SSL_set_ex_data'
...

My static build for libcurl.a completed without error but still fails to link with my application due to these undefined references. Why doesn't the static library for libcurl include the static libraries it depends on (openssl, etc.)?

I presume I need to find all these missing references and track down their static libs as well. Is it that I need to link ALL of these libraries directly to my final executable?


Solution

  • For libraries, like CURL, that have first-class CMake package support, you should use it. Here is how I linked to CURL statically.

    First, we download and build CURL:

    $ git clone [email protected]:curl/curl.git
    $ cmake -G Ninja -S curl/ -B _build/curl -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=NO
    $ cmake --build _build/curl
    $ cmake --install _build/curl --prefix _local
    

    After this, we can write the correct CMakeLists.txt, which only links to imported targets. Notice that this build uses no variables.

    cmake_minimum_required(VERSION 3.23)
    project(test)
    
    find_package(CURL REQUIRED)
    
    add_executable(static-test main.cpp)
    target_link_libraries(static-test PRIVATE CURL::libcurl)
    

    And now we can build this, pointing CMake to our freshly built CURL package, via CMAKE_PREFIX_PATH:

    $ cmake -G Ninja -S . -B _build/test -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=$PWD/_local
    ...
    -- Found CURL: /path/to/_local/lib/cmake/CURL/CURLConfig.cmake (found version "7.85.0-DEV")  
    -- Configuring done
    -- Generating done
    -- Build files have been written to: /path/to/_build/test
    

    and now we can run the build and see that all of the required libraries are there:

    $ cmake --build _build/test --verbose
    [1/2] /usr/bin/c++ -DCURL_STATICLIB -isystem /path/to/_local/include -O3 -DNDEBUG -MD -MT CMakeFiles/static-test.dir/main.cpp.o -MF CMakeFiles/static-test.dir/main.cpp.o.d -o CMakeFiles/static-test.dir/main.cpp.o -c /usr/local/google/home/reinking/test/main.cpp
    [2/3] : && /usr/bin/c++ -O3 -DNDEBUG  CMakeFiles/static-test.dir/main.cpp.o -o static-test  ../../_local/lib/libcurl.a  -ldl  -lpthread  /usr/lib/x86_64-linux-gnu/libssl.so  /usr/lib/x86_64-linux-gnu/libcrypto.so  /usr/lib/x86_64-linux-gnu/libz.so && :
    

    As you can see, curl and its transitive dependencies, dl, pthreads, openssl, libcrypto, and libz, all appear correctly on the link line.