Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
at devShellTools-shell 168 lines 5.9 kB view raw
1# This setup hook causes the fixup phase to rewrite all script 2# interpreter file names (`#! /path') to paths found in $PATH. E.g., 3# /bin/sh will be rewritten to /nix/store/<hash>-some-bash/bin/sh. 4# /usr/bin/env gets special treatment so that ".../bin/env python" is 5# rewritten to /nix/store/<hash>/bin/python. Interpreters that are 6# already in the store are left untouched. 7# A script file must be marked as executable, otherwise it will not be 8# considered. 9 10fixupOutputHooks+=(patchShebangsAuto) 11 12# Run patch shebangs on a directory or file. 13# Can take multiple paths as arguments. 14# patchShebangs [--build | --host | --update] [--] PATH... 15 16# Flags: 17# --build : Lookup commands available at build-time 18# --host : Lookup commands available at runtime 19# --update : Update shebang paths that are in Nix store 20 21# Example use cases, 22# $ patchShebangs --host /nix/store/...-hello-1.0/bin 23# $ patchShebangs --build configure 24 25patchShebangs() { 26 local pathName 27 local update=false 28 29 while [[ $# -gt 0 ]]; do 30 case "$1" in 31 --host) 32 pathName=HOST_PATH 33 shift 34 ;; 35 --build) 36 pathName=PATH 37 shift 38 ;; 39 --update) 40 update=true 41 shift 42 ;; 43 --) 44 shift 45 break 46 ;; 47 -*|--*) 48 echo "Unknown option $1 supplied to patchShebangs" >&2 49 return 1 50 ;; 51 *) 52 break 53 ;; 54 esac 55 done 56 57 echo "patching script interpreter paths in $@" 58 local f 59 local oldPath 60 local newPath 61 local arg0 62 local args 63 local oldInterpreterLine 64 local newInterpreterLine 65 66 if [[ $# -eq 0 ]]; then 67 echo "No arguments supplied to patchShebangs" >&2 68 return 0 69 fi 70 71 local f 72 while IFS= read -r -d $'\0' f; do 73 isScript "$f" || continue 74 75 # read exits unclean if the shebang does not end with a newline, but still assigns the variable. 76 # So if read returns errno != 0, we check if the assigned variable is non-empty and continue. 77 read -r oldInterpreterLine < "$f" || [ "$oldInterpreterLine" ] 78 79 read -r oldPath arg0 args <<< "${oldInterpreterLine:2}" 80 81 if [[ -z "${pathName:-}" ]]; then 82 if [[ -n $strictDeps && $f == "$NIX_STORE"* ]]; then 83 pathName=HOST_PATH 84 else 85 pathName=PATH 86 fi 87 fi 88 89 if [[ "$oldPath" == *"/bin/env" ]]; then 90 if [[ $arg0 == "-S" ]]; then 91 arg0=${args%% *} 92 [[ "$args" == *" "* ]] && args=${args#* } || args= 93 newPath="$(PATH="${!pathName}" type -P "env" || true)" 94 args="-S $(PATH="${!pathName}" type -P "$arg0" || true) $args" 95 96 # Check for unsupported 'env' functionality: 97 # - options: something starting with a '-' besides '-S' 98 # - environment variables: foo=bar 99 elif [[ $arg0 == "-"* || $arg0 == *"="* ]]; then 100 echo "$f: unsupported interpreter directive \"$oldInterpreterLine\" (set dontPatchShebangs=1 and handle shebang patching yourself)" >&2 101 exit 1 102 else 103 newPath="$(PATH="${!pathName}" type -P "$arg0" || true)" 104 fi 105 else 106 if [[ -z $oldPath ]]; then 107 # If no interpreter is specified linux will use /bin/sh. Set 108 # oldpath="/bin/sh" so that we get /nix/store/.../sh. 109 oldPath="/bin/sh" 110 fi 111 112 newPath="$(PATH="${!pathName}" type -P "$(basename "$oldPath")" || true)" 113 114 args="$arg0 $args" 115 fi 116 117 # Strip trailing whitespace introduced when no arguments are present 118 newInterpreterLine="$newPath $args" 119 newInterpreterLine=${newInterpreterLine%${newInterpreterLine##*[![:space:]]}} 120 121 if [[ -n "$oldPath" && ( "$update" == true || "${oldPath:0:${#NIX_STORE}}" != "$NIX_STORE" ) ]]; then 122 if [[ -n "$newPath" && "$newPath" != "$oldPath" ]]; then 123 echo "$f: interpreter directive changed from \"$oldInterpreterLine\" to \"$newInterpreterLine\"" 124 # escape the escape chars so that sed doesn't interpret them 125 escapedInterpreterLine=${newInterpreterLine//\\/\\\\} 126 127 # Preserve times, see: https://github.com/NixOS/nixpkgs/pull/33281 128 timestamp=$(stat --printf "%y" "$f") 129 130 # Manually create temporary file instead of using sed -i 131 # (sed -i on $out/x creates tmpfile /nix/store/x which fails on macos + sandbox) 132 tmpFile=$(mktemp -t patchShebangs.XXXXXXXXXX) 133 sed -e "1 s|.*|#\!$escapedInterpreterLine|" "$f" > "$tmpFile" 134 135 # Make original file writable if it is read-only 136 local restoreReadOnly 137 if [[ ! -w "$f" ]]; then 138 chmod +w "$f" 139 restoreReadOnly=true 140 fi 141 142 # Replace the original file's content with the patched content 143 # (preserving permissions) 144 cat "$tmpFile" > "$f" 145 rm "$tmpFile" 146 if [[ -n "${restoreReadOnly:-}" ]]; then 147 chmod -w "$f" 148 fi 149 150 touch --date "$timestamp" "$f" 151 fi 152 fi 153 done < <(find "$@" -type f -perm -0100 -print0) 154} 155 156patchShebangsAuto () { 157 if [[ -z "${dontPatchShebangs-}" && -e "$prefix" ]]; then 158 159 # Dev output will end up being run on the build platform. An 160 # example case of this is sdl2-config. Otherwise, we can just 161 # use the runtime path (--host). 162 if [[ "$output" != out && "$output" = "$outputDev" ]]; then 163 patchShebangs --build "$prefix" 164 else 165 patchShebangs --host "$prefix" 166 fi 167 fi 168}