95

My code in a C++ project is organised as follows

  • I have several .cpp and .h files which contains my classes
  • I have several .cxx files which have to be compiled against the .cpp files and some external libraries.

Now, each of the .cxx files have a main() method, so I need to add a different executable for each of these files having the same name as the file.

Also, these .cxx files might not get linked to the same external libraries.

I want to write this build in CMake, in which I am kind of a newbie, how do I go about this?

ssb
  • 7,422
  • 10
  • 36
  • 61

3 Answers3

131

My suggestion is to tackle this in two phases:

  1. Build a library from the .cpp and .h files, using add_library
  2. Iterate through all your .cxx files and create an executable from each, using add_executable and foreach

Build the library

This could be something as simple as

file( GLOB LIB_SOURCES lib/*.cpp )
file( GLOB LIB_HEADERS lib/*.h )
add_library( YourLib ${LIB_SOURCES} ${LIB_HEADERS} )

Build all the executables

Simply loop over all the .cpp files and create separate executables.

# If necessary, use the RELATIVE flag, otherwise each source file may be listed 
# with full pathname. RELATIVE may makes it easier to extract an executable name
# automatically.
# file( GLOB APP_SOURCES RELATIVE app/*.cxx )
file( GLOB APP_SOURCES app/*.cxx )
foreach( testsourcefile ${APP_SOURCES} )
    # Cut off the file extension and directory path
    get_filename_component( testname ${testsourcefile} NAME_WE )
    add_executable( ${testname} ${testsourcefile} )
    # Make sure YourLib is linked to each app
    target_link_libraries( ${testname} YourLib )
endforeach( testsourcefile ${APP_SOURCES} )

Some warnings:

  • file( GLOB ) is usually not recommended, because CMake will not automatically rebuild if a new file is added. I used it here, because I do not know your sourcefiles.
  • In some situations, source-files may be found with a full pathname. If necessary, use the RELATIVE flag for file(GLOB ...).
  • Manually setting the source-files requires a change to CMakeLists.txt, which triggers a rebuild. See this question for the (dis-)advantages of globbing.

Concerning "general" CMake info, I advise you to read some of the broad "CMake Overview" questions already asked here on stackoverflow. E.g.:

qz-
  • 674
  • 1
  • 4
  • 14
André
  • 18,348
  • 6
  • 60
  • 74
  • +1 nice answer. Will the executables all be built into the same top-level `build/` directory, or will this respect the directory structure of the underlying source tree? see also [this question](http://stackoverflow.com/q/16857517/819272) – TemplateRex Jun 02 '13 at 11:00
  • I haven't tested it, but assume that it will produce executables in a directory structure similar to the source-dir. Shameless plug: take a look at http://stackoverflow.com/questions/13556885/ on how to modify the output-paths... – André Jun 03 '13 at 07:11
  • 5
    If you are using a newer cmake (3.0.2 is the earliest I found), you can get the filename by using [`get_filename_component(testname ${testsourcefile} NAME_WE)`](https://cmake.org/cmake/help/latest/command/get_filename_component.html) instead of using `string(REPLACE...`. The advantage is this will also remove the rest of the path, which is handy if you're using `EXECUTABLE_OUTPUT_PATH` to set where binaries should go. – Alex Nov 21 '18 at 06:12
  • @André what is the alternative to replace the `file( GLOB APP_SOURCES app/*.cxx )` – ywiyogo Feb 28 '20 at 12:48
  • 1
    I had to modify quite a bit before using it. My configuration is as follows: ```file( GLOB APP_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp ) foreach( sourcefile ${APP_SOURCES} ) file(RELATIVE_PATH filename ${CMAKE_CURRENT_SOURCE_DIR} ${sourcefile}) string( REPLACE ".cpp" "" file ${filename} ) add_executable( ${file} ${sourcefile} ) target_link_libraries( ${file} ndn-cxx boost_system ) endforeach( sourcefile ${APP_SOURCES} )``` – rafee May 07 '20 at 05:19
  • This answer encourage file(GLOB which is bad practice, and isn't needed. –  Dec 01 '21 at 14:09
  • @buildSystemPerson: Have you read the first bullet in "Some Warnings" in this answer? – André Dec 01 '21 at 15:42
  • Yes. But your answer doesn't show to do this without glob either. And considering this is a beginner level question I think this question does more harm than good. –  Dec 01 '21 at 23:07
  • I think this *answer does more harm than good. –  Dec 01 '21 at 23:19
7

I find myself in a similar situation when organizing an OpenGL project with multiple sample files where each of these files contain a main method.

The settings below will generate a separate executable per c/cpp file as well as copying required dependencies to the target bin folder.

Folder Structure

my-project
│── ch01_01.c
│── ch02_01.cpp
│── CMakeLists.txt
│── Resources
│   │── Libraries
│   │   │── glew
│   │   │   │── bin
│   │   │   │── include
│   │   │   │── lib
│   │   │── glfw    
│   │   │   │── include 
│   │   │   │── lib 

CMakeLists.txt

cmake_minimum_required (VERSION 3.9)

project ("my-project")

include_directories(Resources/Libraries/glew/include
                    Resources/Libraries/glfw/include)

link_directories(Resources/Libraries/glew/lib
                 Resources/Libraries/glfw/lib)

link_libraries(opengl32.lib
               glew32.lib
               glfw3.lib)

set(CMAKE_EXE_LINKER_FLAGS "/NODEFAULTLIB:MSVCRT")

file(GLOB SOURCE_FILES *.c *.cpp)

foreach(SOURCE_PATH ${SOURCE_FILES})

    get_filename_component(EXECUTABLE_NAME ${SOURCE_PATH} NAME_WE)

    add_executable(${EXECUTABLE_NAME} ${SOURCE_PATH})

    # Copy required DLLs to the target folder
    add_custom_command(TARGET ${EXECUTABLE_NAME} POST_BUILD
                       COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_SOURCE_DIR}/Resources/Libraries/glew/bin/glew32.dll" 
                                                                     "${CMAKE_BINARY_DIR}/glew32.dll")

endforeach(SOURCE_PATH ${SOURCE_FILES})

Optional Steps

In Visual Studio

  • Open the project with 'Open a local Folder' option in the Start Window

  • When adding a new file you may either:

    • Cancel the dialog asking to automatically add_executable to CMakeLists.txt
    • Disable this behavior by unchecking 'Enable automatic CMake script modification for file operations from folder view' in Tools > Options > CMake

As newly added files are not picked up automatically as CMakeLists.txt is never changed, simply regenerate the cache like so:

  • Project > CMake Cache (x64-Debug) > Delete Cache
  • Project > Generate Cache for my-project

Now you may simply right click a given c/cpp file and Set as Startup Item to be able to debug it with F5.

Environment

  • cmake version 3.18.20081302-MSVC_2
  • Microsoft Visual Studio Community 2019 Version 16.8.3

Starter Template

I put together this starter template on GitHub in case you are interested.

rbento
  • 9,919
  • 3
  • 61
  • 61
1

This CMakeLists.txt works for my OpenCV project
assuming *.cpp files are in the same directory as CMakeLists.txt

cmake_minimum_required(VERSION 3.5)

project(opencv LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(OpenCV REQUIRED)
include_directories( ${OpenCV_INCLUDE_DIRS} )

file( GLOB APP_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp )
foreach( sourcefile ${APP_SOURCES} )
    file(RELATIVE_PATH filename ${CMAKE_CURRENT_SOURCE_DIR} ${sourcefile})
    string( REPLACE ".cpp" "" file ${filename} )
    add_executable( ${file} ${sourcefile} )
    target_link_libraries( ${file} ${OpenCV_LIBS} )
endforeach( sourcefile ${APP_SOURCES} )
Gulzar
  • 23,452
  • 27
  • 113
  • 201
xitong
  • 352
  • 2
  • 9