Bart Kessels
Bart Kessels
Passionate open source software engineer who loves to go backpacking

Use CMake target in another folder

Use CMake target in another folder
This image is generated using Dall-E
  • Prompt: Generate an image of a file that references other files from a directory in a minimalistic flat style
  • Introduction

    With the release of It Depends, I’ve been searching for a way to execute a command after a target has been built. The reason for this is that I’d like to have a flag which will trigger the macdeployqt application on the generated app bundle.

    However, to make things more complicated, I want to do this in CMake on the default It Depends target but I’d like to do this in a different CMakeFile.txt in a different folder from where the It Depends target is created. So roughly the structure looks like this

    • src/
      • CMakeLists.txt (<- It Depends target is created here)
    • packaging/
      • CmakeLists.txt(<- macdeployqt should be triggered here after the build of It Depends)
    • CmakeLists.txt (<- include both folders here)

    Why is this complicated you might ask. Well, you can’t access a target that’s created in another CMakeLists.txt in a different folder.

    Logically thinking you’d add both directories in the root CMakeLists.txt and that’s all there is to it. Unfortunatly it’s not that easy (well almost, I’ll explain later).

    Test setup

    So to test whether it’s possible to add a POST_BUILD command to a target that’s created in another folder, let’s setup a test project.

    Create the following file structure

    • src/
      • main.cpp
      • CMakeLists.txt
    • packaging/
      • CMakeLists.txt
    • CMakeLists.txt

    And give them the following contents

    CMakeLists.txt

    1
    2
    3
    4
    
    cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
    
    add_subdirectory(./src)
    add_subdirectory(./packaging)
    

    src/main.cpp

    1
    2
    3
    4
    
    int main(int argc, char** args)
    {
      return 0;
    }
    

    src/CMakeLists.txt

    1
    2
    3
    4
    5
    6
    
    cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
    
    add_executable(
          test_target
          main.cpp
    )
    

    packaging/CMakeLists.txt

    1
    2
    3
    4
    5
    
    cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
    
    add_custom_command(TARGET test_target POST_BUILD
      COMMAND echo "The project has been built!"
    )
    

    add_subdirectory

    The first thought is to add add_subdirectory(./packaging) in the root CMakeLists.txt and add a reference to the test_target target in the src/CMakeLists.txt to execute a command after the build has finished. So this look exactly like our test setup above.

    When running cmake . I got the following error message

    1
    2
    
    CMake Error at packaging/CMakeLists.txt:3 (add_custom_command):
      TARGET 'test_target' was not created in this directory.
    

    As you see, this message is pretty clear. We are simply not allowed to access the target from another directory.

    include

    So I started searching the CMake documentation and came across the include function. Which, according to the documentation

    Loads and runs CMake code from the file given. Variable reads and writes access the scope of the caller […]

    Thus, meaning that if we call include(../packaging/CmakeLists.txt) from our src/CMakeLists.txt file we are in the same scope as where the target is created.

    If we then run cmake . and build our target, we should be able to execute our custom command after the target is built.

    So modify the CMakeLists.txt in the root and the src/CMakeLists.txt to look like this.

    1
    2
    3
    4
    5
    6
    7
    8
    
    cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
    
    add_executable(
          test_target
          main.cpp
    )
    
    include(../packaging) # <- Add this line
    
    1
    2
    3
    4
    
    cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
    
    add_subdirectory(./src)
    # add_subdirectory(./packaging) # <- Remove this line
    

    Well, this gives us the following error message.

    1
    2
    3
    4
    
    CMake Error at src/CMakeLists.txt:8 (include):
      include requested file is a directory:
    
        ../packaging
    

    Luckily it implicitly mentions the solution, we need to reference a file instead of a directory. So update the include(../packaging) line to include(../packaging/CMakeLists.txt) in src/CMakeLists.txt.

    If we built our target, we see the following correct output:

    1
    2
    
    [2/2] Linking CXX executable src/test_target
    The project has been built!
    

    Solution

    As explained above, we need to use the include function inside our src/CMakeLists.txt file (or the CMakeLists.txt file where you create your target).

    This will allow you to use the same scope inside the packaging folder that you have in the src folder.

    To recap and use the example structure again, we have the following folder structure.

    • scr/
      • main.cpp
      • CMakeLists.txt
    • packaging
      • CMakeLists.txt
    • CMakeLists.txt

    Where the files have the following contents.

    1
    2
    3
    
    cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
    
    add_subdirectory(./src)
    
    1
    2
    3
    4
    
    int main(int argc, char** args)
    {
      return 0;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    
    cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
    
    add_executable(
          test_target
          main.cpp
    )
    
    include(../packaging/CMakeLists.txt)
    
    1
    2
    3
    4
    5
    
    cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
    
    add_custom_command(TARGET test_target POST_BUILD
      COMMAND echo "The project has been built!"
    )
    

    When we build the project, using Ninja in this example, we get the following output.

    1
    2
    3
    4
    5
    
    $ cmake . -G Ninja
    Configuring [...]
    $ ninja test_target
    [2/2] Linking CXX executable src/test_target
    The project has been built!
    

    Categories

    Related articles

    Building a Qt MacOS bundle using CMake

    Build a stand-alone Windows executable of your Qt application using CMake.

    Building a Qt Windows Executable using CMake

    Build a stand-alone Windows executable of your Qt application using CMake.