How to download and compile external library only once with CMake? - c++

I am using the Unity testing library in my project. Ideally, I would like to automatically download the source code from GitHub and put it in the external/ directory, build the library once, regardless of how many different CMake configurations I have (one Debug and one Release CMake configuration), and link the library with my application.
I have tried to use FetchContent, ExternalProject, and add_subdirectory but none of these seem to work quite right.
The issues I am currently facing right now are:
The library is installed in the _deps/ subdirectory of my build/ directory. I want it to be downloaded to a directory called external/ in the project root.
"cmake --build ." builds the library every time. I just want the library to be built once.
"cmake --install ." installs my application and the Unity library. I only want install to install my application when this command is run. The library would be installed in lib/ and include/ before my application is built.
This is my project structure:
project/ <-- Project root
|-- bin/ <-- Application executable
|-- build/ <-- CMake build files
| |-- _deps/ <-- Where Unity is built
|-- doc/ <-- Documentation from Doxygen
|-- include/ <-- Unity header files
|-- lib/ <-- Unity library file
|-- module/ <-- Application source code
|-- CMakeLists.txt <-- CMake configuration file
This is my CMakeLists.txt:
cmake_minimum_required(VERSION 3.15)
project(Project VERSION 1.0.0)
set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR})
include_directories(${PROJECT_SOURCE_DIR}/include)
add_subdirectory(module)
# Add Unity testing framework from GitHub
include(FetchContent)
FetchContent_Declare(
unity
GIT_REPOSITORY https://github.com/ThrowTheSwitch/Unity.git
GIT_TAG v2.5.1
)
FetchContent_MakeAvailable(unity)
FetchContent_GetProperties(unity)
if (NOT unity_POPULATED)
FetchContent_Populate(unity)
add_subdirectory(${unity_SOURCE_DIR} ${unity_BINARY_DIR})
endif()
enable_testing()
add_subdirectory(tests)
I really have no idea how to accomplish this. I looked at this other question and this link but it didn't seem to do everything I wanted it to do. Any help is appreciated.

I was able to make it work using ExternalProject. This is what my CMakeLists.txt looks like now:
include(ExternalProject)
set(UNITY unity_project)
ExternalProject_Add(
unity_project
GIT_REPOSITORY https://github.com/ThrowTheSwitch/Unity.git
GIT_TAG cf949f45ca6d172a177b00da21310607b97bc7a7
PREFIX ${PROJECT_SOURCE_DIR}/external/${UNITY}
CONFIGURE_COMMAND cmake ../${UNITY}
BUILD_COMMAND cmake --build .
INSTALL_COMMAND cmake --install . --prefix ${PROJECT_SOURCE_DIR}
UPDATE_COMMAND ""
)
add_library(unity STATIC IMPORTED)
set_property(TARGET unity PROPERTY IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/lib/libunity.a)
add_dependencies(unity unity_project)
The first issue was solved by the line:
PREFIX ${PROJECT_SOURCE_DIR}/external/${UNITY}
The second issue was solved by the line:
UPDATE_COMMAND ""
The third issue was solved by the line:
INSTALL_COMMAND cmake --install . --prefix ${PROJECT_SOURCE_DIR}

Related

CMake imported targets in add_subdirectory not available in main CMakeLists.txt

I want to build an application that depends on the OpenCV (version 3.4.6) viz module. This module has the VTK library (version 7.1.1) as dependency. I want to use ExternalProject to build both, the vtk library and the opencv viz module and subsequently want to build the main application, all in one cmake run.
.
├── CMakeLists.txt
├── deps
│   └── CMakeLists.txt
└── main.cpp
I am using the cmake ExternalProject module to build both opencv and vtk inside a subdirectory like this:
deps/CMakeLists.txt
cmake_minimum_required(VERSION 3.14)
project(dependencies)
include(ExternalProject)
ExternalProject_add(
vtklib
GIT_REPOSITORY https://github.com/Kitware/vtk
GIT_TAG v7.1.1
GIT_PROGRESS TRUE
UPDATE_COMMAND ""
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}
-DBUILD_TESTING=OFF
-DBUILD_EXAMPLES=OFF
-DVTK_DATA_EXCLUDE_FROM_ALL=ON
-DVTK_USE_CXX11_FEATURES=ON
-Wno-dev
)
add_library(vtk INTERFACE IMPORTED GLOBAL)
add_dependencies(vtk vtklib)
ExternalProject_add(
ocv
GIT_REPOSITORY https://github.com/opencv/opencv
GIT_TAG 3.4.6
GIT_PROGRESS TRUE
UPDATE_COMMAND ""
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}
-DWITH_VTK=ON
-Wno-dev
)
# ExternalProject_Get_Property(ocv install_dir)
# include_directories(${install_dir}/src/ocv/include)
include_directories(${CMAKE_INSTALL_PREFIX}/include)
set(ocv_libdir ${CMAKE_INSTALL_PREFIX}/${CMAKE_VS_PLATFORM_NAME}/vc15)
set(OCV_VERSION 346)
add_dependencies(ocv vtklib)
add_library(opencv_core SHARED IMPORTED)
set_target_properties(opencv_core PROPERTIES
IMPORTED_IMPLIB "${ocv_libdir}/lib/opencv_core${OCV_VERSION}.lib"
IMPORTED_LOCATION "${ocv_libdir}/bin/opencv_core${OCV_VERSION}.dll"
)
add_library(opencv_viz SHARED IMPORTED)
set_target_properties(opencv_viz PROPERTIES
IMPORTED_IMPLIB "${ocv_libdir}/lib/opencv_viz${OCV_VERSION}.lib"
IMPORTED_LOCATION "${ocv_libdir}/bin/opencv_viz${OCV_VERSION}.dll"
)
the main CMakeLists.txt looks like this:
cmake_minimum_required(VERSION 3.14)
project(cmaketest VERSION 0.1 DESCRIPTION "" LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_Flags "${CMAKE_CXX_FLAGS} -std=c++17")
# include_directories(${CMAKE_INSTALL_PREFIX}/include)
add_subdirectory(deps)
add_executable(${PROJECT_NAME} main.cpp)
target_link_libraries(${PROJECT_NAME} opencv_core opencv_viz)
install(
TARGETS ${PROJECT_NAME}
EXPORT "${PROJECT_NAME}-targets"
LIBRARY DESTINATION lib/
ARCHIVE DESTINATION lib/${CMAKE_PROJECT_NAME}
RUNTIME DESTINATION bin
PUBLIC_HEADER DESTINATION include/${CMAKE_PROJECT_NAME}/${PROJECT_NAME}
)
the main.cpp for completeness:
#include <opencv2/viz.hpp>
int main(){}
but it seems that the include_directories and add_library calls inside deps/CMakeLists.txt do not work on the correct scope as i am getting the following error messages:
error C1083: File (Include) can not be opened: "opencv2/viz.hpp"
if i uncomment the include_directories inside the main CMakeLists.txt then i get a linker error (this is not what i want, included directories should be specified inside deps/CMakeLists.txt):
LNK1181: opencv_core.lib can not be opened
If i just copy the contents of deps/CMakeLists.txt in the main CMakeLists.txt in place of the add_subdirectory call everything works fine.
So, how do i get the include directories and the created targets from the subdirectory in my main CMakeLists?
edit:
call to cmake configure:
cmake.exe -B build -S . -G "Visual Studio 17 2022" -A x64 -T v141 -DCMAKE_INSTALL_PREFIX=D:/test
call to cmake build:
cmake.exe --build build --config Release --target install
Unlike to normal targets, which are global, an IMPORTED target by default is local to the directory where it is created.
For extend visibility of the IMPORTED target, use GLOBAL keyword:
add_library(opencv_core SHARED IMPORTED GLOBAL)
This is written in the documentation for add_library(IMPORTED):
The target name has scope in the directory in which it is created and below, but the GLOBAL option extends visibility.
cmake has couple steps:
configuration
generation (depends on configuration)
build (depends on generation)
test (depends on build)
install (depends on build)
Now problem is that your build step depends on result of install step. This happens here:
set(ocv_libdir ${CMAKE_INSTALL_PREFIX}/${CMAKE_VS_PLATFORM_NAME}/vc15)
and when this variable is used.
This is causing that to be able to complete build step you need success in install step. And cmake will do install step after successful build. So you have dependency cycle.
Instead importing opencv as shared library:
add_library(opencv_viz SHARED IMPORTED)
Link your target with targets created by imported project of opnecv.

CMake ExternalProject does not unpack dependencies

I am building an application which uses tinyxml2 and few other dependencies (namely Irrlicht and IrrKlang) that I provide as .zip files in the Dependency subdirectory of my project:
.
├── CMakeLists.txt
├── Dependencies
│   ├── irrKlang-1.5.0.zip
│   ├── irrKlang-32bit-1.5.0.zip
│   ├── irrKlang-64bit-1.5.0.zip
│   ├── irrlicht-1.8.4.zip
│   └── tinyxml2-master.zip
├── Editor
│   ├── CMakeLists.txt
│   └── Sources
│   └── main.cpp
└── Game
   ├── CMakeLists.txt
   └── Sources
   └── main.cpp
NOTE: for a reference, full sources are available on GitHub, here I cut some corners to make the question shorter.
The top-level CMakeFiles.txt is set up is:
cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
project(shoot-them VERSION 1.0.1 LANGUAGES CXX)
include(ExternalProject)
# few platform-specific variables like MAKE_COMMAND, PLATFORM_ARCH, etc.
# libraries (see below)
add_subdirectory(Game)
add_subdirectory(Editor
Both Irrlicht and IrrKlang come with pre-built libraries for Windows for x86, but not for Windows x64 and not for OSX. Hence I add it as a dependency like this (using the if(NOT IRRLICHT_LIBRARY_PATH) just to separate the code into a block:
if(NOT IRRLICHT_LIBRARY_PATH)
ExternalProject_Add(irrlicht-dep
URL ${CMAKE_CURRENT_LIST_DIR}/Dependencies/irrlicht-1.8.4.zip
PREFIX Dependencies/irrlicht
SOURCE_SUBDIR source/Irrlicht
CONFIGURE_COMMAND ""
BUILD_COMMAND "${MAKE_COMMAND}"
INSTALL_COMMAND ""
)
ExternalProject_Get_Property(irrlicht-dep SOURCE_DIR)
set(IRRLICHT_PATH ${SOURCE_DIR})
add_library(irrlicht SHARED IMPORTED GLOBAL)
set_target_properties(
irrlicht PROPERTIES
IMPORTED_LOCATION ${IRRLICHT_PATH}/lib/${IRRLICHT_PATH_SUFFIX}/Irrlicht${CMAKE_LINK_LIBRARY_SUFFIX}
IMPORTED_IMPLIB ${IRRLICHT_PATH}/lib/${IRRLICHT_PATH_SUFFIX}/Irrlicht${CMAKE_IMPORT_LIBRARY_SUFFIX}
INTERFACE_INCLUDE_DIRECTORIES ${IRRLICHT_PATH}/include
)
endif()
I follow the same principles for IrrKlang. But since tinyxml2 comes as a header and a source file and it comes packed with CMakeLists.txt, I just include it like this:
ExternalProject_Add(tinyxml2-dep
URL ${CMAKE_CURRENT_LIST_DIR}/Dependencies/tinyxml2-master.zip
PREFIX Dependencies/tinyxml2
INSTALL_COMMAND ""
)
ExternalProject_Get_Property(tinyxml2-dep SOURCE_DIR)
set(TINYXML2_PATH ${SOURCE_DIR})
add_subdirectory(${TINYXML2_PATH})
I define both Game and Editor sub-projects as follows:
cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
project(Editor VERSION 1.0.1 LANGUAGES CXX)
set(EXECUTABLE_NAME Editor)
set(SOURCES Sources/main.cpp)
# platform-specific variables
set_target_properties(${EXECUTABLE_NAME} PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
CXX_EXTENSIONS ON
)
add_dependencies(${EXECUTABLE_NAME} tinyxml2 irrlicht)
target_link_libraries(${EXECUTABLE_NAME} PUBLIC ${LIBRARIES} tinyxml2 irrlicht)
if(NOT APPLE)
# Copy libraries' DLLs
add_custom_command(
TARGET ${EXECUTABLE_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:tinyxml2> $<TARGET_FILE_DIR:${EXECUTABLE_NAME}>
)
endif()
# TODO: copy DLLs, not LIBs
add_custom_command(
TARGET ${EXECUTABLE_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:irrlicht> $<TARGET_FILE_DIR:${EXECUTABLE_NAME}>
)
The way I use ExternalProject specifically is because
I wanted to fix the versions I build my project with
I did not want to pollute my system with installing the libraries (hence no INSTALL steps, although I may completely misunderstand that concept)
I do not depend on 3rd party repositories being dead (hence shipping the ZIP files with the sources)
I do not rely on unbelievably outdated (and thus potentially non-working) find modules for CMake
To be fair: I am not aware of any best practices of building projects with CMake, so I might very well be completely wrong about all of the above, so please do correct me.
When I build this project in Visual Studio 2019 on Windows, it works like a charm. But whenever I try building the thing on OSX, I get failures:
none of the dependencies gets even unpacked
(because of p.1) the ${TINYXML2_DIR} is never set
(because of p.2) the tinyxml2 directory could not be found and thus added via add_subdirectory()
(because of p.3) the $<TARGET_FILE:tinyxml2> expression does not evaluate
(as a global consequence) the project does not build
The way I build project is rather simple:
cmake -Bbuild -H. && cmake --build build
What am I doing wrong?
Also, what is the right way to handle 3rd party dependencies with CMake?
I am very well aware that CMake is technically just a makefile (roughly speaking, since it is different for every build toolchain) generator, so my question is more about how do I tell CMake to generate the correct build files for each type of dependency that should be built with my project (pre-built, build from sources with CMake, build from sources with a custom command). I thought ExternalProject is supposed to handle just that, but apparently something went horribly wrong along the way.
I have played with both a solution suggested by #Mizux in their comment and had some success with two different approaches.
1. vcpkg
This is arguably the easier of the two. It requires vcpkg installed.
See this commit for example.
Create a manifest file, vcpkg.json in the project root directory, listing all the dependencies used by the project:
{
"name": "PROJECT_NAME",
"version-string": "0.1.0",
"dependencies": [
"irrlicht",
"tinyxml2"
]
}
You can also use CLI to generate the manifest by using vcpkg install command.
Use the find_package from CMake to link libraries to each target - in the child CMakeLists.txt:
find_package(irrlicht CONFIG REQUIRED)
find_package(tinyxml2 CONFIG REQUIRED)
target_link_libraries(${EXECUTABLE_NAME} PUBLIC ${LIBRARIES} tinyxml2::tinyxml2 Irrlicht)
Important: when configuring the project, pass the path to the vcpkg CMake module:
cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=[vcpkg root]/scripts/buildsystems/vcpkg.cmake
2. FetchContent + ExternalProject
Since my project depends on both a library that ships with CMakeLists.txt and a library that does not, this is a nice example.
See this commit for example.
First, as #Mizux mentioned, FetchContent works at configure time - it downloads and unpacks the dependency when you configure the project (call cmake -S . -B build). Then, since irrlicht does not ship with CMakeLists.txt, you either use ExternalProject_Add to build it with custom command (make in my case) or add it as a sub-directory to the project.
FetchContent part:
include(FetchContent)
FetchContent_Declare(irrlicht
URL ${CMAKE_CURRENT_LIST_DIR}/Dependencies/irrlicht-1.8.4.zip
)
FetchContent_GetProperties(irrlicht)
if(NOT irrlicht_POPULATED)
FetchContent_Populate(irrlicht)
endif()
set(IRRLICHT_PATH ${irrlicht_SOURCE_DIR})
ExternalProject part:
include(ExternalProject)
if(NOT WIN32)
ExternalProject_Add(irrlicht-dep
SOURCE_DIR "${IRRLICHT_PATH}/source/Irrlicht"
BUILD_IN_SOURCE true
CONFIGURE_COMMAND ""
BUILD_COMMAND "${MAKE_COMMAND}"
)
endif()
add_library(irrlicht SHARED IMPORTED GLOBAL)
set_target_properties(
irrlicht PROPERTIES
IMPORTED_LOCATION ${IRRLICHT_PATH}/lib/${IRRLICHT_PATH_SUFFIX}/Irrlicht${CMAKE_LINK_LIBRARY_SUFFIX}
IMPORTED_IMPLIB ${IRRLICHT_PATH}/lib/${IRRLICHT_PATH_SUFFIX}/Irrlicht${CMAKE_IMPORT_LIBRARY_SUFFIX}
INTERFACE_INCLUDE_DIRECTORIES ${IRRLICHT_PATH}/include
)
Note the important bits in the ExternalProject configuration:
irrlicht_SOURCE_DIR variable is populated by FetchContent
SOURCE_DIR "${irrlicht_SOURCE_DIR}/source/Irrlicht" - tells ExternalProject where the unpacked sources are; if this directory is not empty (which it should be not, since FetchContent should have populated it at project configuration time), ExternalProject will skip the download phase
BUILD_IN_SOURCE true - builds from within the source directory
CONFIGURE_COMMAND "" - skips the dependency configuration phase, so not cmake will be executed for the dependency
BUILD_COMMAND "make" - uses a specific command to build the dependency from sources
if(NOT WIN32) - only uses ExternalProject to build the dependency; since Irrlicht comes with pre-built libraries for Win32, this should build the thing for the other platforms (including Win64)

how to perform cmake::find_package at build stage only

I'm trying to include to make cmake-based project the library vlc-qt, as an external project from github repository.
The project uses recommended way :
FIND_PACKAGE(VLCQt REQUIRED COMPONENTS Widgets)
cmake performs this command at configuration stage so you see that library is not build on that moment.
what is a right way to avoid this?
Put your own project and the VLCQt project into external projects with ExternalProject_Add and create a top-level CMakeLists.txt file to build them one after another.
Your directory structure will look something like this:
ProjectRoot/
|-- CMakeLists.txt
|-- MyProject/
| |-- sources/
| `-- CMakeLists.txt
`-- modules/
|-- MyProject.cmake
`-- ExternalVLCQt.cmake
The ProjectRoot/modules/ExternalVLCQt.cmake may look like:
set(VLCQT_ROOT ${EXT_INSTALL_PATH}/vlcqt CACHE INTERNAL "")
ExternalProject_Add(vlcqt
URL "http://url.of.source/release.0.1.tar.gz"
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${VLCQT_ROOT}
INSTALL_COMMAND make install
)
list(APPEND GLOBAL_THIRDPARTY_LIB_ARGS "-DVLCQT_ROOT:PATH=${VLCQT_ROOT}")
The ProjectRoot/modules/MyProject.cmake may look like:
ExternalProject_Add(my_project
DEPENDS vlcqt
SOURCE_DIR ${CMAKE_SOURCE_DIR}/MyProject
CMAKE_ARGS
${GLOBAL_THIRDPARTY_LIB_ARGS}
-DCMAKE_INSTALL_PREFIX=${EXT_INSTALL_PATH}/my_project
BUILD_COMMAND make
)
Then finally the ProjectRoot/CMakeLists.txt should contain the following:
cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)
project(MyProject VERSION 0.1)
set(CMAKE_MODULE_PATH
"${CMAKE_CURRENT_SOURCE_DIR}/modules"
${CMAKE_MODULE_PATH}
)
include(ExternalProject)
set_directory_properties(PROPERTIES EP_BASE ${CMAKE_BINARY_DIR}/ExtProjects)
get_directory_property(EXT_BASE_PATH EP_BASE)
set(EXT_INSTALL_PATH ${EXT_BASE_PATH}/Install)
include(ExternalVLCQt)
include(MyProject)
install(DIRECTORY ${EXT_INSTALL_PATH}/my_project DESTINATION .)
You can read more about this pattern here. By this pattern the ProjectRoot/MyProject/CMakeLists.txt will be configured at the build time of the top-level CMakeLists.txt after the vlcqt is built. Therefore the find_package will find the VLCQt package.
Note: In my example the VLCQT_ROOT will be received by the CMakeLists.txt of MyProject where the find_package command is used. This variable is a hint for the find_package command and for different packages this may vary. Every CMake modules used by the find_package has its own varaible requirements.

Linking against an ExternalProject_add dependency in CMAKE

I'm getting this ninja build error below while running Ninja. My CMAKE build command iscmake -G"Ninja" -DCMAKE_BUILD_TYPE=Release.
ninja: error: 'ext_deps/api/src/ext_api/build/src/lib/libapi.a', needed by 'Project', missing and no known rule to make it
Let's say my project consists of an API (downloaded via CMAKE from GitHub) and the implementation (the Project).
The layout would look like:
Project/
-- build/
-- cmake/modules
----- ExternalDep.cmake
----- FindAPI.cmake
-- CMakeLists.txt
-- src/
---- CMakeLists.txt
-- include/
Let's say that in the top-level CMakeLists.txt I do the usual business of setting build settings, CXX flags, et cetera, and then I call include(ExternalDep), which checks if the "API" library is in the user's system (if not it is downloaded via CMAKE).
In src/CMakeLists.txt I try to link against the API library using a
target_link_libraries(${PROJECT_NAME} PRIVATE ${API_LIBRARY})
The first issue I'm having is that before the "API" library can even be downloaded and built, I get the ninja build error I posted above. I'm positive the ExternalDep.cmake is included before I try to add the Project executable and link against the "API" library.
Here's a simplified version of ExternalDep.cmake:
set(EXT_DEPS_PREFIX "ext_deps")
ExternalProject_Add(ext_lib
GIT_REPOSITORY "https://github.com/fake/api.git"
GIT_TAG "master"
PREFIX "${CMAKE_BINARY_DIR}/${EXT_DEPS_PREFIX}/api"
TMP_DIR "${CMAKE_BINARY_DIR}/${EXT_DEPS_PREFIX}/api-tmp"
STAMP_DIR "${CMAKE_BINARY_DIR}/${EXT_DEPS_PREFIX}/api-stamp"
CMAKE_ARGS -DCMAKE_BUILD_TYPE=Release
SOURCE_DIR "${CMAKE_BINARY_DIR}/${EXT_DEPS_PREFIX}/api/ext_api"
BINARY_DIR "${CMAKE_BINARY_DIR}/${EXT_DEPS_PREFIX}/api/ext_api-build"
BUILD_ALWAYS true
TEST_COMMAND "")
add_dependencies(ext_projects ext_api)
set(API_LIBRARY "${CMAKE_BINARY_DIR}/${EXT_DEPS_PREFIX}/api/ext_api-build/src/lib/libapi.a")
I ran into the same issue with Ninja while it worked fine with Unix Makefiles, and I managed to get it to work with Ninja by adding a BUILD_BYPRODUCTS line to my ExternalProject_Add block. Example:
ExternalProject_Add(SDL2_PROJECT
PREFIX 3rdparty
URL https://www.libsdl.org/release/SDL2-2.0.5.tar.gz
URL_MD5 d4055424d556b4a908aa76fad63abd3c
CONFIGURE_COMMAND <SOURCE_DIR>/configure ${SDL2_configure_args} --prefix=<INSTALL_DIR> --disable-shared
INSTALL_COMMAND make install -j9
BUILD_BYPRODUCTS <INSTALL_DIR>/lib/libSDL2.a
)
ExternalProject_Get_Property(SDL2_PROJECT INSTALL_DIR)
set(SDL2_INSTALL_DIR ${INSTALL_DIR})
add_library(SDL2_LIBRARY STATIC IMPORTED GLOBAL)
set_property(TARGET SDL2_LIBRARY PROPERTY IMPORTED_LOCATION ${SDL2_INSTALL_DIR}/lib/libSDL2.a)
add_dependencies(SDL2_LIBRARY SDL2_PROJECT)
I was able to solve this by generating Unix Makefiles instead of Ninja. I'm still not exactly sure if this was the singular issue but it was definitely one of the issues.

CMakeLists configuration to link two C++ projects

I have the following situation: a Project A depends on a Project B, but both are built at the same time. Project A has to include the includes of project B and it needs also to link its libraries. Up to now I've tried this way:
ADD_SUBDIRECTORY(${CMAKE_SOURCE_DIR}/other_project other_project)
and then:
INCLUDE_DIRECTORIES(includ ${CMAKE_SOURCE_DIR}/other_project/include})
LIST(APPEND LINK_LIBS other_project)
in the CMakeLists.txt of Project A
but it doesn't seem to work, the compiler also gives me error when including the headers of Project B saying that they do not exist.
What is the right way to add dependencies in A? How should the CMakeLists.txt look like?
EDIT:
as suggested in the comments, this question was addressed in this, however I'd like to see an example of how to use it in a CMakeList.txt file.
The following is a simple example which builds zlib and then builds libxml2 which depends on the zlib we build. One thing to note, I quickly pulled this example from stuff I've done before. The libxml2 example uses make, thus will only actually build on a system which has it, e.g. Linux, Mac ...
Here is the proposed directory structure for this example ...
src/
-- CMakeLists.txt
CMake/
---- External_ZLib.cmake
---- External_libxml2.cmake
Dowloads/ ( no need to create this directory, CMake will do it for you )
build/ ( Out of source build to prevent littering source tree )
Files:
CMakeLists.txt
# Any version that supports ExternalProject should do
cmake_minimum_required( VERSION 3.1)
project(test_ext_proj)
set(test_ext_proj_CMAKE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/CMake")
set(CMAKE_MODULE_PATH ${test_ext_proj_CMAKE_DIR} ${CMAKE_MODULE_PATH})
set(test_ext_proj_BUILD_INSTALL_PREFIX ${CMAKE_CURRENT_BINARY_DIR}/install)
set(test_ext_proj_DOWNLOAD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/Downloads CACHE PATH "Directory to store downloaded tarballs.")
include(ExternalProject)
include(External_ZLib)
include(External_libxml2)
External_ZLib.cmake:
set(ZLib_version 1.2.8)
set(ZLib_url "http://zlib.net/zlib-${ZLib_version}.tar.gz")
set(ZLib_md5 "44d667c142d7cda120332623eab69f40")
ExternalProject_Add(ZLib
URL ${ZLib_url}
URL_MD5 ${ZLib_md5}
PREFIX ${vision-tpl_BUILD_PREFIX}
DOWNLOAD_DIR ${test_ext_proj_DOWNLOAD_DIR}
INSTALL_DIR ${test_ext_proj_BUILD_INSTALL_PREFIX}
CMAKE_GENERATOR ${gen}
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX:PATH=${test_ext_proj_BUILD_INSTALL_PREFIX}
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
-DCMAKE_C_FLAGS=${CMAKE_C_FLAGS}
)
#This variable is required so other packages can find it.
set(ZLIB_ROOT ${test_ext_proj_BUILD_INSTALL_PREFIX} CACHE PATH "" FORCE)
External_libxml2.cmake:
set(libxml2_release "2.9")
set(libxml2_patch_version 0)
set(libxml2_url "ftp://xmlsoft.org/libxml2/libxml2-sources-${libxml2_release}.${libxml2_patch_version}.tar.gz")
set(libxml2_md5 "7da7af8f62e111497d5a2b61d01bd811")
#We need to state that we're dependent on ZLib so build order is correct
set(_XML2_DEPENDS ZLib)
#This build requires make, ensure we have it, or error out.
if(CMAKE_GENERATOR MATCHES ".*Makefiles")
set(MAKE_EXECUTABLE "$(MAKE)")
else()
find_program(MAKE_EXECUTABLE make)
if(NOT MAKE_EXECUTABLE)
message(FATAL_ERROR "Could not find 'make', required to build libxml2.")
endif()
endif()
ExternalProject_Add(libxml2
DEPENDS ${_XML2_DEPENDS}
URL ${libxml2_url}
URL_MD5 ${libxml2_md5}
PREFIX ${test_ext_proj_BUILD_PREFIX}
DOWNLOAD_DIR ${test_ext_proj_DOWNLOAD_DIR}
INSTALL_DIR ${test_ext_proj_BUILD_INSTALL_PREFIX}
BUILD_IN_SOURCE 1
CONFIGURE_COMMAND ./configure
--prefix=${test_ext_proj_BUILD_INSTALL_PREFIX}
--with-zlib=${ZLIB_ROOT}
BUILD_COMMAND ${MAKE_EXECUTABLE}
INSTALL_COMMAND ${MAKE_EXECUTABLE} install
)