CMake: copy_directory custom command fails on macOS - macos

I have a cross-platform (Windows, Mac, iOS) CMake project that needs to copy binary resources (images, audio, text files, etc.) to the final application. I previously just added every resource to the add_executable command, and that worked fine until I wanted to organize everything into sub-directories.
To copy application assets while keeping the folder structure I ended up using the following custom command in the windows target:
add_custom_command(
TARGET ${APP_TARGET_NAME} PRE_LINK
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/${APP_ASSETS_DIR}
$<TARGET_FILE_DIR:${APP_TARGET_NAME}>/assets
)
Which works quite well. The problem comes when I try to do the same thing on macOS. When I use the same $<TARGET_FILE_DIR> trick on macOS I get the address of the output application bundle (which is very good), so I add a /resources to the end of that and try to copy there, but this fails.
In XCode I get the following error:
PhaseScriptExecution CMake\ PreLink\ Rules /Users/someuser/Documents/Build/Test/Test.build/Debug/Test_MAC.build/Script-2CF8724D08BC49E4A13B9E75.sh
cd /Users/someuser/Documents/GitHub/Test
/bin/sh -c /Users/someuser/Documents/Build/Test/Test.build/Debug/Test_MAC.build/Script-2CF8724D08BC49E4A13B9E75.sh
/Users/someuser/Documents/Build/Test/Debug/Test_MAC.app/Contents/MacOS/Test_MAC -E copy_directory /Users/someuser/Documents/GitHub/Test/assets /Users/someuser/Documents/Build/Test/Debug/Test_MAC.app/Contents/Resources
make: *** [Test_MAC_buildpart_0] Segmentation fault: 11
Command /bin/sh failed with exit code 2
Which isn't particularly helpful. I've tried PRE_LINK and POST_BUILD as well as copy_directory and copy.
Why is this failing? I haven't been able to test on iOS, but I imagine that the two would work in a similar way.
How can I copy my assets to the bundle on macOS (also iOS)?

There's not much helpful documentation for framework/bundle generation with CMake on the net. Most wisdom is hidden in CMakeLists.txt files and not in CMake tutorials.
Both methods are known to work, but are a bit tricky to get right. I used them both together on a versioned framework project with CMake's Unix Makefiles generator.
First you add resources to your framework/bundle (mine has the same name as the project, therefore I use ${PROJECT_NAME} throughout the following lines) either by adding them as arguments to add_library, e.g.
add_library(${PROJECT_NAME} SHARED ${SOURCES} ${HEADERS} ${RESOURCES})
Then set your target properties for a framework/bundle:
if(BUILD_MAC_FRAMEWORK)
# Set the framework target properties just to make sure the relevant links inside the framework
# are created. Because the default framework name differs from the project name we change the
# output name property.
set_target_properties(${PROJECT_NAME} PROPERTIES
FRAMEWORK TRUE
FRAMEWORK_VERSION ${MAC_FRAMEWORK_VERSION}
#PUBLIC_HEADER "${PUBLIC_HEADERS}" # does not work recursively
#PRIVATE_HEADER "${PRIVATE_HEADERS}" # does not work recursively
RESOURCE "${CMAKE_BINARY_DIR}/version.plist" # does not work recursively
MACOSX_FRAMEWORK_IDENTIFIER org.company.sampleFramework.framework
MACOSX_FRAMEWORK_SHORT_VERSION_STRING ${PROJECT_VERSION}
MACOSX_FRAMEWORK_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_FRAMEWORK_INFO_PLIST "${CMAKE_BINARY_DIR}/Info.plist"
OUTPUT_NAME ${MAC_FRAMEWORK_NAME}
)
endif()
Later set the framework path for the resource files with respect to Resources directory with a helper function
# determine which subdirectory this file (header, resource) should be installed into.
function(set_macosx_properties _removable_prefixes _install_prefix _source_files)
foreach(_file ${_source_files})
get_filename_component(_loc "${_file}" DIRECTORY)
foreach(_prefix ${_removable_prefixes})
string(REPLACE "${_prefix}" "" _loc "${_loc}")
endforeach()
set_source_files_properties(${_file} PROPERTIES MACOSX_PACKAGE_LOCATION ${_install_prefix}${_loc})
endforeach()
endfunction()
set_macosx_properties("${CMAKE_SOURCE_DIR}/include;${CMAKE_BINARY_DIR}/include" "Headers" "${HEADERS}")
set_macosx_properties("${CMAKE_SOURCE_DIR}/data;${CMAKE_BINARY_DIR}/data" "Resources" "${RESOURCES}")
The helper function removes the specified prefixes and replaces them with an installation prefix.
Make sure both commands (add_library and set_macosx_properties) are called from the same directory/CMakeLists.txt! Different locations for the commands do not work.
Or you use a call to add_custom_command as follows. I do not remember if other type of targets did not work, but I have always used POST_BUILD custom targets.
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory $<TARGET_FILE_DIR:${PROJECT_NAME}>/Documentation
COMMAND ${CMAKE_COMMAND} -E create_symlink ./Versions/Current/Documentation $<TARGET_FILE_DIR:${PROJECT_NAME}>/../../Documentation
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_BINARY_DIR}/html $<TARGET_FILE_DIR:${PROJECT_NAME}>/Documentation/html)
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E remove $<TARGET_FILE_DIR:${PROJECT_NAME}>/Documentation/html/${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.qch
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/html/${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.qch $<TARGET_FILE_DIR:${PROJECT_NAME}>/Documentation)
Finally, installation is done by single command:
install(TARGETS ${PROJECT_NAME} FRAMEWORK DESTINATION . COMPONENT development)

Related

cmake 'add_custom_command' to pre-process header files?

i'm working on a project requiring cmake. i'd like to add some custom rules to my makefile, but can't quite get my head around how to do it.
both c source files and header files are in the same directory. also in this same directory are a number of .def files, which are the sources for some of the header files #included in the source during compilation.
if i were to do this in a makefile, i'd use a simple rule like
.SUFFIXES: .def
.def.h:
$(PREPROC) $< > $#
how can i do this with cmake ??
i've tried various permutations of the following, both with and without cmake working directory specifications :
add_custom_command(
OUTPUT vvr_const.h
PRE_BUILD
COMMAND preproc vvr_const.def > vvr_const.h
DEPENDS vvr_const.def
)
add_custom_target(vvr_const.h DEPENDS vvr_const.def)
but the header file isn't generated by the time the c source file is compiled, so the compile fails. i've also tried a variation where i replace the last line above with
set_property(SOURCE main.c APPEND PROPERTY OBJECT_DEPENDS vvr_const.h)
in this case, the header file is correctly generated in advance, but make can't find it, and complains that there's no rule to make the target .h.
ideally this would be a general rule, like the make rule above, but i'm not opposed to making a separate rule for each of the .def files if that's what it takes.
cheers.
There are 2 problems with the add_custom_command approach you present:
You did not specify a working directory; by default the command is run in the build directory, not in the source directory.
You rely on shell functionality here (the redirect to a file). Even though this probably still works. You should go with an approach that does not rely on the shell.
To solve issues 1 and 2 I recommend creating a seperate cmake script file receiving the absolute paths to input and output files and using those in the custom command. This allows you to use execute_process to specify the file to write without relying on the platform.
preprocess_def.cmake
# preprocess def file
# parameters INPUT_FILE and OUTPUT_FILE denote the file to use as source
# and the file to write the results to respectively
# use preproc tool to get data to write to the output file
execute_process(COMMAND preproc "${INPUT_FILE}"
RESULT_VARIABLE _EXIT_CODE
OUTPUT_FILE "${OUTPUT_FILE}")
if (_EXIT_CODE)
message(FATAL_ERROR "An error occured when preprocessing the file ${INPUT_FILE}")
endif()
CMakeLists.txt
set(_INPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/vvr_const.def")
set(_OUTPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/vvr_const.h")
# not necessary to use build event here, if we mark the output file as generated
add_custom_command(OUTPUT "${_OUTPUT_FILE}"
COMMAND "${CMAKE_BUILD_TOOL}" -D "OUPUT_FILE=${_OUTPUT_FILE}" -D "INPUT_FILE=${_INPUT_FILE}" -P "${CMAKE_CURRENT_SOURCE_DIR}/preprocess_def.cmake"
DEPENDS "${_INPUT_FILE}")
add_executable(my_target vvr_const.h ...)
set_source_files_properties(vvr_const.h PROPERTIES GENERATED 1)
Documentation from cmake:
PRE_BUILD
On Visual Studio Generators, run before any other rules are executed within the target. On other generators, run just before PRE_LINK commands.
So possibly your command is just running too late.

CMake set_property command with generator expressions using build type

I'm creating a CMake project using v3.14.4 and the "Visual Studio 15 2017 Win64" generator. During the build, a .hlsl file is also to be compiled and stored in the same directory as the .exe. Something is preventing my generator for the hlsl file's VS_SHADER_OBJECT_FILE_NAME property from correctly being processed.
This is my CMake statement:
set_property(SOURCE shader.hlsl PROPERTY VS_SHADER_OBJECT_FILE_NAME
$<$<CONFIG:Release>:Release/shader.dxbc>
$<$<CONFIG:Debug>:Debug/shader.dxbc>)
The result in VS shows the expression was pretty much passed through.
$<$<CONFIG:Release>:Release/shader.dxbc>;$<$<CONFIG:Debug>:Debug/shader.dxbc>
I've used generators successfully with the target_compile_definitions() statement and set_property() is to also be supported. Is my usage malformed or better written a different way?
As pointed out by Tsyvarev, the VS_SHADER_OBJECT_FILE_NAME property doesn't support generator expressions. One solution to this is to copy the compiled shader to the desired destination for each build type, in this case the same directory as the .exe. The below function copy_compiled_shader will copy SHADERBC_FILE to a new directory determined by using a generator function.
set_property(SOURCE shader.hlsl PROPERTY VS_SHADER_OBJECT_FILE_NAME shader.dxbc)
function(copy_compiled_shader SHADERBC_FILE)
add_custom_command(
TARGET TestCS
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "Copying the shader ${SHADERBC_FILE}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${SHADERBC_FILE} $<TARGET_FILE_DIR:TestCS>/${SHADERBC_FILE}
)
endfunction()
copy_compiled_shader(shader.dxbc)

Cmake doesn't recognize custom command as valid source

I have a problem modifying existing CMake based build-system. All I want to do is add different build rule for some of .c files. For the sake of this problem, I will focus on only one file.
The simplified directories tree looks like this
Project:
./src
- file_of_interest.c
- CmakeFiles.txt
other_files.c
CmakeFiles.txt
So in order to compile file_of_interest.c in a different way I do:
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/file_of_interest.s
COMMAND xt-xcc -S ${CMAKE_CURRENT_SOURCE_DIR}/file_of_interest.c
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/file.c
COMMENT "Generating file_of_interest.s"
)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/file_of_interest.c.o
COMMAND xt-xcc ${CMAKE_CURRENT_BINARY_DIR}/file_of_interest.s -o file_of_interest.c.o -c
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/file_of_interest.s
COMMENT "Generating file_of_interest.c.o"
)
message(" ${CMAKE_CURRENT_BINARY_DIR} \n ${CMAKE_CURRENT_SOURCE_DIR}")
target_sources(target_name PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/file_of_interest.c.o)
As you can see I used CMake's message() to print paths to be sure all is set up properly. It should work, but it doesn't! I expect CMake to register file_of_interest.c.o as source of target_name (and this is probably working), then "tie" it with my custom command which produces OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/file_of_interest.c.o and then to tie again ${CMAKE_CURRENT_BINARY_DIR}/file_of_interest.c.o with OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/file_of_interest.s and this certainly doesn't happen as CMake shows error message saying CMake Error at CMakeLists.txt:170 (add_executable): Cannot find source file: path/file_of_interest.c.o
The path is OK. So the question is why does CMake cannot recognize recipes for that file?
It seems that you create an executable (call add_executable) in the top-level CMakeLists.txt, but add_custom_command are called from the subdirectory (src/CMakeLists.txt).
This doesn't work: when CMake processes add_executable and searches for the commands generating its sources, it sees only add_custom_commands created in the same CMakeLists.txt.
From the add_custom_command documentation:
A target created in the same directory (CMakeLists.txt file) that specifies any output of the custom command as a source file is given a rule to generate the file using the command at build time.
It is unrelated that target_sources is called from the same directory as add_custom_command: the target is created by add_executable command, and target_sources just modifies its properties.

Get rid of CMake Rules folder

Is there a way to get rid of the CMake Rules folders in targets in Visual Studio?
In this case the game target uses cotire for precompiled header support and the omg target has a custom command that parses the .mix file and outputs the ..._gen.h file.
Also would it be possible to remove the CMakeLists.txt file from there too? I know it's there for convenience but in my workflow it isn't that useful.
EDIT: Started a second bounty because I really need this - these "CMake Rules" folders are bloating my solution explorer because I have 100+ projects which all have them!
I would accept anything - a CMake way, a VS way (some "solution explorer view" or macro or whatever)...
EDIT 2:
here is sort-of the cmake for the omg target:
add_library(omg SHARED D:/omg.cpp D:/omg.mix)
add_custom_command(OUTPUT D:/omg_gen.h MAIN_DEPENDENCY D:/omg.mix COMMAND python D:/mixify.py D:/omg.mix D:/omg_gen.h)
add_custom_target(${target}_gen_${mix_name_only} DEPENDS ${gen_header})
add_dependencies(omg omg_gen)
target_sources(omg PUBLIC D:/omg_gen.h)
so omg_gen.h is generated from omg.mix and then included in omg.cpp
The .rule files are needed by CMake to attach the custom commands to some "dummy" self-generated file, if there are no input files given. You can see this when you look at the .rule file properties of your Visual Studio project in question (see Custom Build Tool/General/Command Line).
If you're not changing the CMake script code containing those target, you can't get rid of them.
You can only move them into the project's root source folder or any other folder you specify.
I've successfully tested the following example:
cmake_minimum_required(VERSION 2.8)
project(NoRulesSourceGroup NONE)
# Overwrite the rule for "CMake Rules" with do-not-match-anything (-> root)
source_group("CMake Rules" REGULAR_EXPRESSION "^$")
# Move ".rule" files somewhere else
source_group("Some Other Source Group" REGULAR_EXPRESSION "\\.rule$")
add_custom_target(
${PROJECT_NAME}
COMMAND ${CMAKE_COMMAND} -E echo "Hello World"
)
Edit: You could also combine the above regular expression with a match for CMakeLists.txt:
source_group("Some Other Source Group" REGULAR_EXPRESSION "CMakeLists\\.txt|\\.rule$")
Edit: If you can modify your CMake script code, you should add to your add_custom_command() call:
MAIN_DEPENDENCY
Specify the primary input source file to the command. This is treated just like any value given to the DEPENDS option but also suggests to Visual Studio generators where to hang the custom command. At most one custom command may specify a given source file as its main dependency.
In your case you won't even need the additional custom target since you have a dependency through the header file. I've successfully tested the following derived from your question's example:
file(WRITE omg.cpp "")
file(WRITE omg.mix "")
add_library(omg SHARED omg.cpp omg.mix)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/omg_gen.h
MAIN_DEPENDENCY omg.mix
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/omg.mix ${CMAKE_CURRENT_BINARY_DIR}/omg_gen.h
)
target_sources(omg PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/omg_gen.h)
References
source_group()
Source/cmMakefile.cxx

How to test the static_assert in my library with cmake?

in my library code I have a bunch of static_asserts. I want to test if they fire under the expected conditions.
I would like to write a range of test files and
ensure that they fail to compile
check the output of the compilation attempt for the expected message from the static assert
Does anyone know how to do that with cmake?
AFAICT, try_compile is not the answer, because it is executed while running cmake. I need these checks to be executed during make.
You could set up a "nested" project for these tests, configure it as part of your CMake run and then build it using cmake --build; something like this:
Your normal CMakeLists.txt:
# ...
execute_process(
COMMAND ${CMAKE_COMMAND} path/to/test/project
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/TestProject
)
add_test(
NAME StaticAsserts
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/TestProject
COMMAND ${CMAKE_COMMAND} --build .
)
# ...
Of course, the test command could actually be a wrapper script running cmake --build internally and processing its output.
As an alternative, you could move the configuration of the nested project into the test as well, perhaps using CTest to drive the configure & build.
After several more experiments, this is what I am doing now:
add_executable(
fail_test
EXCLUDE_FROM_ALL
fail_test.cpp
)
add_custom_command(OUTPUT fail_test.out
COMMAND ${CMAKE_MAKE_PROGRAM} fail_test > ${CMAKE_CURRENT_BINARY_DIR}/fail_test.out 2>&1 || true
COMMAND grep "int i = row.alpha" ${CMAKE_CURRENT_BINARY_DIR}/fail_test.out > /dev/null
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/fail_test.cpp
COMMENT "fail_test"
)
add_custom_target(test_constraints DEPENDS fail_test.out COMMAND true)
This is what it does:
Create a target for compiling the code that is supposed to fail at compile time. Exclude this target from the default build so that it does not create a mess.
Add a custom command that calls make for this fail-target, pipes the compiler spew into a file, ignores the result code and then greps for the expected compiler message that indicates that the compilation failed due to the expected reason.
Make the custom command depend on the source file, so that the compilation is tested again when the source file is ended.
Add the custom command as dependency to a custom target.
Of course, for more tests, steps 1, 2 and 4 would go into a function.
So now I can call
make test_constraints
to test if the stuff I want to fail compiling actually does so. And if something does not fail as expected, I can even call
make fail_test
to tune the test or its basis until it fails correctly.
For platform independence, the custom command will probably have to be adjusted. Suggestions welcome.
Add this to your CMakeLists.txt:
include(CTest)
# Test that code is NOT able to compile
function(TestStaticCheck TEST_NAME)
add_executable(${TEST_NAME} EXCLUDE_FROM_ALL ${TEST_NAME}.cpp)
target_link_libraries(${TEST_NAME} MyLibrary)
add_test(NAME ${TEST_NAME}
COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target ${TEST_NAME}
)
set_tests_properties(${TEST_NAME} PROPERTIES WILL_FAIL TRUE)
endfunction()
TestStaticCheck(TestFoo)
Then put this in TestFoo.cpp in the same directory as your CMakeLists.txt:
int main()
{
// Code that should NOT compile
}

Resources