Using CMake with setup.py - cmake

For a project I build a C library and implict Python bindings (via GObject introspection) with CMake. I also want to distribute some Python helper modules using distutils. I am able to build and install the module with this CMakeLists.txt
find_program(PYTHON "python")
if (PYTHON)
set(SETUP_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in")
set(SETUP_PY "${CMAKE_CURRENT_BINARY_DIR}/setup.py")
set(DEPS "${CMAKE_CURRENT_SOURCE_DIR}/module/__init__.py")
set(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/build")
configure_file(${SETUP_PY_IN} ${SETUP_PY})
add_custom_command(OUTPUT ${OUTPUT}
COMMAND ${PYTHON}
ARGS setup.py build
DEPENDS ${DEPS})
add_custom_target(target ALL DEPENDS ${OUTPUT})
install(CODE "execute_process(COMMAND ${PYTHON} ${SETUP_PY} install)")
endif()
and the following setup.py.in:
from distutils.core import setup, Extension
if __name__ == '__main__':
setup(name='foo',
version='${PACKAGE_VERSION}',
package_dir={ '': '${CMAKE_CURRENT_SOURCE_DIR}' },
packages=['module'])
Unfortunately, the build step is executed each time I run make. I guess, the problem is related to the output of the custom command which is a directory rather than a file. Now, is there any way to tell CMake to run python setup.py build only when setup.py.in or one of the sources changed?

Only files, not directories, can be reliably used as OUTPUT and DEPENDS. You could modify your custom command to also produce a timestamp file, something like this:
add_custom_command(
OUTPUT ${OUTPUT}/timestamp
COMMAND ${PYTHON} setup.py build
COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT}/timestamp
DEPENDS ${DEPS}
)
add_custom_target(target ALL DEPENDS ${OUTPUT}/timestamp)

Related

Is it possible to determine whether CMake install(CODE) is called from the "install" or "package" stage?

I'm using CMake v3.21.0 to invoke Qt's windeployqt during the install stage by the means of the install(CODE) command as follows:
install(
CODE "
execute_process(
COMMAND \"${CMAKE_COMMAND}\" -E
env PATH=\"${windeployqt_ROOT_DIR}\"
\"${windeployqt_EXECUTABLE}\"
# TODO(2021-08-25 by wolters): This is a different path when CPack is`
# used. How to check for this case and obtain the correct output path?
--dir \"${CMAKE_INSTALL_PREFIX}/${args_INSTALL_SUFFIX}\"
--no-quick-import
--no-system-d3d-compiler
--no-virtualkeyboard
--no-compiler-runtime
--no-webkit2
--no-angle
--no-opengl-sw
--verbose 0
\"\$<TARGET_FILE:${args_TARGET}>\"
)
"
COMPONENT runtime
)
This works fine if installing the project:
cmake --build . --config RelWithDebInfo --target install
But when creating a CPack package the files created by windeployqt are not part of the package (ZIP in this case):
cpack -G ZIP -C RelWithDebInfo -D CPACK_COMPONENTS_ALL="runtime"
I know that the issue is the usage of ${CMAKE_INSTALL_PREFIX} in the CODE.
For the install target this is correct.
For the package target this is not correct. Instead the build directory for the current CPack generator should be used, e.g. ${CMAKE_CURRENT_BINARY_DIR}/_CPack_Packages/win64/ZIP/${CPACK_PACKAGE_FILE_NAME}.
My questions are:
Is there a way to differentiate between install and package target in the CODE section? (pseudo-code: if(CMAKE_IS_PACKAGING))
If there is a way: Is it possible to obtain or dynamically build the directory path to the actual CPack temporary "install" directory?
If both problems can be solved the files generated by windeployqt should be part of the packages generated by CPack.
The variable CMAKE_INSTALL_PREFIX should not be expanded in the CMakeLists.txt, as you are doing. Its actual value at invocation time is available inside the install(CODE) fragments.
Consider the following snippet:
cmake_minimum_required(VERSION 3.21)
project(test NONE)
install(CODE [[message(STATUS "HERE: ${CMAKE_INSTALL_PREFIX}")]])
Note that [[ ... ]] escapes variable expansions (you could also use backslashes). Now if you configure this project with -DCMAKE_INSTALL_PREFIX=/tmp/install, you'll see the message print as you expect.
$ cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/tmp/install
-- Configuring done
-- Generating done
-- Build files have been written to: /home/alex/test/build
$ cmake --build build/ --target install
[0/1] Install the project...
-- Install configuration: ""
-- HERE: /tmp/install
If you now run the install script again without reconfiguring or rebuilding, it will still work:
$ cmake --install build/ --prefix /tmp/other-prefix
-- Install configuration: ""
-- HERE: /tmp/other-prefix
This is how CPack runs your install rules. It does not use the configuration-time value of CMAKE_INSTALL_PREFIX. It expects your project to be relocatable (i.e. bug-free).

Execute process at install stage, every time

I would like to run program to perform do extra installation tasks from CMake. My attempted solution, based on INSTALL(CODE ...) is (this is a real MWE):
macro(MY_EXTRA_STUFF ARG)
execute_process(...)
endmacro()
install(CODE "MY_EXTRA_STUFF(${SOME_ARG})")
but CMake complains when I run ninja install (or make install, depending on generator in use):
[0/1] Install the project...
-- Install configuration: ""
CMake Error at cmake_install.cmake:41 (MY_EXTRA_STUFF):
Unknown CMake command "MY_EXTRA_STUFF".
FAILED: CMakeFiles/install.util
cd /tmp && /usr/bin/cmake -P cmake_install.cmake
ninja: build stopped: subcommand failed.
Is there a way to smuggle my own code into the install stage? The code is too long to fit inside install(CODE "...") nicely. A bonus to do it without an external file. Thanks!
The code passed to install(CODE) is executed as standalone CMake code, thus it shouldn't use definitions (functions,macros, variables) from the rest of CMakeLists.txt.
That is, install(CODE) behaves similar as install(SCRIPT) with a standalone script containing given code.
The thing is that configuration stage (when you call cmake to configure your project) and installation stage, which, as you can see, calls /usr/bin/cmake -P cmake_install.cmake, are separate cmake invocations. These invocations parse different files, so they unaware about context of each other.

Is there a command on CMake to started executable resulted from build?

I'm trying to write generic way to run executable resulted after build using CMake's way.
git clone git#github.com:gargamel/ihatesmurfs.git
cmake -E make_directory build
cmake -Sihatesmurfs -Bbuild
cmake --build build
cmake -E chdir build
Now I want to start executable but on *nix, it's like:
./output
and on Windows:
output.exe
Is there a way to escape this with any possible CMake command?
Expanding on my comment a bit, you can modify the CMakeLists.txt file of the project to include add_custom_command. If your CMake creates an executable named HateSmurfs, you can add the custom command to run the executable after compilation completes:
add_executable(HateSmurfs smurfs.cpp)
# Add this piece of code to run the executable after it is built.
add_custom_command(
TARGET HateSmurfs
POST_BUILD
COMMAND HateSmurfs
)
According to add_custom_command documentation:
COMMAND
If COMMAND specifies an executable target name (created by the add_executable() command) it will automatically be replaced by the location of the executable created at build time.

Using google tests with CMake/Ctest with the new command gtest_discover_tests

I am trying to use googletest with CMake/Ctest. I have several sources files for my tests (each one containing many TEST/TEST_F/... commands) which are located in several directories. I want that the tests related to a given source are executed in the same directory as their source file. Also, I prefer that the build process of a test source file is a test by itself. So I made something like:
file(GLOB_RECURSE test_srcs
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
"tests/*.cpp")
foreach(test_src ${test_srcs})
get_filename_component(test_dir ${test_src} DIRECTORY)
get_filename_component(test_exe ${test_src} )NAME_WE)
add_executable(${test_exe} EXCLUDE_FROM_ALL tests/gtest_main.cpp ${test_src})
set_target_properties(${test_exe}
PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${test_dir}
)
target_link_libraries(${test_exe} gtest)
add_test(NAME build_${test_exe} COMMAND "${CMAKE_COMMAND}" --build ${CMAKE_BINARY_DIR} --target ${test_exe})
set_tests_properties(build_${test_exe} PROPERTIES FIXTURES_SETUP ${test_exe})
gtest_discover_tests(${test_exe}
TEST_LIST list
WORKING_DIRECTORY ${test_dir}
PROPERTIES DEPENDS build_${test_exe}
PROPERTIES FIXTURES_REQUIRED ${test_exe}
)
endforeach()
But it seems that the dependencies I am trying to declare between the tests are not taken into account: the build of the tests does not necessarily occurs before the execution of the underlying tests...
If I use the old gtest_add_tests as in the following instead of gtest_discover_tests, it works:
gtest_add_tests(
TARGET ${test_exe}
SOURCES ${test_src}
WORKING_DIRECTORY ${test_dir}
TEST_LIST tlist
)
set_tests_properties(${tlist} PROPERTIES FIXTURES_REQUIRED ${test_exe})
Am I missing something with gtest_discover_tests?
After having started the bounty, I re-started the research on my own. I found out, the simplest method out there is to have googletest installed system-wide.
So, first install the package. On Ubuntu 18.04, that was supt apt install googletest.
For some reason, I had to build the library (perhaps not necessary somehow though?):
cd /usr/src/googletest
mkdir bin && cd bin
cmake ..
make && make install
After that I have been able to compile and run a test case. My CMakeLists.txt testing section looks like this:
enable_testing()
find_package(GTest REQUIRED)
include(GoogleTest)
add_executable(tests tests/foo_test.cpp tests/bar_test.cpp)
target_link_libraries(tests GTest::GTest GTest::Main)
gtest_discover_tests(tests)
A minimal test case file looks like this in my project:
// tests/foo_test.cpp
#include "gtest/gtest.h"
TEST(Foo, Sum)
{
EXPECT_EQ(2, 1 + 1);
}
Compiling is as easy as:
mkdir bin && cd bin
cmake ..
./tests

CMake py.test detection?

py.test is know tool for testing Python scripts. I am missing, however, CMake macro for the detetion of py.test.
Is there anything of this kind within CMake pool of macros ?
Directly calling py.test from a CMakeLists.txt isn't very portable, because different systems name it quite differently, e.g.:
py.test # pip, as of early 2018
pytest # ditto
py.test-3 # Fedora 26 package
pytest-3 # ditto
Of course, the system might default to version 2 or 3, and might have both version 2 and 3 installed side by side.
Thus, a simple method for reliable executing right pytest version via cmake is to invoke pytest differently:
python3 -m pytest
(instead of py.test or pytest or ...)
If you want to test in cmake if the pytest package is available you can test this with execute_process(), e.g.:
execute_process(COMMAND python3 -m pytest --version
OUTPUT_VARIABLE PYTEST_output
ERROR_VARIABLE PYTEST_error
RESULT_VARIABLE PYTEST_result)
if(${PYTEST_result} UNEQUAL 0)
message(SEND_ERROR "Pytest package not available: ${PYTEST_error}")
endif()