Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 3c3ebf1

Browse files
fix: Patch commands fail trying to re-apply the same patch twice
Fixes #618
1 parent d9364ce commit 3c3ebf1

File tree

3 files changed

+179
-28
lines changed

3 files changed

+179
-28
lines changed

‎.gitignore‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ build/
22
/build*
33
/.vscode
44
*.DS_Store
5+
# Autogenerated code by CPM.cmake
6+
cmake/cpm_apply_patches.cmake

‎cmake/CPM.cmake‎

Lines changed: 87 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,62 @@ macro(cpm_set_policies)
112112
endmacro()
113113
cpm_set_policies()
114114

115+
macro(cpm_generate_apply_patches_script)
116+
set(_cpm_patch_script "${CPM_CURRENT_DIRECTORY}/cpm_apply_patches.cmake")
117+
118+
file(
119+
WRITE "${_cpm_patch_script}"
120+
[=[
121+
# Auto-generated patch application script
122+
separate_arguments(PATCH_FILES)
123+
124+
foreach(patch_file IN LISTS PATCH_FILES)
125+
message(STATUS "Checking patch: ${patch_file}")
126+
127+
execute_process(
128+
COMMAND "${PATCH_EXECUTABLE}" --dry-run -p1
129+
INPUT_FILE "${patch_file}"
130+
RESULT_VARIABLE dry_run_result
131+
OUTPUT_VARIABLE dry_out
132+
ERROR_VARIABLE dry_err
133+
)
134+
135+
if(dry_run_result EQUAL 0)
136+
message(STATUS "Applying patch: ${patch_file}")
137+
execute_process(
138+
COMMAND "${PATCH_EXECUTABLE}" -p1
139+
INPUT_FILE "${patch_file}"
140+
RESULT_VARIABLE apply_result
141+
OUTPUT_VARIABLE apply_out
142+
ERROR_VARIABLE apply_err
143+
)
144+
if(apply_result EQUAL 0)
145+
message(STATUS "Applied patch: ${patch_file}")
146+
else()
147+
message(FATAL_ERROR "Patch failed: ${patch_file}\n${apply_err}")
148+
endif()
149+
else()
150+
execute_process(
151+
COMMAND "${PATCH_EXECUTABLE}" --dry-run -p1 --reverse
152+
INPUT_FILE "${patch_file}"
153+
RESULT_VARIABLE reverse_result
154+
OUTPUT_VARIABLE reverse_out
155+
ERROR_VARIABLE reverse_err
156+
)
157+
if(reverse_result EQUAL 0)
158+
message(STATUS "Patch already applied: ${patch_file}")
159+
else()
160+
message(
161+
FATAL_ERROR "Patch cannot be applied and is not already applied: ${patch_file}\n${dry_err}"
162+
)
163+
endif()
164+
endif()
165+
endforeach()
166+
]=]
167+
)
168+
endmacro()
169+
cpm_generate_apply_patches_script()
170+
115171
option(CPM_USE_LOCAL_PACKAGES "Always try to use `find_package` to get dependencies"
116172
$ENV{CPM_USE_LOCAL_PACKAGES}
117173
)
@@ -541,66 +597,69 @@ endfunction()
541597
# then generates a `PATCH_COMMAND` appropriate for `ExternalProject_Add()`. This command is appended
542598
# to the parent scope's `CPM_ARGS_UNPARSED_ARGUMENTS`.
543599
function(cpm_add_patches)
544-
# Return if no patch files are supplied.
600+
# Return early if no patch files are provided
545601
if(NOT ARGN)
546602
return()
547603
endif()
548604

549-
# Find the patch program.
605+
# -----------------------------------------------------------------------------------------------
606+
# Locate the 'patch' executable
607+
# -----------------------------------------------------------------------------------------------
550608
find_program(PATCH_EXECUTABLE patch)
609+
551610
if(CMAKE_HOST_WIN32 AND NOT PATCH_EXECUTABLE)
552611
# The Windows git executable is distributed with patch.exe. Find the path to the executable, if
553612
# it exists, then search `../usr/bin` and `../../usr/bin` for patch.exe.
554613
find_package(Git QUIET)
555614
if(GIT_EXECUTABLE)
556-
get_filename_component(extra_search_path ${GIT_EXECUTABLE} DIRECTORY)
557-
get_filename_component(extra_search_path_1up ${extra_search_path} DIRECTORY)
558-
get_filename_component(extra_search_path_2up ${extra_search_path_1up} DIRECTORY)
615+
get_filename_component(_git_bin_dir "${GIT_EXECUTABLE}" DIRECTORY)
616+
get_filename_component(_git_root_1up "${_git_bin_dir}" DIRECTORY)
617+
get_filename_component(_git_root_2up "${_git_root_1up}" DIRECTORY)
618+
559619
find_program(
560-
PATCH_EXECUTABLE patch HINTS "${extra_search_path_1up}/usr/bin"
561-
"${extra_search_path_2up}/usr/bin"
620+
PATCH_EXECUTABLE patch HINTS "${_git_root_1up}/usr/bin" "${_git_root_2up}/usr/bin"
562621
)
563622
endif()
564623
endif()
624+
565625
if(NOT PATCH_EXECUTABLE)
566626
message(FATAL_ERROR "Couldn't find `patch` executable to use with PATCHES keyword.")
567627
endif()
568628

569-
# Create a temporary
570-
set(temp_list ${CPM_ARGS_UNPARSED_ARGUMENTS})
629+
# -----------------------------------------------------------------------------------------------
630+
# Resolve and validate all patch file paths
631+
# -----------------------------------------------------------------------------------------------
632+
set(resolved_patch_files)
571633

572-
# Ensure each file exists (or error out) and add it to the list.
573-
set(first_item True)
574-
foreach(PATCH_FILE ${ARGN})
634+
foreach(PATCH_FILE IN LISTS ARGN)
575635
# Make sure the patch file exists, if we can't find it, try again in the current directory.
576636
if(NOT EXISTS "${PATCH_FILE}")
577-
if(NOT EXISTS "${CMAKE_CURRENT_LIST_DIR}/${PATCH_FILE}")
637+
set(_fallback_path "${CMAKE_CURRENT_LIST_DIR}/${PATCH_FILE}")
638+
if(NOT EXISTS "${_fallback_path}")
578639
message(FATAL_ERROR "Couldn't find patch file: '${PATCH_FILE}'")
579640
endif()
580-
set(PATCH_FILE "${CMAKE_CURRENT_LIST_DIR}/${PATCH_FILE}")
641+
set(PATCH_FILE "${_fallback_path}")
581642
endif()
582643

583644
# Convert to absolute path for use with patch file command.
584645
get_filename_component(PATCH_FILE "${PATCH_FILE}" ABSOLUTE)
585-
586-
# The first patch entry must be preceded by "PATCH_COMMAND" while the following items are
587-
# preceded by "&&".
588-
if(first_item)
589-
set(first_item False)
590-
list(APPEND temp_list "PATCH_COMMAND")
591-
else()
592-
list(APPEND temp_list "&&")
593-
endif()
594-
# Add the patch command to the list
595-
list(APPEND temp_list "${PATCH_EXECUTABLE}" "-p1" "<" "${PATCH_FILE}")
646+
list(APPEND resolved_patch_files "${PATCH_FILE}")
596647
endforeach()
597648

598-
# Move temp out into parent scope.
649+
# -----------------------------------------------------------------------------------------------
650+
# Construct the patch command
651+
# -----------------------------------------------------------------------------------------------
652+
string(JOIN " " joined_patch_files ${resolved_patch_files})
653+
654+
set(_patch_command cmake -D "PATCH_FILES=${joined_patch_files}" -D
655+
"PATCH_EXECUTABLE=${PATCH_EXECUTABLE}" -P "${_cpm_patch_script}"
656+
)
657+
658+
list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS PATCH_COMMAND ${_patch_command})
599659
set(CPM_ARGS_UNPARSED_ARGUMENTS
600-
${temp_list}
660+
"${CPM_ARGS_UNPARSED_ARGUMENTS}"
601661
PARENT_SCOPE
602662
)
603-
604663
endfunction()
605664

606665
# method to overwrite internal FetchContent properties, to allow using CPM.cmake to overload

‎test/unit/package_patch.cmake‎

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
2+
3+
include(${CPM_PATH}/testing.cmake)
4+
include(${CPM_PATH}/CPM.cmake)
5+
6+
# ----------------------------------------------------------------------------------------
7+
# Setup: Define common environment
8+
# ----------------------------------------------------------------------------------------
9+
set(CPM_CURRENT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
10+
11+
# ----------------------------------------------------------------------------------------
12+
# Test Case 1: Single patch file
13+
# ----------------------------------------------------------------------------------------
14+
function(run_test_single_patch)
15+
set(_patch1 "${CMAKE_CURRENT_BINARY_DIR}/dummy1.patch")
16+
file(WRITE "${_patch1}" "dummy patch content")
17+
18+
unset(CPM_ARGS_UNPARSED_ARGUMENTS)
19+
cpm_add_patches("${_patch1}")
20+
21+
list(FIND CPM_ARGS_UNPARSED_ARGUMENTS "PATCH_COMMAND" _idx1)
22+
assert_not_equal("${_idx1}" "-1")
23+
24+
math(EXPR _start1 "${_idx1} + 1")
25+
list(SUBLIST CPM_ARGS_UNPARSED_ARGUMENTS ${_start1} -1 _args1)
26+
27+
set(_found1 FALSE)
28+
foreach(arg IN LISTS _args1)
29+
if(arg MATCHES "PATCH_FILES=.*dummy1\\.patch")
30+
set(_found1 TRUE)
31+
endif()
32+
endforeach()
33+
assert_truthy(_found1)
34+
35+
file(REMOVE "${_patch1}")
36+
endfunction()
37+
38+
# ----------------------------------------------------------------------------------------
39+
# Test Case 2: Multiple patch files
40+
# ----------------------------------------------------------------------------------------
41+
function(run_test_multiple_patches)
42+
set(_patch2 "${CMAKE_CURRENT_BINARY_DIR}/dummy2.patch")
43+
set(_patch3 "${CMAKE_CURRENT_BINARY_DIR}/dummy3.patch")
44+
file(WRITE "${_patch2}" "dummy patch 2")
45+
file(WRITE "${_patch3}" "dummy patch 3")
46+
47+
unset(CPM_ARGS_UNPARSED_ARGUMENTS)
48+
cpm_add_patches("${_patch2}" "${_patch3}")
49+
50+
list(FIND CPM_ARGS_UNPARSED_ARGUMENTS "PATCH_COMMAND" _idx2)
51+
assert_not_equal("${_idx2}" "-1")
52+
53+
math(EXPR _start2 "${_idx2} + 1")
54+
list(SUBLIST CPM_ARGS_UNPARSED_ARGUMENTS ${_start2} -1 _args2)
55+
56+
set(_found2 FALSE)
57+
set(_found3 FALSE)
58+
59+
foreach(arg IN LISTS _args2)
60+
if(arg MATCHES "dummy2\\.patch")
61+
set(_found2 TRUE)
62+
endif()
63+
if(arg MATCHES "dummy3\\.patch")
64+
set(_found3 TRUE)
65+
endif()
66+
endforeach()
67+
68+
assert_truthy(_found2)
69+
assert_truthy(_found3)
70+
71+
file(REMOVE "${_patch2}")
72+
file(REMOVE "${_patch3}")
73+
endfunction()
74+
75+
# ----------------------------------------------------------------------------------------
76+
# Test Case 3: No patch files
77+
# ----------------------------------------------------------------------------------------
78+
function(run_test_no_patches)
79+
unset(CPM_ARGS_UNPARSED_ARGUMENTS)
80+
cpm_add_patches()
81+
82+
assert_not_defined(CPM_ARGS_UNPARSED_ARGUMENTS)
83+
endfunction()
84+
85+
# ----------------------------------------------------------------------------------------
86+
# Run all test cases
87+
# ----------------------------------------------------------------------------------------
88+
run_test_single_patch()
89+
run_test_multiple_patches()
90+
run_test_no_patches()

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /