From 6ae0ff626a67a2a81481bf127d07543d1f0cca16 Mon Sep 17 00:00:00 2001 From: Brad King Date: Wed, 18 Mar 2009 11:00:30 -0400 Subject: ENH: Improve add_external_project interface This rewrites the keyword/argument parsing and handling in the AddExternalProject module to use arguments more literally: - The strict keyword-value pairing is gone in favor of keywords with arbitrary non-keyword values. This avoids requiring users to escape spaces and quotes in command lines. - Customized step command lines are now specified with a single keyword _COMMAND instead of putting the arguments in a separate entry (previously called _ARGS). - Build step custom commands now use VERBATIM mode so that arguments are correctly escaped on the command line during builds. --- Modules/AddExternalProject.cmake | 203 ++++++++++++++++++++--------------- Tests/ExternalProject/CMakeLists.txt | 34 +++--- 2 files changed, 132 insertions(+), 105 deletions(-) diff --git a/Modules/AddExternalProject.cmake b/Modules/AddExternalProject.cmake index cbba4f5..b89f64e 100644 --- a/Modules/AddExternalProject.cmake +++ b/Modules/AddExternalProject.cmake @@ -65,12 +65,10 @@ function(get_configure_command_id name cfg_cmd_id_var) endif() endfunction(get_configure_command_id) -function(_aep_get_build_command name step cmd_var args_var) - # No default arguments if command is user-specified. - set(args) - +function(_aep_get_build_command name step cmd_var) set(cmd "${${cmd_var}}") if(NOT cmd) + set(args) get_configure_command_id(${name} cfg_cmd_id) if(cfg_cmd_id STREQUAL "cmake") # CMake project. Select build command based on generator. @@ -84,9 +82,9 @@ function(_aep_get_build_command name step cmd_var args_var) endif() else() # Drive the project with "cmake --build". - get_target_property(cfg_cmd ${name} AEP_CONFIGURE_COMMAND) - if(cfg_cmd) - set(cmd "${cfg_cmd}") + get_target_property(cmake_command ${name} AEP_CMAKE_COMMAND) + if(cmake_command) + set(cmd "${cmake_command}") else() set(cmd "${CMAKE_COMMAND}") endif() @@ -102,17 +100,17 @@ function(_aep_get_build_command name step cmd_var args_var) set(args install) endif() endif() - endif() - # Use user-specified build arguments, if any. - get_property(have_args TARGET ${name} PROPERTY AEP_${step}_ARGS SET) - if(have_args) - get_target_property(args ${name} AEP_${step}_ARGS) + # Use user-specified arguments instead of default arguments, if any. + get_property(have_args TARGET ${name} PROPERTY AEP_${step}_ARGS SET) + if(have_args) + get_target_property(args ${name} AEP_${step}_ARGS) + endif() + + list(APPEND cmd ${args}) endif() - # Return answers to caller. set(${cmd_var} "${cmd}" PARENT_SCOPE) - set(${args_var} "${args}" PARENT_SCOPE) endfunction(_aep_get_build_command) function(mkdir d) @@ -138,24 +136,19 @@ function(add_external_project_download_command name) WORKING_DIRECTORY ${sentinels_dir} COMMENT "No download step for '${name}'" DEPENDS ${sentinels_dir}/CMakeExternals-directories + VERBATIM ) return() else() if(cmd) - set(args "") - get_target_property(download_args ${name} AEP_DOWNLOAD_ARGS) - if(download_args) - set(args "${download_args}") - separate_arguments(args) - endif() - add_custom_command( OUTPUT ${sentinels_dir}/${name}-download - COMMAND ${cmd} ${args} + COMMAND ${cmd} COMMAND ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-download WORKING_DIRECTORY ${downloads_dir} COMMENT "Performing download step for '${name}'" DEPENDS ${sentinels_dir}/CMakeExternals-directories + VERBATIM ) return() else() @@ -198,6 +191,7 @@ function(add_external_project_download_command name) WORKING_DIRECTORY ${source_dir} COMMENT "Performing download step (CVS checkout) for '${name}'" DEPENDS ${sentinels_dir}/${name}-cvsinfo.txt + VERBATIM ) return() endif() @@ -231,6 +225,7 @@ function(add_external_project_download_command name) WORKING_DIRECTORY ${source_dir} COMMENT "Performing download step (SVN checkout) for '${name}'" DEPENDS ${sentinels_dir}/${name}-svninfo.txt + VERBATIM ) return() endif() @@ -259,6 +254,7 @@ function(add_external_project_download_command name) WORKING_DIRECTORY ${source_dir} COMMENT "Performing download step (DIR copy) for '${name}'" DEPENDS ${sentinels_dir}/${name}-dirinfo.txt + VERBATIM ) return() endif() @@ -274,6 +270,7 @@ function(add_external_project_download_command name) WORKING_DIRECTORY ${source_dir} COMMENT "Performing download step (TAR untar) for '${name}'" DEPENDS ${tar} + VERBATIM ) return() endif() @@ -289,6 +286,7 @@ function(add_external_project_download_command name) WORKING_DIRECTORY ${source_dir} COMMENT "Performing download step (TGZ untar) for '${name}'" DEPENDS ${tgz} + VERBATIM ) return() endif() @@ -315,6 +313,7 @@ function(add_external_project_download_command name) WORKING_DIRECTORY ${source_dir} COMMENT "Performing download step (TGZ_URL download and untar) for '${name}'" DEPENDS ${sentinels_dir}/${name}-urlinfo.txt + VERBATIM ) return() endif() @@ -341,6 +340,7 @@ function(add_external_project_download_command name) WORKING_DIRECTORY ${source_dir} COMMENT "Performing download step (TAR_URL download and untar) for '${name}'" DEPENDS ${sentinels_dir}/${name}-urlinfo.txt + VERBATIM ) return() endif() @@ -368,20 +368,14 @@ function(add_external_project_update_command name) return() else() if(cmd) - set(args "") - get_target_property(update_args ${name} AEP_UPDATE_ARGS) - if(update_args) - set(args "${update_args}") - separate_arguments(args) - endif() - add_custom_command( OUTPUT ${sentinels_dir}/${name}-update - COMMAND ${cmd} ${args} + COMMAND ${cmd} COMMAND ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-update WORKING_DIRECTORY ${source_dir}/${name} COMMENT "Performing update step for '${name}'" DEPENDS ${sentinels_dir}/${name}-download + VERBATIM ) return() else() @@ -408,6 +402,7 @@ function(add_external_project_update_command name) WORKING_DIRECTORY ${source_dir}/${name} COMMENT "Performing update step (CVS update) for '${name}'" DEPENDS ${sentinels_dir}/${name}-download + VERBATIM ) # Since the update sentinel is not actually written: set_property(SOURCE ${sentinels_dir}/${name}-update @@ -433,6 +428,7 @@ function(add_external_project_update_command name) WORKING_DIRECTORY ${source_dir}/${name} COMMENT "Performing update step (SVN update) for '${name}'" DEPENDS ${sentinels_dir}/${name}-download + VERBATIM ) # Since the update sentinel is not actually written: set_property(SOURCE ${sentinels_dir}/${name}-update @@ -447,6 +443,7 @@ function(add_external_project_update_command name) WORKING_DIRECTORY ${sentinels_dir} COMMENT "No update step for '${name}'" DEPENDS ${sentinels_dir}/${name}-download + VERBATIM ) endfunction(add_external_project_update_command) @@ -468,6 +465,7 @@ function(add_external_project_configure_command name) COMMAND ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-working_dir DEPENDS ${sentinels_dir}/${name}-update ${file_deps} + VERBATIM ) get_target_property(cmd ${name} AEP_CONFIGURE_COMMAND) @@ -479,34 +477,34 @@ function(add_external_project_configure_command name) WORKING_DIRECTORY ${working_dir} COMMENT "No configure step for '${name}'" DEPENDS ${sentinels_dir}/${name}-working_dir + VERBATIM ) else() if(NOT cmd) - set(cmd ${CMAKE_COMMAND}) - endif() + get_target_property(cmake_command ${name} AEP_CMAKE_COMMAND) + if(cmake_command) + set(cmd "${cmake_command}") + else() + set(cmd "${CMAKE_COMMAND}") + endif() - set(args "") - get_target_property(configure_args ${name} AEP_CONFIGURE_ARGS) - if(configure_args) - set(args "${configure_args}") - separate_arguments(args) - endif() + get_property(cmake_args TARGET ${name} PROPERTY AEP_CMAKE_ARGS) + list(APPEND cmd ${cmake_args}) - get_target_property(cmake_generator ${name} AEP_CMAKE_GENERATOR) - if(cmake_generator) - get_configure_command_id(${name} cfg_cmd_id) - if(cfg_cmd_id STREQUAL "cmake") - list(APPEND args "-G${cmake_generator}" "${source_dir}/${name}") + get_target_property(cmake_generator ${name} AEP_CMAKE_GENERATOR) + if(cmake_generator) + list(APPEND cmd "-G${cmake_generator}" "${source_dir}/${name}") endif() endif() add_custom_command( OUTPUT ${sentinels_dir}/${name}-configure - COMMAND ${cmd} ${args} + COMMAND ${cmd} COMMAND ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-configure WORKING_DIRECTORY ${working_dir} COMMENT "Performing configure step for '${name}'" DEPENDS ${sentinels_dir}/${name}-working_dir + VERBATIM ) endif() endfunction(add_external_project_configure_command) @@ -526,17 +524,19 @@ function(add_external_project_build_command name) WORKING_DIRECTORY ${working_dir} COMMENT "No build step for '${name}'" DEPENDS ${sentinels_dir}/${name}-configure + VERBATIM ) else() - _aep_get_build_command(${name} BUILD cmd args) + _aep_get_build_command(${name} BUILD cmd) add_custom_command( OUTPUT ${sentinels_dir}/${name}-build - COMMAND ${cmd} ${args} + COMMAND ${cmd} COMMAND ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-build WORKING_DIRECTORY ${working_dir} COMMENT "Performing build step for '${name}'" DEPENDS ${sentinels_dir}/${name}-configure + VERBATIM ) endif() endfunction(add_external_project_build_command) @@ -557,18 +557,20 @@ function(add_external_project_install_command name) WORKING_DIRECTORY ${working_dir} COMMENT "No install step for '${name}'" DEPENDS ${sentinels_dir}/${name}-build + VERBATIM ) else() - _aep_get_build_command(${name} INSTALL cmd args) + _aep_get_build_command(${name} INSTALL cmd) add_custom_command( OUTPUT ${sentinels_dir}/${name}-install - COMMAND ${cmd} ${args} + COMMAND ${cmd} COMMAND ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-install COMMAND ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-complete WORKING_DIRECTORY ${working_dir} COMMENT "Performing install step for '${name}'" DEPENDS ${sentinels_dir}/${name}-build + VERBATIM ) endif() endfunction(add_external_project_install_command) @@ -605,6 +607,7 @@ function(add_CMakeExternals_target) COMMAND ${CMAKE_COMMAND} -E make_directory ${tmp_dir} COMMAND ${CMAKE_COMMAND} -E touch ${sentinels_dir}/CMakeExternals-directories COMMENT "Creating CMakeExternals directories" + VERBATIM ) add_custom_target(CMakeExternals ALL @@ -613,19 +616,38 @@ function(add_CMakeExternals_target) endif() endfunction(add_CMakeExternals_target) - -function(is_known_aep_property_key key result_var) - set(${result_var} 0 PARENT_SCOPE) - - if(key MATCHES "^BUILD_ARGS|BUILD_COMMAND|CMAKE_GENERATOR|CONFIGURE_ARGS|CONFIGURE_COMMAND|CONFIGURE_DIR|CVS_REPOSITORY|CVS_MODULE|CVS_TAG|DEPENDS|DOWNLOAD_ARGS|DOWNLOAD_COMMAND|DIR|INSTALL_ARGS|INSTALL_COMMAND|SVN_REPOSITORY|SVN_TAG|TAR|TAR_URL|TGZ|TGZ_URL|UPDATE_ARGS|UPDATE_COMMAND$" - ) - #message(STATUS "info: recognized via MATCHES - key='${key}'") - set(${result_var} 1 PARENT_SCOPE) - else() - message(STATUS "warning: is_known_aep_property_key unknown key='${key}'") - endif() -endfunction(is_known_aep_property_key) - +# Pre-compute a regex to match known keywords. +set(_aep_keyword_regex "^(") +set(_aep_keyword_sep) +foreach(key IN ITEMS + BUILD_ARGS + BUILD_COMMAND + CMAKE_ARGS + CMAKE_COMMAND + CMAKE_GENERATOR + CONFIGURE_COMMAND + CONFIGURE_DIR + CVS_MODULE + CVS_REPOSITORY + CVS_TAG + DEPENDS + DIR + DOWNLOAD_COMMAND + INSTALL_ARGS + INSTALL_COMMAND + SVN_REPOSITORY + SVN_TAG + TAR + TAR_URL + TGZ + TGZ_URL + UPDATE_COMMAND + ) + set(_aep_keyword_regex "${_aep_keyword_regex}${_aep_keyword_sep}${key}") + set(_aep_keyword_sep "|") +endforeach(key) +set(_aep_keyword_regex "${_aep_keyword_regex})$") +set(_aep_keyword_sep) function(add_external_project name) get_external_project_directories(base_dir build_dir downloads_dir install_dir @@ -652,37 +674,44 @@ function(add_external_project name) # new custom target we just added so that we can set up all the build steps # correctly based on target properties. # - # Loop over ARGN by 2's extracting key/value pairs from the non-explicit - # arguments to this function: - # - list(LENGTH ARGN n) - set(i 0) - while(i LESS n) - math(EXPR j ${i}+1) - - list(GET ARGN ${i} key) - list(GET ARGN ${j} value) - - is_known_aep_property_key("${key}" is_known_key) - - if(is_known_key) - if(key STREQUAL "DEPENDS") - if(NOT value STREQUAL "") - add_dependencies(${name} ${value}) - set_property(TARGET ${name} APPEND PROPERTY AEP_FILE_DEPENDS "${sentinels_dir}/${value}-complete") + # We loop through ARGN and consider the namespace starting with an + # upper-case letter followed by at least two more upper-case letters + # or underscores to be keywords. + set(key) + foreach(arg IN LISTS ARGN) + if(arg MATCHES "^[A-Z][A-Z_][A-Z_]+$" AND + NOT arg MATCHES "^(TRUE|FALSE)$") + # Keyword + set(key "${arg}") + if(NOT key MATCHES "${_aep_keyword_regex}") + message(AUTHOR_WARNING "unknown add_external_project keyword: ${key}") + endif() + elseif(key STREQUAL "DEPENDS") + # Value for DEPENDS + if(NOT arg STREQUAL "") + add_dependencies(${name} ${arg}) + set_property(TARGET ${name} APPEND PROPERTY AEP_FILE_DEPENDS "${sentinels_dir}/${arg}-complete") + else() + message(AUTHOR_WARNING "empty DEPENDS value in add_external_project") + endif() + elseif(key) + # Value + if(NOT arg STREQUAL "") + set_property(TARGET ${name} APPEND PROPERTY AEP_${key} "${arg}") + else() + get_property(have_key TARGET ${name} PROPERTY AEP_${key} SET) + if(have_key) + get_property(value TARGET ${name} PROPERTY AEP_${key}) + set_property(TARGET ${name} PROPERTY AEP_${key} "${value};${arg}") else() - message(STATUS "warning: empty DEPENDS value in add_external_project") + set_property(TARGET ${name} PROPERTY AEP_${key} "${arg}") endif() - else() - set_property(TARGET ${name} PROPERTY AEP_${key} "${value}") endif() else() - message(SEND_ERROR "error: unknown add_external_project key with name='${name}' key='${key}' value='${value}'") + # Missing Keyword + message(AUTHOR_WARNING "value with no keyword in add_external_project") endif() - - math(EXPR i ${i}+2) - endwhile() - + endforeach() # Set up custom build steps based on the target properties. # Each step depends on the previous one. diff --git a/Tests/ExternalProject/CMakeLists.txt b/Tests/ExternalProject/CMakeLists.txt index 091e41b..856a713 100644 --- a/Tests/ExternalProject/CMakeLists.txt +++ b/Tests/ExternalProject/CMakeLists.txt @@ -79,7 +79,7 @@ set(proj EmptyNoOpProject) add_external_project(${proj} BUILD_ARGS "" BUILD_COMMAND "" - CONFIGURE_ARGS "" + CMAKE_ARGS "" CONFIGURE_COMMAND "" CONFIGURE_DIR "" CVS_REPOSITORY "" @@ -87,7 +87,6 @@ add_external_project(${proj} CVS_TAG "" DEPENDS "MinimalNoOpProject" DIR "" - DOWNLOAD_ARGS "" DOWNLOAD_COMMAND "" INSTALL_ARGS "" INSTALL_COMMAND "" @@ -97,7 +96,6 @@ add_external_project(${proj} TAR_URL "" TGZ "" TGZ_URL "" - UPDATE_ARGS "" UPDATE_COMMAND "" ) @@ -108,7 +106,7 @@ if(can_build_tutorial_step5) set(proj TutorialStep5-Local) add_external_project(${proj} DIR "${CMAKE_CURRENT_SOURCE_DIR}/../Tutorial/Step5" - CONFIGURE_ARGS "\"-DCMAKE_INSTALL_PREFIX:PATH=${prefix}\" -G \"${CMAKE_GENERATOR}\" \"${source_dir}/${proj}\"" + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${prefix} -G ${CMAKE_GENERATOR} ${source_dir}/${proj} ) endif() @@ -119,14 +117,14 @@ set(proj TutorialStep1-LocalTAR) add_external_project(${proj} TAR "${CMAKE_CURRENT_SOURCE_DIR}/Step1.tar" CMAKE_GENERATOR "${CMAKE_GENERATOR}" - CONFIGURE_ARGS "\"-DCMAKE_INSTALL_PREFIX:PATH=${prefix}\"" + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${prefix} INSTALL_COMMAND "" ) set(proj TutorialStep1-LocalNoDirTAR) add_external_project(${proj} TAR "${CMAKE_CURRENT_SOURCE_DIR}/Step1NoDir.tar" - CONFIGURE_ARGS "\"-DCMAKE_INSTALL_PREFIX:PATH=${prefix}\" -G \"${CMAKE_GENERATOR}\" \"${source_dir}/${proj}\"" + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${prefix} -G ${CMAKE_GENERATOR} ${source_dir}/${proj} INSTALL_COMMAND "" ) @@ -136,7 +134,7 @@ add_external_project(${proj} set(proj TutorialStep1-LocalTGZ) add_external_project(${proj} TGZ "${CMAKE_CURRENT_SOURCE_DIR}/Step1.tgz" - CONFIGURE_ARGS "\"-DCMAKE_INSTALL_PREFIX:PATH=${prefix}\" -G \"${CMAKE_GENERATOR}\" \"${source_dir}/${proj}\"" + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${prefix} -G ${CMAKE_GENERATOR} ${source_dir}/${proj} INSTALL_COMMAND "" ) @@ -144,7 +142,7 @@ set(proj TutorialStep1-LocalNoDirTGZ) add_external_project(${proj} TGZ "${CMAKE_CURRENT_SOURCE_DIR}/Step1NoDir.tgz" CMAKE_GENERATOR "${CMAKE_GENERATOR}" - CONFIGURE_ARGS "\"-DCMAKE_INSTALL_PREFIX:PATH=${prefix}\"" + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${prefix} INSTALL_COMMAND "" ) @@ -168,9 +166,9 @@ if(can_use_cvs) add_external_project(${proj} CVS_REPOSITORY ":pserver:anonymous:cmake@www.cmake.org:/cvsroot/CMake" CVS_MODULE "CMake/Tests/Tutorial/Step1" - CVS_TAG "-D\;2008-12-01 01:00:00 UTC" + CVS_TAG "-D2008-12-01 01:00:00 UTC" CMAKE_GENERATOR "${CMAKE_GENERATOR}" - CONFIGURE_ARGS "\"-DCMAKE_INSTALL_PREFIX:PATH=${prefix}\"" + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${prefix} INSTALL_COMMAND "" ) @@ -180,9 +178,9 @@ if(can_use_cvs) add_external_project(${proj} CVS_REPOSITORY ":pserver:anonymous:cmake@www.cmake.org:/cvsroot/CMake" CVS_MODULE "CMake/Source/kwsys" - CVS_TAG "-r\;CMake-2-6-2" + CVS_TAG -rCMake-2-6-2 CMAKE_GENERATOR "${CMAKE_GENERATOR}" - CONFIGURE_ARGS "\"-DCMAKE_INSTALL_PREFIX:PATH=${prefix}\"" + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${prefix} INSTALL_COMMAND "" ) @@ -194,7 +192,7 @@ if(can_use_cvs) CVS_REPOSITORY ":pserver:anoncvs@public.kitware.com:/cvsroot/KWStyle" CVS_MODULE "KWStyle" CMAKE_GENERATOR "${CMAKE_GENERATOR}" - CONFIGURE_ARGS "\"-DCMAKE_INSTALL_PREFIX:PATH=${prefix}\"" + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${prefix} DEPENDS "EmptyNoOpProject" DEPENDS "TutorialStep1-LocalTAR" DEPENDS "TutorialStep1-LocalNoDirTAR" @@ -215,9 +213,9 @@ if(can_use_svn) set(proj gdcm-md5-20081204) add_external_project(${proj} SVN_REPOSITORY "http://gdcm.svn.sourceforge.net/svnroot/gdcm/trunk/Utilities/gdcmmd5" - SVN_TAG "-r\;{2008-12-04 01:00:00 +0000}" + SVN_TAG "-r{2008-12-04 01:00:00 +0000}" CMAKE_GENERATOR "${CMAKE_GENERATOR}" - CONFIGURE_ARGS "\"-DCMAKE_INSTALL_PREFIX:PATH=${prefix}\"" + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${prefix} ) # SVN by revision number: @@ -225,9 +223,9 @@ if(can_use_svn) set(proj gdcm-md5-r4824) add_external_project(${proj} SVN_REPOSITORY "http://gdcm.svn.sourceforge.net/svnroot/gdcm/trunk/Utilities/gdcmmd5" - SVN_TAG "-r\;4824" + SVN_TAG "-r4824" CMAKE_GENERATOR "${CMAKE_GENERATOR}" - CONFIGURE_ARGS "\"-DCMAKE_INSTALL_PREFIX:PATH=${prefix}\"" + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${prefix} INSTALL_COMMAND "" ) @@ -237,7 +235,7 @@ if(can_use_svn) add_external_project(${proj} SVN_REPOSITORY "http://gdcm.svn.sourceforge.net/svnroot/gdcm/trunk/Utilities/gdcmmd5" CMAKE_GENERATOR "${CMAKE_GENERATOR}" - CONFIGURE_ARGS "\"-DCMAKE_INSTALL_PREFIX:PATH=${prefix}\"" + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${prefix} INSTALL_COMMAND "" ) endif() -- cgit v0.12