summary refs log tree commit diff
path: root/pkgs/build-support
diff options
context:
space:
mode:
authorgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>2022-11-07 00:03:30 +0000
committerGitHub <noreply@github.com>2022-11-07 00:03:30 +0000
commite51ba60580c4c6b612edfae0c163e0ff06e225df (patch)
treedcd8888a677b83282f240e9e8551c917d9c05664 /pkgs/build-support
parent6e2df29235eaec112d3ff48759e13918aba4dcd6 (diff)
parent8eebbcbae50b7b26579589adef4977293aae72ad (diff)
Merge master into staging-next
Diffstat (limited to 'pkgs/build-support')
-rw-r--r--pkgs/build-support/setup-hooks/patch-ppd-files/default.nix25
-rw-r--r--pkgs/build-support/setup-hooks/patch-ppd-files/patch-ppd-hook.sh183
-rw-r--r--pkgs/build-support/setup-hooks/patch-ppd-files/patch-ppd-lines.awk50
-rw-r--r--pkgs/build-support/setup-hooks/patch-ppd-files/test.nix40
-rw-r--r--pkgs/build-support/setup-hooks/patch-ppd-files/test.ppd22
5 files changed, 320 insertions, 0 deletions
diff --git a/pkgs/build-support/setup-hooks/patch-ppd-files/default.nix b/pkgs/build-support/setup-hooks/patch-ppd-files/default.nix
new file mode 100644
index 0000000000000..b3c7b19f37325
--- /dev/null
+++ b/pkgs/build-support/setup-hooks/patch-ppd-files/default.nix
@@ -0,0 +1,25 @@
+{ lib
+, makeSetupHook
+, which
+, callPackage
+}:
+
+let
+  patchPpdFilesHook = makeSetupHook
+    {
+      name = "patch-ppd-files";
+      substitutions.which = lib.attrsets.getBin which;
+      substitutions.awkscript = ./patch-ppd-lines.awk;
+    }
+    ./patch-ppd-hook.sh;
+in
+
+patchPpdFilesHook.overrideAttrs (
+  lib.trivial.flip
+  lib.attrsets.recursiveUpdate
+  {
+    passthru.tests.test = callPackage ./test.nix {};
+    meta.description = "setup hook to patch executable paths in ppd files";
+    meta.maintainers = [ lib.maintainers.yarny ];
+  }
+)
diff --git a/pkgs/build-support/setup-hooks/patch-ppd-files/patch-ppd-hook.sh b/pkgs/build-support/setup-hooks/patch-ppd-files/patch-ppd-hook.sh
new file mode 100644
index 0000000000000..a450ecd7f963a
--- /dev/null
+++ b/pkgs/build-support/setup-hooks/patch-ppd-files/patch-ppd-hook.sh
@@ -0,0 +1,183 @@
+fixupOutputHooks+=(_patchPpdFileCommands4fixupOutputHooks)
+
+
+
+# Install a hook for the `fixupPhase`:
+# If the variable `ppdFileCommands` contains a list of
+# executable names, the hook calls `patchPpdFileCommands`
+# on each output's `/share/cups/model` and `/share/ppds`
+# directories in order to replace calls to those executables.
+
+_patchPpdFileCommands4fixupOutputHooks () {
+    [[ -n $ppdFileCommands ]]  ||  return 0
+    if [[ -d $prefix/share/cups/model ]]; then
+        patchPpdFileCommands "$prefix/share/cups/model" $ppdFileCommands
+    fi
+    if [[ -d $prefix/share/ppds ]]; then
+        patchPpdFileCommands "$prefix/share/ppds" $ppdFileCommands
+    fi
+}
+
+
+
+# patchPpdFileCommands PPD-ROOT PROGNAME...
+#
+# Look for ppd files in the directory PPD-ROOT.
+# Descend into subdirectories, even if they are symlinks.
+# However, ignore ppd files that don't belong to the same
+# prefix ($NIX_STORE/$package_name) as PPD-ROOT-DIR does,
+# to avoid stepping into other package's directories.
+# ppd files may be gzipped; if the are,
+# uncompress them, later recompress them.
+# Skip symlinks to ppd files.
+# PPD-ROOT may also be a single ppd file.
+#
+# Look for the PROGNAME executable in outputs and `buildInputs`,
+# then look for PROGNAME invocations in the ppd files,
+# without path or with common paths like `/usr/bin/$PROGNAME`.
+# Replace those invocations with an absolute path to the
+# corresponding executable from the outputs or `buildInputs`.
+# Executables are searched where CUPS would search them,
+# i.e., in `/bin` and `/lib/cups/filter`.
+#
+# As soon as an executable's path is replaced as
+# described above, the package containing the binary
+# is added to the list of propagated build inputs.
+# This ensures the executable's package is still
+# recognized as runtime dependency of the ppd file
+# even if the ppd file is compressed lateron.
+#
+# PROGNAME may not contain spaces or tabs.
+# The function will also likely fail or produce
+# broken results if PROGNAME contains characters that
+# require shell or regex escaping (e.g. a backslash).
+
+patchPpdFileCommands () {
+
+    local bin binnew binold binoldgrep cupspath path ppdroot ppdrootprefix
+
+    # we will store some temporary data here
+    pushd "$(mktemp -d --tmpdir patch-ppd-file-commands.XXXX)"
+
+    # remember the ppd root path
+    [[ "$1" == $NIX_STORE/* ]]  # ensure it's a store directory
+    ppdroot=$1
+    shift  # now "$@" is the list of binaries
+    ppdrootprefix=${ppdroot%"/${ppdroot#"$NIX_STORE"/*/}"}
+
+    # create `cupspath` (where we should look for binaries),
+    # with these priorities
+    # * outputs of current build before buildInputs
+    # * `/lib/cups/filter' before `/bin`
+    # * add HOST_PATH at end, so we don't miss anything
+    for path in $outputs; do
+        addToSearchPath cupspath "${!path}/lib/cups/filter"
+        addToSearchPath cupspath "${!path}/bin"
+    done
+    for path in ${pkgsHostTarget+"${pkgsHostTarget[@]}"}; do
+        addToSearchPath cupspath "$path/lib/cups/filter"
+        addToSearchPath cupspath "$path/bin"
+    done
+    while read -r -d : path; do
+        addToSearchPath cupspath "$path"
+    done  <<< "${HOST_PATH:+"${HOST_PATH}:"}"
+
+    # create list of compressed ppd files
+    # so we can recompress them later
+    find -L "$ppdroot" -type f -iname '*.ppd.gz' '!' -xtype l -print0  > gzipped
+
+    # decompress gzipped ppd files
+    echo "patchPpdFileCommands: decompressing $(grep -cz '^' < gzipped) gzipped ppd file(s) in $ppdroot"
+    xargs -0r -n 64 -P "$NIX_BUILD_CORES"  gunzip  < gzipped
+
+    # create list of all ppd files to be checked
+    find -L "$ppdroot" -type f -iname '*.ppd' '!' -xtype l -print0  > ppds
+
+    for bin in "$@"; do
+
+        # discover new path
+        binnew=$(PATH=$cupspath '@which@/bin/which' "$bin")
+        echo "patchPpdFileCommands: located binary $binnew"
+
+        # for each binary, we look for the name itself, but
+        # also for a couple of common paths that might be used
+        for binold in {/usr,}/{lib/cups/filter,sbin,bin}/"$bin" "$bin"; do
+
+            # escape regex characters in the old command string
+            binoldgrep=$(sed 's,[]$.*[\^],\\&,g' <<< "$binold")
+            # ...and surround old command with some regex
+            # that singles out shell command invocations
+            # to avoid replacing other strings that might contain the
+            # command name by accident (like "perl" in "perl-script")
+            binoldgrep='\(^\|[;&| '$'\t''"`(]\)'"$binoldgrep"'\($\|[);&| '$'\t''"`<>]\)'
+            # this string is used to *quickly* filter out
+            # unaffected files before the (slower) awk script runs;
+            # note that a similar regex is build in the awk script;
+            # if `binoldgrep` is changed, the awk script should also be checked
+
+            # create list of likely affected files
+            # (might yield exit status != 0 if there are no matches)
+            xargs -0r  grep -lZ "$binoldgrep"  < ppds  > ppds-to-patch  ||  true
+
+            echo "patchPpdFileCommands: $(grep -cz '^' < ppds-to-patch) ppd file(s) contain $binold"
+
+            # actually patch affected ppd files with awk;
+            # this takes some time but can be parallelized;
+            # speed up with LC_ALL=C, https://stackoverflow.com/a/33850386
+            LC_ALL=C xargs -0r -n 64 -P "$NIX_BUILD_CORES"  \
+              awk -i inplace -v old="${binold//\\/\\\\}" -v new="${binnew//\\/\\\\}" -f "@awkscript@"  \
+              < ppds-to-patch
+
+        done
+
+        # create list of affected files
+        xargs -0r  grep -lZF "$binnew"  < ppds  > patched-ppds  ||  true
+
+        echo "patchPpdFileCommands: $(grep -cz '^' < patched-ppds) ppd file(s) patched with $binnew"
+
+        # if the new command is contained in a file,
+        # remember the new path so we can add it to
+        # the list of propagated dependencies later
+        if [[ -s patched-ppds ]]; then
+            printf '%s\0' "${binnew%"/${binnew#"${NIX_STORE}"/*/}"}"  >> dependencies
+        fi
+
+    done
+
+    # recompress ppd files that have been decompressed before
+    echo "patchPpdFileCommands: recompressing $(grep -cz '^' < gzipped) gzipped ppd file(s)"
+    # we can't just hand over the paths of the uncompressed files
+    # to gzip as it would add the lower-cased extension ".gz"
+    # even for files where the original was named ".GZ"
+    xargs -0r -n 1 -P "$NIX_BUILD_CORES"  \
+      "$SHELL" -c 'gzip -9nS ".${0##*.}" "${0%.*}"'  \
+      < gzipped
+
+    # enlist dependencies for propagation;
+    # this is needed in case ppd files are compressed later
+    # (Nix won't find dependency paths in compressed files)
+    if [[ -s dependencies ]]; then
+
+        # weed out duplicates from the dependency list first
+        sort -zu dependencies  > sorted-dependencies
+
+        mkdir -p "$ppdrootprefix/nix-support"
+        while IFS= read -r -d '' path; do
+            printWords "$path" >> "$ppdrootprefix/nix-support/propagated-build-inputs"
+            # stdenv writes it's own `propagated-build-inputs`,
+            # based on the variable `propagatedBuildInputs`,
+            # but only to one output (`outputDev`).
+            # So we also add our dependencies to that variable.
+            # If our file survives as written above, great!
+            # If stdenv overwrits it,
+            # our dependencies will still be added to the file.
+            # The end result might contain too many
+            # propagated dependencies for multi-output packages,
+            # but never a broken package.
+            propagatedBuildInputs+=("$path")
+        done  < sorted-dependencies
+    fi
+
+    popd
+
+}
diff --git a/pkgs/build-support/setup-hooks/patch-ppd-files/patch-ppd-lines.awk b/pkgs/build-support/setup-hooks/patch-ppd-files/patch-ppd-lines.awk
new file mode 100644
index 0000000000000..ddb9171fff32e
--- /dev/null
+++ b/pkgs/build-support/setup-hooks/patch-ppd-files/patch-ppd-lines.awk
@@ -0,0 +1,50 @@
+BEGIN {
+
+  # ppd file keys are separated from their values by a colon,
+  # but "options" may reside between the key name and the colon;
+  # options are separated from the key by spaces
+  # (we also permit tabs to be on the safe side)
+  FS = "[: \t]";
+
+  # escape regex characters in the old and new command strings
+  gsub(/[]\\.^$(){}|*+?[]/, "\\\\&", old);
+  gsub(/\\/, "\\\\&", new);
+  # ...and surround old command with some regex
+  # that singles out shell command invocations
+  # to avoid replacing other strings that might contain the
+  # command name by accident (like "perl" in "perl-script")
+  new = "\\1" new "\\2";
+  old = "(^|[;&| \\t\"`(])" old "($|[);&| \\t\"`<>])";
+  # note that a similar regex is build in the shell script to
+  # filter out unaffected files before this awk script is called;
+  # if the regex here is changed, the shell script should also be checked
+
+  # list of PPD keys that contain executable names or scripts, see
+  # https://refspecs.linuxfoundation.org/LSB_4.0.0/LSB-Printing/LSB-Printing/ppdext.html
+  # https://www.cups.org/doc/spec-ppd.html
+  cmds["*APAutoSetupTool"] = "";
+  cmds["*APPrinterLowInkTool"] = "";
+  cmds["*FoomaticRIPCommandLine"] = "";
+  cmds["*FoomaticRIPPostPipe"] = "";
+  cmds["*cupsFilter"] = "";
+  cmds["*cupsFilter2"] = "";
+  cmds["*cupsPreFilter"] = "";
+
+}
+
+# since comments always start with "*%",
+# this mechanism also properly recognizes (and ignores) them
+
+{
+
+  # if the current line starts a new key,
+  # check if it is a command-containing key;
+  # also reset the `isCmd` flag if a new file begins
+  if ($0 ~ /^\*/ || FNR == 1)  { isCmd = ($1 in cmds) }
+
+  # replace commands if the current keys might contain commands
+  if (isCmd)  { $0 = gensub(old, new, "g") }
+
+  print
+
+}
diff --git a/pkgs/build-support/setup-hooks/patch-ppd-files/test.nix b/pkgs/build-support/setup-hooks/patch-ppd-files/test.nix
new file mode 100644
index 0000000000000..4f2996b235105
--- /dev/null
+++ b/pkgs/build-support/setup-hooks/patch-ppd-files/test.nix
@@ -0,0 +1,40 @@
+{ substituteAll
+, diffutils
+, stdenv
+, patchPpdFilesHook
+}:
+
+let
+  input = substituteAll {
+    src = ./test.ppd;
+    keep = "cmp";
+    patch = "cmp";
+    pathkeep = "/bin/cmp";
+    pathpatch = "/bin/cmp";
+  };
+
+  output = substituteAll {
+    src = ./test.ppd;
+    keep = "cmp";
+    patch = "${diffutils}/bin/cmp";
+    pathkeep = "/bin/cmp";
+    pathpatch = "${diffutils}/bin/cmp";
+  };
+in
+
+stdenv.mkDerivation {
+  name = "${patchPpdFilesHook.name}-test";
+  buildInputs = [ diffutils ];
+  nativeBuildInputs = [ diffutils patchPpdFilesHook ];
+  dontUnpack = true;
+  dontInstall = true;
+  ppdFileCommands = [ "cmp" ];
+  preFixup = ''
+    install -D "${input}" "${placeholder "out"}/share/cups/model/test.ppd"
+    install -D "${input}" "${placeholder "out"}/share/ppds/test.ppd"
+  '';
+  postFixup = ''
+    diff --color --report-identical-files "${output}" "${placeholder "out"}/share/cups/model/test.ppd"
+    diff --color --report-identical-files "${output}" "${placeholder "out"}/share/ppds/test.ppd"
+  '';
+}
diff --git a/pkgs/build-support/setup-hooks/patch-ppd-files/test.ppd b/pkgs/build-support/setup-hooks/patch-ppd-files/test.ppd
new file mode 100644
index 0000000000000..d0ca11ccfe6dd
--- /dev/null
+++ b/pkgs/build-support/setup-hooks/patch-ppd-files/test.ppd
@@ -0,0 +1,22 @@
+*% This comment: might look like a command @keep@
+*% but it should be left untouched
+*SomeKey: do not replace this @keep@
+*APAutoSetupTool: do replace this @patch@
+*FoomaticRIPCommandLine: "patch also  @patch@
+in a multi-line command @patch@
+and another line @patch@
+*SomeKey: "stop patching on new non-command key @keep@
+and remember the key in the next line @keep@"
+*cupsFilter option: recognize keys with options @patch@
+*cupsFilter : handle strange spacing;@patch@
+*cupsFilter	: handle tabulator	@patch@
+*cupsFilter: patch common paths @pathpatch@
+*cupsFilter: patch quoted commands "@patch@"
+*cupsFilter: patch commands in subshell (@patch@)
+*cupsFilter: patch commands in subshell `@pathpatch@`
+*cupsFilter: keep uncommon paths /fancy/@pathkeep@
+*cupsFilter: keep entangled commands-@keep@
+*cupsFilter: keep entangled commands\@keep@
+*cupsFilter: keep entangled commands @keep@()
+*cupsFilter: keep entangled commands @pathkeep@-cmd
+*%cupsFilter: This comment should also be left as is @pathkeep@