about summary refs log tree commit diff
path: root/pkgs/games/build-support
diff options
context:
space:
mode:
authoraszlig <aszlig@redmoonstudios.org>2017-09-13 05:06:32 +0200
committeraszlig <aszlig@redmoonstudios.org>2017-09-13 15:26:28 +0200
commitef6c66560175e4b5a798bd09caa31cd5ebad8127 (patch)
treeffa44ce33da4d3f28c52248076c948e3aee1b63e /pkgs/games/build-support
parent370f8a5e5215e4f1ff34e05b7094bf08eb5cf643 (diff)
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 <aszlig@redmoonstudios.org>
Diffstat (limited to 'pkgs/games/build-support')
-rw-r--r--pkgs/games/build-support/build-game.nix42
-rw-r--r--pkgs/games/build-support/default.nix1
-rw-r--r--pkgs/games/build-support/setup-hooks/auto-patchelf.sh135
3 files changed, 178 insertions, 0 deletions
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)