CMake: How to build external projects and include

2019-01-07 04:08发布

问题:

I have a Project A that exports a static library as a target:

install(TARGETS alib DESTINATION lib EXPORT project_a-targets)
install(EXPORT project_a-targets DESTINATION lib/alib)

Now I want to use Project A as an external project from Project B and include its built targets:

ExternalProject_Add(project_a
  URL ...project_a.tar.gz
  PREFIX ${CMAKE_CURRENT_BINARY_DIR}/project_a
  CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
)

include(${CMAKE_CURRENT_BINARY_DIR}/lib/project_a/project_a-targets.cmake)

The problem is that the include file does not exist yet when CMakeLists of Project B is run.

Is there a way to make the include dependent on the external project being built?

回答1:

I think you're mixing up two different paradigms here.

As you noted, the highly flexible ExternalProject module runs its commands at build time, so you can't make direct use of Project A's import file since it's only created once Project A has been installed.

If you want to include Project A's import file, you'll have to install Project A manually before invoking Project B's CMakeLists.txt - just like any other third-party dependency added this way or via find_file / find_library / find_package.

If you want to make use of ExternalProject_Add, you'll need to add something like the following to your CMakeLists.txt:

ExternalProject_Add(project_a
  URL ...project_a.tar.gz
  PREFIX ${CMAKE_CURRENT_BINARY_DIR}/project_a
  CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
)

include(${CMAKE_CURRENT_BINARY_DIR}/lib/project_a/project_a-targets.cmake)

ExternalProject_Get_Property(project_a install_dir)
include_directories(${install_dir}/include)

add_dependencies(project_b_exe project_a)
target_link_libraries(project_b_exe ${install_dir}/lib/alib.lib)


回答2:

This post has a reasonable answer:

CMakeLists.txt.in:

cmake_minimum_required(VERSION 2.8.2)

project(googletest-download NONE)

include(ExternalProject)
ExternalProject_Add(googletest
  GIT_REPOSITORY    https://github.com/google/googletest.git
  GIT_TAG           master
  SOURCE_DIR        "${CMAKE_BINARY_DIR}/googletest-src"
  BINARY_DIR        "${CMAKE_BINARY_DIR}/googletest-build"
  CONFIGURE_COMMAND ""
  BUILD_COMMAND     ""
  INSTALL_COMMAND   ""
  TEST_COMMAND      ""
)

CMakeLists.txt:

# Download and unpack googletest at configure time
configure_file(CMakeLists.txt.in
               googletest-download/CMakeLists.txt)
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
  WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download )
execute_process(COMMAND ${CMAKE_COMMAND} --build .
  WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download )

# Prevent GoogleTest from overriding our compiler/linker options
# when building with Visual Studio
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)

# Add googletest directly to our build. This adds
# the following targets: gtest, gtest_main, gmock
# and gmock_main
add_subdirectory(${CMAKE_BINARY_DIR}/googletest-src
                 ${CMAKE_BINARY_DIR}/googletest-build)

# The gtest/gmock targets carry header search path
# dependencies automatically when using CMake 2.8.11 or
# later. Otherwise we have to add them here ourselves.
if (CMAKE_VERSION VERSION_LESS 2.8.11)
  include_directories("${gtest_SOURCE_DIR}/include"
                      "${gmock_SOURCE_DIR}/include")
endif()

# Now simply link your own targets against gtest, gmock,
# etc. as appropriate

However it does seem quite hacky. I'd like to propose an alternative solution - use Git submodules.

cd MyProject/dependencies/gtest
git submodule add https://github.com/google/googletest.git
cd googletest
git checkout release-1.8.0
cd ../../..
git add *
git commit -m "Add googletest"

Then in MyProject/dependencies/gtest/CMakeList.txt you can do something like:

cmake_minimum_required(VERSION 3.3)

if(TARGET gtest) # To avoid diamond dependencies; may not be necessary depending on you project.
    return()
endif()

add_subdirectory("googletest")

I haven't tried this extensively yet but it seems cleaner.

Edit: There is a downside to this approach: The subdirectory might run install() commands that you don't want. This post has an approach to disable them but it was buggy and didn't work for me.

Edit 2: If you use add_subdirectory("googletest" EXCLUDE_FROM_ALL) it seems means the install() commands in the subdirectory aren't used by default.



回答3:

You can also force the build of the dependent target in a secondary make process

See my answer on a related topic.



回答4:

What you could try and do is use cmake's export command within your project_a. It works a little bit different then using install command with the EXPORT option in that it produces your a project_a-targets.cmake file when running cmake. The generated import targets within the project_a-targets.cmake file point to initially the none existing library files within your project's binary directory that will be generated only after your run the build command.

To better understand that I'm talking about, simply create a small cmake project that creates a simple library followed by the export command (the code below hasn't been tested):

add_library (project_a lib.cpp)
export (
  TARGETS 
    project_a
  FILE
    project_a-targets.cmake
)

After running the cmake command on your simple example, you should be able to find the project_a-targets.cmake inside your binary directory (or under one of its child folders). Inspecting the file, you might notice that it currently points to a none existing library file. Only after running the build command will the library be there.

So going back to your problem, you will need to update the project-a's CMakeLists.txt to include the export command. Then you need to make sure that after the ExternalProject_Add is processed, it calls the configuration step, which will produce the project_a-targets.cmake, then you can invoke the include(.../project_a-targets.cmake) which should work. Finally you will need to add a dependency between project_b and project_a so that it forces project_a to be built before attempting to build project_b.