1fixupOutputHooks+=(_patchPpdFileCommands4fixupOutputHooks)
2
3
4
5# Install a hook for the `fixupPhase`:
6# If the variable `ppdFileCommands` contains a list of
7# executable names, the hook calls `patchPpdFileCommands`
8# on each output's `/share/cups/model` and `/share/ppds`
9# directories in order to replace calls to those executables.
10
11_patchPpdFileCommands4fixupOutputHooks () {
12 [[ -n $ppdFileCommands ]] || return 0
13 if [[ -d $prefix/share/cups/model ]]; then
14 patchPpdFileCommands "$prefix/share/cups/model" $ppdFileCommands
15 fi
16 if [[ -d $prefix/share/ppds ]]; then
17 patchPpdFileCommands "$prefix/share/ppds" $ppdFileCommands
18 fi
19}
20
21
22
23# patchPpdFileCommands PPD-ROOT PROGNAME...
24#
25# Look for ppd files in the directory PPD-ROOT.
26# Descend into subdirectories, even if they are symlinks.
27# However, ignore ppd files that don't belong to the same
28# prefix ($NIX_STORE/$package_name) as PPD-ROOT-DIR does,
29# to avoid stepping into other package's directories.
30# ppd files may be gzipped; if the are,
31# uncompress them, later recompress them.
32# Skip symlinks to ppd files.
33# PPD-ROOT may also be a single ppd file.
34#
35# Look for the PROGNAME executable in outputs and `buildInputs`,
36# then look for PROGNAME invocations in the ppd files,
37# without path or with common paths like `/usr/bin/$PROGNAME`.
38# Replace those invocations with an absolute path to the
39# corresponding executable from the outputs or `buildInputs`.
40# Executables are searched where CUPS would search them,
41# i.e., in `/bin` and `/lib/cups/filter`.
42#
43# As soon as an executable's path is replaced as
44# described above, the package containing the binary
45# is added to the list of propagated build inputs.
46# This ensures the executable's package is still
47# recognized as runtime dependency of the ppd file
48# even if the ppd file is compressed lateron.
49#
50# PROGNAME may not contain spaces or tabs.
51# The function will also likely fail or produce
52# broken results if PROGNAME contains characters that
53# require shell or regex escaping (e.g. a backslash).
54
55patchPpdFileCommands () {
56
57 local bin binnew binold binoldgrep cupspath path ppdroot ppdrootprefix
58
59 # we will store some temporary data here
60 pushd "$(mktemp -d --tmpdir patch-ppd-file-commands.XXXX)"
61
62 # remember the ppd root path
63 [[ "$1" == $NIX_STORE/* ]] # ensure it's a store directory
64 ppdroot=$1
65 shift # now "$@" is the list of binaries
66 ppdrootprefix=${ppdroot%"/${ppdroot#"$NIX_STORE"/*/}"}
67
68 # create `cupspath` (where we should look for binaries),
69 # with these priorities
70 # * outputs of current build before buildInputs
71 # * `/lib/cups/filter' before `/bin`
72 # * add HOST_PATH at end, so we don't miss anything
73 for path in $(getAllOutputNames); do
74 addToSearchPath cupspath "${!path}/lib/cups/filter"
75 addToSearchPath cupspath "${!path}/bin"
76 done
77 for path in ${pkgsHostTarget+"${pkgsHostTarget[@]}"}; do
78 addToSearchPath cupspath "$path/lib/cups/filter"
79 addToSearchPath cupspath "$path/bin"
80 done
81 while read -r -d : path; do
82 addToSearchPath cupspath "$path"
83 done <<< "${HOST_PATH:+"${HOST_PATH}:"}"
84
85 # create list of compressed ppd files
86 # so we can recompress them later
87 find -L "$ppdroot" -type f -iname '*.ppd.gz' '!' -xtype l -print0 > gzipped
88
89 # decompress gzipped ppd files
90 echo "patchPpdFileCommands: decompressing $(grep -cz '^' < gzipped) gzipped ppd file(s) in $ppdroot"
91 xargs -0r -n 64 -P "$NIX_BUILD_CORES" gunzip < gzipped
92
93 # create list of all ppd files to be checked
94 find -L "$ppdroot" -type f -iname '*.ppd' '!' -xtype l -print0 > ppds
95
96 for bin in "$@"; do
97
98 # discover new path
99 binnew=$(PATH=$cupspath '@which@/bin/which' "$bin")
100 echo "patchPpdFileCommands: located binary $binnew"
101
102 # for each binary, we look for the name itself, but
103 # also for a couple of common paths that might be used
104 for binold in {/usr,}/{lib/cups/filter,sbin,bin}/"$bin" "$bin"; do
105
106 # escape regex characters in the old command string
107 binoldgrep=$(sed 's,[]$.*[\^],\\&,g' <<< "$binold")
108 # ...and surround old command with some regex
109 # that singles out shell command invocations
110 # to avoid replacing other strings that might contain the
111 # command name by accident (like "perl" in "perl-script")
112 binoldgrep='\(^\|[;&| '$'\t''"`(]\)'"$binoldgrep"'\($\|[);&| '$'\t''"`<>]\)'
113 # this string is used to *quickly* filter out
114 # unaffected files before the (slower) awk script runs;
115 # note that a similar regex is build in the awk script;
116 # if `binoldgrep` is changed, the awk script should also be checked
117
118 # create list of likely affected files
119 # (might yield exit status != 0 if there are no matches)
120 xargs -0r grep -lZ "$binoldgrep" < ppds > ppds-to-patch || true
121
122 echo "patchPpdFileCommands: $(grep -cz '^' < ppds-to-patch) ppd file(s) contain $binold"
123
124 # actually patch affected ppd files with awk;
125 # this takes some time but can be parallelized;
126 # speed up with LC_ALL=C, https://stackoverflow.com/a/33850386
127 LC_ALL=C xargs -0r -n 64 -P "$NIX_BUILD_CORES" \
128 awk -i inplace -v old="${binold//\\/\\\\}" -v new="${binnew//\\/\\\\}" -f "@awkscript@" \
129 < ppds-to-patch
130
131 done
132
133 # create list of affected files
134 xargs -0r grep -lZF "$binnew" < ppds > patched-ppds || true
135
136 echo "patchPpdFileCommands: $(grep -cz '^' < patched-ppds) ppd file(s) patched with $binnew"
137
138 # if the new command is contained in a file,
139 # remember the new path so we can add it to
140 # the list of propagated dependencies later
141 if [[ -s patched-ppds ]]; then
142 printf '%s\0' "${binnew%"/${binnew#"${NIX_STORE}"/*/}"}" >> dependencies
143 fi
144
145 done
146
147 # recompress ppd files that have been decompressed before
148 echo "patchPpdFileCommands: recompressing $(grep -cz '^' < gzipped) gzipped ppd file(s)"
149 # we can't just hand over the paths of the uncompressed files
150 # to gzip as it would add the lower-cased extension ".gz"
151 # even for files where the original was named ".GZ"
152 xargs -0r -n 1 -P "$NIX_BUILD_CORES" \
153 "$SHELL" -c 'gzip -9nS ".${0##*.}" "${0%.*}"' \
154 < gzipped
155
156 # enlist dependencies for propagation;
157 # this is needed in case ppd files are compressed later
158 # (Nix won't find dependency paths in compressed files)
159 if [[ -s dependencies ]]; then
160
161 # weed out duplicates from the dependency list first
162 sort -zu dependencies > sorted-dependencies
163
164 mkdir -p "$ppdrootprefix/nix-support"
165 while IFS= read -r -d '' path; do
166 printWords "$path" >> "$ppdrootprefix/nix-support/propagated-build-inputs"
167 # stdenv writes it's own `propagated-build-inputs`,
168 # based on the variable `propagatedBuildInputs`,
169 # but only to one output (`outputDev`).
170 # So we also add our dependencies to that variable.
171 # If our file survives as written above, great!
172 # If stdenv overwrits it,
173 # our dependencies will still be added to the file.
174 # The end result might contain too many
175 # propagated dependencies for multi-output packages,
176 # but never a broken package.
177 appendToVar propagatedBuildInputs "$path"
178 done < sorted-dependencies
179 fi
180
181 popd
182
183}