浏览代码

ExternalProject: Add DOWNLOAD_EXTRACT_TIMESTAMP option and policy

Add the option to keep the current filestamps when extracting an
archive in ExternalProject_Add.

Enabling this option makes the behavior consistent with how
ExternalProject_Add is used when checking out code from revision
control instead of an archive.

Fixes: #22746
Kasper Laudrup 3 年之前
父节点
当前提交
a283e58b51

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

@@ -58,6 +58,7 @@ Policies Introduced by CMake 3.24
 .. toctree::
    :maxdepth: 1
 
+   CMP0135: ExternalProject ignores timestamps in archives by default for the URL download method. </policy/CMP0135>
    CMP0134: Fallback to \"HOST\" Windows registry view when \"TARGET\" view is not usable. </policy/CMP0134>
    CMP0133: The CPack module disables SLA by default in the CPack DragNDrop Generator. </policy/CMP0133>
    CMP0132: Do not set compiler environment variables on first run. </policy/CMP0132>

+ 29 - 0
Help/policy/CMP0135.rst

@@ -0,0 +1,29 @@
+CMP0135
+-------
+
+.. versionadded:: 3.24
+
+When using the ``URL`` download method with the :command:`ExternalProject_Add`
+command, CMake 3.23 and below sets the timestamps of the extracted contents
+to the same as the timestamps in the archive. When the ``URL`` changes, the
+new archive is downloaded and extracted, but the timestamps of the extracted
+contents might not be newer than the previous contents. Anything that depends
+on the extracted contents might not be rebuilt, even though the contents may
+change.
+
+CMake 3.24 and above prefers to set the timestamps of all extracted contents
+to the time of the extraction. This ensures that anything that depends on the
+extracted contents will be rebuilt whenever the ``URL`` changes.
+
+The ``DOWNLOAD_EXTRACT_TIMESTAMP`` option to the
+:command:`ExternalProject_Add` command can be used to explicitly specify how
+timestamps should be handled. When ``DOWNLOAD_EXTRACT_TIMESTAMP`` is not
+given, this policy controls the default behavior. The ``OLD`` behavior for
+this policy is to restore the timestamps from the archive. The ``NEW``
+behavior sets the timestamps of extracted contents to the time of extraction.
+
+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

+ 8 - 0
Help/release/dev/ExternalProject-no-extract-timestamp.rst

@@ -0,0 +1,8 @@
+ExternalProject-no-extract-timestamp
+------------------------------------
+
+* The :command:`ExternalProject_Add` command gained a new
+  ``DOWNLOAD_EXTRACT_TIMESTAMP`` option for controlling whether the timestamps
+  of extracted contents are set to match those in the archive when the ``URL``
+  download method is used. A new policy :policy:`CMP0135` was added to control
+  the default behavior when the new option is not used.

+ 56 - 4
Modules/ExternalProject.cmake

@@ -170,6 +170,19 @@ External Project Definition
         the default name is generally suitable and is not normally used outside
         of code internal to the ``ExternalProject`` module.
 
+      ``DOWNLOAD_EXTRACT_TIMESTAMP <bool>``
+        .. versionadded:: 3.24
+
+        When specified with a true value, the timestamps of the extracted
+        files will match those in the archive. When false, the timestamps of
+        the extracted files will reflect the time at which the extraction
+        was performed. If the download URL changes, timestamps based off
+        those in the archive can result in dependent targets not being rebuilt
+        when they potentially should have been. Therefore, unless the file
+        timestamps are significant to the project in some way, use a false
+        value for this option. If ``DOWNLOAD_EXTRACT_TIMESTAMP`` is not given,
+        the default is false. See policy :policy:`CMP0135`.
+
       ``DOWNLOAD_NO_EXTRACT <bool>``
         .. versionadded:: 3.6
 
@@ -1534,7 +1547,7 @@ function(_ep_write_verifyfile_script script_filename LOCAL hash)
 endfunction()
 
 
-function(_ep_write_extractfile_script script_filename name filename directory)
+function(_ep_write_extractfile_script script_filename name filename directory options)
   set(args "")
 
   if(filename MATCHES "(\\.|=)(7z|tar\\.bz2|tar\\.gz|tar\\.xz|tbz2|tgz|txz|zip)$")
@@ -2761,16 +2774,51 @@ hash=${hash}
         )
       endif()
       list(APPEND cmd ${CMAKE_COMMAND} -P ${stamp_dir}/verify-${name}.cmake)
-      if (NOT no_extract)
+      get_target_property(extract_timestamp ${name} _EP_DOWNLOAD_EXTRACT_TIMESTAMP)
+      if(no_extract)
+        if(NOT extract_timestamp STREQUAL "extract_timestamp-NOTFOUND")
+          message(FATAL_ERROR
+            "Cannot specify DOWNLOAD_EXTRACT_TIMESTAMP when using "
+            "DOWNLOAD_NO_EXTRACT TRUE"
+          )
+        endif()
+        set_property(TARGET ${name} PROPERTY _EP_DOWNLOADED_FILE ${file})
+      else()
+        if(extract_timestamp STREQUAL "extract_timestamp-NOTFOUND")
+          # Default depends on policy CMP0135
+          if(_EP_CMP0135 STREQUAL "")
+            message(AUTHOR_WARNING
+              "The DOWNLOAD_EXTRACT_TIMESTAMP option was not given and policy "
+              "CMP0135 is not set. The policy's OLD behavior will be used. "
+              "When using a URL download, the timestamps of extracted files "
+              "should preferably be that of the time of extraction, otherwise "
+              "code that depends on the extracted contents might not be "
+              "rebuilt if the URL changes. The OLD behavior preserves the "
+              "timestamps from the archive instead, but this is usually not "
+              "what you want. Update your project to the NEW behavior or "
+              "specify the DOWNLOAD_EXTRACT_TIMESTAMP option with a value of "
+              "true to avoid this robustness issue."
+            )
+            set(extract_timestamp TRUE)
+          elseif(_EP_CMP0135 STREQUAL "NEW")
+            set(extract_timestamp FALSE)
+          else()
+            set(extract_timestamp TRUE)
+          endif()
+        endif()
+        if(extract_timestamp)
+          set(options "")
+        else()
+          set(options "--touch")
+        endif()
         _ep_write_extractfile_script(
           "${stamp_dir}/extract-${name}.cmake"
           "${name}"
           "${file}"
           "${source_dir}"
+          "${options}"
         )
         list(APPEND cmd COMMAND ${CMAKE_COMMAND} -P ${stamp_dir}/extract-${name}.cmake)
-      else ()
-        set_property(TARGET ${name} PROPERTY _EP_DOWNLOADED_FILE ${file})
       endif ()
     endif()
   else()
@@ -3438,6 +3486,9 @@ function(ExternalProject_Add name)
       )
     set(cmp0114 "NEW")
   endif()
+  cmake_policy(GET CMP0135 _EP_CMP0135
+    PARENT_SCOPE # undocumented, do not use outside of CMake
+    )
 
   _ep_get_configuration_subdir_suffix(cfgdir)
 
@@ -3483,6 +3534,7 @@ function(ExternalProject_Add name)
     URL_HASH
     URL_MD5
     DOWNLOAD_NAME
+    DOWNLOAD_EXTRACT_TIMESTAMP
     DOWNLOAD_NO_EXTRACT
     DOWNLOAD_NO_PROGRESS
     TIMEOUT

+ 1 - 1
Modules/ExternalProject/extractfile.cmake.in

@@ -29,7 +29,7 @@ file(MAKE_DIRECTORY "${ut_dir}")
 # Extract it:
 #
 message(STATUS "extracting... [tar @args@]")
-execute_process(COMMAND ${CMAKE_COMMAND} -E tar @args@ ${filename}
+execute_process(COMMAND ${CMAKE_COMMAND} -E tar @args@ ${filename} @options@
   WORKING_DIRECTORY ${ut_dir}
   RESULT_VARIABLE rv
 )

+ 4 - 0
Source/cmPolicies.h

@@ -404,6 +404,10 @@ class cmMakefile;
   SELECT(POLICY, CMP0134,                                                     \
          "Fallback to \"HOST\" Windows registry view when \"TARGET\" view "   \
          "is not usable.",                                                    \
+         3, 24, 0, cmPolicies::WARN)                                          \
+  SELECT(POLICY, CMP0135,                                                     \
+         "ExternalProject ignores timestamps in archives by default for the " \
+         "URL download method",                                               \
          3, 24, 0, cmPolicies::WARN)
 
 #define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)

+ 18 - 0
Tests/RunCMake/CMP0135/CMP0135-Common.cmake

@@ -0,0 +1,18 @@
+include(ExternalProject)
+
+set(stamp_dir "${CMAKE_CURRENT_BINARY_DIR}/stamps")
+
+ExternalProject_Add(fake_ext_proj
+  # We don't actually do a build, so we never try to download from this URL
+  URL https://example.com/something.zip
+  STAMP_DIR ${stamp_dir}
+)
+
+# Report whether the --touch option was added to the extraction script
+set(extraction_script "${stamp_dir}/extract-fake_ext_proj.cmake")
+file(STRINGS "${extraction_script}" results REGEX "--touch")
+if("${results}" STREQUAL "")
+  message(STATUS "Using timestamps from archive")
+else()
+  message(STATUS "Using extraction time for the timestamps")
+endif()

+ 1 - 0
Tests/RunCMake/CMP0135/CMP0135-NEW-stdout.txt

@@ -0,0 +1 @@
+Using extraction time for the timestamps

+ 2 - 0
Tests/RunCMake/CMP0135/CMP0135-NEW.cmake

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

+ 1 - 0
Tests/RunCMake/CMP0135/CMP0135-OLD-stdout.txt

@@ -0,0 +1 @@
+Using timestamps from archive

+ 2 - 0
Tests/RunCMake/CMP0135/CMP0135-OLD.cmake

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

+ 10 - 0
Tests/RunCMake/CMP0135/CMP0135-WARN-stderr.txt

@@ -0,0 +1,10 @@
+CMake Warning \(dev\) at .*/Modules/ExternalProject.cmake:[0-9]+ \(message\):
+  The DOWNLOAD_EXTRACT_TIMESTAMP option was not given and policy CMP0135 is
+  not set\.  The policy's OLD behavior will be used\.  When using a URL
+  download, the timestamps of extracted files should preferably be that of
+  the time of extraction, otherwise code that depends on the extracted
+  contents might not be rebuilt if the URL changes\.  The OLD behavior
+  preserves the timestamps from the archive instead, but this is usually not
+  what you want\.  Update your project to the NEW behavior or specify the
+  DOWNLOAD_EXTRACT_TIMESTAMP option with a value of true to avoid this
+  robustness issue\.

+ 1 - 0
Tests/RunCMake/CMP0135/CMP0135-WARN-stdout.txt

@@ -0,0 +1 @@
+Using timestamps from archive

+ 2 - 0
Tests/RunCMake/CMP0135/CMP0135-WARN.cmake

@@ -0,0 +1,2 @@
+
+include(CMP0135-Common.cmake)

+ 3 - 0
Tests/RunCMake/CMP0135/CMakeLists.txt

@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.23)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)

+ 5 - 0
Tests/RunCMake/CMP0135/RunCMakeTest.cmake

@@ -0,0 +1,5 @@
+include(RunCMake)
+
+run_cmake(CMP0135-WARN)
+run_cmake(CMP0135-OLD)
+run_cmake(CMP0135-NEW)

+ 1 - 0
Tests/RunCMake/CMakeLists.txt

@@ -149,6 +149,7 @@ if("${CMAKE_C_COMPILER_ID}" STREQUAL "LCC" OR
 endif()
 
 add_RunCMake_test(CMP0132)
+add_RunCMake_test(CMP0135)
 
 # The test for Policy 65 requires the use of the
 # CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS variable, which both the VS and Xcode

+ 1 - 0
Tests/RunCMake/ExternalProject/Add_StepDependencies.cmake

@@ -4,6 +4,7 @@ if(CMAKE_XCODE_BUILD_SYSTEM VERSION_GREATER_EQUAL 12)
 else()
   cmake_policy(SET CMP0114 OLD) # Test deprecated behavior.
 endif()
+cmake_policy(SET CMP0135 NEW)
 
 include(ExternalProject)
 

+ 1 - 0
Tests/RunCMake/ExternalProject/Add_StepDependencies_no_target.cmake

@@ -4,6 +4,7 @@ if(CMAKE_XCODE_BUILD_SYSTEM VERSION_GREATER_EQUAL 12)
 else()
   cmake_policy(SET CMP0114 OLD) # Test deprecated behavior.
 endif()
+cmake_policy(SET CMP0135 NEW)
 
 include(ExternalProject)
 

+ 1 - 0
Tests/RunCMake/ExternalProject/CMakeLists.txt

@@ -3,4 +3,5 @@ project(${RunCMake_TEST} NONE)
 if(CMAKE_XCODE_BUILD_SYSTEM VERSION_GREATER_EQUAL 12 AND NOT RunCMake_TEST STREQUAL "Xcode-CMP0114")
   cmake_policy(SET CMP0114 NEW)
 endif()
+cmake_policy(SET CMP0135 NEW)
 include(${RunCMake_TEST}.cmake)

+ 1 - 0
Tests/RunCMake/ExternalProject/NO_DEPENDS-CMP0114-NEW-Direct.cmake

@@ -1,4 +1,5 @@
 cmake_policy(SET CMP0114 NEW)
+cmake_policy(SET CMP0135 NEW)
 include(ExternalProject)
 ExternalProject_Add(BAR SOURCE_DIR .  TEST_COMMAND echo test)
 ExternalProject_Add_StepTargets(BAR NO_DEPENDS test)