CMake Policies Design Discussion: Difference between revisions

From KitwarePublic
Jump to navigationJump to search
Line 63: Line 63:
We propose the following solution to this problem.
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:
Each change that introduces a compatibility issue is assigned a new identification number (like CMP_00001).  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:


{| border="1"
{| border="1"
Line 69: Line 69:
! Rule !! Behavior !! Meaning !! Default?
! Rule !! Behavior !! Meaning !! Default?
|-
|-
| QUIET || old || Project suppresses the diagnostic || only in special cases
| OLD || old || Project suppresses the diagnostic || never
|-
| NEW || new || Project declares the case has been resolved || never
|-
|-
| WARN || old || Emit warning when case is encountered || most 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?
|-
|-
| REQUIRED || new || Must use the new behavior || future CMake release, compatibility can be dropped but still checked for
| REQUIRED || new || Must use the new behavior || future CMake release, compatibility can be dropped but still checked for
Line 82: Line 80:
|}
|}


Several releases after a compatibility issue has been introduced we can remove implementation of support for the old behavior and set it to "REQUIRED" internally that requires a project to declare the issue FIXED or to be of a CMake version such that we know it would have generated warning. See the feature lifespan example.
Several releases after a compatibility issue has been introduced we can remove implementation of support for the old behavior and set it to "REQUIRED" internally that requires a project to declare the issue NEW or to be of a CMake version such that we know it would have generated warning. See the feature lifespan example.


==Proposed CMAKE_FEATURE Command==
==Proposed CMAKE_POLICY Command==


We will introduce a new command for projects to use for setting the rule for each feature.
We will introduce a new command for projects to use for setting the rule for each policy.


The signature
The signature


   cmake_feature(SET <feature-id> <QUIET|WARN|ERROR|FIXED>)
   cmake_policy(<OLD|NEW> [policy-id1 [policy-id2 [...]]])


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).
sets a list of policies to use OLD or NEW behavior.  It is an error to specify a policy id that does not exist (because it might refer to a policy id introduced in a future version of CMake).


The signature
The signature


   cmake_feature(VERSION <major[.minor[.patch]]>)
   cmake_policy(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 <code>cmake_minimum_required(VERSION)</code> 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).
sets all rules for policies introduced in the specified version or below to NEW and all other rules to WARN.  It also requires the version of CMake running to be at least that high.
The <code>cmake_minimum_required(VERSION)</code> 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
The signature


   cmake_feature(PUSH|POP)
   cmake_policy(<PUSH|POP>)


pushes or pops the current feature state on a stack.  The stack is automatically pushed/popped when entering a new directory (under <code>add_subdirectory</code> 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
pushes or pops the current policy state on a stack.  The stack is automatically pushed/popped when entering a new directory (under <code>add_subdirectory</code> 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_policy(PUSH)
   cmake_feature(SET CMF_00001 FIXED)
   cmake_policy(NEW CMP_00001)
   ... code using CMF_00001 ...
   ... code using CMP_00001 ...
   cmake_feature(POP)
   cmake_policy(POP)


The command could also provide an alias for each feature id that is more human readable:
The command could also provide an alias for each policy id that is more human readable:


   cmake_feature(SET CMF_ESCAPE_DEFINITIONS FIXED)
   cmake_policy(NEW CMP_ESCAPE_DEFINITIONS)


==Examples==
==Examples==


Let's define a few example feature ids
Let's define a few example policy ids


{| border="1"
{| border="1"
Line 123: Line 122:
! Id !! Alias !! Description
! Id !! Alias !! Description
|-
|-
| CMF_00001 || CMF_ESCAPE_DEFINITIONS || Enable proper escaping of definitions added with ADD_DEFINITIONS
| CMP_00001 || CMP_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
| CMP_00002 || CMP_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
| CMP_00003 || CMP_NO_AUTOMATIC_LINK_PATHS || Disable generation of compatibility -L options on link lines
|}
|}


Line 134: Line 133:
   add_definitions("-DFOO=\\\"hello\\ world\\\"")
   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).
can now produce a warning CMP_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:
The project may suppress the warning in their release branch:


   project(FOO)
   project(FOO)
   cmake_feature(SET CMF_ESCAPE_DEFINITIONS QUIET)
   cmake_policy(OLD CMP_ESCAPE_DEFINITIONS)
   ...
   ...
   add_definitions("-DFOO=\\\"hello\\ world\\\"")
   add_definitions("-DFOO=\\\"hello\\ world\\\"")
Line 146: Line 145:


   project(FOO)
   project(FOO)
   cmake_feature(SET CMF_ESCAPE_DEFINITIONS FIXED)
   cmake_policy(NEW CMP_ESCAPE_DEFINITIONS)
   ...
   ...
   add_definitions("-DFOO=\"hello world\"")
   add_definitions("-DFOO=\"hello world\"")
Line 156: Line 155:
   add_custom_command(OUTPUT foo.txt COMMAND mycmd -with -some -arguments -out foo.txt)
   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.
may now produce a warning CMP_00002 that CMake does not know whether to escape the command.
The project may write
The project may write


   project(FOO)
   project(FOO)
   cmake_feature(SET CMF_ESCAPE_CUSTOM_COMMANDS FIXED)
   cmake_policy(NEW CMP_ESCAPE_CUSTOM_COMMANDS)
   ...
   ...
   add_custom_command(OUTPUT foo.txt COMMAND mycmd -with -some -arguments -out foo.txt)
   add_custom_command(OUTPUT foo.txt COMMAND mycmd -with -some -arguments -out foo.txt)
Line 170: Line 169:
   target_link_libraries(mytarget /path/to/libA.so B)
   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
may now produce a warning CMP_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)
   project(FOO)
   cmake_feature(SET CMF_NO_AUTOMATIC_LINK_PATHS FIXED)
   cmake_policy(NEW CMP_NO_AUTOMATIC_LINK_PATHS)
   ...
   ...
   link_directories(/path/to)
   link_directories(/path/to)
Line 181: Line 180:


   project(FOO)
   project(FOO)
   cmake_feature(VERSION 2.6)
   cmake_policy(VERSION 2.6)


to get all the updated behavior.
to get all the updated behavior.  Future versions of CMake (> 2.6) may then warn on their new policies.


==Feature Lifespan Example==
==Feature Lifespan Example==


Using the CMF_00001 || CMF_ESCAPE_DEFINITIONS example above let's examine how it would work. In CMake 2.4 definitions had to be escaped by the user which probably does not work on MSVC etc. So in 2.6 we want to start switching over to having CMake handle the escaping.  
Using the CMP_00001 || CMP_ESCAPE_DEFINITIONS example above let's examine how it would work. In CMake 2.4 definitions had to be escaped by the user which probably does not work on MSVC etc. So in 2.6 we want to start switching over to having CMake handle the escaping.  


* CMake 2.4: old behavior, issue CMF_0001 does not even exist
* CMake 2.4: old behavior, issue CMP_0001 does not even exist


* CMake 2.6: issue CMF_0001 is created and set to WARN and version number 2.6. By default old projects will still work but will receive a warning if they use this feature. The issue can be either made QUIET <code>cmake_feature(SET CMF_0001 QUIET)</code> with no other changes to the CMakeLists file or the issue can be fixed by changing their code and setting it to FIXED <code>cmake_feature(SET CMF_0001 FIXED)</code>. Or they can ignore it and just live with the warnings.
* CMake 2.6: issue CMP_0001 is created and set to WARN and version number 2.6. By default old projects will still work but will receive a warning if they use this feature. The policy can be either made OLD <code>cmake_policy(OLD CMP_0001)</code> with no other changes to the CMakeLists file or the issue can be fixed by changing their code and setting it to NEW <code>cmake_policy(NEW CMP_0001)</code>. Or they can ignore it and just live with the warnings.


* CMake 2.8: Nothing changes, stays as a warning
* CMake 2.8: Nothing changes, stays as a warning


* CMake 2.10: We change the default value of issue CMF_0001 to REQUIRED. Now if the user's CMakeLists file sets the issue to QUITE or WARN CMake will produce an error indicating that that is no longer an option for feature CMF_0001 and the issue must be fixed or an earlier version of CMake used. But what if the CMakeList file does not specify anything about CMF_0001? In that case we do nothing if the feature is not used. If it is used, we look at the VERSION the CMakeList file was written to. If it was written to CMake 2.6 or later we assume it is fixed. If it is written to an earlier version we assume it is not fixed and report an error.
* CMake 2.10: We change the default value of issue CMP_0001 to REQUIRED. Now if the user's CMakeLists file sets the issue to OLD or does not set a policy at all CMake will produce an error indicating that that is no longer an option for policy CMP_0001 and the issue must be fixed or an earlier version of CMake used. Ther user list file will have to set the policy to NEW explicitly or via <code>cmake_policy(VERSION 2.6)</code> (or higher).


* CMake 2.12: We change the default value of issue CMF_0001 to REQUIRED_NO_CHECK. This is the same as required if the CMakeList file specifies anything about CMF_0001. But if it does not, then we produce an error if the CMakeLists file was written to CMake 2.4 or earlier. We do not check to see if they are using the feature at all. That code has been removed. They must have specified that they are using CMake 2.6 or later via cmake_minimum_required(2.6) or cmake_feature(VERSION 2.6) or later.
* CMake 2.12: We change the default value of issue CMP_0001 to REQUIRED_NO_CHECK.   We do not check to see if they are using the feature at all. That code has been removed. The user must specify NEW for the policy explicitly or via <code>cmake_policy(VERSION 2.6)</code> (or higher) in order to use this version of CMake (whether or not the feature is used).


==Discussion==
==Discussion==
Line 212: Line 211:
** The hello-world example does not need to show version specification
** The hello-world example does not need to show version specification
* We have an exit-path to remove support for a compatibility feature eventually
* We have an exit-path to remove support for a compatibility feature eventually
** Make projects rule to FIXED explicitly or via VERSION specification
** Make projects set the rule to NEW explicitly or via VERSION specification
* Any compatibility bug introduced accidentally and reported by a user can be fixed in CMake by converting it to one of these issues
* Any compatibility bug introduced accidentally and reported by a user can be fixed in CMake by converting it to one of these issues
==Alternative Names==
I'm not happy with the current naming.  Perhaps the command could be <code>cmake_behavior</code> and <code>QUIET|WARN|ERROR|FIXED</code> should be <code>OLD|WARN|ERROR|NEW</code>:
  cmake_behavior(PUSH)
  cmake_behavior(SET CMB_00001 OLD)    # request old behavior
  cmake_behavior(SET CMB_00001 WARN)  # request old behavior with warning
  cmake_behavior(SET CMB_00001 ERROR)  # request error when case is encountered
  cmake_behavior(SET CMB_00001 NEW)    # request new behavior
  cmake_behavior(VERSION 2.6)          # request new behavior as of CMake 2.6
  cmake_behavior(POP)
Or perhaps "<code>cmake_policy</code>"
  cmake_policy(PUSH)
  cmake_policy(SET CMP_00001 OLD)    # request old policy
  cmake_policy(SET CMP_00001 WARN)    # request old policy with warning
  cmake_policy(SET CMP_00001 ERROR)  # request error when case is encountered
  cmake_policy(SET CMP_00001 NEW)    # request new policy
  cmake_policy(VERSION 2.6)          # request new policy as of CMake 2.6
  cmake_policy(POP)

Revision as of 18:37, 27 February 2008

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 CMP_00001). 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?
OLD old Project suppresses the diagnostic never
NEW new Project declares the case has been resolved never
WARN old Emit warning when case is encountered most cases
REQUIRED new Must use the new behavior future CMake release, compatibility can be dropped but still checked for
REQUIRED_NO_CHECK new Must use the new behavior future CMake release, compatibility is dropped as well as the code to check if the file uses this issue

Several releases after a compatibility issue has been introduced we can remove implementation of support for the old behavior and set it to "REQUIRED" internally that requires a project to declare the issue NEW or to be of a CMake version such that we know it would have generated warning. See the feature lifespan example.

Proposed CMAKE_POLICY Command

We will introduce a new command for projects to use for setting the rule for each policy.

The signature

 cmake_policy(<OLD|NEW> [policy-id1 [policy-id2 [...]]])

sets a list of policies to use OLD or NEW behavior. It is an error to specify a policy id that does not exist (because it might refer to a policy id introduced in a future version of CMake).

The signature

 cmake_policy(VERSION <major[.minor[.patch]]>)

sets all rules for policies introduced in the specified version or below to NEW and all other rules to WARN. It also requires the version of CMake running to be at least that high. 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_policy(<PUSH|POP>)

pushes or pops the current policy 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_policy(PUSH)
 cmake_policy(NEW CMP_00001)
 ... code using CMP_00001 ...
 cmake_policy(POP)

The command could also provide an alias for each policy id that is more human readable:

 cmake_policy(NEW CMP_ESCAPE_DEFINITIONS)

Examples

Let's define a few example policy ids

Id Alias Description
CMP_00001 CMP_ESCAPE_DEFINITIONS Enable proper escaping of definitions added with ADD_DEFINITIONS
CMP_00002 CMP_ESCAPE_CUSTOM_COMMANDS Enable proper escaping of custom command lines with ADD_CUSTOM_COMMAND, make VERBATIM argument an error
CMP_00003 CMP_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 CMP_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_policy(OLD CMP_ESCAPE_DEFINITIONS)
 ...
 add_definitions("-DFOO=\\\"hello\\ world\\\"")

or fix the code:

 project(FOO)
 cmake_policy(NEW CMP_ESCAPE_DEFINITIONS)
 ...
 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 CMP_00002 that CMake does not know whether to escape the command. The project may write

 project(FOO)
 cmake_policy(NEW CMP_ESCAPE_CUSTOM_COMMANDS)
 ...
 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 CMP_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_policy(NEW CMP_NO_AUTOMATIC_LINK_PATHS)
 ...
 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_policy(VERSION 2.6)

to get all the updated behavior. Future versions of CMake (> 2.6) may then warn on their new policies.

Feature Lifespan Example

Using the CMP_00001 || CMP_ESCAPE_DEFINITIONS example above let's examine how it would work. In CMake 2.4 definitions had to be escaped by the user which probably does not work on MSVC etc. So in 2.6 we want to start switching over to having CMake handle the escaping.

  • CMake 2.4: old behavior, issue CMP_0001 does not even exist
  • CMake 2.6: issue CMP_0001 is created and set to WARN and version number 2.6. By default old projects will still work but will receive a warning if they use this feature. The policy can be either made OLD cmake_policy(OLD CMP_0001) with no other changes to the CMakeLists file or the issue can be fixed by changing their code and setting it to NEW cmake_policy(NEW CMP_0001). Or they can ignore it and just live with the warnings.
  • CMake 2.8: Nothing changes, stays as a warning
  • CMake 2.10: We change the default value of issue CMP_0001 to REQUIRED. Now if the user's CMakeLists file sets the issue to OLD or does not set a policy at all CMake will produce an error indicating that that is no longer an option for policy CMP_0001 and the issue must be fixed or an earlier version of CMake used. Ther user list file will have to set the policy to NEW explicitly or via cmake_policy(VERSION 2.6) (or higher).
  • CMake 2.12: We change the default value of issue CMP_0001 to REQUIRED_NO_CHECK. We do not check to see if they are using the feature at all. That code has been removed. The user must specify NEW for the policy explicitly or via cmake_policy(VERSION 2.6) (or higher) in order to use this version of CMake (whether or not the feature is used).

Discussion

This solution has the following advantages:

  • All compatibility issues are documented and given unique identifiers
    • Documentation can include association with CMake version ranges (when introduced, when REQUIRED, etc.)
  • We can be more aggressive about detecting cases without worrying about false positives because it is just a warning
  • When generating a warning or error message we can reference the feature id and tell the user what to do
  • Projects can be converted to newer CMake features incrementally
  • Project authors will be motivated by the warnings to update their code, but it will build for users without extra work
  • Code written that does not hit one of these issues will work without any special declarations
    • The hello-world example does not need to show version specification
  • We have an exit-path to remove support for a compatibility feature eventually
    • Make projects set the rule to NEW explicitly or via VERSION specification
  • Any compatibility bug introduced accidentally and reported by a user can be fixed in CMake by converting it to one of these issues