CMake Policies Design Discussion
This page describes a proposal for a formal backwards/forwards compatibility feature.
Motivating Examples
The ADD_DEFINITIONS Command
Consider code such as
add_definitions("-DFOO=\\\"hello\\ world\\\"")
which tries to add the option
-DFOO=\"hello\ world\"
to the compile command line. The code works in CMake 2.4's Unix Makefiles
generator and produces a definition as if
#define FOO "hello world"
appeared in the source code. It works only because of the way the makefile generator happens to place the definition string in the command line. It may not work with the VS IDE generators.
In CMake HEAD we provide the COMPILE_DEFINITIONS
directory property so that one may write
set_property(DIRECTORY PROPERTY COMPILE_DEFINITIONS "FOO=\"hello world\"")
to get the correct behavior on all generators. Since CMake HEAD contains the appropriate escaping support it is desirable to allow the user to write
add_definitions("-DFOO=\"hello world\"")
and get the expected behavior. Unfortunately if we were to start escaping the definitions given to add_definitions
we would break compatibility with projects that are already adding their own escapes. In hindsight we should have either supported escapes from the beginning or make the command give an error that the definition value is not supported, but it is too late now.
A similar problem appears in the add_custom_command
command where support for properly escaping all arguments was added late. The solution currently used by that command is to require the user to add a VERBATIM
argument to the command to get proper escaping. Using that solution for add_definitions
would make the user write
add_definitions(VERBATIM "-DFOO=\"hello world\"")
just to get CMake to do the right thing. For compatibility CMake would have to implement the wrong behavior by default forever.
Missing Link Directories
Projects used to be able to write this (wrong) code and it would work by accident:
add_executable(myexe myexe.c) target_link_libraries(myexe /path/to/libA.so B)
where "B
" is meant to link "/path/to/libB.so
". This code is incorrect
because it asks CMake to link to B
but does not provide the proper
linker search path for it. It used to work by accident because the
-L/path/to
would get added as part of the implementation of linking to
A. The correct code would be
link_directories(/path/to) add_executable(myexe myexe.c) target_link_libraries(myexe /path/to/libA.so B)
or even better
add_executable(myexe myexe.c) target_link_libraries(myexe /path/to/libA.so /path/to/libB.so)
Currently we provide the CMAKE_OLD_LINK_PATHS
variable to allow projects or users to quickly work around the problem. Full compatibility would require us to support thie behavior by default forever. That would allow new projects to be written with the same bug.
An alternative is to require all libraries to be linked via full path (where target names are expanded automatically). Whenever a non-full-path is given we could produce a warning that tells the user to start using find_library
or something like that but then implement the old linker search path computation for compatibility. It is desirable to let projects that have been updated for newer CMake versions tell CMake that they know what they are doing and to not warn or use the compatibility behavior.
Proposed Solution
We propose the following solution to this problem.
Each change that introduces a compatibility issue is assigned a new identification number (like CM00001 or something). Then we try to detect cases in user code where it might be an issue and deal with it. We can maintain in the implementation of CMake a mapping from feature id to a rule to deal with the issue when encountered. The possible means of dealing with such cases are:
Rule | Behavior | Meaning | Default? |
---|---|---|---|
QUIET | old | Project suppresses the diagnostic | only in special cases |
WARN | old | Emit warning when case is encountered | most cases |
ERROR | none | Emit error when case is encountered | only for cases that can be detected with no false positives |
FIXED | new | Project declares the case has been resolved | never? distant future release after creation of issue? |
Several releases after a compatibility issue has been introduced we can remove implementation of support for the old behavior and enable a boolean "REQUIRED" flag internally that requires a project to declare the issue FIXED. Much further in the future we might make the default setting of the issue FIXED and remove the check altogether.
Proposed CMAKE_FEATURE Command
We will introduce a new command for projects to use for setting the rule for each feature. A better name may be needed. Perhaps cmake_compatibility
?
The signature
cmake_feature(SET <feature-id> <QUIET|WARN|ERROR|FIXED>)
sets the current rule for the feature. It is an error to specify a feature id that does not exist (because it might refer to a feature id introduced in a future version of CMake).
The signature
cmake_feature(VERSION <major[.minor[.patch]]>)
sets the current rules for all features to match the preferred (new) behavior as of a given CMake version and also requires the running CMake to be at least that version. The cmake_minimum_required(VERSION)
command can do something similar (but use of new commands versus fixing other issues are distinct cases so the defaults may need to be different).
The signature
cmake_feature(PUSH|POP)
pushes or pops the current feature state on a stack. The stack is automatically pushed/popped when entering a new directory (under add_subdirectory
for example). Within a directory the number of PUSH and POP calls must match or it is an error. This signature is useful in a .cmake script meant for inclusion in other projects. It could write
cmake_feature(PUSH) cmake_feature(SET CMF_00001 FIXED) ... code using CMF_00001 ... cmake_feature(POP)
The command could also provide an alias for each feature id that is more human readable:
cmake_feature(SET CMF_ESCAPE_DEFINITIONS FIXED)
Examples
Let's define a few example feature ids
Id | Alias | Description |
---|---|---|
CMF_00001 | CMF_ESCAPE_DEFINITIONS | Enable proper escaping of definitions added with ADD_DEFINITIONS |
CMF_00002 | CMF_ESCAPE_CUSTOM_COMMANDS | Enable proper escaping of custom command lines with ADD_CUSTOM_COMMAND, make VERBATIM argument an error |
CMF_00003 | CMF_NO_AUTOMATIC_LINK_PATHS | Disable generation of compatibility -L options on link lines |
The code
add_definitions("-DFOO=\\\"hello\\ world\\\"")
can now produce a warning CMF_00001 that CMake does not know whether to escape the definition (only when a non-trivial value is given).
The project may suppress the warning in their release branch:
project(FOO) cmake_feature(SET CMF_ESCAPE_DEFINITIONS QUIET) ... add_definitions("-DFOO=\\\"hello\\ world\\\"")
or fix the code:
project(FOO) cmake_feature(SET CMF_ESCAPE_DEFINITIONS FIXED) ... add_definitions("-DFOO=\"hello world\"")
Either way CMake knows exactly how to handle the code and does not need to warn.
Similarly, the code
add_custom_command(OUTPUT foo.txt COMMAND mycmd -with -some -arguments -out foo.txt)
may now produce a warning CMF_00002 that CMake does not know whether to escape the command. The project may write
project(FOO) cmake_feature(SET CMF_ESCAPE_CUSTOM_COMMANDS FIXED) ... add_custom_command(OUTPUT foo.txt COMMAND mycmd -with -some -arguments -out foo.txt)
to get rid of the warning and use proper escaping.
The code
target_link_libraries(mytarget /path/to/libA.so B)
may now produce a warning CMF_00003 that library B may not have the correct link directory specified but then generate -L/path/to anyway. The project may write
project(FOO) cmake_feature(SET CMF_NO_AUTOMATIC_LINK_PATHS FIXED) ... link_directories(/path/to) target_link_libraries(mytarget /path/to/libA.so B)
Once the project has fixed all issues related to upgrading to CMake 2.6 it can just write
project(FOO) cmake_feature(VERSION 2.6)
to get all the updated behavior.