Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b70460a
Test forwarding of arguments to `FetchCOntent_Declare`
CraigHutchinson Mar 23, 2023
cbe5144
Preserving forwarding of empty string arguments
CraigHutchinson Mar 7, 2023
b847af6
Support EVAL CODE on CMake <3.18
CraigHutchinson Mar 8, 2023
5c1ce67
Fix docs typo
CraigHutchinson Mar 8, 2023
f4fd660
Maintain key-value CMake formatting
CraigHutchinson Mar 21, 2023
a49358e
refactor empty argument passing to not require eval
TheLartians Apr 13, 2023
6b5cd6f
add removed test cases
TheLartians Apr 13, 2023
6b49fcf
Merge branch 'master' of https://github.com/cpm-cmake/CPM.cmake into …
TheLartians May 5, 2026
2eb31c5
Merge branch 'master' of https://github.com/cpm-cmake/CPM.cmake into …
TheLartians May 5, 2026
0bc3c83
ensure to unset undefined variables
TheLartians May 5, 2026
e406086
revert back to parsing into the correct prefix
TheLartians May 5, 2026
151b472
eval seems to be needed after all
TheLartians May 5, 2026
d6f2b26
add comment for cpm_cmake_eval
TheLartians May 5, 2026
20bd8c4
support whitespaces in test artefact removal
TheLartians May 5, 2026
b3b716f
use list functions in encoding / decoding
TheLartians May 5, 2026
d5c2595
support arbitrary CURRENT_LIST_DIR paths
TheLartians May 5, 2026
ae615ba
support forwarding the __CPM_EMPTY_ARG string
TheLartians May 5, 2026
4a34fbb
explicitly test for GIT_SUBMODULES
TheLartians May 5, 2026
35d1103
quote the eval paths
TheLartians May 5, 2026
9a98a0a
move the bracket argument quoting to a helper function
TheLartians May 5, 2026
fe174e3
remove escape escaping and update test case
TheLartians May 5, 2026
d6e38c0
quote CMAKE_CURRENT_LIST_DIR
TheLartians May 5, 2026
658fcc4
add cpm_ prefix and fix typo in cpm_quote_as_bracket_string_literal
TheLartians May 5, 2026
899b0c8
escape and unescape escape characters
TheLartians May 5, 2026
396512e
Merge branch 'master' of https://github.com/cpm-cmake/CPM.cmake into …
TheLartians May 30, 2026
89208f7
Merge branch 'master' of https://github.com/cpm-cmake/CPM.cmake into …
TheLartians May 30, 2026
dfca5f2
Merge branch 'master' into lars/preserve_empty_strings
TheLartians Jun 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
118 changes: 115 additions & 3 deletions cmake/CPM.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,58 @@ function(cpm_override_fetchcontent contentName)
set_property(GLOBAL PROPERTY ${propertyName} TRUE)
endfunction()

# replaces empty arguments with a placeholder to compensate CMake issues with handling empty
# arguments
function(cpm_encode_empty_arguments args outVar)
set(out "")
# note: we don't use string replacement for ';;' -> ';__CPM_EMPTY_ARG;' here, as it would
# interfere with nested arguments
foreach(ARG IN LISTS args)
if(ARG STREQUAL "")
list(APPEND out "__CPM_EMPTY_ARG")
else()
Comment thread
TheLartians marked this conversation as resolved.
# prevent escaped characters from getting resolved early
string(REPLACE "\\" "\\\\\\" ARG "${ARG}")
# allow passing __CPM_EMPTY_ARG string by appending __ to any occurrences
string(REPLACE "__CPM_EMPTY_ARG" "____CPM_EMPTY_ARG" ARG "${ARG}")
list(APPEND out "${ARG}")
Comment thread
TheLartians marked this conversation as resolved.
endif()
endforeach()
set("${outVar}"
"${out}"
PARENT_SCOPE
)
endfunction()

function(cpm_decode_empty_argument arg outVar)
if("${arg}" STREQUAL "__CPM_EMPTY_ARG")
set("${outVar}"
""
PARENT_SCOPE
)
else()
string(REPLACE "\\\\\\" "\\" arg "${arg}")
string(REPLACE "____CPM_EMPTY_ARG" "__CPM_EMPTY_ARG" arg "${arg}")
set("${outVar}"
"${arg}"
PARENT_SCOPE
)
endif()
endfunction()

# replaces placeholder arguments from `cpm_encode_empty_arguments` with empty arguments
function(cpm_decode_empty_arguments args outVar)
set(out "")
foreach(arg IN LISTS args)
cpm_decode_empty_argument("${arg}" arg)
list(APPEND out "${arg}")
endforeach()
set("${outVar}"
"${out}"
PARENT_SCOPE
)
endfunction()

# Download and add a package from source
function(CPMAddPackage)
cpm_set_policies()
Expand Down Expand Up @@ -689,7 +741,25 @@ function(CPMAddPackage)
set(ARGN "${ARGV0};EXCLUDE_FROM_ALL;YES;SYSTEM;YES;${ARGN}")
endif()

cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" "${ARGN}")
# Encode arguments for `cmake_parse_arguments`
cpm_encode_empty_arguments("${ARGN}" "PARSE_ARGS")

# Parse arguments
cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" "${PARSE_ARGS}")

# Decode arguments
foreach(ARG IN LISTS oneValueArgs)
if(DEFINED CPM_ARGS_${ARG})
cpm_decode_empty_argument("${CPM_ARGS_${ARG}}" CPM_ARGS_${ARG})
endif()
endforeach()
foreach(ARG IN LISTS multiValueArgs)
if(DEFINED CPM_ARGS_${ARG})
cpm_decode_empty_arguments("${CPM_ARGS_${ARG}}" CPM_ARGS_${ARG})
endif()
endforeach()

cpm_decode_empty_arguments("${CPM_ARGS_UNPARSED_ARGUMENTS}" CPM_ARGS_UNPARSED_ARGUMENTS)

# Set default values for arguments
if(NOT DEFINED CPM_ARGS_VERSION)
Expand Down Expand Up @@ -958,7 +1028,6 @@ function(CPMAddPackage)
cpm_message(
STATUS "${CPM_INDENT} Adding package ${CPM_ARGS_NAME}@${CPM_ARGS_VERSION} (${PACKAGE_INFO})"
)

if(NOT CPM_SKIP_FETCH)
# CMake 3.28 added EXCLUDE, SYSTEM (3.25), and SOURCE_SUBDIR (3.18) to FetchContent_Declare.
# Calling FetchContent_MakeAvailable will then internally forward these options to
Expand Down Expand Up @@ -1102,14 +1171,56 @@ function(CPMGetPackageVersion PACKAGE OUTPUT)
)
endfunction()

# Quotes the input argument `str` using bracket arguments [==[...]==] containing the minimum amount
# of "=" needed.
function(cpm_quote_as_bracket_string_literal str out_var)
# Find the shortest bracket depth that doesn't appear in the string, so we can safely use a
# bracket string literal: [==[...]==]
set(bracket "")
while(TRUE)
Comment thread
TheLartians marked this conversation as resolved.
string(FIND "${str}" "]${bracket}]" pos)
if(pos EQUAL -1)
break()
endif()
string(APPEND bracket "=")
endwhile()
set(${out_var}
[${bracket}[${str}]${bracket}]
PARENT_SCOPE
)
endfunction()

# Evaluates CMake code (passed as ARGN) while preserving CMAKE_CURRENT_LIST_DIR.
#
# CMake's cmake_language(EVAL CODE ...) resets CMAKE_CURRENT_LIST_DIR, so we prepend a SET() call
# using a bracket string literal — avoiding escaping issues regardless of what characters the path
# contains.
macro(cpm_cmake_eval)
cpm_quote_as_bracket_string_literal("${CMAKE_CURRENT_LIST_DIR}" __cpm_eval_CMAKE_CURRENT_LIST_DIR)
set(__cpm_eval_code "SET(CMAKE_CURRENT_LIST_DIR ${__cpm_eval_CMAKE_CURRENT_LIST_DIR})\n${ARGN}")
if(COMMAND cmake_language)
cmake_language(EVAL CODE "${__cpm_eval_code}")
else()
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/eval.cmake" "${__cpm_eval_code}")
include("${CMAKE_CURRENT_BINARY_DIR}/eval.cmake")
endif()
endmacro()

# declares a package in FetchContent_Declare
function(cpm_declare_fetch PACKAGE)
if(${CPM_DRY_RUN})
cpm_message(STATUS "${CPM_INDENT} Package not declared (dry run)")
return()
endif()

FetchContent_Declare(${PACKAGE} ${ARGN})
# Forward preserving empty string arguments
# (https://gitlab.kitware.com/cmake/cmake/-/merge_requests/4729)
set(quoted_args)
foreach(arg IN LISTS ARGN)
cpm_quote_as_bracket_string_literal("${arg}" quoted_arg)
string(APPEND quoted_args " ${quoted_arg}")
endforeach()
cpm_cmake_eval("FetchContent_Declare(${PACKAGE} ${quoted_args} )")
endfunction()

# returns properties for a package previously defined by cpm_declare_fetch
Expand Down Expand Up @@ -1180,6 +1291,7 @@ endfunction()
# downloads a previously declared package via FetchContent and exports the variables
# `${PACKAGE}_SOURCE_DIR` and `${PACKAGE}_BINARY_DIR` to the parent scope
function(cpm_fetch_package PACKAGE DOWNLOAD_ONLY populated)

set(${populated}
FALSE
PARENT_SCOPE
Expand Down
2 changes: 1 addition & 1 deletion test/unit/broken_dependency/CMakeLists.txt.in
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ include(@CPM_PATH@/CPM.cmake)
CPMAddPackage(
NAME BrokenDependency
SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/dependency
EXCLUDE_FROM_ALL @EXCLUDE_FROM_ALL@
EXCLUDE_FROM_ALL "@EXCLUDE_FROM_ALL@"
)
76 changes: 76 additions & 0 deletions test/unit/forward_arguments.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)

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

set(input "a;;b;c;;;;d\\ef;g;;__CPM_EMPTY_ARG;")
cpm_encode_empty_arguments("${input}" encoded)
foreach(arg IN LISTS encoded)
assert_not_equal("${arg}" "")
endforeach()
cpm_decode_empty_arguments("${encoded}" decoded)
assert_equal("${decoded}" "${input}")

# ignore source cache if set
set(CPM_SOURCE_CACHE "")

# Intercept underlying `FetchContent_Declare`
function(FetchContent_Declare)
set_property(GLOBAL PROPERTY last_FetchContent_Declare_ARGN "${ARGN}")
set_property(
GLOBAL PROPERTY last_FetchContent_Declare_CMAKE_CURRENT_LIST_DIR "${CMAKE_CURRENT_LIST_DIR}"
)
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")
get_property(
last_FetchContent_Declare_CMAKE_CURRENT_LIST_DIR GLOBAL
PROPERTY last_FetchContent_Declare_CMAKE_CURRENT_LIST_DIR
)
assert_equal("${last_FetchContent_Declare_CMAKE_CURRENT_LIST_DIR}" "${CMAKE_CURRENT_LIST_DIR}")

# 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 GIT_SUBMODULES ""
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;GIT_SUBMODULES;;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(CPMAddPackage)
set_property(GLOBAL PROPERTY last_cpmaddpackage_argn "${ARGN}")
endfunction()

# TEST: CPM Module file shall store all arguments including empty strings
include(${CPM_MODULE_PATH}/Findfibonacci.cmake)

get_property(last_cpmaddpackage_argn GLOBAL PROPERTY last_cpmaddpackage_argn)
assert_equal(
"${last_cpmaddpackage_argn}"
"NAME;fibonacci;GIT_REPOSITORY;https://github.com/cpm-cmake/testpack-fibonacci.git;GIT_SUBMODULES;;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: 2 additions & 0 deletions test/unit/options.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ include(CMakePackageConfigHelpers)
include(${CPM_PATH}/testing.cmake)

set(TEST_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/source_dir)
# clean existing build if it exists
file(REMOVE_RECURSE "${TEST_BUILD_DIR}")

set(TEST_DEPENDENCY_NAME Dependency)

Expand Down