Quellcode durchsuchen

add_custom_command: Validate arguments more rigorously

Add a new CMP0175 policy to preserve backward compatibility
for projects that were using unsupported keywords or arguments.

Fixes: #26096, #21089, #18976
Craig Scott vor 1 Jahr
Ursprung
Commit
ec519f3e97

+ 60 - 9
Help/command/add_custom_command.rst

@@ -5,6 +5,8 @@ Add a custom build rule to the generated build system.
 
 There are two main signatures for ``add_custom_command``.
 
+.. _`add_custom_command(OUTPUT)`:
+
 Generating Files
 ^^^^^^^^^^^^^^^^
 
@@ -54,7 +56,7 @@ The options are:
   the appended commands and dependencies apply to all configurations.
 
   The ``COMMENT``, ``MAIN_DEPENDENCY``, and ``WORKING_DIRECTORY``
-  options are currently ignored when APPEND is given, but may be
+  options are currently ignored when ``APPEND`` is given, but may be
   used in the future.
 
 ``BYPRODUCTS``
@@ -82,6 +84,10 @@ The options are:
   The :ref:`Makefile Generators` will remove ``BYPRODUCTS`` and other
   :prop_sf:`GENERATED` files during ``make clean``.
 
+  This keyword cannot be used with ``APPEND`` (see policy :policy:`CMP0175`).
+  All byproducts must be set in the first call to
+  ``add_custom_command(OUTPUT...)`` for the output files.
+
   .. versionadded:: 3.20
     Arguments to ``BYPRODUCTS`` may use a restricted set of
     :manual:`generator expressions <cmake-generator-expressions(7)>`.
@@ -95,11 +101,15 @@ The options are:
 
 ``COMMAND``
   Specify the command-line(s) to execute at build time.
-  If more than one ``COMMAND`` is specified they will be executed in order,
+  At least one ``COMMAND`` would normally be given, but certain patterns
+  may omit it, such as adding commands in separate calls using `APPEND`.
+
+  If more than one ``COMMAND`` is specified, they will be executed in order,
   but *not* necessarily composed into a stateful shell or batch script.
-  (To run a full script, use the :command:`configure_file` command or the
+  To run a full script, use the :command:`configure_file` command or the
   :command:`file(GENERATE)` command to create it, and then specify
-  a ``COMMAND`` to launch it.)
+  a ``COMMAND`` to launch it.
+
   The optional ``ARGS`` argument is for backward compatibility and
   will be ignored.
 
@@ -144,7 +154,8 @@ The options are:
 
 ``COMMENT``
   Display the given message before the commands are executed at
-  build time.
+  build time.  This will be ignored if ``APPEND`` is given, although a future
+  version may use it.
 
   .. versionadded:: 3.26
     Arguments to ``COMMENT`` may use
@@ -204,6 +215,10 @@ The options are:
   ``${CC} "-I$<JOIN:$<TARGET_PROPERTY:foo,INCLUDE_DIRECTORIES>,;-I>" foo.cc``
   to be properly expanded.
 
+  This keyword cannot be used with ``APPEND`` (see policy :policy:`CMP0175`).
+  If the appended commands need this option to be set, it must be set on the
+  first call to ``add_custom_command(OUTPUT...)`` for the output files.
+
 ``CODEGEN``
   .. versionadded:: 3.31
 
@@ -216,6 +231,10 @@ The options are:
   Furthermore, this option is allowed only if policy :policy:`CMP0171`
   is set to ``NEW``.
 
+  This keyword cannot be used with ``APPEND`` (see policy :policy:`CMP0175`).
+  It can only be set on the first call to ``add_custom_command(OUTPUT...)``
+  for the output files.
+
 ``IMPLICIT_DEPENDS``
   Request scanning of implicit dependencies of an input file.
   The language given specifies the programming language whose
@@ -240,6 +259,10 @@ The options are:
   Using a pool that is not defined by :prop_gbl:`JOB_POOLS` causes
   an error by ninja at build time.
 
+  This keyword cannot be used with ``APPEND`` (see policy :policy:`CMP0175`).
+  Job pools can only be specified in the first call to
+  ``add_custom_command(OUTPUT...)`` for the output files.
+
 ``JOB_SERVER_AWARE``
   .. versionadded:: 3.28
 
@@ -251,6 +274,10 @@ The options are:
 
   This option is silently ignored by other generators.
 
+  This keyword cannot be used with ``APPEND`` (see policy :policy:`CMP0175`).
+  Job server awareness can only be specified in the first call to
+  ``add_custom_command(OUTPUT...)`` for the output files.
+
 .. _`GNU Make Documentation`: https://www.gnu.org/software/make/manual/html_node/MAKE-Variable.html
 
 ``MAIN_DEPENDENCY``
@@ -262,6 +289,9 @@ The options are:
   library or an executable) counts as an implicit main dependency which
   gets silently overwritten by a custom command specification.
 
+  This option is currently ignored if ``APPEND`` is given, but a future
+  version may use it.
+
 ``OUTPUT``
   Specify the output files the command is expected to produce.
   Each output file will be marked with the :prop_sf:`GENERATED`
@@ -306,6 +336,10 @@ The options are:
   With the :generator:`Ninja` generator, this places the command in
   the ``console`` :prop_gbl:`pool <JOB_POOLS>`.
 
+  This keyword cannot be used with ``APPEND`` (see policy :policy:`CMP0175`).
+  If the appended commands need access to the terminal, it must be set on
+  the first call to ``add_custom_command(OUTPUT...)`` for the output files.
+
 ``VERBATIM``
   All arguments to the commands will be escaped properly for the
   build tool so that the invoked command receives each argument
@@ -316,11 +350,18 @@ The options are:
   is platform specific because there is no protection of
   tool-specific special characters.
 
+  This keyword cannot be used with ``APPEND`` (see policy :policy:`CMP0175`).
+  If the appended commands need to be treated as ``VERBATIM``, it must be set
+  on the first call to ``add_custom_command(OUTPUT...)`` for the output files.
+
 ``WORKING_DIRECTORY``
   Execute the command with the given current working directory.
-  If it is a relative path it will be interpreted relative to the
+  If it is a relative path, it will be interpreted relative to the
   build tree directory corresponding to the current source directory.
 
+  This option is currently ignored if ``APPEND`` is given, but a future
+  version may use it.
+
   .. versionadded:: 3.13
     Arguments to ``WORKING_DIRECTORY`` may use
     :manual:`generator expressions <cmake-generator-expressions(7)>`.
@@ -406,6 +447,10 @@ The options are:
   :ref:`Makefile Generators`, :ref:`Visual Studio Generators`,
   and the :generator:`Xcode` generator.
 
+  This keyword cannot be used with ``APPEND`` (see policy :policy:`CMP0175`).
+  Depfiles can only be set on the first call to
+  ``add_custom_command(OUTPUT...)`` for the output files.
+
 ``DEPENDS_EXPLICIT_ONLY``
 
   .. versionadded:: 3.27
@@ -421,6 +466,10 @@ The options are:
   This option can be enabled on all custom commands by setting
   :variable:`CMAKE_ADD_CUSTOM_COMMAND_DEPENDS_EXPLICIT_ONLY` to ``ON``.
 
+  This keyword cannot be used with ``APPEND`` (see policy :policy:`CMP0175`).
+  It can only be set on the first call to ``add_custom_command(OUTPUT...)``
+  for the output files.
+
   Only the :ref:`Ninja Generators` actually use this information to remove
   unnecessary implicit dependencies.
 
@@ -575,9 +624,11 @@ of the following is specified:
   Run after all other rules within the target have been executed.
 
 Projects should always specify one of the above three keywords when using
-the ``TARGET`` form.  For backward compatibility reasons, ``POST_BUILD`` is
-assumed if no such keyword is given, but projects should explicitly provide
-one of the keywords to make clear the behavior they expect.
+the ``TARGET`` form.  See policy :policy:`CMP0175`.
+
+All other keywords shown in the signature above have the same meaning as they
+do for the :command:`add_custom_command(OUTPUT)` form of the command.
+At least one ``COMMAND`` must be given, see policy :policy:`CMP0175`.
 
 .. note::
   Because generator expressions can be used in custom commands,

+ 1 - 0
Help/manual/cmake-policies.7.rst

@@ -57,6 +57,7 @@ Policies Introduced by CMake 3.31
 .. toctree::
    :maxdepth: 1
 
+   CMP0175: add_custom_command() rejects invalid arguments. </policy/CMP0175>
    CMP0174: cmake_parse_arguments(PARSE_ARGV) defines a variable for an empty string after a single-value keyword. </policy/CMP0174>
    CMP0173: The CMakeFindFrameworks module is removed. </policy/CMP0173>
    CMP0172: The CPack module enables per-machine installation by default in the CPack WIX Generator. </policy/CMP0172>

+ 40 - 0
Help/policy/CMP0175.rst

@@ -0,0 +1,40 @@
+CMP0175
+-------
+
+.. versionadded:: 3.31
+
+:command:`add_custom_command` rejects invalid arguments.
+
+CMake 3.30 and earlier silently ignored unsupported keywords and missing or
+invalid arguments for the different forms of the :command:`add_custom_command`
+command. CMake 3.31 implements more rigorous argument checking and will flag
+invalid or missing arguments as errors.
+
+The ``OLD`` behavior of this policy will accept the same invalid keywords or
+arguments as CMake 3.30 and earlier. The ``NEW`` behavior will flag the
+following as errors that previously went unreported:
+
+* The ``OUTPUT`` form does not accept ``PRE_BUILD``, ``PRE_LINK``, or
+  ``POST_BUILD`` keywords.
+* When the ``APPEND`` keyword is given, the ``OUTPUT`` form also does not
+  accept ``BYPRODUCTS``, ``COMMAND_EXPAND_LISTS``, ``DEPENDS_EXPLICIT_ONLY``,
+  ``DEPFILE``, ``JOB_POOL``, ``JOB_SERVER_AWARE``, ``USES_TERMINAL``, or
+  ``VERBATIM`` keywords.
+* The ``TARGET`` form requires exactly one of ``PRE_BUILD``, ``PRE_LINK``, or
+  ``POST_BUILD`` to be given.  Previously, if none were given, ``POST_BUILD``
+  was assumed, or if multiple keywords were given, the last one was used.
+* The ``TARGET`` form does not accept ``DEPENDS``, ``DEPENDS_EXPLICIT_ONLY``,
+  ``DEPFILE``, ``IMPLICIT_DEPENDS``, ``MAIN_DEPENDENCY``, ``JOB_POOL``,
+  ``JOB_SERVER_AWARE``, or ``USES_TERMINAL`` keywords.
+* The ``TARGET`` form now requires at least one ``COMMAND`` to be given.
+* If a keyword expects a value to be given after it, but no value is provided,
+  that was previously treated as though the keyword was not given at all.
+* The ``COMMENT`` keyword expects exactly one value after it.  If multiple
+  values are given, or if the ``COMMENT`` keyword is given more than once,
+  this is an error.
+
+.. |INTRODUCED_IN_CMAKE_VERSION| replace:: 3.31
+.. |WARNS_OR_DOES_NOT_WARN| replace:: warns
+.. include:: STANDARD_ADVICE.txt
+
+.. include:: DEPRECATED.txt

+ 203 - 0
Source/cmAddCustomCommandCommand.cxx

@@ -2,11 +2,15 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmAddCustomCommandCommand.h"
 
+#include <algorithm>
+#include <iterator>
+#include <set>
 #include <sstream>
 #include <unordered_set>
 #include <utility>
 
 #include <cm/memory>
+#include <cmext/string_view>
 
 #include "cmCustomCommand.h"
 #include "cmCustomCommandLines.h"
@@ -140,11 +144,82 @@ bool cmAddCustomCommandCommand(std::vector<std::string> const& args,
     keyDEPENDS_EXPLICIT_ONLY,
     keyCODEGEN
   };
+  /* clang-format off */
+  static std::set<std::string> const supportedTargetKeywords{
+    keyARGS,
+    keyBYPRODUCTS,
+    keyCOMMAND,
+    keyCOMMAND_EXPAND_LISTS,
+    keyCOMMENT,
+    keyPOST_BUILD,
+    keyPRE_BUILD,
+    keyPRE_LINK,
+    keyTARGET,
+    keyVERBATIM,
+    keyWORKING_DIRECTORY
+  };
+  /* clang-format on */
+  static std::set<std::string> const supportedOutputKeywords{
+    keyAPPEND,
+    keyARGS,
+    keyBYPRODUCTS,
+    keyCODEGEN,
+    keyCOMMAND,
+    keyCOMMAND_EXPAND_LISTS,
+    keyCOMMENT,
+    keyDEPENDS,
+    keyDEPENDS_EXPLICIT_ONLY,
+    keyDEPFILE,
+    keyIMPLICIT_DEPENDS,
+    keyJOB_POOL,
+    keyJOB_SERVER_AWARE,
+    keyMAIN_DEPENDENCY,
+    keyOUTPUT,
+    keyUSES_TERMINAL,
+    keyVERBATIM,
+    keyWORKING_DIRECTORY
+  };
+  /* clang-format off */
+  static std::set<std::string> const supportedAppendKeywords{
+    keyAPPEND,
+    keyARGS,
+    keyCOMMAND,
+    keyCOMMENT,           // Allowed but ignored
+    keyDEPENDS,
+    keyIMPLICIT_DEPENDS,
+    keyMAIN_DEPENDENCY,   // Allowed but ignored
+    keyOUTPUT,
+    keyWORKING_DIRECTORY  // Allowed but ignored
+  };
+  /* clang-format on */
+  std::set<std::string> keywordsSeen;
+  std::string const* keywordExpectingValue = nullptr;
+  auto const cmp0175 = mf.GetPolicyStatus(cmPolicies::CMP0175);
 
   for (std::string const& copy : args) {
     if (keywords.count(copy)) {
+      // Check if a preceding keyword expected a value but there wasn't one
+      if (keywordExpectingValue) {
+        std::string const msg =
+          cmStrCat("Keyword ", *keywordExpectingValue,
+                   " requires a value, but none was given.");
+        if (cmp0175 == cmPolicies::NEW) {
+          mf.IssueMessage(MessageType::FATAL_ERROR, msg);
+          return false;
+        }
+        if (cmp0175 == cmPolicies::WARN) {
+          mf.IssueMessage(
+            MessageType::AUTHOR_WARNING,
+            cmStrCat(msg, '\n',
+                     cmPolicies::GetPolicyWarning(cmPolicies::CMP0175)));
+        }
+      }
+      keywordExpectingValue = nullptr;
+      keywordsSeen.insert(copy);
+
       if (copy == keySOURCE) {
         doing = doing_source;
+        keywordExpectingValue = &keySOURCE;
       } else if (copy == keyCOMMAND) {
         doing = doing_command;
 
@@ -173,6 +248,7 @@ bool cmAddCustomCommandCommand(std::vector<std::string> const& args,
         codegen = true;
       } else if (copy == keyTARGET) {
         doing = doing_target;
+        keywordExpectingValue = &keyTARGET;
       } else if (copy == keyARGS) {
         // Ignore this old keyword.
       } else if (copy == keyDEPENDS) {
@@ -181,16 +257,20 @@ bool cmAddCustomCommandCommand(std::vector<std::string> const& args,
         doing = doing_outputs;
       } else if (copy == keyOUTPUT) {
         doing = doing_output;
+        keywordExpectingValue = &keyOUTPUT;
       } else if (copy == keyBYPRODUCTS) {
         doing = doing_byproducts;
       } else if (copy == keyWORKING_DIRECTORY) {
         doing = doing_working_directory;
+        keywordExpectingValue = &keyWORKING_DIRECTORY;
       } else if (copy == keyMAIN_DEPENDENCY) {
         doing = doing_main_dependency;
+        keywordExpectingValue = &keyMAIN_DEPENDENCY;
       } else if (copy == keyIMPLICIT_DEPENDS) {
         doing = doing_implicit_depends_lang;
       } else if (copy == keyCOMMENT) {
         doing = doing_comment;
+        keywordExpectingValue = &keyCOMMENT;
       } else if (copy == keyDEPFILE) {
         doing = doing_depfile;
         if (!mf.GetGlobalGenerator()->SupportsCustomCommandDepfile()) {
@@ -198,12 +278,16 @@ bool cmAddCustomCommandCommand(std::vector<std::string> const& args,
                                    mf.GetGlobalGenerator()->GetName()));
           return false;
         }
+        keywordExpectingValue = &keyDEPFILE;
       } else if (copy == keyJOB_POOL) {
         doing = doing_job_pool;
+        keywordExpectingValue = &keyJOB_POOL;
       } else if (copy == keyJOB_SERVER_AWARE) {
         doing = doing_job_server_aware;
+        keywordExpectingValue = &keyJOB_SERVER_AWARE;
       }
     } else {
+      keywordExpectingValue = nullptr; // Value is being processed now
       std::string filename;
       switch (doing) {
         case doing_output:
@@ -288,6 +372,21 @@ bool cmAddCustomCommandCommand(std::vector<std::string> const& args,
           byproducts.push_back(filename);
           break;
         case doing_comment:
+          if (!comment_buffer.empty()) {
+            std::string const msg =
+              "COMMENT requires exactly one argument, but multiple values "
+              "or COMMENT keywords have been given.";
+            if (cmp0175 == cmPolicies::NEW) {
+              mf.IssueMessage(MessageType::FATAL_ERROR, msg);
+              return false;
+            }
+            if (cmp0175 == cmPolicies::WARN) {
+              mf.IssueMessage(
+                MessageType::AUTHOR_WARNING,
+                cmStrCat(msg, '\n',
+                         cmPolicies::GetPolicyWarning(cmPolicies::CMP0175)));
+            }
+          }
           comment_buffer = copy;
           comment = comment_buffer.c_str();
           break;
@@ -351,6 +450,27 @@ bool cmAddCustomCommandCommand(std::vector<std::string> const& args,
 
   // Check for an append request.
   if (append) {
+    std::vector<std::string> unsupportedKeywordsUsed;
+    std::set_difference(keywordsSeen.begin(), keywordsSeen.end(),
+                        supportedAppendKeywords.begin(),
+                        supportedAppendKeywords.end(),
+                        std::back_inserter(unsupportedKeywordsUsed));
+    if (!unsupportedKeywordsUsed.empty()) {
+      std::string const msg =
+        cmJoin(unsupportedKeywordsUsed, ", "_s,
+               "The following keywords are not supported when using "
+               "APPEND with add_custom_command(OUTPUT): "_s);
+      if (cmp0175 == cmPolicies::NEW) {
+        mf.IssueMessage(MessageType::FATAL_ERROR, msg);
+        return false;
+      }
+      if (cmp0175 == cmPolicies::WARN) {
+        mf.IssueMessage(
+          MessageType::AUTHOR_WARNING,
+          cmStrCat(msg, ".\n",
+                   cmPolicies::GetPolicyWarning(cmPolicies::CMP0175)));
+      }
+    }
     mf.AppendCustomCommandToOutput(output[0], depends, implicit_depends,
                                    commandLines);
     return true;
@@ -376,9 +496,92 @@ bool cmAddCustomCommandCommand(std::vector<std::string> const& args,
   cc->SetDependsExplicitOnly(depends_explicit_only);
   if (source.empty() && output.empty()) {
     // Source is empty, use the target.
+    if (commandLines.empty()) {
+      std::string const msg = "At least one COMMAND must be given.";
+      if (cmp0175 == cmPolicies::NEW) {
+        mf.IssueMessage(MessageType::FATAL_ERROR, msg);
+        return false;
+      }
+      if (cmp0175 == cmPolicies::WARN) {
+        mf.IssueMessage(
+          MessageType::AUTHOR_WARNING,
+          cmStrCat(msg, '\n',
+                   cmPolicies::GetPolicyWarning(cmPolicies::CMP0175)));
+      }
+    }
+
+    std::vector<std::string> unsupportedKeywordsUsed;
+    std::set_difference(keywordsSeen.begin(), keywordsSeen.end(),
+                        supportedTargetKeywords.begin(),
+                        supportedTargetKeywords.end(),
+                        std::back_inserter(unsupportedKeywordsUsed));
+    if (!unsupportedKeywordsUsed.empty()) {
+      std::string const msg =
+        cmJoin(unsupportedKeywordsUsed, ", "_s,
+               "The following keywords are not supported when using "
+               "add_custom_command(TARGET): "_s);
+      if (cmp0175 == cmPolicies::NEW) {
+        mf.IssueMessage(MessageType::FATAL_ERROR, msg);
+        return false;
+      }
+      if (cmp0175 == cmPolicies::WARN) {
+        mf.IssueMessage(
+          MessageType::AUTHOR_WARNING,
+          cmStrCat(msg, ".\n",
+                   cmPolicies::GetPolicyWarning(cmPolicies::CMP0175)));
+      }
+    }
+    auto const prePostCount = keywordsSeen.count(keyPRE_BUILD) +
+      keywordsSeen.count(keyPRE_LINK) + keywordsSeen.count(keyPOST_BUILD);
+    if (prePostCount != 1) {
+      std::string msg =
+        "Exactly one of PRE_BUILD, PRE_LINK, or POST_BUILD must be given.";
+      if (cmp0175 == cmPolicies::NEW) {
+        mf.IssueMessage(MessageType::FATAL_ERROR, msg);
+        return false;
+      }
+      if (cmp0175 == cmPolicies::WARN) {
+        msg += " Assuming ";
+        switch (cctype) {
+          case cmCustomCommandType::PRE_BUILD:
+            msg += "PRE_BUILD";
+            break;
+          case cmCustomCommandType::PRE_LINK:
+            msg += "PRE_LINK";
+            break;
+          case cmCustomCommandType::POST_BUILD:
+            msg += "POST_BUILD";
+        }
+        mf.IssueMessage(
+          MessageType::AUTHOR_WARNING,
+          cmStrCat(msg, " to preserve backward compatibility.\n",
+                   cmPolicies::GetPolicyWarning(cmPolicies::CMP0175)));
+      }
+    }
     mf.AddCustomCommandToTarget(target, cctype, std::move(cc));
   } else if (target.empty()) {
     // Target is empty, use the output.
+    std::vector<std::string> unsupportedKeywordsUsed;
+    std::set_difference(keywordsSeen.begin(), keywordsSeen.end(),
+                        supportedOutputKeywords.begin(),
+                        supportedOutputKeywords.end(),
+                        std::back_inserter(unsupportedKeywordsUsed));
+    if (!unsupportedKeywordsUsed.empty()) {
+      std::string const msg =
+        cmJoin(unsupportedKeywordsUsed, ", "_s,
+               "The following keywords are not supported when using "
+               "add_custom_command(OUTPUT): "_s);
+      if (cmp0175 == cmPolicies::NEW) {
+        mf.IssueMessage(MessageType::FATAL_ERROR, msg);
+        return false;
+      }
+      if (cmp0175 == cmPolicies::WARN) {
+        mf.IssueMessage(
+          MessageType::AUTHOR_WARNING,
+          cmStrCat(msg, ".\n",
+                   cmPolicies::GetPolicyWarning(cmPolicies::CMP0175)));
+      }
+    }
     cc->SetOutputs(output);
     cc->SetMainDependency(main_dependency);
     cc->SetDepends(depends);

+ 2 - 0
Source/cmPolicies.h

@@ -537,6 +537,8 @@ class cmMakefile;
   SELECT(POLICY, CMP0174,                                                     \
          "cmake_parse_arguments(PARSE_ARGV) defines a variable for an empty " \
          "string after a single-value keyword.",                              \
+         3, 31, 0, cmPolicies::WARN)                                          \
+  SELECT(POLICY, CMP0175, "add_custom_command() rejects invalid arguments.",  \
          3, 31, 0, cmPolicies::WARN)
 
 #define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)

+ 1 - 0
Tests/RunCMake/add_custom_command/CMP0175-NEW-result.txt

@@ -0,0 +1 @@
+1

+ 29 - 0
Tests/RunCMake/add_custom_command/CMP0175-NEW-stderr.txt

@@ -0,0 +1,29 @@
+CMake Error at CMP0175\.cmake:[0-9]+ \(add_custom_command\):
+  The following keywords are not supported when using
+  add_custom_command\(TARGET\): DEPENDS, DEPENDS_EXPLICIT_ONLY, DEPFILE,
+  JOB_POOL, MAIN_DEPENDENCY
+Call Stack \(most recent call first\):
+  CMP0175-NEW\.cmake:2 \(include\)
+  CMakeLists\.txt:3 \(include\)
+
+
+CMake Error at CMP0175\.cmake:[0-9]+ \(add_custom_command\):
+  The following keywords are not supported when using
+  add_custom_command\(TARGET\): IMPLICIT_DEPENDS, USES_TERMINAL
+Call Stack \(most recent call first\):
+  CMP0175-NEW\.cmake:2 \(include\)
+  CMakeLists\.txt:3 \(include\)
+
+
+CMake Error at CMP0175\.cmake:[0-9]+ \(add_custom_command\):
+  Exactly one of PRE_BUILD, PRE_LINK, or POST_BUILD must be given\.
+Call Stack \(most recent call first\):
+  CMP0175-NEW\.cmake:2 \(include\)
+  CMakeLists\.txt:3 \(include\)
+
+
+CMake Error at CMP0175\.cmake:[0-9]+ \(add_custom_command\):
+  Exactly one of PRE_BUILD, PRE_LINK, or POST_BUILD must be given\.
+Call Stack \(most recent call first\):
+  CMP0175-NEW\.cmake:2 \(include\)
+  CMakeLists\.txt:3 \(include\)

+ 2 - 0
Tests/RunCMake/add_custom_command/CMP0175-NEW.cmake

@@ -0,0 +1,2 @@
+cmake_policy(SET CMP0175 NEW)
+include(CMP0175.cmake)

+ 2 - 0
Tests/RunCMake/add_custom_command/CMP0175-OLD.cmake

@@ -0,0 +1,2 @@
+cmake_policy(SET CMP0175 OLD)
+include(CMP0175.cmake)

+ 72 - 0
Tests/RunCMake/add_custom_command/CMP0175-WARN-stderr.txt

@@ -0,0 +1,72 @@
+CMake Warning \(dev\) at CMP0175\.cmake:[0-9]+ \(add_custom_command\):
+  The following keywords are not supported when using
+  add_custom_command\(TARGET\): DEPENDS, DEPENDS_EXPLICIT_ONLY, DEPFILE,
+  JOB_POOL, MAIN_DEPENDENCY\.
+
+  Policy CMP0175 is not set: add_custom_command\(\) rejects invalid arguments\.
+  Run "cmake --help-policy CMP0175" for policy details\.  Use the cmake_policy
+  command to set the policy and suppress this warning\.
+Call Stack \(most recent call first\):
+  CMP0175-WARN\.cmake:1 \(include\)
+  CMakeLists.txt:3 \(include\)
+This warning is for project developers\.  Use -Wno-dev to suppress it\.
+
+CMake Warning \(dev\) at CMP0175\.cmake:[0-9]+ \(add_custom_command\):
+  The following keywords are not supported when using
+  add_custom_command\(TARGET\): IMPLICIT_DEPENDS, USES_TERMINAL\.
+
+  Policy CMP0175 is not set: add_custom_command\(\) rejects invalid arguments\.
+  Run "cmake --help-policy CMP0175" for policy details\.  Use the cmake_policy
+  command to set the policy and suppress this warning\.
+Call Stack \(most recent call first\):
+  CMP0175-WARN\.cmake:1 \(include\)
+  CMakeLists\.txt:3 \(include\)
+This warning is for project developers\.  Use -Wno-dev to suppress it\.
+
+CMake Warning \(dev\) at CMP0175\.cmake:[0-9]+ \(add_custom_command\):
+  Exactly one of PRE_BUILD, PRE_LINK, or POST_BUILD must be given\.  Assuming
+  POST_BUILD to preserve backward compatibility\.
+
+  Policy CMP0175 is not set: add_custom_command\(\) rejects invalid arguments\.
+  Run "cmake --help-policy CMP0175" for policy details\.  Use the cmake_policy
+  command to set the policy and suppress this warning\.
+Call Stack \(most recent call first\):
+  CMP0175-WARN\.cmake:1 \(include\)
+  CMakeLists\.txt:3 \(include\)
+This warning is for project developers\.  Use -Wno-dev to suppress it\.
+
+CMake Warning \(dev\) at CMP0175\.cmake:[0-9]+ \(add_custom_command\):
+  Exactly one of PRE_BUILD, PRE_LINK, or POST_BUILD must be given\.  Assuming
+  POST_BUILD to preserve backward compatibility\.
+
+  Policy CMP0175 is not set: add_custom_command\(\) rejects invalid arguments\.
+  Run "cmake --help-policy CMP0175" for policy details\.  Use the cmake_policy
+  command to set the policy and suppress this warning\.
+Call Stack \(most recent call first\):
+  CMP0175-WARN\.cmake:1 \(include\)
+  CMakeLists\.txt:3 \(include\)
+This warning is for project developers\.  Use -Wno-dev to suppress it\.
+
+CMake Warning \(dev\) at CMP0175\.cmake:[0-9]+ \(add_custom_command\):
+  At least one COMMAND must be given\.
+
+  Policy CMP0175 is not set: add_custom_command\(\) rejects invalid arguments\.
+  Run "cmake --help-policy CMP0175" for policy details\.  Use the cmake_policy
+  command to set the policy and suppress this warning\.
+Call Stack \(most recent call first\):
+  CMP0175-WARN\.cmake:1 \(include\)
+  CMakeLists\.txt:3 \(include\)
+This warning is for project developers\.  Use -Wno-dev to suppress it\.
+
+CMake Warning \(dev\) at CMP0175\.cmake:[0-9]+ \(add_custom_command\):
+  The following keywords are not supported when using
+  add_custom_command\(OUTPUT\): OUTPUTS, POST_BUILD, PRE_BUILD, PRE_LINK,
+  SOURCE\.
+
+  Policy CMP0175 is not set: add_custom_command\(\) rejects invalid arguments\.
+  Run "cmake --help-policy CMP0175" for policy details\.  Use the cmake_policy
+  command to set the policy and suppress this warning\.
+Call Stack \(most recent call first\):
+  CMP0175-WARN\.cmake:1 \(include\)
+  CMakeLists\.txt:3 \(include\)
+This warning is for project developers\.  Use -Wno-dev to suppress it\.

+ 1 - 0
Tests/RunCMake/add_custom_command/CMP0175-WARN.cmake

@@ -0,0 +1 @@
+include(CMP0175.cmake)

+ 65 - 0
Tests/RunCMake/add_custom_command/CMP0175.cmake

@@ -0,0 +1,65 @@
+enable_language(CXX)
+file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/main.cpp "int main() {}")
+add_executable(main ${CMAKE_CURRENT_BINARY_DIR}/main.cpp)
+
+#============================================================================
+# add_custom_command(TARGET)
+#============================================================================
+
+# Unsupported keywords. Need to test them in batches to avoid other checks.
+add_custom_command(TARGET main
+  POST_BUILD
+  COMMAND ${CMAKE_COMMAND} -E true
+
+  # None of the following are allowed for the TARGET form
+
+  #APPEND   # Has its own check requiring OUTPUT to be set
+  #CODEGEN  # Other checks will fail before the CMP0175 check
+  DEPENDS valueDoesNotMatterHere
+  DEPENDS_EXPLICIT_ONLY YES
+  DEPFILE valueDoesNotMatterHere
+  #IMPLICIT_DEPENDS  # Earlier check fails when DEPFILE is present
+  JOB_POOL valueDoesNotMatterHere
+  MAIN_DEPENDENCY valueDoesNotMatterHere
+  #OUTPUT   # Other checks will fail before the CMP0175 check
+  #OUTPUTS  # Special case, not a documented keyword (used for deprecated form)
+  #SOURCE   # Old signature, special handling makes it hard to check
+  #USES_TERMINAL
+)
+add_custom_command(TARGET main
+  POST_BUILD
+  COMMAND ${CMAKE_COMMAND} -E true
+  # Has to be tested separately due to separate check for clash with DEPFILE
+  IMPLICIT_DEPENDS valueDoesNotMatterHere
+  # Has to be tested separately due to separate check for clash with JOB_POOL
+  USES_TERMINAL NO
+)
+
+# Missing any PRE_BUILD, PRE_LINK, or POST_BUILD
+add_custom_command(TARGET main
+  COMMAND ${CMAKE_COMMAND} -E true
+)
+
+# More than one of PRE_BUILD, PRE_LINK, or POST_BUILD
+add_custom_command(TARGET main
+  PRE_BUILD PRE_LINK POST_BUILD
+  COMMAND ${CMAKE_COMMAND} -E true
+)
+
+# Missing COMMAND
+add_custom_command(TARGET main
+  POST_BUILD
+  COMMENT "Need at least 4 arguments, so added this comment"
+)
+
+#============================================================================
+# add_custom_command(OUTPUT)
+#============================================================================
+
+add_custom_command(OUTPUT blah.txt
+  OUTPUTS
+  POST_BUILD
+  PRE_BUILD
+  PRE_LINK
+  SOURCE
+)

+ 3 - 0
Tests/RunCMake/add_custom_command/RunCMakeTest.cmake

@@ -1,5 +1,8 @@
 include(RunCMake)
 
+run_cmake(CMP0175-OLD)
+run_cmake(CMP0175-WARN)
+run_cmake(CMP0175-NEW)
 run_cmake(AppendLiteralQuotes)
 run_cmake(AppendNoOutput)
 run_cmake(AppendNotOutput)