I'm attempting to compare code output with reference text using CTest (add_test()) and I'm getting some strange results which makes me think I'm not using cmake -E compare_files correctly. The documentation and examples are a bit thin and I haven't found an example similar to what I'm trying to do.
Backstory: I'm recovering some creaky ancient FORTRAN code which expects input via stdin and writes output to stdout. Knowing that I/O redirection in add_test() is discouraged/impossible, I use add_custom_command() as a POST_BUILD action on the executable target to run the code and redirect output. That works as expected - no problems there.
I then set up two tests resembling:
# Compare fresh output and known different example output
add_test(NAME C1_bad
COMMAND ${CMAKE_COMMAND} -E compare_files --ignore-eol C1.OUT C1_bad.OUT
CONFIGURATIONS Debug Release ""
WORKING_DIR "${MY_TEST_DIR}"
)
# Compare fresh output and known identical example output
add_test(NAME C1_ok
COMMAND ${CMAKE_COMMAND} -E compare_files --ignore-eol C1.OUT C1_ok.OUT
CONFIGURATIONS Debug Release ""
WORKING_DIR "${MY_TEST_DIR}"
)
Three files are compared here:
${MY_TEST_DIR}/C1.OUT is generated by the post-build custom command - this is fresh output.
${MY_TEST_DIR}/C1_bad.OUT is copied from the source tree and is different from C1.OUT.
${MY_TEST_DIR}/C1_ok.OUT is copied from the source tree and is binary identical to C1.OUT (confirmed by inspection and MD5 checksum).
Unexpectedly, both tests fail, not just the first one. Running cmake -E compare_files from the command line against each pair of files gives expected results - comparing C1_bad.OUT and C1.OUT fails (i.e. complains that the files are different) but comparing C1_ok.OUT and C1.OUT succeeds (returns no output; silence implies success).
My thought is that cmake -E compare_files does not return status codes expected by CTest (0 to pass, non-zero to fail) or is otherwise not intended for use with CTest. This is with version 3.18.1 on Windows if that matters; I get the same behavior with 3.18.1 on Linux. I don't want to chase down or write my own cross-platform diff utility if CMake has one already built in but it's not clear from everything I've read whether this is an expected or workable use of compare_files - any hints on what I should try next?
Related
I am using a custom command for generating C++ Lexers and Parsers from ANTLR4 grammars. Right now I have the following:
set(MY_PARSER_INC
${PROJECT_SOURCE_DIR}/Headers/MyParser/MyLexer.h
${PROJECT_SOURCE_DIR}/Headers/MyParser/MyParser.h
${PROJECT_SOURCE_DIR}/Headers/MyParser/MyParserBaseVisitor.h
${PROJECT_SOURCE_DIR}/Headers/MyParser/MyParserVisitor.h
)
set(MY_PARSER_SRC
${PROJECT_SOURCE_DIR}/Sources/MyParser/MyLexer.cpp
${PROJECT_SOURCE_DIR}/Sources/MyParser/MyParser.cpp
)
add_custom_command(
OUTPUT ${MY_PARSER_INC} ${MY_PARSER_SRC}
DEPENDS ${PROJECT_SOURCE_DIR}/Grammars/MyGrammar.g4
COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_SOURCE_DIR}/Headers/MyParser/
COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_SOURCE_DIR}/Sources/MyParser/
COMMAND java -cp "${ANTLR_CLASSPATH}" "org.antlr.v4.Tool" -Dlanguage=Cpp -visitor -no-listener -package MY::NESTED::NAMESPACE -encoding iso-8859-1 -o ${PROJECT_SOURCE_DIR}/Sources/MyParser/ ${PROJECT_SOURCE_DIR}/Grammars/MyGrammar.g4
COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/Sources/MyParser/*.h ${PROJECT_SOURCE_DIR}/Headers/MyParser/
COMMAND ${CMAKE_COMMAND} -E remove -f ${PROJECT_SOURCE_DIR}/Sources/MyParser/*.h
COMMENT "generating sources for My Parser"
)
and then I use the output files in my add_library command to maintain the dependency.
This does exactly what I expect it to do. It creates the lexers and parsers correctly. It maintains the dependency between those sources and the target library also correctly. Only one problem: It runs every time! Even when the grammar file has not been modified (I checked the file date on the grammar and generated lexers/parsers to be sure)! I have seen some similar questions online but still can't figure out why this is happening.
Any clue?!
EDIT1:
Adding more info, since it may still be unclear.
After the add_custom_command I have the following:
include_directories(${PROJECT_SOURCE_DIR}/Headers/MyParser/)
add_library(MyLibrary SHARED
${MY_PARSER_INC} ${MY_PARSER_SRC}
other_files.hpp other_files.cpp)
which I assume creates a direct dependency between the generated source files and my target library.
Here's my guess.
You need to make a custom target.
https://cmake.org/cmake/help/latest/command/add_custom_target.html?highlight=custom_target
add_custom_target(custom_target_create_parser_code DEPENDS
${MY_PARSER_INC} ${MY_PARSER_SRC}
)
This will make a target that depends on the custom command you wrote.
Now cmake has something to attach your command to.
Now you need to add the dependency to your static library you mentioned.
https://cmake.org/cmake/help/latest/command/add_dependencies.html
add_dependency(your_static_library custom_target_create_parser_code)
Now cmake shouldn't re-run your custom command code everytime.
EDIT BTW:
Craig Scott's book actually has a subchapter devoted to this topic:
https://crascit.com/professional-cmake/
Chapter 18 Custom Targets
Chapter 18.3 Commands That Generate Files
EDIT #2:
If all else fails try the official cmake discourse:
https://discourse.cmake.org/
Your question is well formed and cmake developers look their for questions.
So you should get an answer.
I would like to define a CMake variable BUILD_TIME_VAR in CMakeLists.txt:
computed with a python script during build phase
I can then access its content with ${VAR}
In other words, the equivalent during build phase of:
execute_process(COMMAND bash -c "python $SCRIPT $FILE" OUTPUT_VARIABLE GEN_TIME_VAR)
The variable is then used to generate a file which is a dependency to make binaries.
The goal is to make the code more easy to read since otherwise, the computation occur several times.
Rather than calling n times, the python script, to compute BUILD_TIME_VAR, I would like to use the script once, to factor the code in this way:
if(expression_1)
add_custom_command(OUTPUT foo
COMMAND cmd_1(${BUILD_TIME_VAR}))
...
elseif(expression_2)
# elseif section.
add_custom_command(OUTPUT foo
COMMAND cmd_2(${BUILD_TIME_VAR}))
...
else(expression_n)
# else section.
add_custom_command(OUTPUT foo
COMMAND cmd_n(${BUILD_TIME_VAR}))
...
endif(expression)
add_custom_target(${BINARY} ALL
DEPENDS foo)
Thanks you for your help.
If I understand your question correctly, you're effectively trying to create a "build-time variable." That is something that the build tool (actually all the build tools supported by CMake) would have to support. I know of no such functionality in build tools (make, ninja, VS, ...), and hence of no support for such a thing in CMake either.
You could emulate this by writing the results to a file and reading that file in all subsequent build steps using it.
I believe you've misinterpreted the role of CMake. In short, CMake generates build files for the back-end system of your choice (Make, ninja, etc.). This is concisely reviewed here, where the configure/generate step is briefly documented along with how it precedes the actual build step.
It's not clear what you're trying to achieve exactly, but maybe defining custom commands and using their outputs to link them to custom targets for further dependency chaining (see DEPENDS here) might be helpful, e.g.
cmake_minimum_required(VERSION 3.11)
project(foobar)
set(FOO_FILES "foo.txt")
add_custom_command(OUTPUT ${FOO_FILES}
COMMAND ${CMAKE_COMMAND} -E touch ${FOO_FILES}
COMMAND echo hello >> ${FOO_FILES}
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
COMMENT "Creating ${FOO_FILES}"
VERBATIM)
add_custom_target(foo DEPENDS ${FOO_FILES})
set(BAR_FILES "bar_dummy.txt") # this is only use to link the custom command to the corresponding custom target
add_custom_command(OUTPUT ${BAR_FILES}
COMMAND ${CMAKE_COMMAND} -E touch ${BAR_FILES}
COMMAND cat ${FOO_FILES}
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
COMMENT "Displaying ${FOO_FILES}"
VERBATIM)
add_custom_target(bar DEPENDS ${BAR_FILES})
add_dependencies(bar foo)
However, you might need to consider the probability of going against the tool, and thus, it might be clearer to have a required step prior to the configuration of your project.
Edit: my question targets the early configure stage where CMake input files are still being parsed and thus have to be present before include() is being called. So the answer found here: Force CMake to generate configure_file target every build does not solve my problem since it generates files after include() statements have been interpreted.
I have a CMakeLists.txt that includes a file which is generated in the configure stage:
execute_process(COMMAND "my-generator -o generated.cmake")
include(generated.cmake)
Apart from the fact that this approach doesn't feel right (not to say elegant) I now need to re-generate that file before every build (my-generator produces output that incorporates the current time).
My assumption is that I can't use add_custom_command() or add_custom_target() because the file would be generated at compile time but needed in the configure-step.
This very old post suggests to touch the input file so I did this:
execute_process(
COMMAND "my-generator -o generated.cmake"
COMMAND cmake -E touch "${CMAKE_CURRENT_LIST_FILE}")
.. which does not produce errors but calling make multiple times won't run the configure step more than once.
What do I do wrong? Is there a better approach?
We have two add_custom_command clauses, where one depends on the other:
The first command compiles an .osl source file into an .oso object file using the oslc compiler:
set (oslc ${PROJECT_SOURCE_DIR}/sandbox/bin/oslc)
add_custom_command (
OUTPUT "${oso_dir}/${oso_filename}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${oso_dir}"
COMMAND "${oslc}" -I"${osl_include_path}" -o "${oso_dir}/${oso_filename}" "${osl_src_abs}"
MAIN_DEPENDENCY ${osl_src_abs}
DEPENDS ${${headers}} ${osl_src_abs} "${oslc}"
)
Notice the dependency on ${oslc}: we explicitly depend on ${oslc} because we need to make sure it exists before we can execute this command.
The second command "builds" (really, deploys) the oslc compiler by copying it from somewhere else:
add_custom_command (
OUTPUT "${PROJECT_SOURCE_DIR}/sandbox/bin/oslc"
COMMAND ${CMAKE_COMMAND} -E copy ${OSL_COMPILER} ${PROJECT_SOURCE_DIR}/sandbox/bin/
)
While this setup works, it has the side effect that both commands are always executed (the second command followed by the first command) even when the .osl input file hasn't been modified.
It seems that this behavior is specific to Windows. It appears to work fine on Linux.
If the dependency to ${oslc} is removed from the first command, the second command is no longer executed at all, even when the oslc compiler is missing; but on the other hand, .osl files are now only recompiled when they have changed since the last build, as desired (as long as oslc is present).
Is there anything wrong with this setup? If not, what is the right way to combine both features: compiling .osl files only when they have changed since the last build, and "building" the oslc compiler (required by the first step) when it doesn't yet exist?
The actual CMake script is available on GitHub:
Entire CMakeLists.txt file
First add_custom_command clause
Second add_custom_command clause
A simple solution, at least for Windows, is to change the second command to
add_custom_command (
TARGET appleseed.shaders
PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${OSL_COMPILER} ${PROJECT_SOURCE_DIR}/sandbox/bin/
)
(notice the PRE_BUILD keyword)
and to remove the explicit dependency to ${oslc} from the first command.
After my program is linked I need to perform some post-processing on it. I added a add_custom_command(TARGET ... and that worked fine. However, this extra custom command runs a script (that is not generated; it's checked into the codebase), and I want the target to be considered out of date if that script changes so it will be rebuilt properly.
The add_dependencies rule seems to only work between top-level elements, which this is not (it's just a script), and there's no DEPENDS element in this form of the add_custom_command that I can use.
How do I do this?
It's unfortunately a bit convoluted, but you can use add_custom_target to invoke CMake in script-processing mode via -P.
You need to use add_custom_target here since it will always execute, even if everything's up to date.
Having made this decision, we need to have the custom target execute commands which will check for a new version of your post-processing script file (let's call this "my_script" and assume it's in your root dir), and if it's changed cause your dependent target to go out of date.
This would comprise:
Compare a previous copy of "my_script" to the current "my_script" in the source tree. If the current "my_script" is different, or if the copy doesn't exist (i.e. this is the first run of CMake) then...
Copy "my_script" from the source tree to the build tree, and...
Touch a source file of the dependent target so that it goes out of date.
All of the commands required inside the CMake script can be achieved using execute_process to invoke cmake -E.
So the CMake script (called e.g. "copy_script.cmake") would be something like:
execute_process(COMMAND ${CMAKE_COMMAND} -E compare_files
${OriginalScript} ${CopiedScript} RESULT_VARIABLE Result)
if(Result)
execute_process(COMMAND ${CMAKE_COMMAND} -E copy
${OriginalScript} ${CopiedScript})
execute_process(COMMAND ${CMAKE_COMMAND} -E touch_nocreate ${FileToTouch})
endif()
The CMake script needs to have required variables passed in via the -D args before calling -P, so the calling CMakeLists.txt would have something like:
set(FileToTouch ${CMAKE_SOURCE_DIR}/src/main.cpp)
add_custom_target(CopyScript ALL ${CMAKE_COMMAND}
-DOriginalScript=${CMAKE_SOURCE_DIR}/my_script
-DCopiedScript=${CMAKE_BINARY_DIR}/my_script
-DFileToTouch=${FileToTouch}
-P ${CMAKE_SOURCE_DIR}/copy_script.cmake)
add_executable(MyExe ${FileToTouch})
This will cause a full rebuild of the executable, since it thinks a source file has been modified. If you only require to force relinking there may be a better way to achieve this.