Consider the following example:
CmakeLists.txt
cmake_minimum_required(VERSION 3.13)
SET(CMAKE_INCLUDE_CURRENT_DIR ON)
project(my_project)
message("PROJECT_NAME is '${PROJECT_NAME}'")
set(my_incls "D:/msys64/usr/include" "D:/msys64/usr/lib/glib-2.0/include")
message("my_incls ${my_incls}")
add_library(test main.c)
target_include_directories(test PRIVATE ${my_incls})
get_target_property(libincl test INCLUDE_DIRECTORIES)
message("libincl ${libincl}")
main.c
#include <stdio.h>
const char greeting[] = "hello world";
int main() {
printf("%s!\n", greeting);
return 0;
}
I do this in MSYS2 bash shell on Windows 10, relative to the folder the above files are in:
mkdir build
cd build
cmake ../ -G "Unix Makefiles"
The output of cmake is:
$ cmake ../ -G "Unix Makefiles"
-- The C compiler identification is GNU 11.3.0
...
PROJECT_NAME is 'my_project'
my_incls D:/msys64/usr/include;D:/msys64/usr/lib/glib-2.0/include
libincl /tmp/cmake_test/D:/msys64/usr/include;/tmp/cmake_test/D:/msys64/usr/lib/glib-2.0/include
-- Configuring done
...
So, you can see that in my_incls the folder paths are listed exactly as specified - but once they have been set as target_include_directories and are read back for printing, they have been changed: namely /tmp/cmake_test (the current working directory - the folder where I placed the files given above) has been prepended to the paths as specified, making them totally unusable.
How can I prevent CMake from modifying paths that I've specified for target_include_directories?
Related
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.
I try to build my archive using linux gcc and got this error:
/usr/bin/ar: -v: No such file or directory
I can't understand how to give linker flags for gcc makefiles
add_library(libMyExample STATIC ${SOURCES} ${HEADERS})
target_include_directories(libMyExample PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
)
set_target_properties(libMyExample PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${LIB_DIR}$<0:>"
STATIC_LIBRARY_OPTIONS -v
PREFIX ""
)
If I delete STATIC_LIBRARY_OPTIONS -v, it's ok.
Consider the following mock project with these files:
# CMakeLists.txt
cmake_minimum_required(VERSION 3.13)
project(myproject)
add_subdirectory(sub)
# sub/CMakeLists.txt
add_library(myLib OBJECT bar.c)
add_executable(foo foo.c)
target_link_libraries(foo PRIVATE myLib)
# sub/foo.c
int main() {return 0;}
# sub/bar.c
void bar(){}
When building in source after cmake -G Unix\ Makefiles -B . -S . I can go to the the directory sub and execute make bar.o. How can I achieve the same results when building out of source? I've tried these commands from the project root:
cmake -G Unix\ Makefiles -B test-build -S .
cmake --build test-build --target sub/bar.o
cmake --build test-build --target bar.o
cmake --build test-build --target bar
I get these kinds of error messages gmake: *** No rule to make target 'sub/bar.o'. Stop.
I know it's possible to build the library, but in reality our library is quite large and I would like to focus on one file at the time.
I have a project whose directory layout looks like:
- src/ #Contains main source code
- ext/ #Contains external libraries and headers from GitHub
- CMakeLists.txt
The problem is that no matter what I do, CMake always seems to pass ext/ to the compiler as a relative path, like this:
/usr/bin/c++ -I../ext mysrc.cpp
I've tried doing both:
include_directories("${PROJECT_SOURCE_DIR}/ext")
include_directories("/home/user/project/ext")
But it doesn't seem to matter. The directory is always passed to -I as ../ext.
Why does this matter? At the end of my build I invoke gcov -r <source file> which tells gcov to generate coverage reports from my source file and any relative paths found within. As a result, gcov is going into ext/ and generating reports for tons of stuff I don't care about and it's taking up a lot of time. If CMake would instead pass in -I/home/user/project/ext then gcov -r would ignore everything in ext/.
As far as I can tell from:
https://cmake.org/cmake/help/v3.13/command/include_directories.html ... this isn't possible, but maybe I'm just missing something?
Edit: This appears to be a problem with specifically the ninja generator. When using the Unix Makefiles generator, everything is passed via absolute paths.
https://gitlab.kitware.com/cmake/cmake/issues/18666
Edit2:
user#antimony:~/cmake_test$ ls
CMakeLists.txt ext src
user#antimony:~/cmake_test$ cat CMakeLists.txt
project(Hello)
add_subdirectory(src)
user#antimony:~/cmake_test$ cat src/CMakeLists.txt
include_directories(
.
${PROJECT_SOURCE_DIR}/ext
)
add_executable(hello_world hello.cpp)
user#antimony:~/cmake_test$ cat src/hello.cpp
#include <useless.h>
int main()
{
hello h;
return 0;
}
user#antimony:~/cmake_test$ cat ext/useless.h
struct hello {
int x;
};
user#antimony:~/cmake_test$ ~/Downloads/cmake-3.13.1-Linux-x86_64/bin/cmake --version
cmake version 3.13.1
CMake suite maintained and supported by Kitware (kitware.com/cmake).
user#antimony:~/cmake_test$ mkdir build && cd build
user#antimony:~/cmake_test/build$ ~/Downloads/cmake-3.13.1-Linux-x86_64/bin/cmake .. -G Ninja
-- The C compiler identification is GNU 7.3.0
-- The CXX compiler identification is GNU 7.3.0
...
-- Build files have been written to: /home/user/cmake_test/build
user#antimony:~/cmake_test/build$ ninja -v
[1/2] /usr/bin/c++ -I../src/. -I../ext -MD -MT src/CMakeFiles/hello_world.dir/hello.o -MF src/CMakeFiles/hello_world.dir/hello.o.d -o src/CMakeFiles/hello_world.dir/hello.o -c ../src/hello.cpp
[2/2] : && /usr/bin/c++ -rdynamic src/CMakeFiles/hello_world.dir/hello.o -o src/hello_world && :
user#antimony:~/cmake_test/build$ cat build.ninja
# CMAKE generated file: DO NOT EDIT!
# Generated by "Ninja" Generator, CMake Version 3.13
# This file contains all the build statements describing the
# compilation DAG.
...
#############################################
# Order-only phony target for hello_world
build cmake_object_order_depends_target_hello_world: phony || src/CMakeFiles/hello_world.dir
build src/CMakeFiles/hello_world.dir/hello.o: CXX_COMPILER__hello_world ../src/hello.cpp || cmake_object_order_depends_target_hello_world
DEP_FILE = src/CMakeFiles/hello_world.dir/hello.o.d
INCLUDES = -I../src/. -I../ext
OBJECT_DIR = src/CMakeFiles/hello_world.dir
OBJECT_FILE_DIR = src/CMakeFiles/hello_world.dir
TARGET_COMPILE_PDB = src/CMakeFiles/hello_world.dir/
TARGET_PDB = src/hello_world.pdb
# =============================================================================
# Link build statements for EXECUTABLE target hello_world
The example shows what may be considered an in-source build. That is when the build directory is the same or a sub-directory of the src folder (not that there is a hard definition or anything, but this does trigger the ninja issue of using relative paths on the command line). Try mkdir ~/cmake_build && cd ~/cmake_build && cmake ~/cmake_test then it should use absolute paths for everything.
Either way there really isn't a specific way to force one or the other. In general cmake generators will use absolute paths for everything that ends up used on the command line. There seems to be issues with Ninja that prevent the generator from using absolute paths for in-source builds (https://github.com/ninja-build/ninja/issues/1251).
When using CMake for cross compiling, one generally specifies a toolchain file via the CMAKE_TOOLCHAIN_FILE option. In GNU terminology, one can specify the host architecture toolset using this file. However, one can generally not expect to be able to execute anything built with this toolchain. So often enough, some build tools need to be compiled for the build architecture.
Consider the following setup. I have two source files genfoo.c and bar.c. During build, genfoo.c needs to be compiled and run. Its output needs to be written to foo.h. Then I can compile bar.c, which #include "foo.h". Since CMake defaults to using the host architecture toolchain, the instructions for bar.c are easy. But how do I tell it to use the build architecture toolchain for compiling genfoo.c? Simply saying add_executable(genfoo genfoo.c) will result in using the wrong compiler.
CMake can only handle one compiler at a time. So - if you don't go the long way to set up the other compiler as a new language - you will end up with two configuration cycles.
I see the following approaches to automate this process:
Taking the example "CMake Cross Compiling - Using executables in the build created during the build?" from the CMake pages as a starting point I'll get:
CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(FooBarTest)
# When crosscompiling import the executable targets
if (CMAKE_CROSSCOMPILING)
set(IMPORT_PATH "IMPORTFILE-NOTFOUND" CACHE FILEPATH "Point it to the export file path from a native build")
file(TO_CMAKE_PATH "${IMPORT_PATH}" IMPORT_PATH_CMAKE)
include(${IMPORT_PATH_CMAKE}/genfooTargets.cmake)
# Then use the target name as COMMAND, CMake >= 2.6 knows how to handle this
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/foo.h
COMMAND genfoo
)
add_executable(bar bar.cpp ${CMAKE_CURRENT_BINARY_DIR}/foo.h)
target_include_directories(bar PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
endif()
# Only build the generator if not crosscompiling
if (NOT CMAKE_CROSSCOMPILING)
add_executable(genfoo genfoo.cpp)
export(TARGETS genfoo FILE "${CMAKE_CURRENT_BINARY_DIR}/genfooTargets.cmake")
endif()
Then using a script like:
build.sh
#!/bin/bash
if [ ! -d hostBuild ]; then
cmake -E make_directory hostBuild
cmake -E chdir hostBuild cmake ..
fi
cmake --build hostBuild
if [ ! -d crossBuild ]; then
cmake -E make_directory crossBuild
cmake -E chdir crossBuild cmake .. -DIMPORT_PATH=${PWD}/hostBuild -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake
fi
cmake --build crossBuild
I'll get the desired results by calling ./build.sh.
Splitting the CMakeLists.txt and maybe even replace the export()/include() with something where I know the output path of my build tools e.g. by using CMAKE_RUNTIME_OUTPUT_DIRECTORY would simplify things:
CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(FooBarTest)
# Then use the target name as COMMAND. CMake >= 2.6 knows how to handle this
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/foo.h
COMMAND genfoo
)
add_executable(bar bar.cpp ${CMAKE_CURRENT_BINARY_DIR}/foo.h)
target_include_directories(bar PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
buildTools/CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(BuildTools)
add_executable(genfoo genfoo.cpp)
build.sh
#!/bin/bash
if [ ! -d crossBuild ]; then
cmake -E make_directory crossBuild
cmake -E chdir crossBuild cmake .. -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake
fi
if [ ! -d hostBuild ]; then
cmake -E make_directory hostBuild
cmake -E chdir hostBuild cmake ../buildTools -DCMAKE_RUNTIME_OUTPUT_DIRECTORY:PATH=${PWD}/crossBuild
fi
cmake --build hostBuild
cmake --build crossBuild
References
Making a CMake library accessible by other CMake packages automatically
CMake build multiple targets in different build directories
How do I make CMake output into a 'bin' dir?
It is possible to do that completely within CMake.
The trick is to run a separate CMake configuring stage within its own space, silently dismissing every crosscompiling setting and using the host's default toolchain, then import the generated outputs into it's parent, crosscompiling build.
First part:
set(host_tools_list wxrc generate_foo)
if(CMAKE_CROSSCOMPILING)
# Pawn off the creation of the host utilities into its own dedicated space
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/host_tools)
file(TO_NATIVE_PATH ${CMAKE_COMMAND} native_cmake_command)
file(TO_NATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR} native_cmake_current_source_dir)
execute_process(
COMMAND "${native_cmake_command}" "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}" "${native_cmake_current_source_dir}"
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/host_tools
)
add_custom_target(host_tools
COMMAND ${CMAKE_COMMAND} --build . --target host_tools --config $<CONFIG>
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/host_tools
)
include(${CMAKE_CURRENT_BINARY_DIR}/host_tools/host_tools.cmake)
foreach(tgt IN ITEMS ${host_tools_list})
add_dependencies(host${tgt} host_tools)
endforeach()
else()
# Add an empty target, host tools are built inplace
add_custom_target(host_tools
DEPENDS ${host_tools_list}
)
endif()
... then you add the usual add_executable and whatever ...
At the end:
if(NOT CMAKE_CROSSCOMPILING)
foreach(tgt IN ITEMS ${host_tools_list})
add_executable(host${tgt} ALIAS ${tgt})
endforeach()
export(TARGETS ${host_tools_list} NAMESPACE host FILE host_tools.cmake)
endif()
When it crosscompiles, it pawns off the creation of the host-run tools into its own dedicated space, and imports the targets as "hostwxrc" and "hostgenerate_foo", with a dependency on generating the host_tools themselves .
When it doesn't crosscompile, it builds wxrc and generate_foo as-is, and aliases them to hostwxrc and hostgenerate_foo.
After this, when you use $<TARGET_FILE:wxrc>, you refer to the wxrc built for the target platform, and $<TARGET_FILE:hostwxrc> refers to the wxrc built for the host platform, regardless whether they are the same or not.