14

I have a tool to generate some cpp and header files and want to add it with ADD_CUSTOM_COMMAND to automatically execute it during the build and add the files to the project. The problem is that the names of (most of) the output files are not known beforehand. How do I add those files?

Kevin
  • 16,549
  • 8
  • 60
  • 74
ACB
  • 1,607
  • 11
  • 31
  • `... execute it during the build and add the files to the project` - What do you mean by adding files to the project? Do you want these files to be shown in IDE? Or you want to make other command to be dependent on these files? – Tsyvarev Oct 05 '15 at 13:12
  • i want the files to be compiled into the binary – ACB Oct 05 '15 at 13:13
  • 2
    Then you should deduce names of these files in some way. It may be e.g. additional script, which is executed at configuration step (via `execute_process()`), parses one of input files and produces list of files. This list can be stored into variable, which value you can use then with `OUTPUT` option of `ADD_CUSTOM_COMMAND`. Note, that you should list all *source* (not headers) files in `add_executable()` call in any case. – Tsyvarev Oct 05 '15 at 13:18
  • 1
    thing is the names might change between configuration and compilation and i wouldnt like to require running configure first everytime before building to be safe. if thats not possible i might have to rethink how to genreate those files – ACB Oct 05 '15 at 13:21
  • 4
    You could make *configuration* step dependent from source files, which affects on names of files to be built into binary. E.g., using `configure_file()` command. So, if `make` will found dependent files to be changed, configuration step will be repeated automatically. – Tsyvarev Oct 05 '15 at 13:26
  • will try that thanks – ACB Oct 05 '15 at 13:27
  • @ruslo: I mean that **sources must be listed** for add_executable(), while headers are optional. It is possible to have (lightweight) **script**, which **generates list** of files, and **tool**, which **generates files themselves**. So ADD_CUSTOM_COMMAND can be used for *tool*. As for `include()`, yes, it can be used as alternative for storing output of `execute_process()` into variable. – Tsyvarev Oct 06 '15 at 07:37
  • 1
    Just following the discussion, could this be a duplicate to [How to add_custom_command() for the CMake build process itself?](http://stackoverflow.com/questions/32670701/how-to-add-custom-command-for-the-cmake-build-process-itself) – Florian Oct 07 '15 at 07:19
  • @Florian Not an exact duplicate, but it seems the solution to that question could work for the issue here. Perhaps, a separate answer here is warranted. :) – Kevin Sep 07 '19 at 13:42
  • @Tsyvarev So the only way is to write an additional script to get all the filenames and then add those filename to the `add_custom_command` argument? – St.Antario Oct 08 '19 at 05:10
  • Look: `add_custom_command` is usable only with `OUTPUT` argument matched to source or header file, listed in `add_executable` (or `add_library`) call. And you need to provide that `OUTPUT` argument at *configuration* stage, when the COMMAND (script) is not run yet. – Tsyvarev Oct 08 '19 at 08:32

1 Answers1

8

Introduction

I assume that your goal is not the usage of ADD_CUSTOM_COMMAND, but rather that:

  • cmake recognizes that your generated files depend on the generation script. This means: when your generation script has changed, then the files are regenerated automatically with different file names when you execute cmake --build the next time.
  • cmake recognizes that your executable/library depends on the generated files, whose file names change as soon as these are regenerated. This means: your executable/library is rebuilt and uses the regenerated files as consequence of the same call of cmake --build

The main problem is that filenames must be known at configuration time, so that the dependencies are known at build time. This means that you need to move the generation of the files to configuration time.

You either need to get the file names as output of your generation script or you use file globbing. Usually I do not recommend the usage of file globbing, because it has some disadvantages regarding proper dependencies. In your case globbing adds real value, but the disadvantage must be compensated.

Below I compiled a complete example project which shows how it could work. I do not say that this is the standard way to do, but I think that it meets the requirements well.

Examplary Setup

I have created a minimum setup which makes all this working:

myProject
|- CMakeLists.txt
|- main.cpp
|- myClass.h
|- generate.sh

myClass.h

#ifndef MY_CLASS_H
#define MY_CLASS_H
int do_something_a();
int do_something_b();
#endif // MY_CLASS_H

main.cpp

#include "myClass.h"
#include <iostream>

int main(const int argc, const char **argv)
{
  int a = do_something_a();
  int b = do_something_b();
    
  std::cout << "A:" << a << ", B:" << b << std::endl;

  return 0;
}

generate.sh Just creates 2 cpp files which implement the do_something_a() and do_something_b(). The generated file names contain a random number, which is also returned by the do_something_*() function. The files are put into subdirectory generated.

#!/usr/bin/env bash

# just creating a new subfolder; remove its contents if already existing
dir1="generated"
if [ ! -d ${dir1} ]; then mkdir ${dir1}; fi
rm -rf ${dir1}/*

# just create a cpp file with random name and implement do_something_a()    
var1=`date +%N`
file1=${dir1}/myClass.${var1}.cpp
echo '#include "myClass.h"' > ${file1}
echo 'int do_something_a() { return '${var1}'; }' >> ${file1}

# just create a cpp file with random name and implement do_something_b()
var2=`date +%N`
file2=${dir1}/myClass.${var2}.cpp
echo '#include "myClass.h"' > ${file2}
echo 'int do_something_b() { return '${var2}'; }' >> ${file2}

CMakeLists.txt

cmake_minimum_required (VERSION 3.10.2)

project(useGenerateSourcesAtBuildStepProj
  VERSION 0.0.0.1
  DESCRIPTION "Minimum Example: Use Generated Files as dependency"
  LANGUAGES CXX
)

# just needed to force re-configuration, if generate.sh has changed.
# that's the tweak to overcome glob disadvantage
configure_file(
  ${CMAKE_CURRENT_SOURCE_DIR}/generate.sh
  ${CMAKE_CURRENT_BINARY_DIR}/generate.sh
)

# regeneration command executed at configuration time
# note: cannot call ${CMAKE_CURRENT_BINARY_DIR}/generate.sh, because not yet existing
execute_process(
  COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/generate.sh
  WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)

# globbing all generated files in sub directory `generated` at configuration time after execute_process
FILE(GLOB GENERATED_SRC_FILES ${CMAKE_CURRENT_BINARY_DIR}/generated/*)

# use generated files to build your executable
add_executable(useGenerateSourcesAtBuildStep main.cpp ${GENERATED_SRC_FILES})
target_include_directories(useGenerateSourcesAtBuildStep PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

Build

Configure:

../bin/Release$ cmake -G"Unix Makefiles" ../..

Output:

-- The CXX compiler identification is GNU 7.5.0
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done    
-- Configuring done
-- Generating done
-- Build files have been written to: <dir>/bin/Release

Build:

../bin/Release$ make

Output:

Scanning dependencies of target useGenerateSourcesAtBuildStep
[ 25%] Building CXX object CMakeFiles/useGenerateSourcesAtBuildStep.dir/main.cpp.o
[ 50%] Building CXX object CMakeFiles/useGenerateSourcesAtBuildStep.dir/generated/myClass.488106246.cpp.o
[ 75%] Building CXX object CMakeFiles/useGenerateSourcesAtBuildStep.dir/generated/myClass.490335510.cpp.o
[100%] Linking CXX executable useGenerateSourcesAtBuildStep
[100%] Built target useGenerateSourcesAtBuildStep

Generated Files:

|- bin
    |- Release
        |- src
            |- generate.sh (due to configure_file)
            |- generated (directory containing all generated files)
            |   |- myClass.488106246.cpp
            |   |- myClass.490335510.cpp
            |- useGenerateSourcesAtBuildStep (your exectuable)

Change generate.sh and rebuild with make:

../bin/Release$ make

Output:

You can see that the configuration step is done again and that the generated files change their name, but are rebuild before linking it all together into your executable:

-- Configuring done
-- Generating done
-- Build files have been written to: /home/tangoal/Documents/data/300_Projects/_software/CPP/lib/test/bin/Release
Scanning dependencies of target useGenerateSourcesAtBuildStep
[ 25%] Building CXX object CMakeFiles/useGenerateSourcesAtBuildStep.dir/generated/myClass.565834060.cpp.o
[ 50%] Building CXX object CMakeFiles/useGenerateSourcesAtBuildStep.dir/generated/myClass.567160574.cpp.o

[ 75%] Linking CXX executable useGenerateSourcesAtBuildStep
[100%] Built target useGenerateSourcesAtBuildStep

Execute the program:

../Release/src$ ./useGenerateSourcesAtBuildStep

Output:

A:565834060, B:567160574
jav
  • 583
  • 5
  • 15
tangoal
  • 724
  • 1
  • 9
  • 28
  • 1
    +1 For the detailed answer. One addition: In order to force reconfiguration upon change to a specific file you can also just mark the file as configuration dependend: `set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS )` – cbandera Jun 17 '21 at 06:22