From be4b9e10afaae750ad184ce1b34d6cb2c3d7fd33 Mon Sep 17 00:00:00 2001
From: Marc Chevrier <marc.chevrier@gmail.com>
Date: Fri, 3 Jun 2022 12:42:41 +0200
Subject: if command: Add PATH_EQUAL operator

---
 Help/command/if.rst                            | 30 +++++++++++++++++++++++++-
 Help/manual/cmake-policies.7.rst               |  1 +
 Help/policy/CMP0139.rst                        | 17 +++++++++++++++
 Help/release/dev/if-PATH_EQUAL.rst             |  5 +++++
 Source/cmConditionEvaluator.cxx                | 26 ++++++++++++++++++++++
 Source/cmConditionEvaluator.h                  |  1 +
 Source/cmPolicies.h                            |  6 +++++-
 Tests/RunCMake/CMP0139/CMP0139-NEW.cmake       | 25 +++++++++++++++++++++
 Tests/RunCMake/CMP0139/CMP0139-OLD-result.txt  |  1 +
 Tests/RunCMake/CMP0139/CMP0139-OLD-stderr.txt  |  8 +++++++
 Tests/RunCMake/CMP0139/CMP0139-OLD.cmake       |  5 +++++
 Tests/RunCMake/CMP0139/CMP0139-WARN-result.txt |  1 +
 Tests/RunCMake/CMP0139/CMP0139-WARN-stderr.txt | 19 ++++++++++++++++
 Tests/RunCMake/CMP0139/CMP0139-WARN.cmake      |  4 ++++
 Tests/RunCMake/CMP0139/CMakeLists.txt          |  3 +++
 Tests/RunCMake/CMP0139/RunCMakeTest.cmake      |  5 +++++
 Tests/RunCMake/CMakeLists.txt                  |  1 +
 17 files changed, 156 insertions(+), 2 deletions(-)
 create mode 100644 Help/policy/CMP0139.rst
 create mode 100644 Help/release/dev/if-PATH_EQUAL.rst
 create mode 100644 Tests/RunCMake/CMP0139/CMP0139-NEW.cmake
 create mode 100644 Tests/RunCMake/CMP0139/CMP0139-OLD-result.txt
 create mode 100644 Tests/RunCMake/CMP0139/CMP0139-OLD-stderr.txt
 create mode 100644 Tests/RunCMake/CMP0139/CMP0139-OLD.cmake
 create mode 100644 Tests/RunCMake/CMP0139/CMP0139-WARN-result.txt
 create mode 100644 Tests/RunCMake/CMP0139/CMP0139-WARN-stderr.txt
 create mode 100644 Tests/RunCMake/CMP0139/CMP0139-WARN.cmake
 create mode 100644 Tests/RunCMake/CMP0139/CMakeLists.txt
 create mode 100644 Tests/RunCMake/CMP0139/RunCMakeTest.cmake

diff --git a/Help/command/if.rst b/Help/command/if.rst
index 64f1c35..c096725 100644
--- a/Help/command/if.rst
+++ b/Help/command/if.rst
@@ -47,7 +47,7 @@ Compound conditions are evaluated in the following order of precedence:
    `GREATER_EQUAL`_, `STREQUAL`_, `STRLESS`_, `STRLESS_EQUAL`_,
    `STRGREATER`_, `STRGREATER_EQUAL`_, `VERSION_EQUAL`_, `VERSION_LESS`_,
    `VERSION_LESS_EQUAL`_, `VERSION_GREATER`_, `VERSION_GREATER_EQUAL`_,
-   and `MATCHES`_.
+   `PATH_EQUAL`_, and `MATCHES`_.
 
 4. Unary logical operator `NOT`_.
 
@@ -314,6 +314,34 @@ Version Comparisons
   Any non-integer version component or non-integer trailing part of a version
   component effectively truncates the string at that point.
 
+Path Comparisons
+""""""""""""""""
+
+.. _PATH_EQUAL:
+
+``if(<variable|string> PATH_EQUAL <variable|string>)``
+ .. versionadded:: 3.24
+  Compares the lexical representations of two paths provided as string
+  literals or variables. No normalization is performed on either path.
+
+  Lexical comparison has the advantage over string comparison to have a
+  knowledge of the structure of the path. So, the following comparison is
+  ``TRUE`` using ``PATH_EQUAL`` operator, but ``FALSE`` with ``STREQUAL``:
+
+  .. code-block:: cmake
+
+    # comparison is TRUE
+    if ("/a//b/c" PATH_EQUAL "/a/b/c")
+       ...
+    endif()
+
+    # comparison is FALSE
+    if ("/a//b/c" STREQUAL "/a/b/c")
+       ...
+    endif()
+
+  See :ref:`cmake_path(COMPARE) <Path COMPARE>` for more details.
+
 Variable Expansion
 ^^^^^^^^^^^^^^^^^^
 
diff --git a/Help/manual/cmake-policies.7.rst b/Help/manual/cmake-policies.7.rst
index a7741f7..1447c17 100644
--- a/Help/manual/cmake-policies.7.rst
+++ b/Help/manual/cmake-policies.7.rst
@@ -58,6 +58,7 @@ Policies Introduced by CMake 3.24
 .. toctree::
    :maxdepth: 1
 
+   CMP0139: The if() command supports path comparisons using PATH_EQUAL operator. </policy/CMP0139>
    CMP0138: MSVC compilers use -ZI instead of /Zi for x86 and x64 by default. </policy/CMP0138>
    CMP0137: try_compile() passes platform variables in project mode. </policy/CMP0137>
    CMP0136: Watcom runtime library flags are selected by an abstraction. </policy/CMP0136>
diff --git a/Help/policy/CMP0139.rst b/Help/policy/CMP0139.rst
new file mode 100644
index 0000000..5a0f4f7
--- /dev/null
+++ b/Help/policy/CMP0139.rst
@@ -0,0 +1,17 @@
+CMP0139
+-------
+
+.. versionadded:: 3.24
+
+The :command:`if` command supports path comparisons using ``PATH_EQUAL``
+operator.
+
+The ``OLD`` behavior for this policy is to ignore the ``PATH_EQUAL`` operator.
+The ``NEW`` behavior is to interpret the ``PATH_EQUAL`` operator.
+
+This policy was introduced in CMake version 3.24.
+CMake version |release| warns when the policy is not set and uses
+``OLD`` behavior.  Use the :command:`cmake_policy` command to set
+it to ``OLD`` or ``NEW`` explicitly.
+
+.. include:: DEPRECATED.txt
diff --git a/Help/release/dev/if-PATH_EQUAL.rst b/Help/release/dev/if-PATH_EQUAL.rst
new file mode 100644
index 0000000..45f02e8
--- /dev/null
+++ b/Help/release/dev/if-PATH_EQUAL.rst
@@ -0,0 +1,5 @@
+if-PATH_EQUAL
+-------------
+
+* The :command:`if` command gains the capability to compare paths by using the
+  ``PATH_EQUAL`` operator. See policy :policy:`CMP0139`.
diff --git a/Source/cmConditionEvaluator.cxx b/Source/cmConditionEvaluator.cxx
index 141c4eb..5de012a 100644
--- a/Source/cmConditionEvaluator.cxx
+++ b/Source/cmConditionEvaluator.cxx
@@ -16,6 +16,7 @@
 
 #include "cmsys/RegularExpression.hxx"
 
+#include "cmCMakePath.h"
 #include "cmExpandedCommandArgument.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
@@ -58,6 +59,7 @@ auto const keyVERSION_GREATER = "VERSION_GREATER"_s;
 auto const keyVERSION_GREATER_EQUAL = "VERSION_GREATER_EQUAL"_s;
 auto const keyVERSION_LESS = "VERSION_LESS"_s;
 auto const keyVERSION_LESS_EQUAL = "VERSION_LESS_EQUAL"_s;
+auto const keyPATH_EQUAL = "PATH_EQUAL"_s;
 
 cmSystemTools::CompareOp const MATCH2CMPOP[5] = {
   cmSystemTools::OP_LESS, cmSystemTools::OP_LESS_EQUAL,
@@ -217,6 +219,7 @@ cmConditionEvaluator::cmConditionEvaluator(cmMakefile& makefile,
   , Policy54Status(makefile.GetPolicyStatus(cmPolicies::CMP0054))
   , Policy57Status(makefile.GetPolicyStatus(cmPolicies::CMP0057))
   , Policy64Status(makefile.GetPolicyStatus(cmPolicies::CMP0064))
+  , Policy139Status(makefile.GetPolicyStatus(cmPolicies::CMP0139))
 {
 }
 
@@ -775,6 +778,29 @@ bool cmConditionEvaluator::HandleLevel2(cmArgumentList& newArgs,
         this->Makefile.IssueMessage(MessageType::AUTHOR_WARNING, e.str());
       }
     }
+
+    else if (this->IsKeyword(keyPATH_EQUAL, *args.next)) {
+
+      if (this->Policy139Status != cmPolicies::OLD &&
+          this->Policy139Status != cmPolicies::WARN) {
+
+        cmValue lhs = this->GetVariableOrString(*args.current);
+        cmValue rhs = this->GetVariableOrString(*args.nextnext);
+        const auto result = cmCMakePath{ *lhs } == cmCMakePath{ *rhs };
+        newArgs.ReduceTwoArgs(result, args);
+      }
+
+      else if (this->Policy139Status == cmPolicies::WARN) {
+        std::ostringstream e;
+        e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0139)
+          << "\n"
+             "PATH_EQUAL will be interpreted as an operator "
+             "when the policy is set to NEW.  "
+             "Since the policy is not set the OLD behavior will be used.";
+
+        this->Makefile.IssueMessage(MessageType::AUTHOR_WARNING, e.str());
+      }
+    }
   }
   return true;
 }
diff --git a/Source/cmConditionEvaluator.h b/Source/cmConditionEvaluator.h
index 37b7825..9486b16 100644
--- a/Source/cmConditionEvaluator.h
+++ b/Source/cmConditionEvaluator.h
@@ -79,4 +79,5 @@ private:
   cmPolicies::PolicyStatus Policy54Status;
   cmPolicies::PolicyStatus Policy57Status;
   cmPolicies::PolicyStatus Policy64Status;
+  cmPolicies::PolicyStatus Policy139Status;
 };
diff --git a/Source/cmPolicies.h b/Source/cmPolicies.h
index f13432b..256d483 100644
--- a/Source/cmPolicies.h
+++ b/Source/cmPolicies.h
@@ -417,7 +417,11 @@ class cmMakefile;
          cmPolicies::WARN)                                                    \
   SELECT(POLICY, CMP0138,                                                     \
          "MSVC compilers use -ZI instead of /Zi for x86 and x64 by default.", \
-         3, 24, 0, cmPolicies::WARN)
+         3, 24, 0, cmPolicies::WARN)                                          \
+  SELECT(                                                                     \
+    POLICY, CMP0139,                                                          \
+    "The if() command supports path comparisons using PATH_EQUAL operator.",  \
+    3, 24, 0, cmPolicies::WARN)
 
 #define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
 #define CM_FOR_EACH_POLICY_ID(POLICY)                                         \
diff --git a/Tests/RunCMake/CMP0139/CMP0139-NEW.cmake b/Tests/RunCMake/CMP0139/CMP0139-NEW.cmake
new file mode 100644
index 0000000..8144696
--- /dev/null
+++ b/Tests/RunCMake/CMP0139/CMP0139-NEW.cmake
@@ -0,0 +1,25 @@
+cmake_policy(SET CMP0139 NEW)
+
+
+set(path "a///b/c")
+if (NOT path PATH_EQUAL "a/b/c")
+  message(SEND_ERROR "if(PATH_EQUAL): '${path}' not equal to 'a/b/c'")
+endif()
+set(path2 "a/b/c")
+if (NOT path PATH_EQUAL path2)
+  message(SEND_ERROR "if(PATH_EQUAL): '${path}' not equal to '${path2}'")
+endif()
+
+set (path "a/b/d/../c")
+if (path PATH_EQUAL "a/b/c")
+  message(SEND_ERROR "if(PATH_EQUAL): '${path}' equal to 'a/b/c'")
+endif()
+set(path2 "a/b/c")
+if ("a/b/d/../c" PATH_EQUAL path2)
+  message(SEND_ERROR "if(PATH_EQUAL): 'a/b/d/../c' equal to '${path2}'")
+endif()
+
+cmake_path(NORMAL_PATH path)
+if (NOT path PATH_EQUAL "a/b/c")
+  message(SEND_ERROR "if(PATH_EQUAL): '${path}' not equal to 'a/b/c'")
+endif()
diff --git a/Tests/RunCMake/CMP0139/CMP0139-OLD-result.txt b/Tests/RunCMake/CMP0139/CMP0139-OLD-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CMP0139/CMP0139-OLD-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CMP0139/CMP0139-OLD-stderr.txt b/Tests/RunCMake/CMP0139/CMP0139-OLD-stderr.txt
new file mode 100644
index 0000000..1cfb319
--- /dev/null
+++ b/Tests/RunCMake/CMP0139/CMP0139-OLD-stderr.txt
@@ -0,0 +1,8 @@
+CMake Error at CMP0139-OLD.cmake:[0-9]+ \(if\):
+  if given arguments:
+
+    "/path1" "PATH_EQUAL" "/path2"
+
+  Unknown arguments specified
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/CMP0139/CMP0139-OLD.cmake b/Tests/RunCMake/CMP0139/CMP0139-OLD.cmake
new file mode 100644
index 0000000..e813a47
--- /dev/null
+++ b/Tests/RunCMake/CMP0139/CMP0139-OLD.cmake
@@ -0,0 +1,5 @@
+cmake_policy(SET CMP0139 OLD)
+
+if("/path1" PATH_EQUAL "/path2")
+  message("PATH_EQUAL recognized")
+endif()
diff --git a/Tests/RunCMake/CMP0139/CMP0139-WARN-result.txt b/Tests/RunCMake/CMP0139/CMP0139-WARN-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CMP0139/CMP0139-WARN-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CMP0139/CMP0139-WARN-stderr.txt b/Tests/RunCMake/CMP0139/CMP0139-WARN-stderr.txt
new file mode 100644
index 0000000..6a873ca
--- /dev/null
+++ b/Tests/RunCMake/CMP0139/CMP0139-WARN-stderr.txt
@@ -0,0 +1,19 @@
+CMake Warning \(dev\) at CMP0139-WARN.cmake:[0-9]+ \(if\):
+  Policy CMP0139 is not set: The if\(\) command supports path comparisons using
+  PATH_EQUAL operator.  Run "cmake --help-policy CMP0139" for policy details.
+  Use the cmake_policy command to set the policy and suppress this warning.
+
+  PATH_EQUAL will be interpreted as an operator when the policy is set to
+  NEW.  Since the policy is not set the OLD behavior will be used.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
+This warning is for project developers.  Use -Wno-dev to suppress it.
+
+CMake Error at CMP0139-WARN.cmake:[0-9]+ \(if\):
+  if given arguments:
+
+    "/path1" "PATH_EQUAL" "/path2"
+
+  Unknown arguments specified
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/CMP0139/CMP0139-WARN.cmake b/Tests/RunCMake/CMP0139/CMP0139-WARN.cmake
new file mode 100644
index 0000000..d74753e
--- /dev/null
+++ b/Tests/RunCMake/CMP0139/CMP0139-WARN.cmake
@@ -0,0 +1,4 @@
+
+if("/path1" PATH_EQUAL "/path2")
+  message("PATH_EQUAL recognized")
+endif()
diff --git a/Tests/RunCMake/CMP0139/CMakeLists.txt b/Tests/RunCMake/CMP0139/CMakeLists.txt
new file mode 100644
index 0000000..18dfd26
--- /dev/null
+++ b/Tests/RunCMake/CMP0139/CMakeLists.txt
@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.2)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/CMP0139/RunCMakeTest.cmake b/Tests/RunCMake/CMP0139/RunCMakeTest.cmake
new file mode 100644
index 0000000..2dbea3c
--- /dev/null
+++ b/Tests/RunCMake/CMP0139/RunCMakeTest.cmake
@@ -0,0 +1,5 @@
+include(RunCMake)
+
+run_cmake(CMP0139-OLD)
+run_cmake(CMP0139-WARN)
+run_cmake(CMP0139-NEW)
diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt
index 3ca01ec..7e17450 100644
--- a/Tests/RunCMake/CMakeLists.txt
+++ b/Tests/RunCMake/CMakeLists.txt
@@ -150,6 +150,7 @@ endif()
 
 add_RunCMake_test(CMP0132)
 add_RunCMake_test(CMP0135)
+add_RunCMake_test(CMP0139)
 
 # The test for Policy 65 requires the use of the
 # CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS variable, which both the VS and Xcode
-- 
cgit v0.12