瀏覽代碼

file(GENERATE): Add policy CMP0070 to define relative path behavior

Previously `file(GENERATE)` did not define any behavior for relative
paths given to the `OUTPUT` or `INPUT` arguments.  Define behavior
consistent with CMake conventions and add a policy to provide
compatibility for projects that relied on the old accidental behavior.

Fixes: #16786
Brad King 8 年之前
父節點
當前提交
82be694c7a

+ 5 - 0
Help/command/file.rst

@@ -291,6 +291,8 @@ from the input content to produce the output content.  The options are:
 
 
 ``INPUT <input-file>``
 ``INPUT <input-file>``
   Use the content from a given file as input.
   Use the content from a given file as input.
+  A relative path is treated with respect to the value of
+  :variable:`CMAKE_CURRENT_SOURCE_DIR`.  See policy :policy:`CMP0070`.
 
 
 ``OUTPUT <output-file>``
 ``OUTPUT <output-file>``
   Specify the output file name to generate.  Use generator expressions
   Specify the output file name to generate.  Use generator expressions
@@ -298,6 +300,9 @@ from the input content to produce the output content.  The options are:
   name.  Multiple configurations may generate the same output file only
   name.  Multiple configurations may generate the same output file only
   if the generated content is identical.  Otherwise, the ``<output-file>``
   if the generated content is identical.  Otherwise, the ``<output-file>``
   must evaluate to an unique name for each configuration.
   must evaluate to an unique name for each configuration.
+  A relative path (after evaluating generator expressions) is treated
+  with respect to the value of :variable:`CMAKE_CURRENT_BINARY_DIR`.
+  See policy :policy:`CMP0070`.
 
 
 Exactly one ``CONTENT`` or ``INPUT`` option must be given.  A specific
 Exactly one ``CONTENT`` or ``INPUT`` option must be given.  A specific
 ``OUTPUT`` file may be named by at most one invocation of ``file(GENERATE)``.
 ``OUTPUT`` file may be named by at most one invocation of ``file(GENERATE)``.

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

@@ -51,6 +51,14 @@ The :variable:`CMAKE_MINIMUM_REQUIRED_VERSION` variable may also be used
 to determine whether to report an error on use of deprecated macros or
 to determine whether to report an error on use of deprecated macros or
 functions.
 functions.
 
 
+Policies Introduced by CMake 3.10
+=================================
+
+.. toctree::
+   :maxdepth: 1
+
+   CMP0070: Define file(GENERATE) behavior for relative paths. </policy/CMP0070>
+
 Policies Introduced by CMake 3.9
 Policies Introduced by CMake 3.9
 ================================
 ================================
 
 

+ 25 - 0
Help/policy/CMP0070.rst

@@ -0,0 +1,25 @@
+CMP0070
+-------
+
+Define :command:`file(GENERATE)` behavior for relative paths.
+
+CMake 3.10 and newer define that relative paths given to ``INPUT`` and
+``OUTPUT`` arguments of ``file(GENERATE)`` are interpreted relative to the
+current source and binary directories, respectively.  CMake 3.9 and lower did
+not define any behavior for relative paths but did not diagnose them either
+and accidentally treated them relative to the process working directory.
+Policy ``CMP0070`` provides compatibility with projects that used the old
+undefined behavior.
+
+This policy affects behavior of relative paths given to ``file(GENERATE)``.
+The ``OLD`` behavior for this policy is to treat the paths relative to the
+working directory of CMake.  The ``NEW`` behavior for this policy is to
+interpret relative paths with respect to the current source or binary
+directory of the caller.
+
+This policy was introduced in CMake version 3.10.  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

+ 7 - 0
Help/release/dev/file-generate-relative-paths.rst

@@ -0,0 +1,7 @@
+file-generate-relative-paths
+----------------------------
+
+* The :command:`file(GENERATE)` command now interprets relative paths
+  given to its ``OUTPUT`` and ``INPUT`` arguments with respect to the
+  caller's current binary and source directories, respectively.
+  See policy :policy:`CMP0070`.

+ 61 - 1
Source/cmGeneratorExpressionEvaluationFile.cxx

@@ -20,11 +20,13 @@
 cmGeneratorExpressionEvaluationFile::cmGeneratorExpressionEvaluationFile(
 cmGeneratorExpressionEvaluationFile::cmGeneratorExpressionEvaluationFile(
   const std::string& input,
   const std::string& input,
   CM_AUTO_PTR<cmCompiledGeneratorExpression> outputFileExpr,
   CM_AUTO_PTR<cmCompiledGeneratorExpression> outputFileExpr,
-  CM_AUTO_PTR<cmCompiledGeneratorExpression> condition, bool inputIsContent)
+  CM_AUTO_PTR<cmCompiledGeneratorExpression> condition, bool inputIsContent,
+  cmPolicies::PolicyStatus policyStatusCMP0070)
   : Input(input)
   : Input(input)
   , OutputFileExpr(outputFileExpr)
   , OutputFileExpr(outputFileExpr)
   , Condition(condition)
   , Condition(condition)
   , InputIsContent(inputIsContent)
   , InputIsContent(inputIsContent)
+  , PolicyStatusCMP0070(policyStatusCMP0070)
 {
 {
 }
 }
 
 
@@ -58,6 +60,8 @@ void cmGeneratorExpressionEvaluationFile::Generate(
 
 
   if (cmSystemTools::FileIsFullPath(outputFileName)) {
   if (cmSystemTools::FileIsFullPath(outputFileName)) {
     outputFileName = cmSystemTools::CollapseFullPath(outputFileName);
     outputFileName = cmSystemTools::CollapseFullPath(outputFileName);
+  } else {
+    outputFileName = this->FixRelativePath(outputFileName, PathForOutput, lg);
   }
   }
 
 
   std::map<std::string, std::string>::iterator it =
   std::map<std::string, std::string>::iterator it =
@@ -118,6 +122,8 @@ void cmGeneratorExpressionEvaluationFile::Generate(cmLocalGenerator* lg)
     std::string inputFileName = this->Input;
     std::string inputFileName = this->Input;
     if (cmSystemTools::FileIsFullPath(inputFileName)) {
     if (cmSystemTools::FileIsFullPath(inputFileName)) {
       inputFileName = cmSystemTools::CollapseFullPath(inputFileName);
       inputFileName = cmSystemTools::CollapseFullPath(inputFileName);
+    } else {
+      inputFileName = this->FixRelativePath(inputFileName, PathForInput, lg);
     }
     }
     lg->GetMakefile()->AddCMakeDependFile(inputFileName);
     lg->GetMakefile()->AddCMakeDependFile(inputFileName);
     cmSystemTools::GetPermissions(inputFileName.c_str(), perm);
     cmSystemTools::GetPermissions(inputFileName.c_str(), perm);
@@ -167,3 +173,57 @@ void cmGeneratorExpressionEvaluationFile::Generate(cmLocalGenerator* lg)
     }
     }
   }
   }
 }
 }
+
+std::string cmGeneratorExpressionEvaluationFile::FixRelativePath(
+  std::string const& relativePath, PathRole role, cmLocalGenerator* lg)
+{
+  std::string resultPath;
+  switch (this->PolicyStatusCMP0070) {
+    case cmPolicies::WARN: {
+      std::string arg;
+      switch (role) {
+        case PathForInput:
+          arg = "INPUT";
+          break;
+        case PathForOutput:
+          arg = "OUTPUT";
+          break;
+      }
+      std::ostringstream w;
+      /* clang-format off */
+      w <<
+        cmPolicies::GetPolicyWarning(cmPolicies::CMP0070) << "\n"
+        "file(GENERATE) given relative " << arg << " path:\n"
+        "  " << relativePath << "\n"
+        "This is not defined behavior unless CMP0070 is set to NEW.  "
+        "For compatibility with older versions of CMake, the previous "
+        "undefined behavior will be used."
+        ;
+      /* clang-format on */
+      lg->IssueMessage(cmake::AUTHOR_WARNING, w.str());
+    }
+      CM_FALLTHROUGH;
+    case cmPolicies::OLD:
+      // OLD behavior is to use the relative path unchanged,
+      // which ends up being used relative to the working dir.
+      resultPath = relativePath;
+      break;
+    case cmPolicies::REQUIRED_IF_USED:
+    case cmPolicies::REQUIRED_ALWAYS:
+    case cmPolicies::NEW:
+      // NEW behavior is to interpret the relative path with respect
+      // to the current source or binary directory.
+      switch (role) {
+        case PathForInput:
+          resultPath = cmSystemTools::CollapseFullPath(
+            relativePath, lg->GetCurrentSourceDirectory());
+          break;
+        case PathForOutput:
+          resultPath = cmSystemTools::CollapseFullPath(
+            relativePath, lg->GetCurrentBinaryDirectory());
+          break;
+      }
+      break;
+  }
+  return resultPath;
+}

+ 12 - 1
Source/cmGeneratorExpressionEvaluationFile.h

@@ -10,6 +10,7 @@
 #include <vector>
 #include <vector>
 
 
 #include "cmGeneratorExpression.h"
 #include "cmGeneratorExpression.h"
+#include "cmPolicies.h"
 #include "cm_auto_ptr.hxx"
 #include "cm_auto_ptr.hxx"
 #include "cm_sys_stat.h"
 #include "cm_sys_stat.h"
 
 
@@ -21,7 +22,8 @@ public:
   cmGeneratorExpressionEvaluationFile(
   cmGeneratorExpressionEvaluationFile(
     const std::string& input,
     const std::string& input,
     CM_AUTO_PTR<cmCompiledGeneratorExpression> outputFileExpr,
     CM_AUTO_PTR<cmCompiledGeneratorExpression> outputFileExpr,
-    CM_AUTO_PTR<cmCompiledGeneratorExpression> condition, bool inputIsContent);
+    CM_AUTO_PTR<cmCompiledGeneratorExpression> condition, bool inputIsContent,
+    cmPolicies::PolicyStatus policyStatusCMP0070);
 
 
   void Generate(cmLocalGenerator* lg);
   void Generate(cmLocalGenerator* lg);
 
 
@@ -35,12 +37,21 @@ private:
                 cmCompiledGeneratorExpression* inputExpression,
                 cmCompiledGeneratorExpression* inputExpression,
                 std::map<std::string, std::string>& outputFiles, mode_t perm);
                 std::map<std::string, std::string>& outputFiles, mode_t perm);
 
 
+  enum PathRole
+  {
+    PathForInput,
+    PathForOutput
+  };
+  std::string FixRelativePath(std::string const& filePath, PathRole role,
+                              cmLocalGenerator* lg);
+
 private:
 private:
   const std::string Input;
   const std::string Input;
   const CM_AUTO_PTR<cmCompiledGeneratorExpression> OutputFileExpr;
   const CM_AUTO_PTR<cmCompiledGeneratorExpression> OutputFileExpr;
   const CM_AUTO_PTR<cmCompiledGeneratorExpression> Condition;
   const CM_AUTO_PTR<cmCompiledGeneratorExpression> Condition;
   std::vector<std::string> Files;
   std::vector<std::string> Files;
   const bool InputIsContent;
   const bool InputIsContent;
+  cmPolicies::PolicyStatus PolicyStatusCMP0070;
 };
 };
 
 
 #endif
 #endif

+ 2 - 1
Source/cmMakefile.cxx

@@ -592,7 +592,8 @@ void cmMakefile::AddEvaluationFile(
   CM_AUTO_PTR<cmCompiledGeneratorExpression> condition, bool inputIsContent)
   CM_AUTO_PTR<cmCompiledGeneratorExpression> condition, bool inputIsContent)
 {
 {
   this->EvaluationFiles.push_back(new cmGeneratorExpressionEvaluationFile(
   this->EvaluationFiles.push_back(new cmGeneratorExpressionEvaluationFile(
-    inputFile, outputName, condition, inputIsContent));
+    inputFile, outputName, condition, inputIsContent,
+    this->GetPolicyStatus(cmPolicies::CMP0070)));
 }
 }
 
 
 std::vector<cmGeneratorExpressionEvaluationFile*>
 std::vector<cmGeneratorExpressionEvaluationFile*>

+ 3 - 0
Source/cmPolicies.h

@@ -206,6 +206,9 @@ class cmMakefile;
          cmPolicies::WARN)                                                    \
          cmPolicies::WARN)                                                    \
   SELECT(POLICY, CMP0069,                                                     \
   SELECT(POLICY, CMP0069,                                                     \
          "INTERPROCEDURAL_OPTIMIZATION is enforced when enabled.", 3, 9, 0,   \
          "INTERPROCEDURAL_OPTIMIZATION is enforced when enabled.", 3, 9, 0,   \
+         cmPolicies::WARN)                                                    \
+  SELECT(POLICY, CMP0070,                                                     \
+         "Define file(GENERATE) behavior for relative paths.", 3, 10, 0,      \
          cmPolicies::WARN)
          cmPolicies::WARN)
 
 
 #define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
 #define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)

+ 13 - 0
Tests/RunCMake/File_Generate/CMP0070-NEW-check.cmake

@@ -0,0 +1,13 @@
+foreach(f
+    "${RunCMake_TEST_SOURCE_DIR}/relative-input-NEW.txt"
+    "${RunCMake_TEST_BINARY_DIR}/relative-output-NEW.txt"
+    )
+  if(EXISTS "${f}")
+    file(READ "${f}" content)
+    if(NOT content MATCHES "^relative-input-NEW[\r\n]*$")
+      string(APPEND RunCMake_TEST_FAILED "File\n  ${f}\ndoes not have expected content.\n")
+    endif()
+  else()
+    string(APPEND RunCMake_TEST_FAILED "Missing\n  ${f}\n")
+  endif()
+endforeach()

+ 2 - 0
Tests/RunCMake/File_Generate/CMP0070-NEW.cmake

@@ -0,0 +1,2 @@
+cmake_policy(SET CMP0070 NEW)
+file(GENERATE OUTPUT relative-output-NEW.txt INPUT relative-input-NEW.txt)

+ 13 - 0
Tests/RunCMake/File_Generate/CMP0070-OLD-check.cmake

@@ -0,0 +1,13 @@
+foreach(f
+    "${RunCMake_TEST_BINARY_DIR}/relative-input-OLD.txt"
+    "${RunCMake_TEST_BINARY_DIR}/relative-output-OLD.txt"
+    )
+  if(EXISTS "${f}")
+    file(READ "${f}" content)
+    if(NOT content MATCHES "^relative-input-OLD[\r\n]*$")
+      string(APPEND RunCMake_TEST_FAILED "File\n  ${f}\ndoes not have expected content.\n")
+    endif()
+  else()
+    string(APPEND RunCMake_TEST_FAILED "Missing\n  ${f}\n")
+  endif()
+endforeach()

+ 3 - 0
Tests/RunCMake/File_Generate/CMP0070-OLD.cmake

@@ -0,0 +1,3 @@
+cmake_policy(SET CMP0070 OLD)
+file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/relative-input-OLD.txt "relative-input-OLD\n")
+file(GENERATE OUTPUT relative-output-OLD.txt INPUT relative-input-OLD.txt)

+ 13 - 0
Tests/RunCMake/File_Generate/CMP0070-WARN-check.cmake

@@ -0,0 +1,13 @@
+foreach(f
+    "${RunCMake_TEST_BINARY_DIR}/relative-input-WARN.txt"
+    "${RunCMake_TEST_BINARY_DIR}/relative-output-WARN.txt"
+    )
+  if(EXISTS "${f}")
+    file(READ "${f}" content)
+    if(NOT content MATCHES "^relative-input-WARN[\r\n]*$")
+      string(APPEND RunCMake_TEST_FAILED "File\n  ${f}\ndoes not have expected content.\n")
+    endif()
+  else()
+    string(APPEND RunCMake_TEST_FAILED "Missing\n  ${f}\n")
+  endif()
+endforeach()

+ 27 - 0
Tests/RunCMake/File_Generate/CMP0070-WARN-stderr.txt

@@ -0,0 +1,27 @@
+^CMake Warning \(dev\) in CMakeLists.txt:
+  Policy CMP0070 is not set: Define file\(GENERATE\) behavior for relative
+  paths.  Run "cmake --help-policy CMP0070" for policy details.  Use the
+  cmake_policy command to set the policy and suppress this warning.
+
+  file\(GENERATE\) given relative INPUT path:
+
+    relative-input-WARN.txt
+
+  This is not defined behavior unless CMP0070 is set to NEW.  For
+  compatibility with older versions of CMake, the previous undefined behavior
+  will be used.
+This warning is for project developers.  Use -Wno-dev to suppress it.(
++
+CMake Warning \(dev\) in CMakeLists.txt:
+  Policy CMP0070 is not set: Define file\(GENERATE\) behavior for relative
+  paths.  Run "cmake --help-policy CMP0070" for policy details.  Use the
+  cmake_policy command to set the policy and suppress this warning.
+
+  file\(GENERATE\) given relative OUTPUT path:
+
+    relative-output-WARN.txt
+
+  This is not defined behavior unless CMP0070 is set to NEW.  For
+  compatibility with older versions of CMake, the previous undefined behavior
+  will be used.
+This warning is for project developers.  Use -Wno-dev to suppress it.)+$

+ 2 - 0
Tests/RunCMake/File_Generate/CMP0070-WARN.cmake

@@ -0,0 +1,2 @@
+file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/relative-input-WARN.txt "relative-input-WARN\n")
+file(GENERATE OUTPUT relative-output-WARN.txt INPUT relative-input-WARN.txt)

+ 4 - 0
Tests/RunCMake/File_Generate/RunCMakeTest.cmake

@@ -1,5 +1,9 @@
 include(RunCMake)
 include(RunCMake)
 
 
+run_cmake(CMP0070-NEW)
+run_cmake(CMP0070-OLD)
+run_cmake(CMP0070-WARN)
+
 run_cmake(CommandConflict)
 run_cmake(CommandConflict)
 if("${RunCMake_GENERATOR}" MATCHES "Visual Studio|Xcode")
 if("${RunCMake_GENERATOR}" MATCHES "Visual Studio|Xcode")
   run_cmake(OutputConflict)
   run_cmake(OutputConflict)

+ 1 - 0
Tests/RunCMake/File_Generate/relative-input-NEW.txt

@@ -0,0 +1 @@
+relative-input-NEW