Browse Source

Merge topic 'ExternalProject-configure-handled-by-build'

7155e358c9 ExternalProject: Add CONFIGURE_HANDLED_BY_BUILD option

Acked-by: Kitware Robot <[email protected]>
Merge-request: !5626
Brad King 4 years ago
parent
commit
438ed46c13

+ 8 - 0
Help/release/dev/external-project-configure-handled-by-build.rst

@@ -0,0 +1,8 @@
+external-project-configure-handled-by-build
+-------------------------------------------
+
+* The :module:`ExternalProject` function ``ExternalProject_Add`` learned a new
+  ``CONFIGURE_HANDLED_BY_BUILD`` option to have subsequent runs of the configure
+  step be triggered by the build step when an external project dependency
+  rebuilds instead of always rerunning the configure step when an external
+  project dependency rebuilds.

+ 44 - 12
Modules/ExternalProject.cmake

@@ -543,6 +543,18 @@ External Project Definition
         When ``BUILD_IN_SOURCE`` option is enabled, the ``BUILD_COMMAND``
         is used to point to an alternative directory within the source tree.
 
+    ``CONFIGURE_HANDLED_BY_BUILD <bool>``
+      .. versionadded:: 3.20
+
+      Enabling this option relaxes the dependencies of the configure step on
+      other external projects to order-only. This means the configure step will
+      be executed after its external project dependencies are built but it will
+      not be marked dirty when one of its external project dependencies is
+      rebuilt. This option can be enabled when the build step is smart enough
+      to figure out if the configure step needs to be rerun. CMake and Meson are
+      examples of build systems whose build step is smart enough to know if the
+      configure step needs to be rerun.
+
   **Build Step Options:**
     If the configure step assumed the external project uses CMake as its build
     system, the build step will also. Otherwise, the build step will assume a
@@ -3083,6 +3095,23 @@ function(_ep_add_patch_command name)
   )
 endfunction()
 
+function(_ep_get_file_deps var name)
+  set(file_deps)
+
+  get_property(deps TARGET ${name} PROPERTY _EP_DEPENDS)
+  foreach(dep IN LISTS deps)
+    get_property(dep_type TARGET ${dep} PROPERTY TYPE)
+    if(dep_type STREQUAL "UTILITY")
+      get_property(is_ep TARGET ${dep} PROPERTY _EP_IS_EXTERNAL_PROJECT)
+      if(is_ep)
+        _ep_get_step_stampfile(${dep} "done" done_stamp_file)
+        list(APPEND file_deps ${done_stamp_file})
+      endif()
+    endif()
+  endforeach()
+
+  set("${var}" "${file_deps}" PARENT_SCOPE)
+endfunction()
 
 function(_ep_extract_configure_command var name)
   get_property(cmd_set TARGET ${name} PROPERTY _EP_CONFIGURE_COMMAND SET)
@@ -3191,19 +3220,13 @@ endfunction()
 function(_ep_add_configure_command name)
   ExternalProject_Get_Property(${name} binary_dir tmp_dir)
 
-  # Depend on other external projects (file-level).
   set(file_deps)
-  get_property(deps TARGET ${name} PROPERTY _EP_DEPENDS)
-  foreach(dep IN LISTS deps)
-    get_property(dep_type TARGET ${dep} PROPERTY TYPE)
-    if(dep_type STREQUAL "UTILITY")
-      get_property(is_ep TARGET ${dep} PROPERTY _EP_IS_EXTERNAL_PROJECT)
-      if(is_ep)
-        _ep_get_step_stampfile(${dep} "done" done_stamp_file)
-        list(APPEND file_deps ${done_stamp_file})
-      endif()
-    endif()
-  endforeach()
+  get_property(configure_handled_by_build TARGET ${name}
+               PROPERTY _EP_CONFIGURE_HANDLED_BY_BUILD)
+  if(NOT configure_handled_by_build)
+    # Depend on other external projects (file-level)
+    _ep_get_file_deps(file_deps ${name})
+  endif()
 
   _ep_extract_configure_command(cmd ${name})
 
@@ -3254,6 +3277,14 @@ endfunction()
 function(_ep_add_build_command name)
   ExternalProject_Get_Property(${name} binary_dir)
 
+  set(file_deps)
+  get_property(configure_handled_by_build TARGET ${name}
+               PROPERTY _EP_CONFIGURE_HANDLED_BY_BUILD)
+  if(configure_handled_by_build)
+    # Depend on other external projects (file-level)
+    _ep_get_file_deps(file_deps ${name})
+  endif()
+
   get_property(cmd_set TARGET ${name} PROPERTY _EP_BUILD_COMMAND SET)
   if(cmd_set)
     get_property(cmd TARGET ${name} PROPERTY _EP_BUILD_COMMAND)
@@ -3296,6 +3327,7 @@ function(_ep_add_build_command name)
       BYPRODUCTS \${build_byproducts}
       WORKING_DIRECTORY \${binary_dir}
       DEPENDEES configure
+      DEPENDS \${file_deps}
       ALWAYS \${always}
       ${log}
       ${uses_terminal}

+ 19 - 0
Tests/RunCMake/ExternalProject/CONFIGURE_HANDLED_BY_BUILD-rebuild-check.cmake

@@ -0,0 +1,19 @@
+file(TIMESTAMP "${STAMP_DIR}/proj1-configure" PROJ1_CONFIGURE_TIMESTAMP_AFTER "%s")
+# When BUILD_ALWAYS is set, the build stamp is never created.
+file(TIMESTAMP "${STAMP_DIR}/proj2-configure" PROJ2_CONFIGURE_TIMESTAMP_AFTER "%s")
+file(TIMESTAMP "${STAMP_DIR}/proj2-build" PROJ2_BUILD_TIMESTAMP_AFTER "%s")
+
+if(NOT PROJ1_CONFIGURE_TIMESTAMP_BEFORE EQUAL PROJ1_CONFIGURE_TIMESTAMP_AFTER)
+  set(RunCMake_TEST_FAILED "Unexpected rebuild of proj1 configure step (${PROJ1_CONFIGURE_TIMESTAMP_BEFORE} != ${PROJ1_CONFIGURE_TIMESTAMP_AFTER})")
+  return()
+endif()
+
+if(NOT PROJ2_CONFIGURE_TIMESTAMP_BEFORE EQUAL PROJ2_CONFIGURE_TIMESTAMP_AFTER)
+  set(RunCMake_TEST_FAILED "Unexpected rebuild of proj2 configure step (${PROJ2_CONFIGURE_TIMESTAMP_BEFORE} != ${PROJ2_CONFIGURE_TIMESTAMP_AFTER})")
+  return()
+endif()
+
+if(PROJ2_BUILD_TIMESTAMP_BEFORE EQUAL PROJ2_BUILD_TIMESTAMP_AFTER)
+  set(RunCMake_TEST_FAILED "proj2 build step did not rebuild (${PROJ2_BUILD_TIMESTAMP_BEFORE} != ${PROJ2_BUILD_TIMESTAMP_AFTER})")
+  return()
+endif()

+ 28 - 0
Tests/RunCMake/ExternalProject/CONFIGURE_HANDLED_BY_BUILD.cmake

@@ -0,0 +1,28 @@
+include(ExternalProject)
+
+# Given this setup, on the first build, both configure steps and both build
+# steps will run. On a noop rebuild, only the build steps will run. Without
+# CONFIGURE_HANDLED_BY_BUILD, the configure step of proj2 would also run on a
+# noop rebuild.
+
+ExternalProject_Add(proj1
+  DOWNLOAD_COMMAND ""
+  SOURCE_DIR ""
+  CONFIGURE_COMMAND ${CMAKE_COMMAND} -E echo "Doing something"
+  # file(TIMESTAMP) gives back the timestamp in seconds so we sleep a second to
+  # make sure we get a different timestamp on the stamp file
+  BUILD_COMMAND ${CMAKE_COMMAND} -E sleep 1
+  INSTALL_COMMAND ""
+  BUILD_ALWAYS ON
+  STAMP_DIR "stamp"
+)
+ExternalProject_Add(proj2
+  DOWNLOAD_COMMAND ""
+  SOURCE_DIR ""
+  CONFIGURE_COMMAND ${CMAKE_COMMAND} -E echo "Doing something"
+  BUILD_COMMAND ${CMAKE_COMMAND} -E sleep 1
+  INSTALL_COMMAND ""
+  CONFIGURE_HANDLED_BY_BUILD ON
+  DEPENDS proj1
+  STAMP_DIR "stamp"
+)

+ 30 - 0
Tests/RunCMake/ExternalProject/RunCMakeTest.cmake

@@ -151,3 +151,33 @@ endif()
 if(doSubstitutionTest)
     __ep_test_with_build(Substitutions)
 endif()
+
+function(__ep_test_CONFIGURE_HANDLED_BY_BUILD)
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/CONFIGURE_HANDLED_BY_BUILD-build)
+  run_cmake(CONFIGURE_HANDLED_BY_BUILD)
+
+  if(RunCMake_GENERATOR_IS_MULTI_CONFIG)
+    set(BUILD_CONFIG --config Debug)
+    set(STAMP_DIR "${RunCMake_TEST_BINARY_DIR}/stamp/Debug")
+  else()
+    set(BUILD_CONFIG "")
+    set(STAMP_DIR "${RunCMake_TEST_BINARY_DIR}/stamp")
+  endif()
+
+  set(RunCMake_TEST_NO_CLEAN 1)
+  run_cmake_command(CONFIGURE_HANDLED_BY_BUILD-build ${CMAKE_COMMAND} --build . ${BUILD_CONFIG})
+
+  # Calculate timestamps before rebuilding so we can compare before and after in
+  # CONFIGURE_HANDLED_BY_BUILD-rebuild-check.cmake
+
+  file(TIMESTAMP "${STAMP_DIR}/proj1-configure" PROJ1_CONFIGURE_TIMESTAMP_BEFORE "%s")
+  # When BUILD_ALWAYS is set, the build stamp is never created.
+  file(TIMESTAMP "${STAMP_DIR}/proj2-configure" PROJ2_CONFIGURE_TIMESTAMP_BEFORE "%s")
+  file(TIMESTAMP "${STAMP_DIR}/proj2-build" PROJ2_BUILD_TIMESTAMP_BEFORE "%s")
+
+  run_cmake_command(CONFIGURE_HANDLED_BY_BUILD-rebuild ${CMAKE_COMMAND} --build . ${BUILD_CONFIG})
+endfunction()
+
+if(NOT RunCMake_GENERATOR MATCHES "Visual Studio 9 ")
+  __ep_test_CONFIGURE_HANDLED_BY_BUILD()
+endif()