Skip to content
Open
1 change: 1 addition & 0 deletions .cmake-format
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ include: ["cmake/.cmake-format-additional_commands-cpm"]
format:
tab_size: 2
line_width: 100
line_ending: auto
dangle_parens: true
59 changes: 54 additions & 5 deletions cmake/CPM.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ function(cpm_add_patches)
# Make sure the patch file exists, if we can't find it, try again in the current directory.
if(NOT EXISTS "${PATCH_FILE}")
if(NOT EXISTS "${CMAKE_CURRENT_LIST_DIR}/${PATCH_FILE}")
message(FATAL_ERROR "Couldn't find patch file: '${PATCH_FILE}'")
message(FATAL_ERROR "Couldn't find patch file: '${CMAKE_CURRENT_LIST_DIR}/${PATCH_FILE}'")
endif()
set(PATCH_FILE "${CMAKE_CURRENT_LIST_DIR}/${PATCH_FILE}")
endif()
Expand Down Expand Up @@ -644,8 +644,50 @@ function(cpm_override_fetchcontent contentName)
set_property(GLOBAL PROPERTY ${propertyName} TRUE)
endfunction()

macro(cpm_cmake_eval)
set(__CPM_ARGN "SET(CMAKE_CURRENT_LIST_DIR ${CMAKE_CURRENT_LIST_DIR})\n${ARGN}")
if(COMMAND cmake_language)
# ensure that the `CMAKE_CURRENT_LIST_DIR` is correctly set inside the call
cmake_language(EVAL CODE "${__CPM_ARGN}")
else()
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/eval.cmake "${__CPM_ARGN}")
include(${CMAKE_CURRENT_BINARY_DIR}/eval.cmake)
endif()
endmacro()

# Download and add a package from source
function(CPMAddPackage)
macro(CPMAddPackage)
set(__CPM_ARGN "${ARGN}")
list(LENGTH __CPM_ARGN __CPM_ARGN_Length)
if(__CPM_ARGN_Length EQUAL 1)
cpm_add_package_single_arg(${ARGN})
else()
# Forward preserving empty string arguments
# (https://gitlab.kitware.com/cmake/cmake/-/merge_requests/4729)
set(__CPM_ARGN_Quoted)
foreach(__CPM_ARG IN LISTS __CPM_ARGN)
string(APPEND __CPM_ARGN_Quoted " [==[${__CPM_ARG}]==]")
endforeach()
cpm_cmake_eval("cpm_add_package_multi_arg( ${__CPM_ARGN_Quoted} )")
endif()
endmacro()
Comment on lines +659 to +673
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is, however, a specific case to be aware of which can result in unexpected behavior. Because macros treat their arguments as string substitutions rather than as variables, if they use ARGN in a place where a variable name is expected, the variable it will refer to will be in the scope from which the macro is called, not the ARGN from the macro’s own arguments.

From chapter 9.2 professional-cmake


macro(cpm_add_package_single_arg arg)
cpm_set_policies()
cpm_parse_add_package_single_arg("${arg}" __ARGN_multi)

# The shorthand syntax implies EXCLUDE_FROM_ALL
# cmake-format: off
list(APPEND __ARGN_multi
EXCLUDE_FROM_ALL YES
SYSTEM YES
)
# cmake-format: on

cpm_add_package_multi_arg(${__ARGN_multi}) # Forward function arguments to CPMAddPackage()
endmacro()

function(cpm_add_package_multi_arg)
cpm_set_policies()

set(oneValueArgs
Expand Down Expand Up @@ -687,7 +729,7 @@ function(CPMAddPackage)
set(ARGN "${ARGV0};EXCLUDE_FROM_ALL;YES;SYSTEM;YES;${ARGN}")
endif()

cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" "${ARGN}")
cmake_parse_arguments(PARSE_ARGV 0 CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}")

# Set default values for arguments
if(NOT DEFINED CPM_ARGS_VERSION)
Expand Down Expand Up @@ -1107,7 +1149,13 @@ function(cpm_declare_fetch PACKAGE)
return()
endif()

FetchContent_Declare(${PACKAGE} ${ARGN})
# Forward preserving empty string arguments
# (https://gitlab.kitware.com/cmake/cmake/-/merge_requests/4729)
set(__argsQuoted)
foreach(__item IN LISTS ARGN)
string(APPEND __argsQuoted " [==[${__item}]==]")
endforeach()
cpm_cmake_eval("FetchContent_Declare(${PACKAGE} ${__argsQuoted} )")
endfunction()

# returns properties for a package previously defined by cpm_declare_fetch
Expand Down Expand Up @@ -1313,8 +1361,9 @@ function(cpm_prettify_package_arguments OUT_VAR IS_IN_COMMENT)
EXCLUDE_FROM_ALL
SOURCE_SUBDIR
)

set(multiValueArgs URL OPTIONS DOWNLOAD_COMMAND)
cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
cmake_parse_arguments(PARSE_ARGV 2 CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}")

foreach(oneArgName ${oneValueArgs})
if(DEFINED CPM_ARGS_${oneArgName})
Expand Down
61 changes: 61 additions & 0 deletions test/unit/forward_arguments.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)

# This test case assumes that no cache is set
set(CPM_SOURCE_CACHE "")

include(${CPM_PATH}/CPM.cmake)
include(${CPM_PATH}/testing.cmake)

# Intercept underlying `FetchContent_Declare`
function(FetchContent_Declare)
set_property(GLOBAL PROPERTY last_FetchContent_Declare_ARGN "${ARGN}")
endfunction()
cpm_declare_fetch(PACKAGE VERSION INFO EMPTY "" ANOTHER)

# TEST:`cpm_declare_fetch` shall forward empty arguments
get_property(last_FetchContent_Declare_ARGN GLOBAL PROPERTY last_FetchContent_Declare_ARGN)
assert_equal("${last_FetchContent_Declare_ARGN}" "PACKAGE;VERSION;INFO;EMPTY;;ANOTHER")

# TEST:`CPMDeclarePackage` shall store all including empty
CPMDeclarePackage(FOO EMPTY "" ANOTHER)
assert_equal("${CPM_DECLARATION_FOO}" "EMPTY;;ANOTHER")

# Stub the actual fetch
set(fibonacci_POPULATED YES)
set(fibonacci_SOURCE_DIR ".")
set(fibonacci_BINARY_DIR ".")
macro(FetchContent_GetProperties)

endmacro()

# TEST:`CPMAddPackage` shall call `FetchContent_declare` with unmodified arguments including any
# Empty-string arguments
CPMAddPackage(
NAME fibonacci
GIT_REPOSITORY https://github.com/cpm-cmake/testpack-fibonacci.git
VERSION 1.2.3 EMPTY_OPTION "" COMMAND_WITH_EMPTY_ARG foo "" bar
)
get_property(last_FetchContent_Declare_ARGN GLOBAL PROPERTY last_FetchContent_Declare_ARGN)
assert_equal(
"${last_FetchContent_Declare_ARGN}"
"fibonacci;EMPTY_OPTION;;COMMAND_WITH_EMPTY_ARG;foo;;bar;GIT_REPOSITORY;https://github.com/cpm-cmake/testpack-fibonacci.git;GIT_TAG;v1.2.3"
)

# Intercept underlying `cpm_add_package_multi_arg`
function(cpm_add_package_multi_arg)
set_property(GLOBAL PROPERTY last_cpm_add_package_multi_arg_ARGN "${ARGN}")
endfunction()

# TEST: CPM Module file shall store all arguments including empty strings
include(${CPM_MODULE_PATH}/Findfibonacci.cmake)
get_property(
last_cpm_add_package_multi_arg_ARGN GLOBAL PROPERTY last_cpm_add_package_multi_arg_ARGN
)
assert_equal(
"${last_cpm_add_package_multi_arg_ARGN}"
"NAME;fibonacci;GIT_REPOSITORY;https://github.com/cpm-cmake/testpack-fibonacci.git;VERSION;1.2.3;EMPTY_OPTION;;COMMAND_WITH_EMPTY_ARG;foo;;bar"
)

# remove generated files
file(REMOVE_RECURSE ${CPM_MODULE_PATH})
file(REMOVE ${CPM_PACKAGE_LOCK_FILE})
2 changes: 1 addition & 1 deletion test/unit/local_dependency/dependency/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)

option(DEFINE_ALTERNATIVE_FUNCTION "define the alternative function" OFF)
option(LEAKED_OPTION "this option will be leaked to the outer scope" OFF)
Expand Down
Loading