I need to use a property from an external project. So far I'm successfully doing that this way:
ExternalProject_Add(
some_ep_name
...
)
# Get source dir
ExternalProject_Get_Property(some_ep_name SOURCE_DIR)
# Set source dir value into less generically named variable
set(SOME_EP_NAME_SOURCE_DIR "${SOURCE_DIR}")
This works, but it seems unnecessarily verbose, and it annoys me a little. I was hoping I could use a generator expression, like so:
"$<TARGET_PROPERTY:some_ep_name,SOURCE_DIR>"
But it seems like this doesn't work. Before I give up, I wanted to check if I was doing something wrong or if anyone knows a better way.
All "properties" of ExternalProject are known at configuration time. So they don't require support of generator expressions, which main intention is usage for values not known at configuration time (but known at build time).
If you found "unnecessarily verbose" having several lines of code for save external project' property into the variable, you may create a macro/function for incorporate all these lines. Then calling the macro/function will use only single line of code:
function(ExternalProject_Property_to_Var VAR eproject prop)
ExternalProject_Get_Property(${eproject} ${eprop})
set(${VAR} ${${eprop}} PARENT_SCOPE)
endfunction()
...
ExternalProject_Property_to_Var(SOME_EP_NAME_SOURCE_DIR some_ep_name SOURCE_DIR)
Related
Not sure how to perfectly word this from the title, but I am new to CMake and slowly progressing through the online tutorial.
I am up to Step 4 and sometimes find it confusing when mixing passed values that in my eyes are strings, and thus in all programming languages I expect them to have quotation marks or some sort around them. However sometimes I create new targets with the same names. I will elaborate with an example. I reworded some things from the tutorial to make it a bit more clear for me to see what they actually do.
In the root CMakeLists.txt I have this file,
cmake_minimum_required(VERSION 3.10)
project(My_Project VERSION 1.0)
add_library(tutorial_compiler_flags INTERFACE)
target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)
option(USE_MYMATH "Use tutorial provided math implementation" TRUE)
configure_file(src/sqrt.h.in src/sqrt.h)
if(USE_MYMATH)
add_subdirectory(MathFunctions)
list(APPEND EXTRA_LIBS MathFunctions)
endif()
add_executable(compute_square_root src/sqrt.cxx)
target_link_libraries(compute_square_root PUBLIC ${EXTRA_LIBS} tutorial_compiler_flags)
target_include_directories(compute_square_root PUBLIC "${PROJECT_BINARY_DIR}/src")
Inside of MathFunctions I have
add_library(MathFunctions mysqrt.cxx)
target_include_directories(MathFunctions INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
Here is where the confusion can come from. Notice that in
add_subdirectory(MathFunctions)
MathFunctions is kind of treated as a string in my eyes, because it is now looking for the directory current_location/MathFunctions. However inside of of the MathFunctions CMakeLists.txt it now creates a target with the exact same spelling from the line "add_library(MathFunctions mysqrt.cxx)", this is then immediately referenced afterwards from the "target_include_directories(MathFunctions, ...".
Here, target_include_directories is referring to the target MathFunctions we just created. Now, when we leave that CMakeLists.txt we now have another line "list(APPEND EXTRA_LIBS MathFunctions)". Now I some confusion, like, is this MathFunctions referring to the target we just made? Is it a string called "MathFunctions"? In the documentation for target_link_libraries it says that it has to be a target created by add_library so I assume it is referring to the previous add_library(MathFunctions ...) call. I find the scoping weird here too, since we are referring to something that was made from a child, inside a different call.
Do we have certain rules in CMake for this kind of behaviour? THanks
All command parameters are treated as strings in cmake. Parameters are separated by whitespace unless quoted. The exact effect of a parameter depends on the command.
The following commands have the same effect:
add_subdirectory(MathFunctions)
add_subdirectory("MathFunctions")
In the case of add_library the first parameter is treated as the target name. CMake internally keeps track of targets and stores several pieces of information for them. The target name MathFunctions is entirely unrelated to the name of the subdirectory added via add_subdirectory; you could rename the directory to FooBar and use add_subdirectory(FooBar) and nothing would change.
There are several commands you pass the target name to to modify the properties of the cmake target as well as commands that treat the name of cmake targets specially e.g.:
target_include_directories: the target to modify the [INTERFACE_]INCLUDE_DIRECTORIES property for is passed
target_link_directories: the target to modify the [INTERFACE_]LINK_DIRECTORIES property for is passed
set_target_properties: One or more targets to set properties for are passed
target_link_libraries: The cmake target linked to is passed. Furthermore cmake library targets may be specified as libraries to be linked to the target.
add_test: If you use the name of a cmake target in the COMMAND part, the test logic uses the path to the target instead.
...
As for scope:
Variable values you write are actually set for the current scope only, but reading variables cmake looks into ancestor scopes, unless a variable is found in the current scope. CMake targets are visible in the whole cmake project though from the point of the parsing of the command creating the target(add_library, add_executable, add_custom_target): cmake targets are "global objects". (Exceptions exist for imported libs and alias targets, but that's probably nothing relevant to you right now.)
I've got the following generator expression working, which sets the /GS flag if the compiler is MSVC and it sets it for the build configurations RelWithDebInfo and Release:
target_compile_options(mytarget PRIVATE
"$<$<CONFIG:Release>: $<$<CXX_COMPILER_ID:MSVC>:/GS>>
$<$<CONFIG:RelWithDebInfo>:$<$<CXX_COMPILER_ID:MSVC>:/GS>>")
Now I also want to let the user configure this, and I've added an option:
option(MYTARGET_ENABLE_GS "Enable /GS" OFF)
So now, I (obviously) want to enable the /GS flag if the user enabled this option, and if they did, I want to add it if the compiler is MSVC, and it should be added to the Release and RelWithDebInfo configurations.
This is pretty nested, and I can't seem to get it right. This is as far as I got:
target_compile_options(mytarget PRIVATE
"$<$<BOOL:MYTARGET_ENABLE_GS>:
$<$<CONFIG:Release>: $<$<CXX_COMPILER_ID:MSVC>:/GS>>
$<$<CONFIG:RelWithDebInfo>:$<$<CXX_COMPILER_ID:MSVC>:/GS>>>")
Edit: Fixed, see below.
I've had to use the $<$<BOOL:...>> because that "translates" the option (which can be on/off or true/false, to 0 or 1, which the generator expression needs.
However above line doesn't work: It doesn't add (or not add) /GS.
I'd like to know:
1) Where is my mistake? How to do this? And
Stuff like this results in pretty convoluted nested expressions, that are really hard to read - imagine 6 months down the line reading that line of code again, even if it is documented. And it's so easy to misplace a > or something like that.
I could probably use "manual" ifs to make this more readable, but imagine having 5-10 of such options - writing an if/end with a target_compile_options inside results in like 15-30 lines of if/end code, which is also not very pretty to look at. What's the best way to do this?
Edit: I was nearly there. Variables have to be enclosed with ${...} in generator expressions. So it's for example:
target_compile_options(mytarget PRIVATE
"$<$<BOOL:${MYTARGET_ENABLE_GS}>:
$<$<CONFIG:Release>: $<$<CXX_COMPILER_ID:MSVC>:/GS>>
$<$<CONFIG:RelWithDebInfo>:$<$<CXX_COMPILER_ID:MSVC>:/GS>>>")
and that works great.
Which still leaves point "2)", which I would be keen to getting insights on.
When you generate a MSVC solution, the build type (Release, Debug, etc.) is unknown until you actually run the build. Thus, using generator expression for that is correct.
But for the 2 other variables (does user set MYTARGET_ENABLE_GS to OFF and is generation performed for MSVC), they are resolved at configuration time. So, you don't have to check them in a generator expression. You could simply write:
if(MSVC AND MYTARGET_ENABLE_GS)
target_compile_options(mytarget PRIVATE "$<$<OR:$<CONFIG:Release>,$<CONFIG:RelWithDebInfo>>:/GS>")
endif()
This solution also uses $<OR:?[,?]...> generator expression to regroup under the same expression both cases you build in Release OR RelWithDebInfo
Whenever you may use if command, use it. Generator expressions are not replacement for if command.
Generator expressions allow to use conditions dependent on build type. Because on multi-configuration build systems, like Visual Studio, build type isn't known at configuration stage, you cannot use such condition in if command.
But generator expressions are ugly, so do not use them when it is not needed.
There is nothing bad in
if(MYTARGET_ENABLE_GS)
target_compile_options(mytarget PRIVATE "$<$<CONFIG:Release>:$<$<CXX_COMPILER_ID:MSVC>:/GS>>$<$<CONFIG:RelWithDebInfo>:$<$<CXX_COMPILER_ID:MSVC>:/GS>>")
endif()
If you want to check an option at the beginning, but create a target later, you may store generator expression in the variable, and use this variable later:
set(additional_options)
# Depending on parameters, add options to 'additional_options' list.
if(MYTARGET_ENABLE_GS)
list(APPEND additional_options "$<$<CONFIG:Release>:$<$<CXX_COMPILER_ID:MSVC>:/GS>>$<$<CONFIG:RelWithDebInfo>:$<$<CXX_COMPILER_ID:MSVC>:/GS>>")
endif()
if(<other option>)
list(APPEND additional_options <...>)
endif()
# ...
target_compile_options(mytarget PRIVATE ${additional_options})
CMake's documentation a generator expressions works if your config matches any configuration listed after CONFIG:, but I couldn't find the syntax documented anywhere. Trial and error show the following works correctly:
if(MSVC AND MYTARGET_ENABLE_GS)
target_compile_options(mytarget PRIVATE "$<$<CONFIG:Release,RelWithDebInfo>:/GS>")
endif()
I have a program on my computer, let's say C:/Tools/generate_v23_debug.exe
I have a FindGenerate.cmake file which allows CMake to find that exact path to the executable.
So in my CMake code, I do:
find_program(Generate)
if (NOT Generate_FOUND)
message(FATAL_ERROR "Generator not found!")
So CMake has found the executable. Now I want to call this program in a custom command statement. Should I use COMMAND Generator or COMMAND ${GENERATOR_EXECUTABLE}? Will both of these do the same thing? Is one preferred over the other? Is name_EXECUTABLE a variable that CMake will define (it's not in the FindGenerate.cmake file), or is it something specific to someone else's example code I'm looking at? Will COMMAND Generator be expanded to the correct path?
add_custom_command(
OUTPUT blahblah.txt
COMMAND Generator inputfile1.log
DEPENDS Generator
)
find_program stores its result into the variable given as a first argument. You can verify this by inserting some debug output:
find_program(GENERATOR Generate)
message(${GENERATOR})
Note that find_program does not set any additional variables beyond that. In particular, you mentioned Generate_FOUND and GENERATOR_EXECUTABLE in your question and neither of those gets introduced implicitly by the find_program call.
The second mistake in your program is the use of the DEPENDS option on the add_custom_command. DEPENDS is used to model inter-target dependencies at build time and not to manipulate control flow in the CMakeLists. For example, additional custom command can DEPEND on the output of your command (blahblah.txt), but a custom command cannot DEPEND on the result of a previous find operation.
A working example might look something like this:
find_program(GENERATOR Generate)
if(NOT GENERATOR)
message(FATAL_ERROR "Generator not found!")
endif()
add_custom_command(
OUTPUT blahblah.txt
COMMAND ${GENERATOR} inputfile1.log
)
P.S.: You asked why the code examples were not properly formatted in your question. You indented everything correctly, but you need an additional newline between normal text and code paragraphs. I edited your question accordingly.
Consider the following:
SET(TEST_DIR, "test")
INSTALL(PROGRAMS scripts/foo.py DESTINATION ${TEST_DIR})
INSTALL(PROGRAMS scripts/foo.py DESTINATION #TEST_DIR#)
The first INSTALL command does not work. The second does. Why is that? What is the difference between those two? I have not found any reference to ## expansion except in the context of creation of configuration files. Everything else only uses ${} expansion.
UPDATE: OK, obvious bug in the above. My SET() command has an extraneous comma. Removing it, such that it looks like:
SET(TEST_DIR "test")
results in both ## and ${} expansions working. Still wondering (a) what is the meaning of ## as opposed to ${}, and why only the former worked with my incorrect SET() statement.
According to the documentation for the configure_file() command when configuring a file both the ${VAR} form and #VAR# form will be replaced VAR's value. Based on your experience above and some testing I did both forms are replaced when CMake evaluates your CMakeLists.txt, too. Since this is not documented I would recommend against using the #VAR# from in your CMakeLists.txt
Note that when using configure_file() you can restrict replacement to only the #VAR# form by using the #ONLY argument.
As far as I know, the #VAR# syntax is only used when replacing variables with the configure_file command.
Note that the configure_file command allows for an extra option #ONLY. Using it you can specify that only the #VAR#'s are replaced, but that the ${VAR}'s are kept.
As an example, this can be useful when generating e.g. a cmake-file which is later to be used with CMake again. E.g. when building your project, the #VAR# will be replaced when using configure_file. After you distributed your project and someone else uses the generated UseProject.cmake file, the ${VAR}$ entries will be replaced.
I feel a little stupid right now. After recently converting a few smaller projects to use CMake, I decided to also get rid of a few "Platform_Config.h" files. These files contain a few preprocessing directives like #define USE_NEW_CACHE and control compilation.
How would I 'convert' these defines to be controlled with CMake? Ideally by using these "cache" variables the user can easily edit.
There are two options. You can use the add_definitions method to pass defines as compiler flags: E.g. somewhere in your projects cmakelists.txt:
add_definitions( -DUSE_NEW_CACHE )
CMake will make sure the -D prefix is converted to the right flag for your compiler (/D for msvc and -D for gcc).
Alternatively, check out configure_file. It is more complex, but may be better suited to your original approach with a Platform_Config file.
You can create an input-file, similar to your original Platform_Config.h and add "#cmakedefine" lines to it.
Let's call in Platform_Config.h.in:
// In Platform_Config.h.in
#cmakedefine USE_NEW_CACHE
// end of Platform_Config.h.in
When then running
configure_file( ${CMAKE_SOURCE_DIR}/Platform_Config.h.in ${CMAKE_BINARY_DIR}/common/Platform_Config.h )
it will generate a new Platform_Config file in your build-dir. Those variables in cmake which are also a cmakedefine will be present in the generated file, the other ones will be commented out or undefed.
Of course, you should make sure the actual, generated file is then correctly found when including it in your source files.
option command might provide what you are looking for.
use it with the COMPILE DEFINITIONS property on the target and i think you are done.
To set the property on the target, use the command set target properties
option(DEBUGPRINTS "Prints a lot of debug prints")
target(myProgram ...)
if(DEBUGPRINTS)
set_target_properties(myProgram PROPERTIES COMPILE_DEFINITIONS "DEBUGPRINTS=1")
endif()
edit:
The option i wrote in the example shows up as a checkbox in the CMake GUI.
In case you want to set defines per target: Since 2.8.11 you can use target_compile_definitions.
In earlier versions you probably don't want to use set_target_properties as is, since it overwrites any defines you set previously. Call get_target_property first instead, then merge with previous values. See add_target_definitions here.
Use target_compile_options. Do not quote your define or it not be detected as a define. CMake parses off the /define and adds the actual define to the DefineConstants section of the csproj, if there are quotes it will put the entire quoted string in the AdditionalOptions section of the csproj.
An example from one of my projects that uses generator expressions:
target_compile_options( ${LIBRARY_NAME} PRIVATE
$<${IS_ART_ITERATION_BUILD}:/define:ART_ITERATION_BUILD>
)
An example without generator expressions:
target_compile_options( ${LIBRARY_NAME} PRIVATE
/define:GRAPHICS_VULKAN
)