CMake, custom command and dependencies - cmake

During my build process, a file generated by add_custom_command must the be compiled as C++. That's working but the generated file contains an include directive. When I modify the included file, the generated file is not recompiled.
There is an option IMPLICIT_DEPENDS for add_custom_command. But it does remake the generated file instead of just compiling it. And the documentation has a note
Note that the IMPLICIT_DEPENDS option is currently supported only for Makefile generators and will be ignored by other generators.
What's the recommended way to be able to recompile the generated file without remaking it and without depending on a given generator?
An example:
% more foo.h
#ifndef FOO_H
#define FOO_H
int const value = 36;
#endif
% more foo.in
#include "foo.h"
#include <iostream>
int main()
{
std::cout << "The answer is " << value << '\n';
return 0;
}
% more CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(Test)
add_custom_command(OUTPUT foo.cpp
DEPENDS foo.in
COMMAND cp ${CMAKE_CURRENT_SOURCE_DIR}/foo.in foo.cpp)
set_source_files_properties(foo.cpp PROPERTIES INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR})
add_executable(foo foo.cpp)
% mkdir build
% cd build
% cmake ..
...
% make
[ 33%] Generating foo.cpp
Scanning dependencies of target foo
[ 66%] Building CXX object CMakeFiles/foo.dir/foo.cpp.o
[100%] Linking CXX executable foo
[100%] Built target foo
% ./foo
The answer is 36
% sed -i.bak -e 's/36/42/' ../foo.h
% make
[100%] Built target foo
% ./foo
The answer is 36
I expect the second make to recompile foo.cpp (but not to regenerate it) so that the answer is 42.
Adding
IMPLICIT_DEPENDS CXX ${CMAKE_CURRENT_SOURCE_DIR}/foo.in
to the CMakeLists.txt generates a correct executable but regenerates foo.cpp when not needed and it not available for other generators according to the documentation.

Related

Problems building MRPT program with cmake

I hate cmake, as I always seem to spend more time trying to get cmake to compile and build a program than I do writing and debugging the program cmake is supposed to be building.
Anyway, the latest problem is this.
I have some code that starts ...
#include <mrpt/obs/CObservationBatteryState.h>
#include <mrpt/obs/CObservationComment.h>
#include <mrpt/obs/CObservation2DRangeScan.h>
#include <mrpt/maps/CSimplePointsMap.h>
#include <mrpt/comms/CSerialPort.h>
#include <mrpt/poses/CPose3D.h>
#include <mrpt/system/os.h>
a fairly simple CMakeLists.txt that looks like...
project(some_sort_of_name)
cmake_minimum_required(VERSION 3.8)
find_package(MRPT COMPONENTS obs maps comms )
if(CMAKE_COMPILER_IS_GNUCXX AND NOT CMAKE_BUILD_TYPE MATCHES "Debug")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")
endif()
add_executable(lidar lidar.c)
add_executable(lidar_obs lidar_obs.cpp)
target_link_libraries(lidar_obs mrpt::obs mrpt::maps)
And the compilation fails with...
[ 50%] Built target lidar
[ 75%] Building CXX object CMakeFiles/lidar_obs.dir/lidar_obs.cpp.o
/home/mike/work/minipupper/lidar/lidar_obs.cpp:5:10: fatal error: mrpt/comms/CSerialPort.h: No such file or directory
5 | #include <mrpt/comms/CSerialPort.h>
So it is happy with compiling & building "lidar" which is a simple 'C' program with no calls to MRPT, but when it comes to compiling lidar_obs, it is happy to find the include files for obs and maps but not comms.
The include files are there, i.e.
$ ls /usr/include/mrpt/comms/include/mrpt
comms comms.h
$ ls /usr/include/mrpt/comms/include/mrpt/comms
CClientTCPSocket.h CInterfaceFTDI.h CSerialPort.h CServerTCPSocket.h net_utils.h nodelets.h registerAllClasses.h
just like e.g. obs & maps
$ ls /usr/include/mrpt/obs
include
$ /usr/include/mrpt/obs/include
mrpt
$ ls /usr/include/mrpt/obs/include/mrpt
maps obs obs.h
$ ls /usr/include/mrpt/obs/include/mrpt/maps
CMetricMapEvents.h CMetricMap.h CSimpleMap.h metric_map_types.h TMetricMapInitializer.h TMetricMapTypesRegistry.h
What is going on with cmake ? How does it find some files and not others.
You were missing the link command to also include the mrpt-comms library:
target_link_libraries(lidar_obs mrpt::obs mrpt::maps mrpt::comms)
# ^^^^^^^^^^^
CMake with exported libraries work like that: "linking" an imported cmake target, also includes the "-I" flags (for #includes) apart of the link -l flags themselves.

Using GLSLC's depfile to make included files automatically trigger recompile of SPIRV in CMake

I'm trying to create a cmake function that automatically recompiles glsl to spirv upon changes to the shader files. Right now direct dependencies work, ie the shaders I use as compile arguments. However I make heavy use of #include feature that glslc provides, and by default I can't get changes in that stuff to trigger recompile. I made sure that I'm using the Ninja
Right now I have the following CMake function and arguments:
cmake -DCMAKE_BUILD_TYPE=Debug "-DCMAKE_MAKE_PROGRAM=JETBRAINSPATH/bin/ninja/win/ninja.exe" -G Ninja "PATH_TO_CURRENT_DIRECTORY"
function
set(GLSLC "$ENV{VULKAN_SDK}/Bin/glslc")
function(target_shader_function SHADER_TARGET)
foreach (SHADER_SOURCE_FILEPATH ${ARGN})
get_filename_component(SHADER_SOURCE_FILENAME ${SHADER_SOURCE_FILEPATH} NAME)
get_filename_component(SHADER_SOURCE_DIRECTORY ${SHADER_SOURCE_FILEPATH} DIRECTORY)
set(SHADER_TARGET_NAME "${SHADER_TARGET}_${SHADER_SOURCE_FILENAME}")
set(SHADER_BINARY_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/spirv")
set(SHADER_FINAL_BINARY_FILEPATH "${SHADER_BINARY_DIRECTORY}/${SHADER_SOURCE_FILENAME}.spv")
#we can use depfiles instead
#https://stackoverflow.com/questions/60420700/cmake-invocation-of-glslc-with-respect-to-includes-dependencies
add_custom_command(
OUTPUT ${SHADER_FINAL_BINARY_FILEPATH}
DEPENDS ${SHADER_SOURCE_FILEPATH}
DEPFILE ${SHADER_SOURCE_FILEPATH}.d
COMMAND ${CMAKE_COMMAND} -E make_directory ${SHADER_BINARY_DIRECTORY}
COMMAND ${GLSLC} -MD -MF ${SHADER_SOURCE_FILEPATH}.d -O ${SHADER_SOURCE_FILEPATH} -o ${SHADER_FINAL_BINARY_FILEPATH} --target-env=vulkan1.2 -I ${CMAKE_SOURCE_DIR}/shaderutils
DEPENDS ${SHADER_SOURCE_FILEPATH}
# BYPRODUCTS ${SHADER_FINAL_BINARY_FILEPATH} ${SHADER_SOURCE_FILEPATH}.d causes ninja to no longer work
COMMENT "Compiling SPIRV for \nsource: \n\t${SHADER_SOURCE_FILEPATH} \nbinary: \n\t${SHADER_FINAL_BINARY_FILEPATH} \n"
)
add_custom_target(${SHADER_TARGET_NAME} DEPENDS ${SHADER_FINAL_BINARY_FILEPATH} ${SHADER_SOURCE_FILEPATH}.d)
add_dependencies(${SHADER_TARGET} ${SHADER_TARGET_NAME})
endforeach (SHADER_SOURCE_FILEPATH)
endfunction()
and I use it like this:
cmake_minimum_required(VERSION 3.21)
cmake_policy(SET CMP0116 NEW)
project(my_workspace)
add_executable(my_target main.cpp)
...
target_shader_function(my_target
${CMAKE_CURRENT_SOURCE_DIR}/shaders/example.comp
)
main.cpp
#include <iostream>
int main(){
std::cout << "hello world!" << std::endl;
return 0;
}
Again, everything works fine if I change, for example, example.comp.
However, lets say I have the following shader (lets say that this is example.comp):
#version 460
#include "fooutils.glsl"
#define WORKGROUP_SIZE 1024
layout (local_size_x = WORKGROUP_SIZE, local_size_y = 1, local_size_z = 1) in;
layout(set = 0, binding = 0) buffer MyBufferBlock{
float data[];
}
void main(){
uint tidx = gl_GlobalInvocationID.x;
data[tidx] += foo(tidx);
}
and I include the following:
#ifndef FOOUTILS_GLSL
#define FOOUTILS_GLSL
float foo(uint tidx){
return mod(tidx, 4.51);
}
#endif //FOOUTILS_GLSL
and I change fooutils.glsl after everything is compiled once (for example in a way that stops it from compiling),
#ifndef FOOUTILS_GLSL
#define FOOUTILS_GLSL
float foo(uint tidx){
return x;
return mod(tidx, 4.51);
}
#endif //FOOUTILS_GLSL
I don't get a recompile triggered. I had assumed that ninja would use this info to accomplish this, but I haven't seen it happen.
How do I use this depfile to force a recompile when an include dependency changes?
Here's my working implementation. But first, here's my terminal output so you can see it's working:
$ tree
.
├── CMakeLists.txt
├── main.cpp
├── shaders
│   └── example.comp
└── shaderutils
└── fooutils.glsl
$ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
...
$ cmake --build build/
[1/3] Compiling SPIRV: shaders/example.comp -> spirv/example.spv
[2/3] Building CXX object CMakeFiles/my_target.dir/main.cpp.o
[3/3] Linking CXX executable my_target
$ cmake --build build/
ninja: no work to do.
$ touch shaderutils/fooutils.glsl
$ cmake --build build/
[1/1] Compiling SPIRV: shaders/example.comp -> spirv/example.spv
$ cat build/spirv/example.d
spirv/example.spv: /path/to/shaders/example.comp /path/to/shaderutils/fooutils.glsl
$ cat build/CMakeFiles/d/*.d
spirv/example.spv: \
../shaders/example.comp \
../shaderutils/fooutils.glsl
Now on to the implementation
cmake_minimum_required(VERSION 3.22)
project(test)
function(target_shader_function TARGET)
find_package(Vulkan REQUIRED)
if (NOT TARGET Vulkan::glslc)
message(FATAL_ERROR "Could not find glslc")
endif ()
foreach (source IN LISTS ARGN)
cmake_path(ABSOLUTE_PATH source OUTPUT_VARIABLE source_abs)
cmake_path(GET source STEM basename)
set(depfile "spirv/${basename}.d")
set(output "spirv/${basename}.spv")
set(dirs "$<TARGET_PROPERTY:${TARGET},INCLUDE_DIRECTORIES>")
set(include_flags "$<$<BOOL:${dirs}>:-I$<JOIN:${dirs},;-I>>")
add_custom_command(
OUTPUT "${output}"
COMMAND "${CMAKE_COMMAND}" -E make_directory spirv
COMMAND Vulkan::glslc -MD -MF "${depfile}" -O "${source_abs}"
-o "${output}" --target-env=vulkan1.2 "${include_flags}"
DEPENDS "${source_abs}"
BYPRODUCTS "${depfile}"
COMMENT "Compiling SPIRV: ${source} -> ${output}"
DEPFILE "${depfile}"
VERBATIM
COMMAND_EXPAND_LISTS
)
set(shader_target "${TARGET}_${basename}")
add_custom_target("${shader_target}"
DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${output}")
add_dependencies("${TARGET}" "${shader_target}")
endforeach ()
endfunction()
add_executable(my_target main.cpp)
target_shader_function(my_target shaders/example.comp)
target_include_directories(
my_target PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/shaderutils")
With a CMake minimum version of 3.20 or greater, CMP0116 will be set, which adjusts depfiles that were generated with relative paths to be relative to the top-level binary directory. You can see this in action in the last two command outputs.
For compatibility with this policy, the command to invoke glslc is careful to use only absolute paths or paths relative to ${CMAKE_CURRENT_BINARY_DIR}.
To increase the reusability of this function, I had it reuse the include paths from the TARGET rather than hard-coding shaderutils.
Also remember to always pass absolute paths to the DEPENDS arguments of add_custom_{command,target} to avoid surprising path resolution behaviors.
Finally, since CMake actually comes with a FindVulkan module that can locate glslc, we use that to get the Vulkan::glslc target. Per the documentation, it can be overridden by setting Vulkan_GLSLC_EXECUTABLE.
Terminal logs for VS2022 on Windows with MSVC:
> cmake -S . -B build
...
> cmake --build build --config Release
Checking Build System
Compiling SPIRV: shaders/example.comp -> spirv/example.spv
Building Custom Rule D:/test/CMakeLists.txt
Building Custom Rule D:/test/CMakeLists.txt
main.cpp
my_target.vcxproj -> D:\test\build\Release\my_target.exe
Building Custom Rule D:/test/CMakeLists.txt
> cmake --build build --config Release -- -noLogo
my_target.vcxproj -> D:\test\build\Release\my_target.exe
> notepad shaderutils\fooutils.glsl
> cmake --build build --config Release -- -noLogo
Compiling SPIRV: shaders/example.comp -> spirv/example.spv
my_target.vcxproj -> D:\test\build\Release\my_target.exe
> cmake --build build --config Release -- -noLogo
my_target.vcxproj -> D:\test\build\Release\my_target.exe
and again with Ninja instead of msbuild:
> cmake -G Ninja -S . -B build -DCMAKE_BUILD_TYPE=Release ^
-DVulkan_ROOT=C:/VulkanSDK/1.2.198.1
...
> powershell "cmake --build build | tee output.txt"
[1/3] Compiling SPIRV: shaders/example.comp -> spirv/example.spv
[2/3] Building CXX object CMakeFiles\my_target.dir\main.cpp.obj
[3/3] Linking CXX executable my_target.exe
> powershell "cmake --build build | tee output.txt"
ninja: no work to do.
> notepad shaderutils\fooutils.glsl
> powershell "cmake --build build | tee output.txt"
[1/1] Compiling SPIRV: shaders/example.comp -> spirv/example.spv
The little powershell + tee trick is just to keep the Ninja command log from overwriting itself. I could use --verbose, but then the full command lines would be printed, rather than the tidy summaries.

How to properly link libraries from vcpkg in a CMake project

I'm testing vcpkg (on macOS) with a CMake project.
Since not all vcpkg packages have CMake find modules, I'm trying with a package that doesn't have one: libuuid
This is the directory tree relative to libuuid I can see from the vcpkg root:
$ find packages/libuuid_x64-osx
packages/libuuid_x64-osx
packages/libuuid_x64-osx/include
packages/libuuid_x64-osx/include/uuid
packages/libuuid_x64-osx/include/uuid/uuid.h
packages/libuuid_x64-osx/BUILD_INFO
packages/libuuid_x64-osx/lib
packages/libuuid_x64-osx/lib/libuuid.a
packages/libuuid_x64-osx/CONTROL
packages/libuuid_x64-osx/debug
packages/libuuid_x64-osx/debug/lib
packages/libuuid_x64-osx/debug/lib/libuuid.a
packages/libuuid_x64-osx/share
packages/libuuid_x64-osx/share/libuuid
packages/libuuid_x64-osx/share/libuuid/copyright
Example program:
#include <iostream>
#include <uuid/uuid.h>
int main(int argc, char **argv)
{
std::cout << "Hello, world!" << std::endl;
return 0;
}
Example CMakeLists.txt
cmake_minimum_required(VERSION 3.13)
project(vcpkg_example_project)
add_executable(app app.cpp)
target_link_libraries(app uuid)
If I understand correctly, vcpkg's philosophy is not to provide missing CMake find-modules, but to simply have #include <libfoo/foo.h> work out of the box. And in fact the example above compiles fine. But fails to find -luuid:
$ cmake -DCMAKE_TOOCHAIN_FILE=/Users/me/Dev/vcpkg/scripts/buildsystems/vcpkg.cmake ..
...
$ cmake --build .
Scanning dependencies of target app
[ 50%] Building CXX object CMakeFiles/app.dir/app.cpp.o
[100%] Linking CXX executable app
ld: library not found for -luuid
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make[2]: *** [app] Error 1
make[1]: *** [CMakeFiles/app.dir/all] Error 2
make: *** [all] Error 2
What am I missing?
Also, I see there is a installed/x64-osx/lib/libuuid.a. Shouldn't installed/x64-osx/lib automatically added as lib path by the toolchain cmake script?
I'd make a target out of uuid. From what you describe, most probably an Interface Library called uuid. You can add_target_include_directories and target_link_libraries for the headers and any libraries, and then add it to the rest of your project.
So something like this:
add_library(uuid INTERFACE)
if(${CMAKE_BUILD_TYPE} STREQUAL "Release")
find_library(LIBUUID uuid "${CMAKE_CURRENT_SOURCE_DIR}/packages/libuuid_x64-osx/lib/")
else()
find_library(LIBUUID uuid "${CMAKE_CURRENT_SOURCE_DIR}/packages/libuuid_x64-osx/debug/lib/")
endif()
target_link_libraries(uuid INTERFACE "${LIBUUID}")
target_include_directories(uuid SYSTEM INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/packages/libuuid_x64-osx/include")
I'd then do an add_subdirectory to the library's folder and link to uuid

How can I build multiple targets using cmake --build

I have a CMake build with a bunch of different targets A, B, C, etc.
An external application is tasked with building, and currently does so by calling
cmake --build .
However, this builds all targets, and sometimes I only want to build a subset, like A and B but not C.
The --target flag can only be given once, and only accepts a single target.
I guess I could let CMake generate the appropriate Makefile, and then call make A B explicitly, but that takes away the nice thing about cmake --build being build system agnostic.
Is there a nice way to solve this?
CMake version 3.15 added support for this feature. Simply list all targets on the command line as follows:
cmake --build . --target Library1 Library2
Maybe not the "nicest" way, but definitely a solution would be to introduce a custom top-level target and make the needed targets depend on it. For example:
cmake_minimum_required(VERSION 3.9) # can be lower
project(demo LANGUAGES C)
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/a.c"
[[
#include <stdio.h>
int main(void) { printf("a\n"); return 0; }
]])
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/b.c"
[[
#include <stdio.h>
int main(void) { printf("b\n"); return 0; }
]])
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/c.c"
[[
#include <stdio.h>
int main(void) { printf("c\n"); return 0; }
]])
add_executable(A "${CMAKE_CURRENT_BINARY_DIR}/a.c")
add_executable(B "${CMAKE_CURRENT_BINARY_DIR}/b.c")
add_executable(C "${CMAKE_CURRENT_BINARY_DIR}/c.c")
set(DEMO_ENABLE_TARGETS "" CACHE
STRING "Targets to be built in demo simultaneously (default: none)")
if(NOT "${DEMO_ENABLE_TARGETS}" STREQUAL "")
add_custom_target(enabled_targets)
foreach(target IN LISTS DEMO_ENABLE_TARGETS)
add_dependencies(enabled_targets ${target})
endforeach()
endif()
Then invoke
$ cmake -H. -Bbuild -DDEMO_ENABLE_TARGETS="B;C"
$ cmake --build build --target enabled_targets
and only B and C will be built.
Note that you have to specify DEMO_ENABLE_TARGETS's contents as a list, otherwise it'll break.

How to integrate such kind of source generator into CMake build chain?

I'm writing a C++ project which use a code generator (perl xsubpp). It generates C/C++ source code from XS file. As xsubpp sometimes produce incomplete output file, I want to have it run before the actual binary target is built, regardless there exists generated source file.
I can find out two ways to achieve it:
# the target is always out-of-date, so the command is always run
add_custom_target(...)
add_library(lib_name ...)
add_dependencies(lib_name ...)
and
add_library(lib_name ...)
# the command is always run before lib_name is build
add_custom_command(TARGET lib_name PRE_BUILD ...)
However, none of them works, because add_library() checks source file at configure time. The source file must either exist, or as an output target of add_custom_command().
For the first way, the add_custom_target() don't have the concept of output target; and for the second way, the add_custom_command() is used as an auxiliary of lib_name, which also don't have the concept of output target.
The following works for me. I hope that this is what you want.
The source (foo.cpp) is re-generated every time I run make.
src/C_generated/CMakeLists.txt:
add_custom_target(generate_foo
touch ${CMAKE_CURRENT_SOURCE_DIR}/script.sh
COMMENT "add_custom_target, touch script.sh"
)
ADD_CUSTOM_COMMAND(
TARGET generate_foo
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/script.sh
ARGS "some args"
COMMENT "custom commands, executing script.sh"
)
set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/foo.cpp PROPERTIES GENERATED 1)
add_library(LIBC ${CMAKE_CURRENT_BINARY_DIR}/foo.cpp)
ADD_DEPENDENCIES(LIBC generate_foo)
src/C_generated/script.sh:
#!/bin/bash
echo "Running script.sh"
echo "#include <stdio.h>" > foo.cpp
echo "/*" >> foo.cpp
date >> foo.cpp
echo "*/" >> foo.cpp
echo >> foo.cpp
echo "void testC()" >> foo.cpp
echo "{" >> foo.cpp
echo " printf(\"Generated source.\");" >> foo.cpp
echo "}" >> foo.cpp
Main CMakeLists.txt which combines generated source with non-generated source:
project(test)
cmake_minimum_required(VERSION 2.8)
INCLUDE_DIRECTORIES(src)
ADD_SUBDIRECTORY(src/A)
ADD_SUBDIRECTORY(src/B)
# Generated files only.
ADD_SUBDIRECTORY(src/C_generated)
# Combine the different libraries into one.
add_library(TESTLIB STATIC src/dummy.c)
ADD_DEPENDENCIES(TESTLIB LIBA)
ADD_DEPENDENCIES(TESTLIB LIBB)
ADD_DEPENDENCIES(TESTLIB LIBC)
GET_TARGET_PROPERTY(LIBA_LOC LIBA LOCATION)
GET_TARGET_PROPERTY(LIBB_LOC LIBB LOCATION)
GET_TARGET_PROPERTY(LIBC_LOC LIBC LOCATION)
SET_TARGET_PROPERTIES(TESTLIB PROPERTIES STATIC_LIBRARY_FLAGS "${LIBA_LOC} ${LIBB_LOC} ${LIBC_LOC}")
Download this example from:
https://dl.dropboxusercontent.com/u/68798379/cmake-code-generator.tar.bz2