CMake使用ExternalProject管理仅头文件库的疑问:为何需两次构建命令及同时使用include_directories与target_include_directories
Let's break down your two questions one by one, and also fix some key issues in your CMake setup along the way.
1. Why do you need two separate CMake runs?
The core problem here lies in how ExternalProject_Add operates: it executes the download (and build/install steps, if configured) during the build phase of your CMake project, not during the initial configure phase.
Here's what happens in each step:
- First run (
GET_LIBS=ON,BUILD_MY_PROJECTS=OFF):
CMake configures the project but skips the executable-building block entirely. When you runcmake --build, theExternalProject_Add(eigen)target triggers, cloning the Eigen repo into${CMAKE_BINARY_DIR}/install/eigen. - Second run (
GET_LIBS=OFF,BUILD_MY_PROJECTS=ON):
Now that Eigen's source files are already present in the target directory, CMake can properly configure your executable target and locate the required header files during the configure phase.
This two-step approach is a basic "superbuild" setup, but it's far from ideal. You can simplify this to a single build by either:
- Using
FetchContent(CMake 3.11+) instead ofExternalProject_Addfor header-only libraries (it downloads dependencies during the configure phase, eliminating the need for two separate runs) - Structuring your superbuild properly, where
ExternalProject_Adddrives both dependency download and your main project's build.
2. Why do both include_directories and target_include_directories(INTERFACE) seem necessary?
This is caused by a misuse of the INTERFACE visibility in target_include_directories:
INTERFACEmeans the include directory is only passed to other targets that depend onmy_exe, not tomy_exeitself. Sincemy_exeis an executable (not a library that other targets link against), usingINTERFACEhere does nothing for the executable's own compilation.include_directoriesadds the path globally, so all targets (includingmy_exe) can see it—which is why your build works when you include it. But global includes are bad practice, as they can lead to naming conflicts or unintended side effects across your project.
The simple fix:
Replace INTERFACE with PRIVATE (or PUBLIC if you ever convert my_exe into a library that other targets depend on). This applies the include directory directly to my_exe during its compilation, making include_directories completely unnecessary:
if (BUILD_MY_PROJECTS) add_executable(my_exe main.cpp) # Remove the global include_directories line entirely target_include_directories(my_exe PRIVATE "${CMAKE_BINARY_DIR}/install/eigen/") endif ()
Bonus: Clean up your Eigen setup with FetchContent
Your current ExternalProject_Add has redundant settings (like CMAKE_INSTALL_PREFIX, which does nothing since you set INSTALL_COMMAND ""). For header-only libraries, FetchContent is a much cleaner alternative. Here's a rewritten version that eliminates the two-step build entirely:
cmake_minimum_required(VERSION 3.19) project(superbuild LANGUAGES CXX) if (BUILD_MY_PROJECTS) include(FetchContent) FetchContent_Declare( eigen GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git GIT_TAG 3.4.0 ) FetchContent_MakeAvailable(eigen) add_executable(my_exe main.cpp) # Eigen automatically sets up a target Eigen3::Eigen with FetchContent target_link_libraries(my_exe PRIVATE Eigen3::Eigen) endif ()
With this setup, you can run a single command to both download Eigen and build your executable:
cmake -DBUILD_MY_PROJECTS=ON -G "Visual Studio 17 2022" -A x64 .. && cmake --build . --config Release
No more separate GET_LIBS runs needed—FetchContent handles downloading during the configure phase if the repo isn't already present.
内容的提问来源于stack exchange,提问作者PetrasVestartasEPFL

