3

My C++ project doggo has a doggo/external/ directory for third-party code. Currently it contains gtest and a CMakeLists.txt:

# Google gtest for unit testing.
add_subdirectory(gtest)
message("gtest include dir: ${gtest_SOURCE_DIR}")
include_directories(${gtest_SOURCE_DIR})

My top-level doggo/CMakeLists.txt contains the line add_subdirectory(external) to find and build the third-party libraries. Everything works like a charm -- I can include gtest with #include <gtest/gtest.h>. Now I'd like to add the randomkit C library to doggo/external/, as is done here: randomkit from numpy.

How can I get randomkit to build in my doggo/external/ dir? What should the doggo/external/CMakeLists.txt look like?

I should then be able to include the C headers for use in my x.cpp files by including the headers inside an extern "C" { ... } block (details here).

UPDATE: How do I install randomkit here? I've included a CMakeLists.txt entry like that above but for randomkit, and the directory looks like,

external
├── CMakeLists.txt
├── gtest
│   └── ...
└── randomkit
    ├── CMakeLists.txt
    ├── distributions.c
    ├── distributions.h
    ├── randomkit.c
    └── randomkit.h

and the randomkit/CMakeLists.txt:

project(randomkit)
file(GLOB SOURCES "*.c")
add_library(randomkit SHARED ${SOURCES})
INSTALL(
    DIRECTORY ${CMAKE_SOURCE_DIR}/
    DESTINATION "/usr/local/"
    #DESTINATION ""
    FILES_MATCHING PATTERN "*.h*")

(second DESTINATION commented out to show I tried that as well)

Yet when I run the build steps for my top-level project doggo I get an error trying to #include <randomkit/distributions.h>:

doggo/src/random_fooz.cpp:10:37: fatal error: randomkit/distributions.h: No such file or directory

UPDATE 2: doggo/CMakeLists.txt:

project(doggo)
# Find and build third-party libraries
add_subdirectory(external)
# Add source dirs to the search path so cmake can find headers
include_directories(${CMAKE_SOURCE_DIR}/include/)
# Collect source files and build
file(GLOB_RECURSE doggo_srcs ${CMAKE_SOURCE_DIR}/src/*.cpp)
add_library(doggo ${doggo_srcs})
# Setup executables
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/bin/)
add_subdirectory(exec)
# Tests
add_subdirectory(test)
BoltzmannBrain
  • 5,082
  • 11
  • 46
  • 79
  • A couple of questions: (1) Do you have the `gtest` code in a sub-sub-directory `doggo/external/gtest` and you're adding `doggo/external/randomkit` as another such directory? (2) Is your question primarily about `cmake` directory structure and `CMakeLists.txt` file content, or about the mechanics of using C with C++? The answer to Q1 is mostly about practicalities; having two or more independent projects in a single directory would be anarchy. If the answer to Q2 is that you're worried about the mechanics of CMake, then the duplicate is not helpful (it is incorrect). – Jonathan Leffler Apr 27 '18 at 15:08
  • (1) Yes. (2) The former. Having several third-parties in an `external/` subdirectory is not uncommon, although I agree it should be limited to < three and only simple libraries, and probably avoided for a project that is to be scaled and platform-ind. So `doggo` is a personal project where I much prefer this structure w/ `external/`. I can additionally get the C++ library [matplotpp](https://code.google.com/archive/p/matplotpp/) to build just as easily as gtest here. It seems a small, simple C library like randomkit should be easy too, but it needs its own CMakeLists.txt. – BoltzmannBrain Apr 27 '18 at 15:30
  • It is recommended to incorporate gtest using ExternalProject cf https://github.com/google/googletest/tree/master/googletest#incorporating-into-an-existing-cmake-project – Mizux Apr 27 '18 at 16:15
  • @JonathanLeffler I've updated the question to include a CMakeLists.txt for the third-party `randomkit` project. Please see if you can help! – BoltzmannBrain Apr 30 '18 at 22:10
  • I can spell `cmake` — I've not really learned it. I'm sorry, I can't reliably help you. – Jonathan Leffler May 01 '18 at 00:06

1 Answers1

0

In the randomkit/CMakeLists.txt write:

project(randomkit)
file(GLOB SOURCES "*.c")
add_library(randomkit SHARED ${SOURCES})
target_include_directories(randomkit PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
INSTALL(
    DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/
    DESTINATION "include"            # this a the subdirectory with ${CMAKE_INSTALL_PREFIX}
    FILES_MATCHING PATTERN "*.h*")

In the main CMakeLists.txt, you do:

add_library(doggo ${doggo_srcs})
target_link_libraries(doggo PUBLIC randomkit)
target_include_directories(doggo PUBLIC ${CMAKE_SOURCE_DIR}/include/)

Don’t use include_directories.

Now, because the randomkit target has the PUBLIC property with the right include directories, those include directories will be automatically used when building the doggo library. And again, because the doggo library has include directories and libraries in its public interface, executables that you link to doggo will automatically be linked to these libraries, and find their include files.

Note that the INSTALL command in randomkit/CMakeLists.txt is only executed when you actually run the install target. When building, the include files must be found in the source tree.

Cris Luengo
  • 55,762
  • 10
  • 62
  • 120
  • 1. `doggo/CMakeLists.txt` shouldn't have to link to every third-party library in `doggo/external`, just use `add_subdirectory(external)` to get to `doggo/external/CMakeLists.txt`. This works for gtest -- gtest's CMakeLists.txt doesn't know about doggo's, and vice-versa. – BoltzmannBrain Apr 30 '18 at 23:45
  • 2. `doggo/CMakeLists.txt` sets up executables w/ `set(EXECUTABLE_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/bin/)` and `add_subdirectory(exec)`. Then there's an `exec/CMakeLists.txt` that finds the executables and calls `add_executable(${executable} ${local_src})` then `target_link_libraries(${executable} doggo ${doggo_LIBRARIES})`. For now I actually don't have any executables to run, just some unit tests in `doggo/test/`. – BoltzmannBrain Apr 30 '18 at 23:45
  • Note that you can do the same with header-only libraries: you can make a fake target and set its include path, do when you link to this target you get the right include directories set. – Cris Luengo Apr 30 '18 at 23:58
  • See Update2 above with main CMakeLists.txt. – BoltzmannBrain May 01 '18 at 01:03
  • Still fails w/ `fatal error: randomkit/distributions.h: No such file or directory` – BoltzmannBrain May 01 '18 at 01:25
  • If you want to do `#include “randomkit/distributions.h”`, then you need to add the `external` directory to your include path, not the `randomkit` directory as I suggested. Either change the `target_include_directories` for randomkit, or change how you include the header file in your code. – Cris Luengo May 01 '18 at 01:37
  • Same error: `fatal error: external/randomkit/distributions.h: No such file or directory` – BoltzmannBrain May 01 '18 at 01:44
  • @BoltzmannBrain: I think I need more info to understand why that wouldn’t work. It certainly has worked for me in many projects. Try adding `target_include_directories(doggo PUBLIC external)` to the main CMakeLists.txt file. – Cris Luengo May 01 '18 at 01:50