# SPDX-FileCopyrightText: 2016 Gleb Popov <6yearold@gmail.com> # # SPDX-License-Identifier: BSD-3-Clause #[=======================================================================[.rst: ECMWinResolveSymlinks -------------------------- Resolve pseudo-symlinks created by git when cloning on Windows. :: ecm_win_resolve_symlinks() When git checks out a repository with UNIX symlinks on Windows machine, it creates a text file for each symlink, containing a relative path to the real file. This function would recursively walk over specified directory and replace pseudo-symlinks with corresponding real file's contents. It would then run ``git update-index --assume-unchanged`` on them to trick git. This is useful for projects like "breeze-icons" that contain many identical icons implemented as symlinks. Since 5.28 #]=======================================================================] function(ECM_WIN_RESOLVE_SYMLINKS _dir) get_filename_component(dir ${_dir} ABSOLUTE) find_program(GIT_EXECUTABLE git) if(NOT GIT_EXECUTABLE) message(SEND_ERROR "Git executable not found!") endif() message(STATUS "Resolving symlinks in ${dir}...") _find_symlinks(${dir} symlinks) _portioned_list(symlinks ${symlinks}) foreach(s IN LISTS symlinks) string(REPLACE ":" ";" s ${s}) _assume_unchanged(NO ${dir} "${s}") _checkout_symlinks(${dir} "${s}") _resolve_symlinks(${dir} "${s}") _assume_unchanged(YES ${dir} "${s}") endforeach() message(STATUS "Resolving symlinks in ${dir}... Done.") # touch cache every build to force CMake to re-run these functions every time if(NOT TARGET wrs_touch_cache) add_custom_target(wrs_touch_cache ALL COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_BINARY_DIR}/CMakeCache.txt ) endif() endfunction() function(_assume_unchanged mode dir symlinks) if(mode) set(flag --assume-unchanged) else() set(flag --no-assume-unchanged) endif() execute_process(COMMAND ${GIT_EXECUTABLE} update-index ${flag} ${symlinks} WORKING_DIRECTORY ${dir} ) endfunction() function(_find_symlinks dir outvar) execute_process(COMMAND ${GIT_EXECUTABLE} ls-files -s WORKING_DIRECTORY ${dir} OUTPUT_VARIABLE out OUTPUT_STRIP_TRAILING_WHITESPACE ) set(${outvar}) if(out) string(REPLACE "\n" ";" out ${out}) foreach(f IN LISTS out) # 120000 0db97052931e18484b6705f3bc644484ef9403b0 0 icons-dark/actions/16/CVnamespace.svg string(REPLACE "\t" ";" f "${f}") string(REPLACE " " ";" f "${f}") list(GET f 0 mode) if(mode STREQUAL "120000") list(GET f 3 symlink) list(APPEND ${outvar} ${symlink}) endif() endforeach() endif() set(${outvar} ${${outvar}} PARENT_SCOPE) endfunction() # In functions like _checkout_symlinks() the command line can become too lengthy for Windows. # So we partition it, but in a hacky way due to CMake doesn't have list of lists. function(_portioned_list outvar) list(LENGTH ARGN arglen) if(arglen EQUAL 0) set(${outvar} "" PARENT_SCOPE) return() endif() set(init) set(tail) math(EXPR range "${arglen} - 1") foreach(i RANGE ${range}) list(GET ARGN ${i} v) string(LENGTH "${init}" initlen) string(LENGTH ${v} vlen) math(EXPR sumlen "${initlen} + ${vlen}") if(sumlen LESS 8192) list(APPEND init ${v}) else() list(APPEND tail ${v}) endif() endforeach() _portioned_list(tail_portioned ${tail}) string(REPLACE ";" ":" init "${init}") # Generally this is not safe, because filepath can contain ':' character. But not on Windows. Phew. set(${outvar} ${init} ${tail_portioned} PARENT_SCOPE) endfunction() function(_checkout_symlinks dir symlinks) execute_process(COMMAND ${GIT_EXECUTABLE} checkout ${symlinks} WORKING_DIRECTORY ${dir} ) endfunction() function(_resolve_symlinks dir symlinks) foreach(s IN LISTS symlinks) file(READ ${dir}/${s} sdata) get_filename_component(sdir ${dir}/${s} DIRECTORY) set(f "${sdir}/${sdata}") file(READ ${f} fdata) file(WRITE ${dir}/${s} ${fdata}) endforeach() endfunction()