cmake third party project step - cmake

We are using a vendor code as third party project in our source code. The Vendor code uses Makefile, for which we wrote new CMake add_custom_target for vendor source code.
To copy library from vendor specific build/lib dir to Our CMAKE binary/library dir, I coded Step to copy all the libs as
ExternalProject_Add_Step(CopyStep)
However I see that whenever I build, CopyStep is executed all the time. Is there any way to control the CopyStep to exec only if there is change in library (something similar as Make, whenever there is not code change, source code is not rebuilt).
Let me know if there is any other way I could do copy etc.

For make an ExternalProject's step to be re-executed only when some file(s) are changed, add DEPENDS option to it:
ExternalProject_Add_Step(extLibrary CopyStep
COMMAND cp <BINARY_DIR>/lib/libext.a <...>
DEPENDS <BINARY_DIR>/lib/libext.a
)
Alternatively (e.g. if you don't want to list all files you depends on), you may make the step to be a part of the build step. For that, modify ExternalProject_Add by adding appropriate command:
ExternalProject_Add(extLibrary
...
BUILD_COMMAND make # Need to explicitely specify build command.
COMMAND cp -r <BINARY_DIR>/lib <...> # Additional action for the build step
)

Related

Is it possible to force CMake to run add_compile_definitions() each time?

I have an embedded project (using ESP-IDF which builds projects with CMake), where I have a props.json file that contains several settings (e.g. "device type"). For example based on the actual value of "deviceType" the CMake open and read props.json by calling execute_process() and jq, then defines C preprocessor macros, such as: DEVICE_TYPE_A by using add_compile_definitions().
The problem is that, this will run only when I modify the CMakeLists.txt or clean the whole project, but I don't want to recompile each components when I change the props.json only the files that I wrote (so, depend on the settings). I'd like to make CMake read the file each time I build the project without cleaning it.
I did my research, so I know there are add_custom_target() and add_custom_command() that behave that way, however add_compile_definitions() cannot be called in a script. Is there a solution to achieve this or should I just use a header file configured by configure_file() and leave add_compile_definitions() alone?
This is actually pretty easy and you don't need to manually reconfigure CMake. Just add the following to the CMakeLists.txt in the directory containing your props.json file:
set_property(DIRECTORY . APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS props.json)
This will add props.json to the list of files that the CMake-generated build scans when determining whether to re-run the CMake configure step. See the docs on CMAKE_CONFIGURE_DEPENDS for more detail.
In general, you should never need to manually re-run CMake1 after the first configure. If you do, it is an indication that you have not communicated all of the necessary information for CMake to generate a correct build system.
1 There is one notable exception: Xcode is known to be buggy when re-running the CMake configure step automatically.

How to use glib-compile-resources with CMake

As any GTK project grows, GTK applications tend to be bundled with gresources to separate out code and UI design. This is very useful because UI/UX designers don't need to know code in order to... well design and ultimately contribute their skills and effort to the project.
Not only designers but programmers too benefit a lot! Because code becomes heavily "logic or problem solving" instead of maintaining both UI and logic code together in one single file.
However, to compile our GResource we need glib-compile-resources utility tool. The command usually goes like this:
glib-compile-resources --generate-source --target=<output-file> <input-file>
But how do I create a build script that compiles our gresource files and link it with our target project? I'm still a newbie learning CMake and I've gotten far enough to know what a target is, how to set a variable, how to link a target, and also how to pull in the required GTK packages for linking. But I don't have any clue how to proceed ahead with solving this :(
A solution to this is using add_custom_command() to compile your gresources. But first here's a breakdown of what you need for your CMake script:
Pull in glib-compile-resources as executable program - find_program()
Define how to compile your gresource - add_custom_command()
Then define your custom target - add_custom_target()
Tell CMake that resource is a generated file - set_source_files_properties()
Finally, add your custom target to your project target as a dependency - add_dependencies()
Here's a sample CMake script:
cmake_minimum_required(VERSION 3.15)
project(dummy)
# Step 1:
find_program(GLIB_COMPILE_RESOURCES NAMES glib-compile-resources REQUIRED)
set(GRESOURCE_C test.gresource.c)
set(GRESOURCE_XML test.gresource.xml)
# Step 2:
add_custom_command(
OUTPUT ${GRESOURCE_C}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMAND ${GLIB_COMPILE_RESOURCES}
ARGS
--target=${CMAKE_CURRENT_BINARY_DIR}/${GRESOURCE_C}
${GRESOURCE_XML}
VERBATIM
MAIN_DEPENDENCY ${GRESOURCE_XML}
DEPENDS
for.glade
bar.glade
)
# Step 3:
add_custom_target(
dummy-resource
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${GRESOURCE_C}
)
# Step 4:
add_executable(${PROJECT_NAME} dummy.c ${CMAKE_CURRENT_BINARY_DIR}/${GRESOURCE_C})
set_source_files_properties(
${CMAKE_CURRENT_BINARY_DIR}/${GRESOURCE_C}
PROPERTIES GENERATED TRUE
)
# Step 5:
add_dependencies(${PROJECT_NAME} dummy-resource)
Brief explanation
add_custom_command()
OUTPUT - This is your generated resource file
WORKING_DIRECTORY - Where your XML and glade files are located
VERBATIM - Makes sure our COMMAND receives ARGS unchanged
MAIN_DEPENDENCY - for glib-compile-resources <input-file>
DEPENDS - Your glade file(s). If any of the file changes then your target build is triggered :)
add_custom_target()
dummy-resource - That's your custom target name
DEPENDS - The output your custom target needs in order to trigger your custom command
set_source_files_properties()
When you first generate your build files using cmake command, your resource file isn't generated yet. So CMake will run into error because it doesn't know where your resource file is or where it's coming from. We need to tell CMake "Don't fail, our resource file is generated later"
Use --generate-dependencies instead of hard-coding
Now you might notice we are duplicating our effort ie., when we add new glade files or remove existing ones (or any other resources such as icon, sounds, css files, etc) we have to edit both our XML and CMake script files. glib-compile-resources already provide dependency generation so we can use that in our CMake script and make it smart.
The trick is to change your .xml file to .xml.in as a configuration file. So when that configuration file changes, you call glib tool with --generate-dependencies, get new dependency output values, and send that to add_custom_command(... DEPENDS). Now we have an intelligent CMake :)
If you want to approach this method then the below post would be really helpful:
Use list as dependencies on add_custom_command
Good luck :)

How do I use ExternalProject_Add to get a simple make invocation?

I need ExternalProject_Add to invoke just one command for an external project whose makefile does all the needed steps. I need to have that command be "make WITH_OPTION1=no WITH_OPTION2=no" in the directory at the top of the external project's source tree. This is one of a few dozen external projects we use. Most of them fit the model of 'configure; make; make install' but a good third don't and this is one that I thought would be easy.
If I try to have the make command invoked like this:
ExternalProject_Add(build-example
PREFIX "${CMAKE_CURRENT_BINARY_DIR}/example"
DEPENDS ""
SOURCE_DIR "${PROJECT_TOPDIR}/External/example"
CONFIGURE_COMMAND ""
BUILD_COMMAND COMMAND make WITH_OPTION1=no WITH_OPTION2=no
INSTALL_COMMAND "")
The resulting make step is done from the wrong directory and therefore the Makefile is not found.
If I add a -C option to the make command it seems to use the right directory, but it ignores the WITH_OPTION stuff. That doesn't compile properly, because those features use things I don't want to provide, and if it did work, I still don't want those features.
If I then add quotes around the entire desired make command, it goes wrong. Apparently the command is passed to 'sh' in such a way that sh fails.
If I use a mechanism to pass the command into a 'configure-file' step and invoke that file from the BUILD_COMMAND, then CMake actually tries to use a broken CMakeLists.txt file that's in the external project, and I wonder what makes CMake think that it should do so.
I just want to invoke the makefile properly, using CMake to organize that build within the dozens of other builds that have to be done to build the complete project.

Rebuild configuration file when there are no CMake changes

I have a project with a configuration file in it.
configure_file(version.h.in version/version.h)
The same code base is used for multiple build in a day, without cleaning between builds. The version.h file will be built the first time the build is run, but subsequent builds will not rebuild (and update the revision number) in the version.h.
Is there a way I can force CMake to always rebuild version.h regardless of changes, or better yet based on a change (or lack of change) in a subversion revision number?
There are a few issues you need to overcome to have this work.
When cmake is run any version information you extract from subversion is baked into the generated build files, and so becomes static.
Cmake is only rerun if it detects the generated build files have become out of date (eg: if a CMakeLists.txt file is updated)
You can create a custom_target which will be run every time you build (from the docs: "The target has no output file and is ALWAYS CONSIDERED OUT OF DATE") which generates the file, but that will force you to rebuild your generated version file every time.
Here is an approach which overcomes all of the above hurdles:
First, create a library which will contain the compiled version information.
add_library(version STATIC ${CMAKE_CURRENT_BINARY_DIR}/version_gen.cc)
Now, add an artificial dependency between the library and a custom_target called gen_version
add_dependencies(version gen_version)
This will force gen_version to be run before building the version library.
Now create a custom_target called gen_version which will generate the version information:
add_custom_target(
gen_version
ALL
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/gen_version.cmake)
This custom target is added to the ALL target, and will be run every time you build. It will execute another cmake script called gen_version.cmake
This is the first trick. We get cmake to execute a subprocess and run a new cmake script on each build, in which the required version information is calculated anew each build.
The next trick is to call configure_file with a temporary output file, and only update the real version file if necessary. This prevents the need to recompile when the version doesn't change.
In the below example I show obtaining the version information from git - you can swap this for your subversion method.
gen_version.cmake:
# obtain the git version
execute_process(
OUTPUT_VARIABLE ${VERSION}
COMMAND git rev-parse --short HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
# configure the version file, but output to a temporary location
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/version_gen.in
${CMAKE_CURRENT_BINARY_DIR}/version_gen.cc.tmp
)
# compare with the real version file
execute_process(
COMMAND
${CMAKE_COMMAND} -E compare_files
${CMAKE_CURRENT_BINARY_DIR}/version_gen.cc.tmp
${CMAKE_CURRENT_BINARY_DIR}/version_gen.cc
RESULT_VARIABLE
VERSION_NEEDS_UPDATING
OUTPUT_QUIET
ERROR_QUIET
)
# update the real version file if necessary
if(VERSION_NEEDS_UPDATING)
execute_process(
COMMAND
${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_BINARY_DIR}/version_gen.cc.tmp
${CMAKE_CURRENT_BINARY_DIR}/version_gen.cc
)
endif()
set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/version_gen.cc
PROPERTIES GENERATED TRUE)
For a pure CMake solution #stevelorimer's answer is great and the way to go. But, you can also look to your CI system (if you have one) and your RCS. You asked:
Is there a way I can force CMake to always rebuild version.h regardless of changes...?
The simplest way is to just touch your version.h.in as part of your build process. If you next call cmake, version.h will be rebuilt. If you call (let's say) make, it will re-run cmake and rebuild version.h.
...or better yet based on a change (or lack of change) in a subversion revision number?
If you have access to your svn repo you can add a pre-commit hook to your repo that modifies version.h.in (or even version.h) in situ, and adds it to the commit. While possibly more complicated than the CMake solution, it has the added benefit of not using SVN or CMake to determine the revision, possibly allowing other tooling to scrape the revision.

How to trigger update of ExternalProject when UPDATE_DISCONNECTED set to ON

I have following CMakeLists.txt file in external directory in my project root. It is supposed fetch Catch (header only unit test library) for me.
include(ExternalProject)
ExternalProject_Add(
Catch
# I want to have it downloaded only once,
# therefore CMAKE_CURRENT_SOURCE_DIR which is projectRoot/external
PREFIX ${CMAKE_CURRENT_SOURCE_DIR}/Catch
GIT_REPOSITORY https://github.com/philsquared/Catch.git
# disables auto update on every build
UPDATE_DISCONNECTED 1
# disable following, since it is not needed
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_DIR ""
INSTALL_COMMAND ""
)
It works well for me, except one thing.
I have set UPDATE_DISCONNECTED to 1 since I do not want to check for updates in every build I make (checking for updates takes some time).
But I would still like to have the opportunity to update external project from CMake itself. Eg. by doing make Catch_update or make external_update_all or whatever.
Is there better way to do that than writing custom target calling git pull in external project directory? If yes, then how? Thanks!
The ExternalProject module has a Target Option, STEP_TARGETS, to which you can add, for instance, "update". In your call to ExternalProject_Add, simply add a line, STEP_TARGETS update. That will automatically create a target (in your case, Catch-update), which calls a pretty sophisticated CMake script (in your case, probably projectRoot/external/Catch/tmp/Catch-gitupdate.cmake). The ExternalProject module itself creates this gitupdate script. The script does a lot more than a simple pull; it will stash local changes, as needed, and pop them back, for instance. I just used this about a week ago and was pretty happy with it.
From the CMake documentation for the UPDATE_DISCONNECTED option (boldface added by me):
When enabled, this option causes the update step to be skipped. It does not, however, prevent the download step. The update step can still be added as a step target (see ExternalProject_Add_StepTargets()) and called manually. This is useful if you want to allow developers to build the project when disconnected from the network (the network may still be needed for the download step though).
When you call ExternalProject_Add with a STEP_TARGETS option, however, ExternalProject_Add_StepTargets is called for you automatically.
One final thing: You don't need to call find_package(Git REQUIRED), as you did in the first answer. By virtue of setting GIT_REPOSITORY in your call to ExternalProject_Add, the module itself will attempt to find git (and will issue a FATAL_ERROR message if it can't).
OK, so after additional searching, this seems to be the only way to do this. Add custom target with an update. It is not pretty, but it is working.
#we need git executable
find_package(Git REQUIRED)
# update Catch target
add_custom_target(external-Catch-update
COMMENT "Updated Catch"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/Catch/src/Catch
COMMAND ${GIT_EXECUTABLE} pull
DEPENDS Catch)