10

I have a python script that parses all of the C++ source files in the project's directory, looks for some stuff in the files, and then generates a file. This python script works fine, but I want it to automatically run before building my C++ project.

So basically, I want this python script to run before every build, so if any .h or .cpp files have been changed. I'd also like it to run if the python script itself has been changed. I have the python script in question, genenums.py, in the same directory as my C++ source files such as main.cpp, etc.

I've tried experimenting with add_custom_command based on documentation, but I can't get cmake to ever run this python script in any instance. I'm not sure how to make this work right, as I'm new to cmake.

Here's my current cmake file:

cmake_minimum_required(VERSION 3.9)
project(enum_test)

set(CMAKE_CXX_STANDARD 17)

include_directories(include)

find_package( PythonInterp 2.7 REQUIRED )
find_package( PythonLibs 2.7 REQUIRED )

add_custom_command(
 COMMAND ${PYTHON_EXECUTABLE} genenums.py
 DEPENDS genenums.py $(CMAKE_CURRENT_BINARY_DIR)
 OUTPUT enums.h
 WORKING_DIRECTORY $(CMAKE_CURRENT_BINARY_DIR)
 COMMENT "Generating enums"
)

add_executable(enum_test main.cpp test.h test.cpp)
irfna
  • 525
  • 1
  • 4
  • 13
  • EXECUTE_PROCESS (COMMAND PYTHON .. ) , have you tried using this ? – Archie Yalakki Feb 27 '18 at 23:08
  • @ArchieYalakki I just tried it, but the script doesn't run, just like almost everything I've tried. I have something that runs now, but it runs in parallel to the build and cmake kills the script before it can do much anyway. – irfna Feb 27 '18 at 23:17
  • https://cmake.org/cmake/help/v3.0/command/execute_process.html – Archie Yalakki Feb 27 '18 at 23:21
  • @ArchieYalakki I tested it again since you insist, but it still has the issue of cmake terminating the process before it can complete. It is also running during the cmake configuration I think, not before the build. It's not executing before the build at all, which is when I need it to run. – irfna Feb 27 '18 at 23:33

1 Answers1

16

Ok, I have a foolproof, non-ugly way to have cmake run any kind of commands right before a build to build a dependency, waiting until the commands are done before doing the build.

add_custom_target(
 run ALL
 COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/genenums.py ${CMAKE_CURRENT_SOURCE_DIR}
 BYPRODUCTS enums.h
 COMMENT "Generating enums"
)

add_dependencies(enum_test run)

The two key parts are the add_custom_target and add_dependencies, both are required to make this work. Place both after the add_executable in CMakeLists.txt. enum_test refers to the target created by add_executable (the first name in its list), so you'd set that to your project's name.

You can name the custom target to whatever you like (I used run here) by changing the run in both add_custom_target and add_dependencies to something else.

There's one additional catch with add_custom_target... WORKING_DIRECTORY in that did nothing for my python script. Even when I tried to set the WORKING_DIRECTORY to ${CMAKE_CURRENT_SOURCE_DIR}, the script executed in the default ${CMAKE_CURRENT_BINARY_DIR} anyway.

So for this one catch, whatever commands you're using need to be able to take a command line argument of ${CMAKE_CURRENT_SOURCE_DIR} and use that to operate in the source directory properly (assuming that's your goal). That's why I have ${CMAKE_CURRENT_SOURCE_DIR} at the end of this line:

 COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/genenums.py ${CMAKE_CURRENT_SOURCE_DIR}

Here's the full CMakeLists.txt with the working setup, should be fairly easy to adapt to any particular project's CMakeLists.txt.

cmake_minimum_required(VERSION 3.9)
project(enum_test)

set(CMAKE_CXX_STANDARD 17)

include_directories(include)

find_package( PythonInterp 2.7 REQUIRED )

add_executable(enum_test enums.h main.cpp test.h test.cpp)

add_custom_target(
 run ALL
 COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/genenums.py ${CMAKE_CURRENT_SOURCE_DIR}
 BYPRODUCTS enums.h
 COMMENT "Generating enums"
)

add_dependencies(enum_test run)
irfna
  • 525
  • 1
  • 4
  • 13
  • I believe you don't need `find_package( PythonLibs 2.7 REQUIRED )`, as you do not link agains Python – R2RT Mar 01 '18 at 18:14
  • @R2RT Thanks, I'll remove that – irfna Mar 02 '18 at 03:56
  • 1
    Great answer. Just to add, you don't need the extra "working directory" argument, you can do `os.chdir(os.path.dirname(sys.argv[0]))`, see https://stackoverflow.com/questions/1432924/python-change-the-scripts-working-directory-to-the-scripts-own-directory – Adamski Aug 05 '20 at 12:56
  • 1
    THANK YOU! I was banging my head against the (it turns out) non-portable PRE_BUILD variant of add_custom_command. This works just how I wanted. – Bogatyr Feb 26 '21 at 15:05
  • 1
    Thanks you very much, turns out order of writing the add_custom_target is critical. Meaning that the order must be: add_executable/library() add_custom_target() add_dependencies() I tried to write add_custom_target() add_executable() .. But that failed depending on timing, if I use -j 25 for example then it sometimes succeeded. – Piepypye Jul 22 '21 at 00:42