From 7155e358c9c0cdf7aa32f6c0dd6e67e112ebbac4 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 16 Dec 2020 20:39:35 +0000 Subject: ExternalProject: Add CONFIGURE_HANDLED_BY_BUILD option Fixes #21592. --- ...external-project-configure-handled-by-build.rst | 8 ++++ Modules/ExternalProject.cmake | 56 +++++++++++++++++----- .../CONFIGURE_HANDLED_BY_BUILD-rebuild-check.cmake | 19 ++++++++ .../CONFIGURE_HANDLED_BY_BUILD.cmake | 28 +++++++++++ Tests/RunCMake/ExternalProject/RunCMakeTest.cmake | 30 ++++++++++++ 5 files changed, 129 insertions(+), 12 deletions(-) create mode 100644 Help/release/dev/external-project-configure-handled-by-build.rst create mode 100644 Tests/RunCMake/ExternalProject/CONFIGURE_HANDLED_BY_BUILD-rebuild-check.cmake create mode 100644 Tests/RunCMake/ExternalProject/CONFIGURE_HANDLED_BY_BUILD.cmake diff --git a/Help/release/dev/external-project-configure-handled-by-build.rst b/Help/release/dev/external-project-configure-handled-by-build.rst new file mode 100644 index 0000000..4a1fac8 --- /dev/null +++ b/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. diff --git a/Modules/ExternalProject.cmake b/Modules/ExternalProject.cmake index f0ce7d4..2d4f602 100644 --- a/Modules/ExternalProject.cmake +++ b/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 `` + .. 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 @@ -3081,6 +3093,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) @@ -3189,19 +3218,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}) @@ -3252,6 +3275,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) @@ -3294,6 +3325,7 @@ function(_ep_add_build_command name) BYPRODUCTS \${build_byproducts} WORKING_DIRECTORY \${binary_dir} DEPENDEES configure + DEPENDS \${file_deps} ALWAYS \${always} ${log} ${uses_terminal} diff --git a/Tests/RunCMake/ExternalProject/CONFIGURE_HANDLED_BY_BUILD-rebuild-check.cmake b/Tests/RunCMake/ExternalProject/CONFIGURE_HANDLED_BY_BUILD-rebuild-check.cmake new file mode 100644 index 0000000..887da0f --- /dev/null +++ b/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() diff --git a/Tests/RunCMake/ExternalProject/CONFIGURE_HANDLED_BY_BUILD.cmake b/Tests/RunCMake/ExternalProject/CONFIGURE_HANDLED_BY_BUILD.cmake new file mode 100644 index 0000000..c86a60e --- /dev/null +++ b/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" +) diff --git a/Tests/RunCMake/ExternalProject/RunCMakeTest.cmake b/Tests/RunCMake/ExternalProject/RunCMakeTest.cmake index 598671f..22b8d24 100644 --- a/Tests/RunCMake/ExternalProject/RunCMakeTest.cmake +++ b/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() -- cgit v0.12