StoneThrow
StoneThrow

Reputation: 6275

How can I create multiple cmake targets with one dependent on the output of the other?

I'm learning cmake, coming from a make/makefile background.

I have the following directory structure:

.
├── CMakeLists.txt
├── src
│   ├── CMakeLists.txt
│   ├── foo.cpp
│   └── foo.h
└── test
    ├── CMakeLists.txt
    └── unit
        ├── CMakeLists.txt
        └── unit_test.cpp

3 directories, 7 files

Intention/goal:

  1. I want the content of the ./src/ directory to be compiled into libfoo.a, and I want foo.h and libfoo.a installed to /tmp/foo/include/foo/ and /tmp/foo/lib/, respectively.
  2. I want unit_test.cpp to #include <foo/foo.h> and link with -lfoo.

Question:
In my top-level CMakeLists.txt, I have add_subdirectory statements for both src and test directories. When I run make install (which uses the cmake-generated Makefiles), it looks like it's descending into the test/ directory before the install statements from src/CMakeLists.txt have been executed.
How can I achieve the stated goal?
I think what I am looking for is a way to create another make target...so that I can run "make install" first, which installs the files at their respective destinations, then - as a separate step - run another make target, which compiles the unit test files and makes use of libfoo.a and foo.h at their installed locations.

Problem:
As-is (files below), when I run make install, compilation of unit_test.cpp fails because the file foo/foo.h isn't found because it hasn't been "installed" yet:

cd /home/user/projects/scratch/cmake/build/test/unit && /usr/bin/c++    -I/tmp/foo/include   -o CMakeFiles/unit_test.dir/unit_test.cpp.o -c /home/user/projects/scratch/cmake/test/unit/unit_test.cpp
/home/user/projects/scratch/cmake/test/unit/unit_test.cpp:1:21: fatal error: foo/foo.h: No such file or directory
 #include <foo/foo.h>
                     ^
compilation terminated.

What I have tried:
If I comment-out the add_subdirectory( test ) in the top-level CMakeLists.txt, then foo.h and libfoo.a are installed in the desired locations per goal #1.


The Files:

Apologies - I know this is an awkward way to distribute my project content, but I'm not aware of any easier/better way to share this. I've tried, though, to keep file content minimal.

# <root>/CMakeLists.txt
cmake_minimum_required(VERSION 3.7)
set(CMAKE_INSTALL_PREFIX /tmp/foo/)
add_subdirectory(src)

add_subdirectory(test) # If I comment this out, then "make install" succeeds
# <root>/src/CMakeLists.txt
add_library( foo foo.cpp )
install(TARGETS foo DESTINATION lib)
install(FILES foo.h DESTINATION include/foo)
// foo.cpp
#include "foo.h"
#include <iostream>

void foo() {
  std::cout << __FUNCTION__ << std::endl;
}
// foo.h
#ifndef FOO_H
#define FOO_H

void foo();

#endif
# <root>/test/CMakeLists.txt
add_subdirectory(unit)
# <root>/test/unit/CMakeLists.txt
add_executable( unit_test unit_test.cpp )
target_include_directories( unit_test PUBLIC /tmp/foo/include )
target_link_libraries( unit_test PUBLIC /tmp/foo/lib )
// unit_test.cpp
#include <foo/foo.h>

int main( int argc, char* argv[] ) {
  foo();
  return 0;
}

Upvotes: 0

Views: 490

Answers (1)

KamilCuk
KamilCuk

Reputation: 141190

Don't install it. Just compile it as you want as it is and create unit tests as you want. Use target_link_libraries to link between cmake targets. With the following directory structure:

.
├── CMakeLists.txt
├── src
│   ├── CMakeLists.txt
│   └── foo
|       └── foo.cpp
|       └── foo.h
└── test
    ├── CMakeLists.txt
    └── unit
        └── unit_test.cpp

# CMakeLists.txt
cmake_minimum_required(VERSION 3.11)
include(CTest) # From memory, not sure
add_subdirectory(src)
if (BUILD_TESTING)
    add_subdirectory(unit)
endif()

# src/CMakeLists.txt
add_library(foo foo/foo.cpp foo/foo.h)
target_include_directories(foo PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

# unit/CMakeLists.txt
add_executable(unit_test unit/unit_test.cpp)
target_link_libraries(unit_test PUBLIC foo)
add_test(NAME unit_test COMMAND unit_test)

Then configure&build&unit-test with:

cd the_project
cmake -S. -B_build
cmake --build _build
( cd _build && ctest )

Then if you want to let users install foo, add the whole install( stuff to src/CMakeLists.txt. There's little need to install when unit testing - cmake will manage dependencies and linking by itself. Installing is only used when you really want to install the stuff - I do not see the need to install anything to unit_test (unless you want to test the installation itself).

Upvotes: 1

Related Questions