From 23aab9ecce264b7ea198fc9d36f382a4bb4fbe28 Mon Sep 17 00:00:00 2001 From: Craig Scott Date: Sun, 24 Jan 2021 23:33:51 +1100 Subject: ExternalProject: Avoid scanning docs for keywords, use include_guard() The previous implementation was scanning the documentation in the file at runtime to determine the set of supported keywords for each public function. This was fragile, made it difficult to restructure the documentation and was sometimes observable in runtime performance measurements. Change to a more conventional approach where supported keywords are explicitly listed in the code. The internal _ExternalProject_SELF variable is no longer needed. CMake now provides CMAKE_CURRENT_FUNCTION_LIST_DIR which can be used for the same purpose and avoids having to set a variable when the module is read. This also removes the requirement that the module must be included by the current or a parent scope. It is now enough that the module has been included once somewhere before calling any of its functions. The above changes combined mean that the module can now use include_guard() and avoid having to re-parse the very long file every time. --- Modules/ExternalProject.cmake | 232 ++++++++++++++++----- .../ExternalProject/IncludeScope-Add-result.txt | 1 - .../ExternalProject/IncludeScope-Add-stderr.txt | 7 - .../ExternalProject/IncludeScope-Add.cmake | 12 -- .../IncludeScope-Add_Step-result.txt | 1 - .../IncludeScope-Add_Step-stderr.txt | 7 - .../ExternalProject/IncludeScope-Add_Step.cmake | 13 -- Tests/RunCMake/ExternalProject/RunCMakeTest.cmake | 2 - 8 files changed, 178 insertions(+), 97 deletions(-) delete mode 100644 Tests/RunCMake/ExternalProject/IncludeScope-Add-result.txt delete mode 100644 Tests/RunCMake/ExternalProject/IncludeScope-Add-stderr.txt delete mode 100644 Tests/RunCMake/ExternalProject/IncludeScope-Add.cmake delete mode 100644 Tests/RunCMake/ExternalProject/IncludeScope-Add_Step-result.txt delete mode 100644 Tests/RunCMake/ExternalProject/IncludeScope-Add_Step-stderr.txt delete mode 100644 Tests/RunCMake/ExternalProject/IncludeScope-Add_Step.cmake diff --git a/Modules/ExternalProject.cmake b/Modules/ExternalProject.cmake index 903dd57..56525080 100644 --- a/Modules/ExternalProject.cmake +++ b/Modules/ExternalProject.cmake @@ -1,6 +1,8 @@ # Distributed under the OSI-approved BSD 3-Clause License. See accompanying # file Copyright.txt or https://cmake.org/licensing for details. +include_guard(GLOBAL) + #[=======================================================================[.rst: ExternalProject --------------- @@ -1111,60 +1113,37 @@ cmake_policy(PUSH) cmake_policy(SET CMP0054 NEW) # if() quoted variables not dereferenced cmake_policy(SET CMP0057 NEW) # if() supports IN_LIST -# Pre-compute a regex to match documented keywords for each command. -math(EXPR _ep_documentation_line_count "${CMAKE_CURRENT_LIST_LINE} - 4") -file(STRINGS "${CMAKE_CURRENT_LIST_FILE}" lines - LIMIT_COUNT ${_ep_documentation_line_count} - REGEX "^\\.\\. command:: [A-Za-z0-9_]+|^ +``[A-Z0-9_]+ [^`]*``$") -foreach(line IN LISTS lines) - if("${line}" MATCHES "^\\.\\. command:: ([A-Za-z0-9_]+)") - if(_ep_func) - string(APPEND _ep_keywords_${_ep_func} ")$") - endif() - set(_ep_func "${CMAKE_MATCH_1}") - #message("function [${_ep_func}]") - set(_ep_keywords_${_ep_func} "^(") - set(_ep_keyword_sep) - elseif("${line}" MATCHES "^ +``([A-Z0-9_]+) [^`]*``$") - set(_ep_key "${CMAKE_MATCH_1}") - # COMMAND should never be included as a keyword, - # for ExternalProject_Add(), as it is treated as a - # special case by argument parsing as an extension - # of a previous ..._COMMAND - if("x${_ep_key}x" STREQUAL "xCOMMANDx" AND - "x${_ep_func}x" STREQUAL "xExternalProject_Addx") - continue() - endif() - #message(" keyword [${_ep_key}]") - string(APPEND _ep_keywords_${_ep_func} - "${_ep_keyword_sep}${_ep_key}") - set(_ep_keyword_sep "|") - endif() -endforeach() -if(_ep_func) - string(APPEND _ep_keywords_${_ep_func} ")$") -endif() - -# Save regex matching supported hash algorithm names. -set(_ep_hash_algos "MD5|SHA1|SHA224|SHA256|SHA384|SHA512|SHA3_224|SHA3_256|SHA3_384|SHA3_512") -set(_ep_hash_regex "^(${_ep_hash_algos})=([0-9A-Fa-f]+)$") +macro(_ep_get_hash_algos out_var) + set(${out_var} + MD5 + SHA1 + SHA224 + SHA256 + SHA384 + SHA512 + SHA3_224 + SHA3_256 + SHA3_384 + SHA3_512 + ) +endmacro() -set(_ExternalProject_SELF "${CMAKE_CURRENT_LIST_FILE}") -get_filename_component(_ExternalProject_SELF_DIR "${_ExternalProject_SELF}" PATH) +macro(_ep_get_hash_regex out_var) + _ep_get_hash_algos(${out_var}) + list(JOIN ${out_var} "|" ${out_var}) + set(${out_var} "^(${${out_var}})=([0-9A-Fa-f]+)$") +endmacro() -function(_ep_parse_arguments f name ns args) +function(_ep_parse_arguments f keywords name ns args) # Transfer the arguments to this function into target properties for the # new custom target we just added so that we can set up all the build steps # correctly based on target properties. # - # We loop through ARGN and consider the namespace starting with an + # Because some keywords can be repeated, we can't use cmake_parse_arguments(). + # Instead, we loop through ARGN and consider the namespace starting with an # upper-case letter followed by at least two more upper-case letters, # numbers or underscores to be keywords. - if(NOT DEFINED _ExternalProject_SELF) - message(FATAL_ERROR "error: ExternalProject module must be explicitly included before using ${f} function") - endif() - set(key) foreach(arg IN LISTS args) @@ -1173,7 +1152,7 @@ function(_ep_parse_arguments f name ns args) if(arg MATCHES "^[A-Z][A-Z0-9_][A-Z0-9_]+$" AND NOT (("x${arg}x" STREQUAL "x${key}x") AND ("x${key}x" STREQUAL "xCOMMANDx")) AND NOT arg MATCHES "^(TRUE|FALSE)$") - if(_ep_keywords_${f} AND arg MATCHES "${_ep_keywords_${f}}") + if(arg IN_LIST keywords) set(is_value 0) endif() endif() @@ -1198,9 +1177,6 @@ function(_ep_parse_arguments f name ns args) endif() else() set(key "${arg}") - if(key MATCHES GIT) - get_property(have_key TARGET ${name} PROPERTY ${ns}${key} SET) - endif() endif() endforeach() endfunction() @@ -1432,7 +1408,7 @@ function(_ep_write_gitupdate_script script_filename git_EXECUTABLE git_tag git_r endif() configure_file( - "${_ExternalProject_SELF_DIR}/ExternalProject-gitupdate.cmake.in" + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExternalProject-gitupdate.cmake.in" "${script_filename}" @ONLY ) @@ -1461,6 +1437,7 @@ function(_ep_write_downloadfile_script script_filename REMOTE LOCAL timeout inac set(SHOW_PROGRESS "SHOW_PROGRESS") endif() + _ep_get_hash_regex(_ep_hash_regex) if("${hash}" MATCHES "${_ep_hash_regex}") set(ALGO "${CMAKE_MATCH_1}") string(TOLOWER "${CMAKE_MATCH_2}" EXPECT_VALUE) @@ -1541,13 +1518,14 @@ function(_ep_write_downloadfile_script script_filename REMOTE LOCAL timeout inac # * USERPWD_ARGS # * HTTP_HEADERS_ARGS configure_file( - "${_ExternalProject_SELF_DIR}/ExternalProject-download.cmake.in" + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExternalProject-download.cmake.in" "${script_filename}" @ONLY ) endfunction() function(_ep_write_verifyfile_script script_filename LOCAL hash) + _ep_get_hash_regex(_ep_hash_regex) if("${hash}" MATCHES "${_ep_hash_regex}") set(ALGO "${CMAKE_MATCH_1}") string(TOLOWER "${CMAKE_MATCH_2}" EXPECT_VALUE) @@ -1561,7 +1539,7 @@ function(_ep_write_verifyfile_script script_filename LOCAL hash) # * EXPECT_VALUE # * LOCAL configure_file( - "${_ExternalProject_SELF_DIR}/ExternalProject-verify.cmake.in" + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExternalProject-verify.cmake.in" "${script_filename}" @ONLY ) @@ -2250,7 +2228,21 @@ function(ExternalProject_Add_Step name step) _ep_get_complete_stampfile(${name} complete_stamp_file) _ep_get_step_stampfile(${name} ${step} stamp_file) - _ep_parse_arguments(ExternalProject_Add_Step + set(keywords + COMMAND + COMMENT + DEPENDEES + DEPENDERS + DEPENDS + INDEPENDENT + BYPRODUCTS + ALWAYS + EXCLUDE_FROM_MAIN + WORKING_DIRECTORY + LOG + USES_TERMINAL + ) + _ep_parse_arguments(ExternalProject_Add_Step "${keywords}" ${name} _EP_${step}_ "${ARGN}") get_property(independent TARGET ${name} PROPERTY _EP_${step}_INDEPENDENT) @@ -2747,7 +2739,10 @@ function(_ep_add_download_command name) elseif(url) get_filename_component(work_dir "${source_dir}" PATH) get_property(hash TARGET ${name} PROPERTY _EP_URL_HASH) + _ep_get_hash_regex(_ep_hash_regex) if(hash AND NOT "${hash}" MATCHES "${_ep_hash_regex}") + _ep_get_hash_algos(_ep_hash_algos) + list(JOIN _ep_hash_algos "|" _ep_hash_algos) message(FATAL_ERROR "URL_HASH is set to\n ${hash}\n" "but must be ALGO=value where ALGO is\n ${_ep_hash_algos}\n" "and value is a hex string.") @@ -3505,7 +3500,136 @@ function(ExternalProject_Add name) set_property(TARGET ${name} PROPERTY _EP_CMP0114 "${cmp0114}") - _ep_parse_arguments(ExternalProject_Add ${name} _EP_ "${ARGN}") + set(keywords + # + # Directory options + # + PREFIX + TMP_DIR + STAMP_DIR + LOG_DIR + DOWNLOAD_DIR + SOURCE_DIR + BINARY_DIR + INSTALL_DIR + # + # Download step options + # + DOWNLOAD_COMMAND + # + URL + URL_HASH + URL_MD5 + DOWNLOAD_NAME + DOWNLOAD_NO_EXTRACT + DOWNLOAD_NO_PROGRESS + TIMEOUT + INACTIVITY_TIMEOUT + HTTP_USERNAME + HTTP_PASSWORD + HTTP_HEADER + TLS_VERIFY # Also used for git clone operations + TLS_CAINFO + NETRC + NETRC_FILE + # + GIT_REPOSITORY + GIT_TAG + GIT_REMOTE_NAME + GIT_SUBMODULES + GIT_SUBMODULES_RECURSE + GIT_SHALLOW + GIT_PROGRESS + GIT_CONFIG + GIT_REMOTE_UPDATE_STRATEGY + # + SVN_REPOSITORY + SVN_REVISION + SVN_USERNAME + SVN_PASSWORD + SVN_TRUST_CERT + # + HG_REPOSITORY + HG_TAG + # + CVS_REPOSITORY + CVS_MODULE + CVS_TAG + # + # Update step options + # + UPDATE_COMMAND + UPDATE_DISCONNECTED + # + # Patch step options + # + PATCH_COMMAND + # + # Configure step options + # + CONFIGURE_COMMAND + CMAKE_COMMAND + CMAKE_GENERATOR + CMAKE_GENERATOR_PLATFORM + CMAKE_GENERATOR_TOOLSET + CMAKE_GENERATOR_INSTANCE + CMAKE_ARGS + CMAKE_CACHE_ARGS + CMAKE_CACHE_DEFAULT_ARGS + SOURCE_SUBDIR + CONFIGURE_HANDLED_BY_BUILD + # + # Build step options + # + BUILD_COMMAND + BUILD_IN_SOURCE + BUILD_ALWAYS + BUILD_BYPRODUCTS + # + # Install step options + # + INSTALL_COMMAND + # + # Test step options + # + TEST_COMMAND + TEST_BEFORE_INSTALL + TEST_AFTER_INSTALL + TEST_EXCLUDE_FROM_MAIN + # + # Logging options + # + LOG_DOWNLOAD + LOG_UPDATE + LOG_PATCH + LOG_CONFIGURE + LOG_BUILD + LOG_INSTALL + LOG_TEST + LOG_MERGED_STDOUTERR + LOG_OUTPUT_ON_FAILURE + # + # Terminal access options + # + USES_TERMINAL_DOWNLOAD + USES_TERMINAL_UPDATE + USES_TERMINAL_CONFIGURE + USES_TERMINAL_BUILD + USES_TERMINAL_INSTALL + USES_TERMINAL_TEST + # + # Target options + # + DEPENDS + EXCLUDE_FROM_ALL + STEP_TARGETS + INDEPENDENT_STEP_TARGETS + # + # Miscellaneous options + # + LIST_SEPARATOR + ) + _ep_parse_arguments(ExternalProject_Add "${keywords}" ${name} _EP_ "${ARGN}") _ep_set_directories(${name}) _ep_get_step_stampfile(${name} "done" done_stamp_file) _ep_get_step_stampfile(${name} "install" install_stamp_file) diff --git a/Tests/RunCMake/ExternalProject/IncludeScope-Add-result.txt b/Tests/RunCMake/ExternalProject/IncludeScope-Add-result.txt deleted file mode 100644 index d00491f..0000000 --- a/Tests/RunCMake/ExternalProject/IncludeScope-Add-result.txt +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/Tests/RunCMake/ExternalProject/IncludeScope-Add-stderr.txt b/Tests/RunCMake/ExternalProject/IncludeScope-Add-stderr.txt deleted file mode 100644 index ff3e5c1..0000000 --- a/Tests/RunCMake/ExternalProject/IncludeScope-Add-stderr.txt +++ /dev/null @@ -1,7 +0,0 @@ -^CMake Error at .*/Modules/ExternalProject.cmake:[0-9]+ \(message\): - error: ExternalProject module must be explicitly included before using - ExternalProject_Add function -Call Stack \(most recent call first\): - .*/Modules/ExternalProject.cmake:[0-9]+ \(_ep_parse_arguments\) - IncludeScope-Add.cmake:[0-9]+ \(ExternalProject_Add\) - CMakeLists.txt:[0-9]+ \(include\)$ diff --git a/Tests/RunCMake/ExternalProject/IncludeScope-Add.cmake b/Tests/RunCMake/ExternalProject/IncludeScope-Add.cmake deleted file mode 100644 index 1061ffd..0000000 --- a/Tests/RunCMake/ExternalProject/IncludeScope-Add.cmake +++ /dev/null @@ -1,12 +0,0 @@ -function(IncludeScope_IncludeOnly) - include(ExternalProject) -endfunction() - -IncludeScope_IncludeOnly() - -ExternalProject_Add(MyProj - SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR} - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" -) diff --git a/Tests/RunCMake/ExternalProject/IncludeScope-Add_Step-result.txt b/Tests/RunCMake/ExternalProject/IncludeScope-Add_Step-result.txt deleted file mode 100644 index d00491f..0000000 --- a/Tests/RunCMake/ExternalProject/IncludeScope-Add_Step-result.txt +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/Tests/RunCMake/ExternalProject/IncludeScope-Add_Step-stderr.txt b/Tests/RunCMake/ExternalProject/IncludeScope-Add_Step-stderr.txt deleted file mode 100644 index cbad4be..0000000 --- a/Tests/RunCMake/ExternalProject/IncludeScope-Add_Step-stderr.txt +++ /dev/null @@ -1,7 +0,0 @@ -^CMake Error at .*/Modules/ExternalProject.cmake:[0-9]+ \(message\): - error: ExternalProject module must be explicitly included before using - ExternalProject_Add_Step function -Call Stack \(most recent call first\): - .*/Modules/ExternalProject.cmake:[0-9]+ \(_ep_parse_arguments\) - IncludeScope-Add_Step.cmake:[0-9]+ \(ExternalProject_Add_Step\) - CMakeLists.txt:[0-9]+ \(include\)$ diff --git a/Tests/RunCMake/ExternalProject/IncludeScope-Add_Step.cmake b/Tests/RunCMake/ExternalProject/IncludeScope-Add_Step.cmake deleted file mode 100644 index 2a820f8..0000000 --- a/Tests/RunCMake/ExternalProject/IncludeScope-Add_Step.cmake +++ /dev/null @@ -1,13 +0,0 @@ -function(IncludeScope_DefineProj) - include(ExternalProject) - ExternalProject_Add(MyProj - SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR} - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - ) -endfunction() - -IncludeScope_DefineProj() - -ExternalProject_Add_Step(MyProj extraStep COMMENT "Foo") diff --git a/Tests/RunCMake/ExternalProject/RunCMakeTest.cmake b/Tests/RunCMake/ExternalProject/RunCMakeTest.cmake index 976655a..3205dd5 100644 --- a/Tests/RunCMake/ExternalProject/RunCMakeTest.cmake +++ b/Tests/RunCMake/ExternalProject/RunCMakeTest.cmake @@ -8,8 +8,6 @@ unset(ENV{https_proxy}) run_cmake(BadIndependentStep1) run_cmake(BadIndependentStep2) -run_cmake(IncludeScope-Add) -run_cmake(IncludeScope-Add_Step) run_cmake(NoOptions) run_cmake(SourceEmpty) run_cmake(SourceMissing) -- cgit v0.12