@@ -112,6 +112,62 @@ macro(cpm_set_policies)
112112endmacro ()
113113cpm_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+ 
115171option (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`. 
543599function (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- 
604663endfunction ()
605664
606665# method to overwrite internal FetchContent properties, to allow using CPM.cmake to overload 
0 commit comments