From ef6c66560175e4b5a798bd09caa31cd5ebad8127 Mon Sep 17 00:00:00 2001 From: aszlig Date: Wed, 13 Sep 2017 05:06:32 +0200 Subject: pkgs/games: Add a new buildGame function The main functionality for this function is to gather missing dependencies in ELF executables and shared libraries and using patchelf to set the right RPATH. All of the dependencies are searched based on what we have in one of the buildInputs variables, so all we need to do is list them in there. One thing that's still left to solve is adding libraries to the RPATH which are only required at runtime. An example for this would be the pulseaudio library. Signed-off-by: aszlig --- pkgs/games/build-support/build-game.nix | 42 +++++++ pkgs/games/build-support/default.nix | 1 + .../build-support/setup-hooks/auto-patchelf.sh | 135 +++++++++++++++++++++ 3 files changed, 178 insertions(+) create mode 100644 pkgs/games/build-support/build-game.nix create mode 100644 pkgs/games/build-support/setup-hooks/auto-patchelf.sh (limited to 'pkgs/games/build-support') diff --git a/pkgs/games/build-support/build-game.nix b/pkgs/games/build-support/build-game.nix new file mode 100644 index 00000000..2ada5eb7 --- /dev/null +++ b/pkgs/games/build-support/build-game.nix @@ -0,0 +1,42 @@ +{ stdenv, lib, file }: + +{ buildInputs ? [] +, nativeBuildInputs ? [] +, installCheckPhase ? "" +, ... +}@attrs: + +stdenv.mkDerivation ({ + buildInputs = [ stdenv.cc.cc ] ++ buildInputs; + + nativeBuildInputs = [ + file ./setup-hooks/auto-patchelf.sh + ] ++ nativeBuildInputs; + + doInstallCheck = true; + + installCheckPhase = '' + runHook preInstallCheck + + echo "checking dependencies for libraries and executables" >&2 + + local errors="$( + IFS=$'\n' + for elf in $(findElfs "$prefix"); do checkElfDep "$elf"; done + )" + + if [ -n "$errors" ]; then + echo "$errors" >&2 + exit 1 + fi + + ${installCheckPhase} + + runHook postInstallCheck + ''; + + dontStrip = true; + dontPatchELF = true; +} // removeAttrs attrs [ + "buildInputs" "nativeBuildInputs" "installCheckPhase" +]) diff --git a/pkgs/games/build-support/default.nix b/pkgs/games/build-support/default.nix index 8b6b466d..23e03c3b 100644 --- a/pkgs/games/build-support/default.nix +++ b/pkgs/games/build-support/default.nix @@ -1,5 +1,6 @@ { callPackage, ... }: { + buildGame = callPackage ./build-game.nix {}; buildUnity = callPackage ./build-unity.nix {}; } diff --git a/pkgs/games/build-support/setup-hooks/auto-patchelf.sh b/pkgs/games/build-support/setup-hooks/auto-patchelf.sh new file mode 100644 index 00000000..b0982a45 --- /dev/null +++ b/pkgs/games/build-support/setup-hooks/auto-patchelf.sh @@ -0,0 +1,135 @@ +declare -a autoPatchelfLibs + +gatherLibraries() { + autoPatchelfLibs+=("$1/lib") +} + +envHooks+=(gatherLibraries) + +isExecutable() { + [ "$(file -b -N --mime-type "$1")" = application/x-executable ] +} + +findElfs() { + find "$1" -type f -exec "$SHELL" -c ' + while [ -n "$1" ]; do + mimeType="$(file -b -N --mime-type "$1")" + if [ "$mimeType" = application/x-executable \ + -o "$mimeType" = application/x-sharedlib ]; then + echo "$1" + fi + shift + done + ' -- {} + +} + +declare -a cachedDependencies + +addToDepCache() { + local existing + for existing in "${cachedDependencies[@]}"; do + if [ "$existing" = "$1" ]; then return; fi + done + cachedDependencies+=("$1") +} + +declare -gi depCacheInitialised=0 +declare -gi doneRecursiveSearch=0 +declare -g foundDependency + +getDepsFromSo() { + ldd "$1" 2> /dev/null | sed -n -e 's/[^=]*=> *\(.\+\) \+([^)]*)$/\1/p' +} + +checkElfDep() { + local errors ldout="$(ldd "$1")" + if errors="$(echo "$ldout" | grep -F "not found")"; then + echo -e "Library dependencies missing for $1:\n$errors" + fi +} + +populateCacheWithRecursiveDeps() { + local so found foundso + for so in "${cachedDependencies[@]}"; do + local IFS=$'\n' + for found in $(getDepsFromSo "$so"); do + local libdir="${found%/*}" + local base="${found##*/}" + local soname="${base%.so*}" + for foundso in "${found%/*}/$soname".so*; do + addToDepCache "$foundso" + done + done + done +} + +findDependency() { + local filename="$1" + local lib dep + + if [ $depCacheInitialised -eq 0 ]; then + for lib in "${autoPatchelfLibs[@]}"; do + for so in "$lib/"*.so*; do addToDepCache "$so"; done + done + depCacheInitialised=1 + fi + + for dep in "${cachedDependencies[@]}"; do + if [ "$filename" = "${dep##*/}" ]; then + foundDependency="$dep" + return 0 + fi + done + + if [ $doneRecursiveSearch -eq 0 ]; then + populateCacheWithRecursiveDeps + doneRecursiveSearch=1 + findDependency "$filename" || return 1 + return 0 + fi + return 1 +} + +autoPatchelfFile() { + local toPatch="$1" + local dep + + local interpreter="$(cat $NIX_CC/nix-support/dynamic-linker)" + if isExecutable "$toPatch"; then + patchelf --set-interpreter "$interpreter" "$toPatch" + fi + + patchelf --remove-rpath "$toPatch" + + local rpath="" + local missing="$( + ldd "$toPatch" | sed -n -e 's/^[\t ]*\([^ ]\+\) => not found.*/\1/p' + )" + local IFS=$'\n' + for dep in $missing; do + echo -n "searching for dependency $dep..." >&2 + if findDependency "$dep"; then + rpath="$rpath${rpath:+:}${foundDependency%/*}" + echo " found: $foundDependency" >&2 + else + echo " not found" >&2 + fi + done + + if [ -n "$rpath" ]; then + echo "setting RPATH of $toPatch to $rpath" >&2 + patchelf --set-rpath "$rpath" "$toPatch" + fi +} + +autoPatchelf() { + echo "automatically fixing dependencies for ELF files" >&2 + + local i IFS=$'\n' + cachedDependencies+=( + $(find "$out" \! -type d \( -name '*.so' -o -name '*.so.*' \)) + ) + for i in $(findElfs "$prefix"); do autoPatchelfFile "$i"; done +} + +postInstallHooks+=(autoPatchelf) -- cgit 1.4.1