1# SPDX-FileCopyrightText: 2016 Gleb Popov <6yearold@gmail.com> 2# 3# SPDX-License-Identifier: BSD-3-Clause 4 5#[=======================================================================[.rst: 6ECMWinResolveSymlinks 7-------------------------- 8 9Resolve pseudo-symlinks created by git when cloning on Windows. 10 11:: 12 13 ecm_win_resolve_symlinks(<dir>) 14 15When git checks out a repository with UNIX symlinks on Windows machine, 16it creates a text file for each symlink, containing a relative path to the 17real file. 18This function would recursively walk over specified directory and replace 19pseudo-symlinks with corresponding real file's contents. It would then run 20git update-index --assume-unchanged on them to trick git. 21 22This is useful for projects like "breeze-icons" that contain many identical 23icons implemented as symlinks. 24 25Since 5.28 26#]=======================================================================] 27 28function(ECM_WIN_RESOLVE_SYMLINKS _dir) 29 get_filename_component(dir ${_dir} ABSOLUTE) 30 find_program(GIT_EXECUTABLE git) 31 if(NOT GIT_EXECUTABLE) 32 message(SEND_ERROR "Git executable not found!") 33 endif() 34 35 message(STATUS "Resolving symlinks in ${dir}...") 36 _find_symlinks(${dir} symlinks) 37 _portioned_list(symlinks ${symlinks}) 38 foreach(s IN LISTS symlinks) 39 string(REPLACE ":" ";" s ${s}) 40 _assume_unchanged(NO ${dir} "${s}") 41 _checkout_symlinks(${dir} "${s}") 42 _resolve_symlinks(${dir} "${s}") 43 _assume_unchanged(YES ${dir} "${s}") 44 endforeach() 45 message(STATUS "Resolving symlinks in ${dir}... Done.") 46 47 # touch cache every build to force CMake to re-run these functions every time 48 if(NOT TARGET wrs_touch_cache) 49 add_custom_target(wrs_touch_cache ALL 50 COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_BINARY_DIR}/CMakeCache.txt 51 ) 52 endif() 53endfunction() 54 55function(_assume_unchanged mode dir symlinks) 56 if(mode) 57 set(flag --assume-unchanged) 58 else() 59 set(flag --no-assume-unchanged) 60 endif() 61 62 execute_process(COMMAND ${GIT_EXECUTABLE} update-index ${flag} ${symlinks} 63 WORKING_DIRECTORY ${dir} 64 ) 65endfunction() 66 67function(_find_symlinks dir outvar) 68 execute_process(COMMAND ${GIT_EXECUTABLE} ls-files -s 69 WORKING_DIRECTORY ${dir} 70 OUTPUT_VARIABLE out 71 OUTPUT_STRIP_TRAILING_WHITESPACE 72 ) 73 74 set(${outvar}) 75 if(out) 76 string(REPLACE "\n" ";" out ${out}) 77 78 foreach(f IN LISTS out) 79 # 120000 0db97052931e18484b6705f3bc644484ef9403b0 0 <tab> icons-dark/actions/16/CVnamespace.svg 80 string(REPLACE "\t" ";" f "${f}") 81 string(REPLACE " " ";" f "${f}") 82 list(GET f 0 mode) 83 if(mode STREQUAL "120000") 84 list(GET f 3 symlink) 85 list(APPEND ${outvar} ${symlink}) 86 endif() 87 endforeach() 88 endif() 89 set(${outvar} ${${outvar}} PARENT_SCOPE) 90endfunction() 91 92# In functions like _checkout_symlinks() the command line can become too lengthy for Windows. 93# So we partition it, but in a hacky way due to CMake doesn't have list of lists. 94function(_portioned_list outvar) 95 list(LENGTH ARGN arglen) 96 97 if(arglen EQUAL 0) 98 set(${outvar} "" PARENT_SCOPE) 99 return() 100 endif() 101 102 set(init) 103 set(tail) 104 math(EXPR range "${arglen} - 1") 105 foreach(i RANGE ${range}) 106 list(GET ARGN ${i} v) 107 string(LENGTH "${init}" initlen) 108 string(LENGTH ${v} vlen) 109 math(EXPR sumlen "${initlen} + ${vlen}") 110 if(sumlen LESS 8192) 111 list(APPEND init ${v}) 112 else() 113 list(APPEND tail ${v}) 114 endif() 115 endforeach() 116 117 _portioned_list(tail_portioned ${tail}) 118 string(REPLACE ";" ":" init "${init}") # Generally this is not safe, because filepath can contain ':' character. But not on Windows. Phew. 119 set(${outvar} ${init} ${tail_portioned} PARENT_SCOPE) 120endfunction() 121 122function(_checkout_symlinks dir symlinks) 123 execute_process(COMMAND ${GIT_EXECUTABLE} checkout ${symlinks} 124 WORKING_DIRECTORY ${dir} 125 ) 126endfunction() 127 128function(_resolve_symlinks dir symlinks) 129 foreach(s IN LISTS symlinks) 130 file(READ ${dir}/${s} sdata) 131 get_filename_component(sdir ${dir}/${s} DIRECTORY) 132 set(f "${sdir}/${sdata}") 133 file(READ ${f} fdata) 134 file(WRITE ${dir}/${s} ${fdata}) 135 endforeach() 136endfunction() 137