CMake: static library ignored (objects compiled twice) [duplicate] - cmake

I'm trying to move my project to CMake, and at the same time have some optimization on the compilation process.
Here's the deal:
I have several subdirs that are (have to be) each compiled into a static library (this works).
I want to gather all the object files from each subdir into another bigger, complete, static library.
It looks like this:
.
libBig.a # made from object from subdir1 and subdir2
subdir1/
src/
libSubdir1.a
subdir2/
src/
libSubdir2.a
Today, I managed to use a global variable in which every subdir CMakeLists.txt will append its own source files. I use this variable as a "source" input in my big library:
# the big library depends on all the source files
# ${all_src} is automatically filled with each subdir's cpp file
get_property( BigLib_src GLOBAL PROPERTY all_src)
add_library( Big STATIC ${BigLib_src}) # recompiles all the sources
Now, this works, not too bad, but the thing is, all my source files get compiled twice: once for the subdir library, and once for the big library.
CMake seems to forget that it has already built them.
I have to keep the subdir libraries and ar can't merge two static libraries.
Do you know how to do that?

You can use the new OBJECT library feature introduced in CMake 2.8.8. The idea is explained here. Basically, the OBJECT library is a similar concept to the convenience library known from Autotools to group object files.
Check the complete CMake OBJECT library tutorial.

As of CMake 2.8.8, you can do this using the OBJECT library type. See mloksot's answer. The old situation was that each target had its own directory and CMake would build every dependent object for every target. This guards against a case where one source file could be used multiple times with different CFLAGS. (Note that by default CMake is more conservative than automake here: automake will stop reusing object files for different targets only if the targets are built with different CFLAGS (and probably CPPFLAGS, too).

Related

How is CMake supposed to work for shared libraries?

I am trying to figure out how CMake is supposed to work for shared libraries. I create a shared library like so:
(in /mdp_opt/CMakeLists.txt:)
add_library (mdp_opt SHARED librarycomponent.cpp)
Now, a test executable uses this library:
(/test/CMakeLists.txt:)
add_executable (test test.cpp)
target_link_libraries(test PRIVATE mdp_opt)
If the library is marked STATIC (instead of SHARED as above), I can cmake -> built (under Visual Studio) and successfully run the executable. When it is marked SHARED (as in above), I need to do two things. First thing, is adding:
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
That is fine. However, now it still only works if I copy the file mdp_opt.dll from build/x64-debug/mdp_opt to build/x64-debug/test. But I don’t understand why this is needed?
In the book “professional CMake”, I read:
LINK_LIBRARIES
This target property holds a list of all libraries the target should
link to directly. It is initially empty when the target is created and
it supports generator expressions. An associated interface property
INTERFACE_LINK_LIBRARIES is supported. Each library listed can be one
of the following (emphasis mine):
• A path to a library, usually specified as an absolute path.
• Just the library name without a path, usually also without any
platform-specific file name prefix (e.g. lib) or suffix (e.g. .a, .so,
.dll).
• The name of a CMake library target. CMake will convert this to a
path to the built library when generating the linker command,
including supplying any prefix or suffix to the file name as
appropriate for the platform. Because CMake handles all the various
platform differences and paths on the project’s behalf, using a CMake
target name is generally the preferred method.
I was under the impression that
target_link_libraries(test PRIVATE mdp_opt)
expresses that I intend to link the output associated with the target mdp_opt with the test executable? Also, in my reading of the above book excerpt, my understanding is that the location of the .dll will convert to a path? If the purpose of this conversion is not to somehow tell the executable where to find the shared library, then what is that conversion for?
Basically, can anybody tell me how CMake is supposed to work for shared libraries, and why is works like that? Is a manual post-copy (possibly via CMake instructions) really needed, and the best for this scenario (of intermediate builds while developing)?
On windows you need to export symbols of a shared library or add a linker option that results in symbols being exported; otherwise the .lib file for linking the dll simply isn't generated. This is why you need
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
, use __declspec( dllexport ) or similar.
The other issue is the way windows locates dlls: It basically first searches the directory containing the executable and then continues the search via the PATH environment variable. This can result in issues locating the dll at runtime.
You can avoid this issue by setting the directory to place the runtime artefacts before generating the first one of the targets involved:
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
...
add_library(mdp_opt ...)
...
add_executable(test ...)
Alternatively you could simply change the environment variable visible to the vs debugger:
set_target_properties(test PROPERTIES VS_DEBUGGER_ENVIRONMENT "PATH=$<TARGET_FILE_DIR:mdp_opt>;$ENV{PATH}")
If you're working with CTest, there's also a similar property for tests: ENVIRONMENT
If the purpose of [target_link_libraryes] is not to somehow tell the executable where to find the shared library, then what is that conversion for?
You still need to link a dll, or more precisely the corresponding import library; this is what target_link_libraries does, in addition to adding build system dependencies, transferring some properties (e.g. include directories with PUBLIC visibility), ect.

CMake parallel build for Fortran-90+ with modules

I have a problem building multiple Fortran-90+ executable targets using CMake, when I use Fortran modules that are shared by all executables, and build in parallel using make -j. The issue seems to be that the compiled object files are placed in different subdirectories for each target, CMakeFiles/targetName.dir/src/file.f90.o, while the module files are placed in the same directory for every target (I can change this directory by setting Fortran_MODULE_DIRECTORY, but it will still be the same dir for all module files). The problem is then that all targets start writing these module files in parallel, and I get a
Fatal Error: Can't rename module file 'module.mod0' to 'module.mod': No such file or directory
when using gfortran (which apparently creates a .mod0 file and then renames it to .mod). The problem does not arise when I issue make without the -j option (serial build).
I can see two solutions, but I don't know how to implement them. Firstly, put the object files for all targets in the same directory rather than target-specific directories. This may be the preferred option, since I won't have to compile the shared source files N times for N targets. The make process will then recognise that the object files exist, and not compile the corresponding source files again, hence not touching the .mod(0) files again (I may need to make all following targets depend on the first).
The second solution would be to put the .mod(0) files in the target-specific directories, so that they are not overwritten or removed by the other targets. This would solve my problem, even though it would still involve more compiling than necessary. I have no idea how to accomplish either option, so any hint there or alternative solution is welcome.
The answer I was looking for was provided in the comments to the question by #RaulLaasner:
I would create an additional target in the form of a core library of the relevant source files, which can then be linked to all executables. This should work in parallel. The mod files can still be in put into a single directory.
I used add_library() and target_link_libraries() to achieve this.
Note that e.g. Gentoo ebuild scripts add --as-needed to the linker, which may cause undefined references in your core library when you link it and external libraries to form the executable. To prevent this, make sure you link the external libraries to your core library first. To this end, my CMakeListst.txt contains:
add_library( "Core" STATIC src/functions.f90 src/routines.f90 ) # creates libCore.a
target_link_libraries( Core ${EXTERNAL_LIBRARIES} ) # link external libraries to libCore.a
...
add_executable( myProgram1 src/myProgram1.f90 ) # creates the first executable
target_link_libraries( myProgram1 Core ) # links libCore.a to myProgram1
The last two lines can be repeated to build the other executables (e.g. using foreach()).

CMake: header-only library with generated files

I have a library that needs to carry some constant data injected from the content of non-source files (in this case, OpenGL shader code). To achieve this, I'm using add_custom_command() to generate include files that I can then #include into my code to initialize const static variables.
This works perfectly with regular libraries (static or shared), but now I'd like to make my library header-only. The ability of C++ to let static methods return static data without running the risk of having that data duplicated in each translation unit ("magic statics") makes this possible.
The problem however is that CMake seems to assume that an INTERFACE library (which is the CMake feature that I'm using to create header-only libraries) does not need building - which, in this case, is wrong.
(I realize that there is no actual obligation for my library to be header-only. In this particular case, the reason I want this is that I would like the library, which is doing OpenGL, to remain independent of any specific binding library [such as GLEW or GLee or the newcomer glbinding]. By keeping my library header-only, I can leave that choice to the user - all he needs to do is #include the header of the binding library before mine.)
Does anyone see a way to have CMake trigger the header-generating custom commands, at the latest when the consumer project is being built?
EDIT: I just realized that I could have the "best of both worlds" as it were by keeping my library static but still keeping all my code except for the constant data in the header files. That way, there would still be no need to choose a specific OpenGL binding library.
However, there are still advantages to having a library be header-only - simplicity of use for one - so I'm leaving my question open.
EDIT #2: Here is the relevant part of my CMakeLists.txt file (I only stripped the library dependencies - all header-only - from the end):
set(SHADER_FILES "src/vertex.glsl" "src/fragment.glsl")
add_library(libGPCGUIGLRenderer INTERFACE)
target_sources(libGPCGUIGLRenderer INTERFACE ${SHADER_FILES})
target_include_directories(libGPCGUIGLRenderer BEFORE
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
# Embed shader files
source_group("Shader files" FILES ${SHADER_FILES})
set(GENERATED "${CMAKE_CURRENT_BINARY_DIR}/generated")
target_include_directories(libGPCGUIGLRenderer INTERFACE ${GENERATED})
# Find the GPC Bin2C utility
find_package(GPCBin2C REQUIRED)
# Add a custom target and a dependency for each shader file
foreach(shader ${SHADER_FILES})
get_filename_component(name "${shader}" NAME)
set(shader_header "${GENERATED}/${name}.h")
add_custom_command(
OUTPUT ${shader_header}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${shader}
COMMAND GPCBin2C --input=${CMAKE_CURRENT_SOURCE_DIR}/${shader} --output=${shader_header}
)
target_sources(libGPCGUIGLRenderer INTERFACE ${shader_header})
endforeach()
Creating a static library with headers as the only sources worked for me. It is, of course, only a work-around.
Creating a static library with only header files results in an empty library. Mine says !<arch> as the only content.
CMake will automatically get the dependencies correct across sub-directories.
Since all sources are headers, you need to tell CMake which linker language should be used.
Code:
set(OUTDIR "${CMAKE_CURRENT_BINARY_DIR}/generated_include")
add_custom_command(
OUTPUT "${OUTDIR}/outfile.h"
# Replace the next two lines with a proper generating script.
COMMAND mkdir -p ${OUTDIR}
COMMAND touch ${OUTDIR}/outfile.h
)
# Note, I am only adding header files to the library.
add_library(generated-headers STATIC
"${OUTDIR}/outfile.h"
)
set_target_properties(generated-headers
PROPERTIES LINKER_LANGUAGE CXX)
target_include_directories(generated-headers PUBLIC ${OUTDIR})
Use in other directories like this:
# In any other directory of the same CMake project:
add_executable(main main.cpp)
target_link_libraries(main generated-headers)
Tested on CMake 3.2, 3.8 and 3.9. Using Ninja and Make generators.
You can use target_sources in CMake 3.1 to tell consumers to compile interface files:
add_library(source_only INTERFACE)
target_sources(source_only INTERFACE foo.cpp)
http://www.cmake.org/cmake/help/v3.1/command/target_sources.html
I ran into comparable problems when trying to use glad: https://github.com/Dav1dde/glad
It uses a custom CMake command to build a binding, which means the files you need to include in the project which uses glad do not exist, so that CMake does not build glad (which would create those files)...
I did not get to try it yet, but example 3 of the following link seems to be a good solution and I believe it may work in your case:
https://samthursfield.wordpress.com/2015/11/21/cmake-dependencies-between-targets-and-files-and-custom-commands/

How is CMake used? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 5 years ago.
Improve this question
It is notoriously difficult to get any useful information on CMake as a beginner. So far, I've seen a few tutorials on how to set up some very basic project or another. However, none of these explain the reasoning behind anything that is shown in them, always leaving many holes to fill.
What does calling CMake on a CMakeLists mean? Is it supposed to be called once per build tree or what? How do I use different settings for each build if they all use the same CMakeLists.txt file from the same source?
Why does each subdirectory need its own CMakeLists file? Would it make sense to use CMake on a CMakeLists.txt other than the one at the root of the project? If so, in what cases?
What's the difference between specifying how to build an executable or library from the CMakeLists file in their own subdirectory versus doing it in the CMakeLists file at the root of all source?
Can I make a project for Eclipse and another for Visual Studio, just changing the -G option when calling CMake? Is that even how it's used?
None of the tutorials, documentation pages or questions/answers I've seen so far give any useful insight towards understanding how to use CMake. The examples are just not thorough. No matter what tutorials I read, I feel like I'm missing something important.
There are many questions asked by CMake newbies like me that don't ask this explicitly, but that make obvious the fact that, as newbs, we have no idea how to deal with CMake or what to make of it.
What is CMake for?
According to Wikipedia:
CMake is [...] software for managing the build process of software
using a compiler-independent method. It is designed to support
directory hierarchies and applications that depend on multiple
libraries. It is used in conjunction with native build environments
such as make, Apple's Xcode, and Microsoft Visual Studio.
With CMake, you no longer need to maintain separate settings specific to your compiler/build environment. You have one configuration, and that works for many environments.
CMake can generate a Microsoft Visual Studio solution, an Eclipse project or a Makefile maze from the same files without changing anything in them.
Given a bunch of directories with code in them, CMake manages all the dependencies, build orders and other tasks that your project needs done before it can be compiled. It does NOT actually compile anything. To use CMake, you must tell it (using configuration files called CMakeLists.txt) what executables you need compiled, what libraries they link to, what directories there are in your project and what is inside of them, as well as any details like flags or anything else you need (CMake is quite powerful).
If this is correctly set up, you then use CMake to create all of the files that your "native build environment" of choice needs to do its job. In Linux, by default, this means Makefiles. So once you run CMake, it will create a bunch of files for its own use plus some Makefiles. All you need to do thereafter is type "make" in the console from the root folder every time you're done editing your code, and bam, a compiled and linked executable is made.
How does CMake work? What does it do?
Here is an example project setup that I will use throughout:
simple/
CMakeLists.txt
src/
tutorial.cxx
CMakeLists.txt
lib/
TestLib.cxx
TestLib.h
CMakeLists.txt
build/
The contents of each file are shown and discussed later on.
CMake sets your project up according to the root CMakeLists.txt of your project, and does so in whatever directory you executed cmake from in the console. Doing this from a folder that isn't the root of your project produces what is called an out-of-source build, which means files created during compilation (obj files, lib files, executables, you know) will be placed in said folder, kept separate from the actual code. It helps reduce clutter and is preferred for other reasons as well, which I will not discuss.
I do not know what happens if you execute cmake on any other than the root CMakeLists.txt.
In this example, since I want it all placed inside the build/ folder, first I have to navigate there, then pass CMake the directory in which the root CMakeLists.txt resides.
cd build
cmake ..
By default, this sets everything up using Makefiles as I've said. Here is what the build folder should look like now:
simple/build/
CMakeCache.txt
cmake_install.cmake
Makefile
CMakeFiles/
(...)
src/
CMakeFiles/
(...)
cmake_install.cmake
Makefile
lib/
CMakeFiles/
(...)
cmake_install.cmake
Makefile
What are all of these files? The only thing you have to worry about is the Makefile and the project folders.
Notice the src/ and lib/ folders. These have been created because simple/CMakeLists.txt points to them using the command add_subdirectory(<folder>). This command tells CMake to look in said folder for another CMakeLists.txt file and execute that script, so every subdirectory added this way must have a CMakeLists.txt file within. In this project, simple/src/CMakeLists.txt describes how to build the actual executable and simple/lib/CMakeLists.txt describes how to build the library. Every target that a CMakeLists.txt describes will be placed by default in its subdirectory within the build tree. So, after a quick
make
in console done from build/, some files are added:
simple/build/
(...)
lib/
libTestLib.a
(...)
src/
Tutorial
(...)
The project is built, and the executable is ready to be executed. What do you do if you want the executables put in a specific folder? Set the appropriate CMake variable, or change the properties of a specific target. More on CMake variables later.
How do I tell CMake how to build my project?
Here are the contents, explained, of each file in the source directory:
simple/CMakeLists.txt:
cmake_minimum_required(VERSION 2.6)
project(Tutorial)
# Add all subdirectories in this project
add_subdirectory(lib)
add_subdirectory(src)
The minimum required version should always be set, according to the warning CMake throws when you don't. Use whatever your version of CMake is.
The name of your project can be used later on, and hints towards the fact you can manage more than one project from the same CMake files. I won't delve into that, though.
As mentioned before, add_subdirectory() adds a folder to the project, which means CMake expects it to have a CMakeLists.txt within, which it will then run before continuing. By the way, if you happen to have a CMake function defined you can use it from other CMakeLists.txts in subdirectories, but you have to define it before you use add_subdirectory() or it won't find it. CMake is smarter about libraries, though, so this is likely the only time you will run into this kind of problem.
simple/lib/CMakeLists.txt:
add_library(TestLib TestLib.cxx)
To make your very own library, you give it a name and then list all the files it's built from. Straightforward. If it needed another file, foo.cxx, to be compiled, you would instead write add_library(TestLib TestLib.cxx foo.cxx). This also works for files in other directories, for instance add_library(TestLib TestLib.cxx ${CMAKE_SOURCE_DIR}/foo.cxx). More on the CMAKE_SOURCE_DIR variable later.
Another thing you can do with this is specify that you want a shared library. The example: add_library(TestLib SHARED TestLib.cxx). Fear not, this is where CMake begins to make your life easier. Whether it's shared or not, now all you need to handle to use a library created in this way is the name you gave it here. The name of this library is now TestLib, and you can reference it from anywhere in the project. CMake will find it.
Is there a better way to list dependencies? Definitely yes. Check down below for more on this.
simple/lib/TestLib.cxx:
#include <stdio.h>
void test() {
printf("testing...\n");
}
simple/lib/TestLib.h:
#ifndef TestLib
#define TestLib
void test();
#endif
simple/src/CMakeLists.txt:
# Name the executable and all resources it depends on directly
add_executable(Tutorial tutorial.cxx)
# Link to needed libraries
target_link_libraries(Tutorial TestLib)
# Tell CMake where to look for the .h files
target_include_directories(Tutorial PUBLIC ${CMAKE_SOURCE_DIR}/lib)
The command add_executable() works exactly the same as add_library(), except, of course, it will generate an executable instead. This executable can now be referenced as a target for things like target_link_libraries(). Since tutorial.cxx uses code found in the TestLib library, you point this out to CMake as shown.
Similarly, any .h files #included by any sources in add_executable() that are not in the same directory as the source have to be added somehow. If not for the target_include_directories() command, lib/TestLib.h would not be found when compiling Tutorial, so the entire lib/ folder is added to the include directories to be searched for #includes. You might also see the command include_directories() which acts in a similar fashion, except that it does not need you to specify a target since it outright sets it globally, for all executables. Once again, I'll explain CMAKE_SOURCE_DIR later.
simple/src/tutorial.cxx:
#include <stdio.h>
#include "TestLib.h"
int main (int argc, char *argv[])
{
test();
fprintf(stdout, "Main\n");
return 0;
}
Notice how the "TestLib.h" file is included. No need to include the full path: CMake takes care of all that behind the scenes thanks to target_include_directories().
Technically speaking, in a simple source tree like this you can do without the CMakeLists.txts under lib/ and src/ and just adding something like add_executable(Tutorial src/tutorial.cxx) to simple/CMakeLists.txt. It's up to you and your project's needs.
What else should I know to properly use CMake?
(AKA topics relevant to your understanding)
Finding and using packages: The answer to this question explains it better than I ever could.
Declaring variables and functions, using control flow, etc.: check out this tutorial that explains the basics of what CMake has to offer, as well as being a good introduction in general.
CMake variables: there are plenty, so what follows is a crash course to get you on the right track. The CMake wiki is a good place to get more in-depth information on variables and ostensibly other things as well.
You may want to edit some variables without rebuilding the build tree. Use ccmake for this (it edits the CMakeCache.txt file). Remember to configure when done with the changes and then generate makefiles with the updated configuration.
Read the previously referenced tutorial to learn about using variables, but long story short:
set(<variable name> value) to change or create a variable.
${<variable name>} to use it.
CMAKE_SOURCE_DIR: The root directory of source. In the previous example, this is always equal to /simple
CMAKE_BINARY_DIR: The root directory of the build. In the previous example, this is equals to simple/build/, but if you ran cmake simple/ from a folder such as foo/bar/etc/, then all references to CMAKE_BINARY_DIR in that build tree would become /foo/bar/etc.
CMAKE_CURRENT_SOURCE_DIR: The directory in which the current CMakeLists.txt is in. This means it changes throughout: printing this from simple/CMakeLists.txt yields /simple, and printing it from simple/src/CMakeLists.txt yields /simple/src.
CMAKE_CURRENT_BINARY_DIR: You get the idea. This path would depend not only on the folder the build is in, but also on the current CMakeLists.txt script's location.
Why are these important? Source files will obviously not be in the build tree. If you try something like target_include_directories(Tutorial PUBLIC ../lib) in the previous example, that path will be relative to the build tree, that is to say it will be like writing ${CMAKE_BINARY_DIR}/lib, which will look inside simple/build/lib/. There are no .h files in there; at most you will find libTestLib.a. You want ${CMAKE_SOURCE_DIR}/lib instead.
CMAKE_CXX_FLAGS: Flags to pass on to the compiler, in this case the C++ compiler. Also worth noting is CMAKE_CXX_FLAGS_DEBUG which will be used instead if CMAKE_BUILD_TYPE is set to DEBUG. There are more like these; check out the CMake wiki.
CMAKE_RUNTIME_OUTPUT_DIRECTORY: Tell CMake where to put all executables when built. This is a global setting. You can, for instance, set it to bin/ and have everything neatly placed there. EXECUTABLE_OUTPUT_PATH is similar, but deprecated, in case you stumble upon it.
CMAKE_LIBRARY_OUTPUT_DIRECTORY: Likewise, a global setting to tell CMake where to put all library files.
Target properties: you can set properties that affect only one target, be it an executable or a library (or an archive... you get the idea). Here is a good example of how to use it (with set_target_properties().
Is there an easy way to add sources to a target automatically? Use GLOB to list everything in a given directory under the same variable. Example syntax is FILE(GLOB <variable name> <directory>/*.cxx).
Can you specify different build types? Yes, though I'm not sure about how this works or the limitations of this. It probably requires some if/then'ning, but CMake does offer some basic support without configuring anything, like defaults for the CMAKE_CXX_FLAGS_DEBUG, for instance.
You can either set your build type from within the CMakeLists.txt file via set(CMAKE_BUILD_TYPE <type>) or by calling CMake from console with the appropriate flags, for example cmake -DCMAKE_BUILD_TYPE=Debug.
Any good examples of projects that use CMake? Wikipedia has a list of open-source projects that use CMake, if you want to look into that. Online tutorials have been nothing but a letdown to me so far in this regard, however this Stack Overflow question has a pretty cool and easy-to-understand CMake setup. It's worth a look.
Using variables from CMake in your code: Here's a quick and dirty example (adapted from some other tutorial):
simple/CMakeLists.txt:
project (Tutorial)
# Setting variables
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 1)
# Configure_file(<input> <output>)
# Copies a file <input> to file <output> and substitutes variable values referenced in the file content.
# So you can pass some CMake variables to the source code (in this case version numbers)
configure_file (
"${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
"${PROJECT_SOURCE_DIR}/src/TutorialConfig.h"
)
simple/TutorialConfig.h.in:
// Configured options and settings
#define Tutorial_VERSION_MAJOR #Tutorial_VERSION_MAJOR#
#define Tutorial_VERSION_MINOR #Tutorial_VERSION_MINOR#
The resulting file generated by CMake, simple/src/TutorialConfig.h:
// Configured options and settings
#define Tutorial_VERSION_MAJOR 1
#define Tutorial_VERSION_MINOR 1
With clever use of these you can do cool things like turning off a library and such. I do recommend taking a look at that tutorial as there are some slightly more advanced things that are bound to be very useful on larger projects, sooner or later.
For everything else, Stack Overflow is brimming with specific questions and concise answers, which is great for everyone except the uninitiated.

CMake find_package dependency on subproject

I have the following directory layout:
main_folder
+ static_lib1
+ executable
Both 'static_lib1' and 'executable' have a full CMakeLists so that they can be
built independently.
The 'executable' depends on 'static_lib1'. It uses find_package() to locate 'static_lib1'.
The main_folder contains a CMakeLists that includes both 'static_lib1' and 'executable' via add_subdirectory for conveniently building the whole project in one go.
Everything works fine if I manually build 'static_lib1' and then 'executable'. But when running the CMakeLists from the main folder, I get an error because find_package is unable to find the library files from 'static_lib1' which have not yet been built.
How can I resolve this while keeping the CMakeLists files separate (i.e. without including the static_lib's CMakeLists from the executable's CMakeLists)?
In executable's CMakeLists.txt you can check if you are building stand-alone or as part of project:
if( CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR )
# stand-alone build
find_package(static_lib1)
else()
include_directories(../static_lib1)
link_directories(../static_lib1)
...
target_link_libraries(executable static_lib1)
endif()
Switch from a file-based approach to a target-based approach for handling the dependency from executable to static_lib1.
The original problem occurred because executable called find_package for locating static_lib1, which then attempted to fill a variable like STATIC_LIB1_LIBRARY with the paths to the library files by calling find_library. executable then consumes the content of that variable in a target_link_libraries(executable ${STATIC_LIB1_LIBRARY}) call. The problem here is, since those library files only get generated as part of the build, that call to find_library will not be able to find anything.
Building executable needs to support two scenarios here:
Building standalone, where a pre-compiled version of static_lib1 is located somewhere on the disc.
Building from main_folder, where both executable and static_lib1 are part of the same build.
The approach from the question supports scenario 1, but not scenario 2.
Instead of using using a variable to communicate a dependency between the two builds, use a target. The CMakeLists.txt for static_lib1 likely creates a library target like add_library(static_lib1 [...]). In executable we now simply do target_link_libraries(executable PUBLIC static_lib1). This is sufficient to support scenario 2.
To also allow for scenario 1 at the same time, we look at the call to find_package(static_lib1) in the CMakeLists.txt for executable. Instead of providing a variable like before, this call now needs to provide a target static_lib1 for consumption.
So we adapt the find script for static_lib1 to the following behavior:
If a target static_lib1 already exists, there's nothing to be done and the find script can just return (this is scenario 2).
Otherwise, we call find_library to locate the library file on disc (as before in the original approach) and then create a new imported target: add_library(static_lib1 STATIC IMPORTED). We then configure all relevant properties of the static library to that target. For instance, to add the location of the library file, we could do
set_target_properties(static_lib1 PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES "CXX"
IMPORTED_LOCATION ${STATIC_LIB1_LIBRARY}
)
To support multi-config generators like MSVC, instead of setting IMPORTED_LOCATION and IMPORTED_LINK_INTERFACE_LANGUAGES, you will want to set the configuration specific properties like IMPORTED_LOCATION_DEBUG and IMPORTED_LOCATION_RELEASE instead. Since this can get quite tedious to do manually, you can have CMake generate this information (and a bunch of other convenient stuff) for you in a package script. The find mechanism for package scripts works slightly different under the hood, but the code in the CMakeLists.txt for executable will look just the same, a simple call to find_package(static_lib1). The main difference is that this call will then not dispatch to a hand-written find script, but to a package script that was automatically generated by CMake as part of the build process of static_lib1.
I guess I will leave this answer for posterity since only recently I have searched for a solution to this problem and found out that...
Since CMake 3.24 it is possible!
It is possible to override subsequent calls to find_package() with FetchContent_Declare() flag OVERRIDE_FIND_PACKAGE.
Your
add_subdirectory("path/to/static_lib1")
call has to be replaced in main_folder/CMakeLists.txt with:
include(FetchContent)
FetchContent_Declare(
static_lib1
SOURCE_DIR "path/to/static_lib1"
OVERRIDE_FIND_PACKAGE
)
Any calls to find_package(static_lib1) will call FetchContent_MakeAvailable() for you, virtually making it identical to add_subdirectory() call.
You can read more about OVERRIDE_FIND_PACKAGE in CMake documentation.