nih-at / libzip

A C library for reading, creating, and modifying zip archives.
https://libzip.org/
Other
835 stars 271 forks source link

When using `BUILD_SHARED_LIBS=FALSE`, libzip doesn't link all of its dependencies statically #464

Open nightmareci opened 1 week ago

nightmareci commented 1 week ago

I think it's fair to assume that a user of libzip expects, when linking to it statically, that no shared dependencies are carried over to the user of libzip. Currently, at least zlib is being linked shared even when using the CMake option -DBUILD_SHARED_LIBS=FALSE, so zlib ends up becoming a shared library link of the user of libzip.

I'm well aware that it's pretty safe to assume there's a system-installed zlib on any current POSIX/POSIX-like platform, like Linux or macOS, but that's not the case of Windows.

0-wiz-0 commented 1 week ago

You'll have to provide more details about what you're doing. When libzip is built with -DBUILD_SHARED_LIBS=FALSE on Unix (NetBSD in my case), there is only one output library generated: lib/libzip.a, the static one. For linking (again, on Unix) the example program, I can then do:

$ gcc -static -I ../lib -I . -o in-memory ../examples/in-memory.c lib/libzip.a /usr/lib/libz.a /usr/pkg/lib/libzstd.a /usr/lib/libbz2.a  /usr/lib/liblzma.a /usr/lib/libcrypto.a

and I end up with a static binary that does not use any shared libraries.

$ file in-memory
in-memory: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, for NetBSD 10.99.12, with debug_info, not stripped

(Please note that we do not have Windows expertise.)

nightmareci commented 1 week ago

Your usage pattern example, that of directly executing a command line compiler where each dependency is manually passed into the compilation command, not utilizing CMake entirely with a project linking to libzip, is not relevant to the issue. I guess I need to clarify my usage pattern.

I'm working entirely within a CMake project, that links to libzip via its CMake support, not direct command line usage, nor a manually-written makefile. The behavior I'm wanting of libzip's CMake support is that when a CMake project links libzip statically (my project), libzip's own CMake script code have libzip itself link to the static library form of zlib, or any of the other dependencies associated with the features of libzip enabled at build time, so a CMake project linking to libzip doesn't end up having any dynamic library links (outside of core operating system dynamic libraries, like glibc, of course). Presently, the executable in my project linking to libzip ends up with a dynamic link to zlib even when linking statically to libzip, not producing the result you achieved with the explicit compilation command.

0-wiz-0 commented 1 week ago

I understand what you're doing better, but you're basically asking me to reproduce your setup to find out if it's even a bug in libzip or in your usage of CMake. Please provide a simple example where you successfully statically link against zlib, but fail if you do the same with libzip.

nightmareci commented 6 days ago

I figured out the "right solution" in the current structure of libzip's CMake code: In the CMake documentation for the FindZLIB module, the right way to get zlib statically linked is to enable the ZLIB_USE_STATIC_LIBS CMake option before find_package(ZLIP ... or find_dependency(ZLIB ..., though that requires CMake 3.24 or higher. So libzip can check if the currently-in-use CMake version is 3.24 or higher, and if so, libzip would enable that option when BUILD_SHARED_LIBS is disabled. Not all distributions install both shared and static zlib (encountered that myself, in current Fedora Linux), but that's not an issue of libzip's concern, and can be worked around by building/installing zlib from source and pointing CMAKE_FIND_ROOT_PATH to the root of where the installed static zlib is.

All that needs to be done is to add a few lines to CMakeLists.txt and libzip-config.cmake.in before zlib is brought in.

CMakeLists.txt:

# ...
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24)
  option(ZLIB_USE_STATIC_LIBS "Use static zlib libraries" ${ZIP_STATIC})
endif()

find_package(ZLIB 1.1.2 REQUIRED)
# ...

libzip-config.cmake.in:

# ...
if (NOT IS_SHARED)
  include(CMakeFindDependencyMacro)
  set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_LIST_DIR}/modules")

  set(ENABLE_BZIP2 @BZIP2_FOUND@)
  set(ENABLE_LZMA @LIBLZMA_FOUND@)
  set(ENABLE_ZSTD @ZSTD_FOUND@)
  set(ENABLE_GNUTLS @GNUTLS_FOUND@)
  set(ENABLE_MBEDTLS @MBEDTLS_FOUND@)
  set(ENABLE_OPENSSL @OPENSSL_FOUND@)

  if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24)
    set(ZLIB_USE_STATIC_LIBS ON)
  endif()

  find_dependency(ZLIB 1.1.2)
  if(ENABLE_BZIP2)
    find_dependency(BZip2)
  endif()
# ...

The user of libzip does end up getting a dynamic link to zlib if the feature options, that themselves depend on zlib, aren't disabled (though maybe building them all with BUILD_SHARED_LIBS=OFF and using CMAKE_FIND_ROOT_PATH would work around that), but disabling them all does result in the using-executable having no zlib dynamic link, nor anything else. That's an issue for those libraries to resolve, but I can live with that not being fixed, as I only want bare-minimum support to open deflate ZIP archives in my project, I can live without all of the other features of libzip. Also, at least some of the other dependencies don't have a force-static option like zlib does; the force-static option of zlib is available because zlib can install both dynamic and static builds simultaneously.

So I'd be satisfied with the addition of using static zlib in CMakeLists.txt and libzip-config.cmake.in as shown above, you can close this issue once that change has been made.