12

I'm using cmake v3.13 and I want to change my ExternalProject_Add() for the SEAL library to:

include(FetchContent)
# Get the seal library
set(SEAL "seal")
FetchContent_Declare(
        ${SEAL}
        GIT_REPOSITORY  https://github.com/microsoft/SEAL
        GIT_TAG         v3.5.2

)
FetchContent_GetProperties(${SEAL})
if(NOT ${SEAL}_POPULATED)
    FetchContent_Populate(${SEAL})
    add_subdirectory(${${SEAL}_SOURCE_DIR} ${${SEAL}_BINARY_DIR})
endif()

When I was using ExternalProject_Add() I've used CMAKE_ARGS -DBUILD_SHARED_LIBS=ON and this doesn't work with FetchContent_Declare() that only downloads the library.

The SEAL v3.5.2 CMakeLists.txt uses this to check if a shared library needs to be built:

# Should we build also the shared library?
set(BUILD_SHARED_LIBS_STR "Build shared library")
option(BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS_STR} OFF)
if(MSVC AND BUILD_SHARED_LIBS)
    message(WARNING "This build system only supports a static build; disabling `BUILD_SHARED_LIBS`")
    set(BUILD_SHARED_LIBS OFF CACHE BOOL ${BUILD_SHARED_LIBS_STR} FORCE)
endif()

# Conditionally build the shared library
if(BUILD_SHARED_LIBS)
    add_library(seal_shared SHARED $<TARGET_OBJECTS:seal_obj>)
    set_target_properties(seal_shared PROPERTIES OUTPUT_NAME seal)
    seal_set_version(seal_shared)
    seal_set_soversion(seal_shared)
    seal_set_language(seal_shared)
    seal_set_include_directories(seal_shared)
    seal_link_threads(seal_shared)

    # Conditionally add MSGSL include directory to build interface
    if(SEAL_USE_MSGSL AND NOT MSVC)
        target_include_directories(seal_shared PUBLIC $<BUILD_INTERFACE:${MSGSL_INCLUDE_DIR}>)
    endif()

    if(SEAL_USE_ZLIB AND NOT MSVC)
        # In the shared build we link zlibstatic into the shared library
        target_link_libraries(seal_shared PRIVATE zlibstatic)
    endif()

    seal_install_target(seal_shared SEALTargets)
endif()

Is there a way to download the SEAL library using FetchContent_Declare() and then use some CMakeLists setting to pass the CMAKE_ARGS -DBUILD_SHARED_LIBS=ON argument to the downloaded library when building it?

TalG
  • 677
  • 1
  • 9
  • 26
  • @Tsyvarev I don't set `BUILD_SHARED_LIBS` to any other value so their default setting should create the **libseal.so** in the _/cmake-build-debug/_deps/seal-src/lib/_ path but all I get there is the static **libseal-3.5.a**. – TalG May 30 '20 at 11:26

1 Answers1

17

When build some project at the top-level, you may pass a parameter to it using command line option

-D<VARIABLE>=<VALUE>

(ExternalProject_Add builds the project "as if" top-level, so the option passing is technically the same).

When build some project as a subproject using add_subdirectory approach, you may use the same command line option

-D<VARIABLE>=<VALUE>

for top-level project, and this parameter will be propagated to the subproject too.

If passing the parameter to the top-level project is not desired, then you may emulate the parameter setting inside CMakeLists.txt using set(CACHE INTERNAL) command flow:

set(<PARAMETER> <VALUE> CACHE INTERNAL "<some description>")

Make sure this line is issued before add_subdirectory() call (otherwise it won't affect the subproject).

So in your case you may use following code:

if(NOT ${SEAL}_POPULATED)
    FetchContent_Populate(${SEAL})
    # Make subproject to use 'BUILD_SHARED_LIBS=ON' setting.
    set(BUILD_SHARED_LIBS ON CACHE INTERNAL "Build SHARED libraries")
    add_subdirectory(${${SEAL}_SOURCE_DIR} ${${SEAL}_BINARY_DIR})
endif()

All above works perfectly when top-level project doesn't use the parameter set for subproject.

If both top-level project and subproject are affected by the same parameter, and you want to hardcode the parameter for the subdproject only, then things become more complicated. You need to restore the parameter after add_subdirectory call:

if(NOT ${SEAL}_POPULATED)
    FetchContent_Populate(${SEAL})

    # Store the old value of the 'BUILD_SHARED_LIBS'
    set(BUILD_SHARED_LIBS_OLD ${BUILD_SHARED_LIBS})
    # Make subproject to use 'BUILD_SHARED_LIBS=ON' setting.
    set(BUILD_SHARED_LIBS ON CACHE INTERNAL "Build SHARED libraries")

    add_subdirectory(${${SEAL}_SOURCE_DIR} ${${SEAL}_BINARY_DIR})

    # Restore the old value of the parameter
    set(BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS_OLD} CACHE BOOL "Type of libraries to build" FORCE)
endif()

# ...

# The library will be created according to "original" value for BUILD_SHARED_LIBS option.
add_library(top_lib top_lib.c)

Note, that in case of restoring parameter, set(CACHE TYPE FORCE) command flow is used instead of set(CACHE INTERNAL). This restores not only a value of the CACHE variable, but also its type, which is shown in CMake GUI.

Tsyvarev
  • 60,011
  • 17
  • 110
  • 153
  • Thank you for the detailed explanation! Could you please explain `add_library()` I've tried `add_library(${SEAL}_LIB ${${SEAL}_SOURCE_DIR})` with `target_link_libraries(hmmenc-client PRIVATE ${SEAL}_LIB)`, but I only get `CMake Error: Cannot determine link language for target "seal_LIB". CMake Error: CMake can not determine linker language for target: seal_LIB`. I also didn't see the **libseal.so** anywhere that was supposed to be created with `BUILD_SHARED_LIBS ON`. – TalG May 30 '20 at 21:17
  • 1
    The second snippet describes the case when your (main, top-level) project also has `add_library` calls which do not specify SHARED/STATIC option and thus depends on `BUILD_SHARED_LIBS` setting. If you don't have such calls, then resort to the first snippet: it is simpler. Note, that in CMake `add_library` takes list of **source files**. Passing to it `${${SEAL}_SOURCE_DIR}`, which denotes a directory, is illegal. – Tsyvarev May 30 '20 at 23:21
  • Thanks! I left the second snippet and removed the `add_library()` part. I find it not a bad practice to restore the values back to their original settings especially, if they were supposed to be set only for one project part. Anyway, now I see **libseal.so*** under */cmake-build-debug/_deps/seal-src/lib/* – TalG May 31 '20 at 09:03
  • Thank you very much for explaining the difference between both variants regarding ```CMAKE_ARGS```, I searched an eternity and found solutions for ```ExternalProject_Add``` and I got crazy confused why it didn't work for ```FetchContent_Declare```. You, Sir, are a life saver! – Thomas Lang Feb 09 '21 at 18:20
  • `set( CACHE INTERNAL "")` implies `` is `STRING` type, so for `BOOL` type it might be improper. – Kelvin Hu Feb 24 '21 at 04:15
  • Type of the CACHE variable is meaningful for CMake GUI only. Internally every variable in CMake is a string. – Tsyvarev Feb 24 '21 at 07:36