CMake shared library from object library & DLL exports - cmake

Background
I use cmake to build an open-source library.
The project is setup to do the following:
Build a cmake OBJECT library named gpds-objs
Build a STATIC library named gpds-static from gpds-objs
Build a SHARED library named gpds-shared from gpds-objs
Furthermore, I'm using cmake's generate_export_header() to generate the necessary export macros.
The relevant parts of the cmake script looks like this:
# Set project information
project(gpds
VERSION 1.0.0
LANGUAGES CXX
HOMEPAGE_URL "https://gpds.simulton.com"
)
# Some bacis cmake configuration
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE ON)
# List of private source files
set(SOURCES_PRIVATE
# ...
)
# List of private header files
set(HEADERS_PRIVATE
# ...
)
# List of public header files
set(HEADERS_PUBLIC
# ...
)
# Define targets
set(NAME gpds)
set(TARGET-OBJS ${NAME}-objs)
set(TARGET-STATIC ${NAME}-static)
set(TARGET-SHARED ${NAME}-shared)
################################################################################
# Object library #
################################################################################
add_library(${TARGET-OBJS} OBJECT)
target_compile_features(
gpds-objs
PUBLIC
cxx_std_17
)
target_sources(
${TARGET-OBJS}
PRIVATE
${SOURCES_PRIVATE}
${HEADERS_PRIVATE}
)
target_include_directories(
${TARGET-OBJS}
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>
PRIVATE
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include/gpds>
)
################################################################################
# Shared library #
################################################################################
add_library(${TARGET-SHARED} SHARED)
target_link_libraries(
${TARGET-SHARED}
PUBLIC
gpds-objs
)
target_compile_definitions(
${TARGET-SHARED}
PRIVATE
gpds_shared_EXPORTS # We're building this library!
)
################################################################################
# Static library #
################################################################################
add_library(${TARGET-STATIC} STATIC)
target_link_libraries(
${TARGET-STATIC}
PUBLIC
gpds-objs
)
target_compile_definitions(
${TARGET-STATIC}
PUBLIC
GPDS_STATIC_DEFINE
)
# Common library properties
set_target_properties(
${TARGET-OBJS}
${TARGET-STATIC}
${TARGET-SHARED}
PROPERTIES
OUTPUT_NAME "gpds"
ARCHIVE_OUTPUT_NAME "gpds"
VERSION ${PROJECT_VERSION}
POSITION_INDEPENDENT_CODE 1
)
################################################################################
# Export header #
################################################################################
include(GenerateExportHeader)
generate_export_header(
${TARGET-SHARED}
BASE_NAME gpds
EXPORT_FILE_NAME gpds_export.hpp
DEPRECATED_MACRO_NAME "GPDS_DEPRECATED"
NO_DEPRECATED_MACRO_NAME "GPDS_NO_DEPRECATED"
EXPORT_MACRO_NAME "GPDS_EXPORT"
NO_EXPORT_MACRO_NAME "GPDS_NO_EXPORT"
STATIC_DEFINE "GPDS_STATIC_DEFINE"
DEFINE_NO_DEPRECATED
)
A class definition within the library looks like this:
#include "gpds_export.hpp"
namespace gpds
{
class GPDS_EXPORT container
{
// ...
};
}
For completeness, here's the relevant part of gpds_export.hpp which gets generated by cmake's generate_export_header():
#ifdef GPDS_STATIC_DEFINE
# define GPDS_EXPORT
# define GPDS_NO_EXPORT
#else
# ifndef GPDS_EXPORT
# ifdef gpds_shared_EXPORTS
/* We are building this library */
# define GPDS_EXPORT __declspec(dllexport)
# else
/* We are using this library */
# define GPDS_EXPORT __declspec(dllimport)
# endif
# endif
# ifndef GPDS_NO_EXPORT
# define GPDS_NO_EXPORT
# endif
#endif
The problem
The problem I'm running into is when building gpds-objs I am presented with the following messages:
C:\Users\joel\Documents\projects\gpds\lib\src\value.cpp:7:1: warning: 'gpds::value::value(const gpds::value&)' redeclared without dllimport attribute: previous dllimport ignored [-Wattributes]
7 | value::value(const value& other) :
| ^~~~~
C:\Users\joel\Documents\projects\gpds\lib\src\value.cpp:17:1: warning: 'gpds::value::value(gpds::value&&)' redeclared without dllimport attribute: previous dllimport ignored [-Wattributes]
17 | value::value(value&& other) :
| ^~~~~
C:\Users\joel\Documents\projects\gpds\lib\src\value.cpp:25:1: warning: 'virtual gpds::value::~value()' redeclared without dllimport attribute: previous dllimport ignored [-Wattributes]
25 | value::~value() noexcept
| ^~~~~
C:\Users\joel\Documents\projects\gpds\lib\src\value.cpp:33:6: warning: 'void gpds::value::from_string(std::string&&)' redeclared without dllimport attribute: previous dllimport ignored [-Wattributes]
33 | void value::from_string(std::string&& string)
I am unsure how to solve this properly.
From what I understand the problem is that building the gpds-objs target does not define any of the relevant export macros (neither GPDS_STATIC_DEFINE nor gpds_shared_EXPORTS). Therefore, GPDS_EXPORT gets defined to __declspec(dllimport) which is incorrect as we're not building a shared library with the gpds-objs target.
One solution I can think of is defining gpds_shared_EXPORTS on the gpds-objs target instead of the gpds-shared target. However, this would mean that it's also defined when building the gpds-static target. This might be fine as long as gpds-static defines GPDS_STATIC_DEFINE.
What is the correct way of handling this?

You cannot use the same OBJECT library both for shared and static libraries.
An OBJECT library determines how the source files should be compiled.
But source files should be compiled differently for a static and for a shared libraries.
Documentation for GenerateExportHeader describes, how the same generated header file could be used for static and shared libraries:
add_library(shared_variant SHARED ${lib_SRCS})
add_library(static_variant ${lib_SRCS})
generate_export_header(shared_variant BASE_NAME libshared_and_static)
set_target_properties(static_variant PROPERTIES
COMPILE_FLAGS -DLIBSHARED_AND_STATIC_STATIC_DEFINE)
Here the export header is generated for the shared library, and the static library has additional compile definition for reuse that header.
Note again, that different compile options, used for shared and static libraries, mean that you cannot reuse object files between these libraries. You could only reuse sources.

Related

FetchContent is not setting include path

I'm trying to link a fortran project against jsonfortran. Below is a mwe which fails because the module file cannot be found. This happens when the include path is not properly set.
main.F90
program thing_program
use json_module, only : json_ck
implicit none
character(kind=json_ck, len=:), allocatable :: json_string
json_string = "{}"
write(*, *) json_string
end program thing_program
CMakeLists.txt
cmake_minimum_required(VERSION 3.21)
project(
myproj
VERSION 0.0.0
LANGUAGES Fortran
)
# don't build json-fortran docs
set(SKIP_DOC_GEN ON CACHE INTERNAL "")
#json fortran uses the compiler id as an identifier
string(TOLOWER ${CMAKE_Fortran_COMPILER_ID} compiler_id)
include(FetchContent)
FetchContent_Declare(jsonfortran-${compiler_id}
GIT_REPOSITORY https://github.com/jacobwilliams/json-fortran.git
GIT_TAG 3ab8f98209871875325c6985dd0e50085d1c82c2 # 8.3.0
# if the project is installed system-wide, use that instead of building it ourselves
FIND_PACKAGE_ARGS NAMES jsonfortran-${compiler_id}
)
FetchContent_MakeAvailable(jsonfortran-${compiler_id})
add_executable( thing main.F90 )
target_link_libraries(thing
PUBLIC
jsonfortran-${compiler_id}
)
Before using FetchContent, I was able to add jsonfortran with find_package, but I had to use target_link_libraries(thing PUBLIC jsonfortran-${compiler_id}::jsonfortran) to get cmake to generate properly. After using FetchContent, that same call to target_link_libraries fails, but works if I change it to this target_link_libraries(thing PUBLIC jsonfortran-${compiler_id}).
I'm guessing that the includes directory isn't being properly set so the fortran mod files cannot be found because the call to target_link_libraries is somehow incorrect, but I am not sure how to make it function properly. Any idea what's going on?
The actual error, if you're interested:
[ 6%] Building Fortran object CMakeFiles/thing.dir/main.F90.o
/Users/kshores/Downloads/thing/main.F90:2:7:
2 | use json_module, only : json_ck
| 1
Fatal Error: Cannot open module file 'json_module.mod' for reading at (1): No such file or directory
compilation terminated.
Update: The mod files are created and placed into _deps/jsonfortran-gnu-build when they should be placed into _deps/jsonfortran-gnu-build/include. With make VERBOSE=1, I see that in fact the include directory is added -I/Users/kshores/Downloads/thing/build/_deps/jsonfortran-gnu-build/include, but the mod files are not there for some reason.

CMake problem finding an include file in another subproject

I have a project that we will call proj.
It has a subproject call config which does not link to anything because we want to manage it separately from the primary image.
But config has some configuration files, lets call them sys_config.h and user_config.h, that define the structure that others need to access and to be clean about it, I want those stored with the config subproject.
Finally, there is a library, we will call lib that is included in proj. It needs to be able to reference those config include files.
So, the file structure is approximately like this:
proj
main
src
include
(this references and includes lib below)
config
src
sys_config.c
user_config.c
include
config
sys_config.h
user_config.h
mylib
src
mylib.c
#include <config/sys_config.h>
or
#include <sys_config.h>
include
mylib.h
Now, what do I put in lib's CMakeLists.txt to get it to be able to see sys_config.h?
For example, this does not work:
target_include_directories(mylib
PUBLIC include
PRIVATE "{$CMAKE_SOURCE_DIR}/config/include"
)
nor does this work:
target_include_directories(mylib
PUBLIC include
PRIVATE "{$CMAKE_SOURCE_DIR}/config/include/config"
)
nor does this:
target_include_directories(mylib
PUBLIC include
PRIVATE "config/include"
)
My hope was maybe if I go back to the top and include the full path to config/include it would find it but it doesn't.
I feel that I must have a complete misunderstanding of how target_include_directories() is supposed to work and what it is supposed to do for me.
Roger
With modern CMake, notion of a subdir / subproject is to be an independent library / executable. Each subproject (lib / exe) should define set of PUBLIC / PRIVATE headers. See - https://cmake.org/cmake/help/latest/command/target_include_directories.html. Rest dependent projects should just link with subproject libs. Inclusion of libs is internally managed by CMake.
Sample cmake files based on your directory layout (I have not tested them so treat them as reference purpose only).
Top level cmake file # proj/CMakeLists.txt
cmake_minimum_required(VERSION 3.9)
project(main)
subdirs(config)
subdirs(mylib)
add_executable(hello main/src/main.c)
target_link_libraries(hello config mylib)
install(TARGETS hello DESTINATION bin)
config cmake file # proj/config/CMakeLists.txt
cmake_minimum_required(VERSION 3.9)
add_library(config src/sys_config.c src/user_config.c)
target_include_directories(config PUBLIC include)
# uncomment if you have any internal headers within config/src
# target_include_directories(config PRIVATE src)
As best practice keep all your module config exposed headers at config/include/config/. This will make them to be imported as #include "config/sys_config.h" in mylib source files.
mylib cmake file # proj/mylib/CMakeLists.txt
cmake_minimum_required(VERSION 3.9)
add_library(mylib src/mylib.c)
target_include_directories(mylib PUBLIC include)
# uncomment if you have any internal headers in proj/mylib/src
# target_include_directories(mylib PRIVATE src)
target_link_libraries(mylib config)

CMake: How to append extra compile definition to a dependent lib build

I have a shared library, libShared, which used to build different executables. What do I want to do is based on the build executable to add additional compile definition to the shared library.
CMakeLists.txt in my lib folder:
...
add_definition(-Dfoo -Dbar)
add_library(shared ${SOURCES})
CMakeLists.txt in exe1 folder:
...
add_executable(exe1 ${SOUCE_FILES})
add_dependencies(exe1 shared)
# <How Do I append -DForExe1 compile definition to shared>?
target_link_libraries(exe1 shared)
CMakeLists.txt in exe2 folder:
...
add_executable(exe2 ${SOURCES})
add_dependentcies(exe2 shared)
# <How Do I append -DForExe2 compile definition to shared>?
target_link_libraries(exe2 shared)
How do I do this in CMake?
One cannot have single library target with different compile definitions.
Different compile definitions means different compilation actions, resulted in different objects files. But this would defeat the very concept of a library as something already compiled (and linked, in case of shared libraries).
If you have small number of possible compile definitions' set for a library, you may define library target for every set:
# Common definitions for any library.
add_definition(-Dfoo -Dbar)
# Common sources for any library
set(LIB_SOURCES ...)
# The library specialized for exe1.
add_library(lib_exe1 SHARED ${LIB_SOURCES})
# Library-specific definitions
target_compile_definitions(lib_exe1 PRIVATE -DForExe1)
# The library specialized for exe2.
add_library(lib_exe2 SHARED ${LIB_SOURCES})
# Library-specific definitions
target_compile_definitions(lib_exe2 PRIVATE -DForExe2)
Resulted libraries may be used when needed:
add_executable(exe1 ${SOUCE_FILES})
target_link_libraries(exe1 lib_exe1)
If your library should be parametrized for some compile definition, consider to create a CMake function/macro, which creates a library instance when needed:
function(add_lib_for lib_name purpose_name)
add_library(${lib_name} SHARED <sources>)
target_compile_definitions(${lib_name} PRIVATE
-Dfoo -Dbar # Common definitions
-DFor${purpose_name} # Specific definition
)
endfunction()
Usage:
add_executable(exe1 ${SOUCE_FILES})
add_lib_for(lib_exe1 Exe1)
target_link_libraries(exe1 lib_exe1)

How to remove compile dependencies for cmake static libraries?

Consider this CMake setup:
add_library( A STATIC modules/a/src/src1.cpp modules/a/src/src2.cpp )
target_include_directories( A PUBLIC modules/a/inc )
target_compile_definitions( A PUBLIC USING_A=1 )
add_library( B STATIC modules/b/src/src1.cpp modules/b/src/src2.cpp )
target_include_directories( B PUBLIC modules/b/inc )
target_compile_definitions( B PUBLIC USING_B_WRAPPER=1 )
target_link_libraries( B PUBLIC A )
add_library( C STATIC modules/c/src/src1.cpp )
target_include_directories( C PUBLIC modules/c/inc )
target_link_libraries( C PUBLIC B )
Let's assume that headers from modules/b/inc include headers from modules/a/inc, so any consumer of B library must also add modules/a/inc to its includes path and also add USING_A=1 preprocessor definition.
Let's use the Ninja Generator (however the problem occurs with any generator, including Makefile, Xcode and Visual Studio):
cmake -GNinja ../path/to/source
And let's build the C library:
ninja libC.a
Everything builds correctly, however lots of time gets wasted into building libA.a and libB.a just to build the libC.a.
If I change the C's dependency on B to INTERFACE, then libA.a and libB.a are not built, but compilation of modules/c/src/src1.cpp fails because include directories and compile definitions that should be inherited from B and its dependencies are not inherited.
Is there a way to tell CMake that specific target should not compile-depend on specific target specified in target_link_libraries list (link-dependency should remain)? I am aware that sometimes those dependencies are required (such as if, for example, A depended on some custom command generating headers for it dependees), but this is not the case here. I am searching for a solution to specify for each static library target whether or not it should compile-depend on another static library target, while still keeping the link dependency and obtaining all correct compile flags from its dependencies.
Currently I know no way for issue target_link_libraries without complete target-level dependencies. And not sure that things will change in a near future. CMake developer's post from the bugreport:
Using target_link_libraries has always been enough to get an ordering dependency and many projects depend on this. We cannot change that for any target type.
What can be done without changing semantics is for the Ninja generator to detect when the transitive closure of a dependency doesn't have any custom commands and in that case drop ordering dependencies on it from the compilation rules and custom commands. Only the full dependency of the link step on the dependency's library file is needed. Work on this would be better discussed on the developer mailing list.
That answer on related question suggests to refuse from target_link_libraries between STATIC libraries, which removes some unneded dependencies. I have adapted that scenario for your purpose:
Non-natural way
Express "compile-time dependencies" with INTERFACE libraries. Real libraries will be used only for link an executable or a SHARED library.
# Compile-time dependency.
add_library( A_compile INTERFACE )
target_include_directories( A_compile PUBLIC modules/a/inc )
target_compile_definitions( A_compile PUBLIC USING_A=1 )
# Real library.
add_library( A STATIC modules/a/src/src1.cpp modules/a/src/src2.cpp )
target_link_libraries(A A_compile)
# Compile-time dependency.
add_library( B_compile INTERFACE )
target_include_directories( B_compile PUBLIC modules/b/inc )
target_compile_definitions( B_compile PUBLIC USING_B_WRAPPER=1 )
target_link_libraries( B_compile PUBLIC A_compile )
# Real library
add_library( B STATIC modules/b/src/src1.cpp modules/b/src/src2.cpp )
target_link_libraries(B B_compile)
# Final STATIC library.
add_library( C STATIC modules/c/src/src1.cpp )
target_include_directories( C PUBLIC modules/c/inc )
target_link_libraries( C PUBLIC B_compile )
# ...
# Creation executable or non-STATIC library, linked with C
add_executable(my_exe ...)
# Need to manually list libraries, "compile-time linked" to C.
target_link_libraries(my_exe C B A)
Becase for executable or non-STATIC library target_link_libraries uses real STATIC libraries, these STATIC libraries will be built before even object files for such executable/SHARED library.
We've been using the following function to link static libraries against one another without causing build order dependencies between them for several years now. While not perfect, we've been quite happy with it overall.
function(target_link_static_libraries target)
if(BUILD_STATIC_LIBS_IN_PARALLEL)
target_link_libraries(${target} INTERFACE ${ARGN})
foreach(lib ${ARGN})
target_include_directories(${target} PUBLIC $<TARGET_PROPERTY:${lib},INTERFACE_INCLUDE_DIRECTORIES>)
target_include_directories(${target} SYSTEM PUBLIC $<TARGET_PROPERTY:${lib},INTERFACE_SYSTEM_INCLUDE_DIRECTORIES>)
target_compile_definitions(${target} PUBLIC $<TARGET_PROPERTY:${lib},INTERFACE_COMPILE_DEFINITIONS>)
target_compile_options(${target} PUBLIC $<TARGET_PROPERTY:${lib},INTERFACE_COMPILE_OPTIONS>)
endforeach()
else()
target_link_libraries(${target} PUBLIC ${ARGN})
endif()
endfunction()
Known drawbacks:
Boolean transitive properties like PIC don't get automatically get applied.
The dependencies aren't shown when generating dependency graphs.
When an error occurs finding a dependency, it often spits out hundreds of lines of error messages, since we essentially defer the checking for the existence of libraries until it has been propagated out to dozens of targets.
Improvements or fixes for any of the above would be very much appreciated.

Fortran module files not found by CMake

I have a split project in Fortran with a subdirectory as a library:
# ./CMakeLists.txt
cmake_minimum_required (VERSION 2.8)
project (Simulation Fortran)
enable_language(Fortran)
add_subdirectory(lib)
add_executable(Simulation main.f90)
include_directories(lib)
add_dependencies(Simulation physicalConstants)
target_link_libraries(Simulation physicalConstants)
The root directory contains only a single Fortran source code file:
! ./main.f90:
program simulation
use physicalConstants
implicit none
write(*,*) "Boltzmann constant:", k_b
end program simulation
And my subdirectory lib contains another CMakeLists.txt as well as a Fortran module source file:
# ./lib/CMakeLists.txt:
cmake_minimum_required (VERSION 2.8)
enable_language(Fortran)
project(physicalConstants)
add_library( physicalConstants SHARED physicalConstants.f90)
! ./lib/physicalConstants.f90:
module physicalConstants
implicit none
save
real, parameter :: k_B = 1.38e-23
end module physicalConstants
I tried to build those using cmake. Make generates the physicalconstants.mod in the lib directory, but this file is not found during the build process of main.f90.o:
Fatal Error: Can't open module file 'physicalconstants.mod' for reading at (1): No such file or directory
What am I missing here?
For target A to successfully use modules from target B, the directory where B stores module files must be among A's include directories.
Variant 1
One way to achieve that is to set the property Fortran_MODULE_DIRECTORY on target B and then add that property's contents to include directories of A.
You're claiming to support ancient CMake 2.8.0, in which you'll need to do something like this:
add_executable(Simulation main.f90)
include_directories(lib)
# note that add_dependencies call is not necessary when you're actually linking
target_link_libraries(Simulation physicalConstants)
get_property(moduleDir TARGET physicalConstants PROPERTY Fortran_MODULE_DIRECTORY)
include_directories(${moduleDir})
In more modern CMake, you could do this instead:
add_executable(Simulation main.f90)
include_directories(lib)
target_link_libraries(Simulation physicalConstants)
target_include_directories(Simulation PUBLIC $<TARGET_PROPERTY:physicalConstants,Fortran_MODULE_DIRECTORY>)
You can even create a function for it:
function(LinkFortranLibraries Target)
target_link_libraries(Target ${ARGN})
foreach(Lib IN LISTS ARGN)
target_include_directories(Simulation PUBLIC $<TARGET_PROPERTY:${Lib},Fortran_MODULE_DIRECTORY>)
endforeach()
endfunction()
and then use it like this:
add_executable(Simulation main.f90)
include_directories(lib)
LinkFortranLibraries(Simulation physicalConstants)
Variant 2
If you do not use the Fortran_MODULE_DIRECTORY property, module files are stored in the binary directory corresponding to the source directory of the target producing them. This can be retrieved from the target's property BINARY_DIR, which you could use exactly as Fortran_MODULE_DIRECTORY in variant 1.
However, CMake 2.8.0 does not support the target property BINARY_DIR, so you will have to "reconstruct" its value manually:
add_executable(Simulation main.f90)
include_directories(lib ${CMAKE_CURRENT_BINARY_DIR}/lib)
target_link_libraries(Simulation physicalConstants)