diff --git a/pkgs/opengl/default.nix b/pkgs/opengl/default.nix index 2b64d73..d79ad43 100644 --- a/pkgs/opengl/default.nix +++ b/pkgs/opengl/default.nix @@ -3,7 +3,7 @@ # Add a setup hook to the mesa_noglu package that automatically adds # a libvglfaker.so dependency to executables that depend on libGL.so. -{ super, lib, buildEnv, makeSetupHook, file, bash }: +{ super, lib, buildEnv, makeSetupHook, file, bash, xorg }: let autoVirtualGLHook = @@ -27,7 +27,7 @@ let makeSetupHook { name = "insert-virtualgl"; deps = [ file virtualglLib ]; - substitutions = { inherit virtualglLib; }; + substitutions = { inherit virtualglLib bash; inherit (xorg) libXext; }; } ./insert-virtualgl.sh; in { diff --git a/pkgs/opengl/insert-virtualgl.sh b/pkgs/opengl/insert-virtualgl.sh index c73b742..3b781e1 100755 --- a/pkgs/opengl/insert-virtualgl.sh +++ b/pkgs/opengl/insert-virtualgl.sh @@ -1,3 +1,10 @@ +# Quote the given arguement +# +VGL_quote() { + printf '%s' "${*@Q}" +} + + # Stream producers # # These produce a stream of escaped new-line separated items @@ -12,16 +19,22 @@ VGL_sourceS() { # Stream the results of find (correctly escapes all filenames) # VGL_findS() { - declare -a options=("$@") file + declare options=("$@") file find "${options[@]}" -print0 | while read -d '' -r file; do VGL_sourceS "$file" done } -# Stream the closure of the list libraries from an elf file +# Stream the list of directly required libraries from an elf file # VGL_elfLibsS() { + patchelf --print-needed "$1" +} + +# Stream the resolved libraries from an elf file +# +VGL_elfLibsResolvedS() { declare file=$1 line ldd "$file" | while read line; do @@ -42,6 +55,16 @@ VGL_elfLibsS() { done } +# Stream the list of rpaths from an elf file +# +VGL_elfRPathS() { + declare file=$1 entry + patchelf --print-rpath "$file" | + while read -d ':' -r entry; do + VGL_sourceS "$entry" + done +} + # Stream transformers @@ -49,30 +72,41 @@ VGL_elfLibsS() { # Take in a set of options and transform a stream of new-line separated items # -# Group all items onto a single line separated by given deliminator (or space) -# -VGL_joinS() { - declare deliminator=${1- } next - if read next; then - printf '%q' "$next" - while read next; do - printf '%s%q' "$deliminator" "$next" - done - printf '\n' - fi -} - # Remove all items for which the given command returns false # -VGL_filterS() { - declare command=("$@") +VGL_testKeepS() { + declare command=$1 next while read next; do - if eval $(printf '%q ' "${command[@]}" "$next"); then + set -- "$next" + if (eval "$command"); then VGL_sourceS "$next" fi done } +# Remove all items which match one of the given values +# +VGL_valuesRemoveS() { + declare -A values + declare value + for value in "$@"; do + values[$value]= + done + while read value; do + [[ ${values[$value]+yes} ]] \ + || VGL_sourceS "$value" + done +} + +# Add addition to end unless test succeeds on at least one +# +VGL_testSuffixNoneS() { + declare command=$1 addition=$2 + eval "declare items=($(cat))" + VGL_sourceS "${items[@]}" + VGL_sourceS "${items[@]}" | VGL_isTestAnyS "$command" \ + || VGL_sourceS "$addition" +} # Stream consumers @@ -82,10 +116,11 @@ VGL_filterS() { # Return true iff given command returns true for some item # -VGL_anyS() { - declare command=("$@") +VGL_isTestAnyS() { + declare command="$1" next while read next; do - if eval $(printf '%q ' "${command[@]}" "$next"); then + set -- "$next" + if (eval "$command"); then return 0 fi done @@ -93,6 +128,19 @@ VGL_anyS() { } +# Return true iff given command returns true for all item +# +VGL_isTestAllS() { + declare command="$1" next + while read next; do + set -- "$next" + if ! (eval "$command"); then + return 1 + fi + done + return 0 +} + # Tests for the filter transformer # @@ -123,33 +171,97 @@ VGL_isLibGL() { +# Update elf files +# + +# Run given stream element on rpath stream and write result to file if differs from original +# +VGL_elfFilterRPathS() { + declare file=$1 command=$2 + eval "declare pathsOld=($(VGL_elfRPathS "$file"))" + eval "declare pathsNew=($(VGL_sourceS "${pathsOld[@]}" | eval "$command"))" + [[ ${pathsNew[@]@Q} = ${pathsOld[@]@Q} ]] \ + || if (( ${#pathsNew[@]} > 0 )); + then ( IFS=:; set -x; patchelf --set-rpath "${pathsNew[*]}" "$file" ) + else ( set -x; patchelf --remove-rpath "$file" ) + fi +} + +# Run given stream element on directly required libraries stream and write result to file if differs from original +# +VGL_elfFilterLibsS() { + declare file=$1 command=$2 + eval "declare libsOld=($(VGL_elfLibsS "$file"))" + eval "declare libsNew=($(VGL_sourceS "${libsOld[@]}" | eval "$command"))" + eval "declare libs=($(VGL_sourceS "${libsOld[@]}" | VGL_valuesRemoveS "${libsNew[@]}"))" + (( ${#libs[@]} < 1 )) \ + || ( eval "set -x; patchelf$(printf ' --remove-needed %q' "${libs[@]}") ${file@Q}") + eval "declare libs=($(VGL_sourceS "${libsNew[@]}" | VGL_valuesRemoveS "${libsOld[@]}"))" + (( ${#libs[@]} < 1 )) \ + || ( eval "set -x; patchelf$(printf ' --add-needed %q' "${libs[@]}") ${file@Q}") +} + +# Patch elf file to be able to use OpenGL properly (not a stream function so can be called in normal way) +# +VGL_patch() { + declare files=("$@") file + for file in "${files[@]}"; do + # glibc 2.3 ld.so supports a --preload option that would make this just a simple wrapper + # + # mv "$file" "${file%/*}/.${file##*/}.vgl" + # cat > "$file" <&2 + cp -a "$file" "${file%/*}/.${file##*/}.vgl-yes" + VGL_elfFilterLibsS "${file%/*}/.${file##*/}.vgl-yes" 'VGL_testSuffixNoneS '"$(VGL_quote '[[ $1 =~ ^(.*/)?libvglfaker.so ]]')"' @virtualglLib@/lib/libvglfaker.so' + # patchelf --add-needed @virtualglLib@/lib/libvglfaker.so "${file%/*}/.${file##*/}.vgl-yes" + + # Non-VGL versions may need fixing to load libXext so NVIDIA binary libs are happy + # + # Ugly, but putting this here instead of with the NVIDIA libs, allows us not to to fix one version for all + + mv "$file" "${file%/*}/.${file##*/}.vgl-no" + VGL_elfFilterLibsS "${file%/*}/.${file##*/}.vgl-no" 'VGL_testSuffixNoneS '"$(VGL_quote '[[ $1 =~ ^(.*/)?libXext.so ]]')"' libXext.so.6' + VGL_elfFilterRPathS "${file%/*}/.${file##*/}.vgl-no" 'VGL_testSuffixNoneS '"$(VGL_quote '[[ $1 = @libXext@/lib ]]')"' @libXext@/lib' + + # Replace with chooser script based on whether VGL_DISPLAY is set or not + cat > "$file" <&2 - + set -x for output in $outputs; do - VGL_findS "${!output}" -type f | VGL_filterS VGL_isElfBin | { - declare file - while read file; do - VGL_elfLibsS "$file" | - if VGL_anyS VGL_isLibGL; then - VGL_sourceS "$file" - fi - done - } | { - declare file - while read file; do - printf 'patchelf --add-needed @virtualglLib@/lib/libvglfaker.so %q\n' "$file" >&2 - patchelf --add-needed @virtualglLib@/lib/libvglfaker.so "$file" - done - } + VGL_findS "${!output}" -type f \ + | VGL_testKeepS 'VGL_isElfBin "$1"' \ + | VGL_testKeepS 'VGL_elfLibsResolvedS "$1" | VGL_isTestAnyS '"$(VGL_quote 'VGL_isLibGL "$1"')" \ + | VGL_isTestAllS 'VGL_patch "$1"' done eval "$opts"