#!/usr/bin/env bash
#
# Premise:
# Shared libraries (Qt, airmap, etc) on Linux are built without knowing where
# they will be installed to; they assume they will be installed to the system.
#
# QGC does not install to the system. Instead it copies them to `Qt/libs`. The
# libraries need to have their rpath set correctly to ensure that the built
# application and its libraries are found at runtime. Without this step then the
# libraries won't be found. You might not notice it if you have the libraries
# installed in your system. A lot of systems will have the Qt libs installed,
# but they might be a different version. It "might" work, it might cause subtle
# bugs, or it might not work at all.
#
# It's possible there's no current rpath for a particular library. It's also
# possible that the library has other dependencies in its existing rpath. So
# updating the rpath is non-trivial and it's a real shame that qmake doesn't
# do this for us.
#
# To patch the libraries `readelf` and `patchelf` tools are used.
#
# If the libraries' rpath isn't set correctly then LD_LIBRARY_PATH would need
# to be used, like such:
# LD_LIBRARY_PATH=./Qt/libs ./QGroundControl
#

# -e: stop on error
# -u: undefined variable use is an error
# -o pipefail: if any part of a pipeline fails, then the whole pipeline fails.
set -euo pipefail

# To set these arguments, set them as an environment variable. For example:
# SEARCHDIR=/opt/qgc-deploy/Qt RPATHDIR=/opt/qgc-deploy/Qt/libs ./linux-post-link.sh
: "${SEARCHDIR:=./Qt}"
: "${RPATHDIR:="${SEARCHDIR}/libs"}"

# find:
#    type f (files)
#    that end with '.so'
#    or that end with '.so.5'
#    and are executable
#    silence stderr (find will complain if it doesn't have permission to traverse)
find "${SEARCHDIR}" \
    -type f \
    -iname '*.so' \
    -o -iname '*.so.5' \
    -executable \
    2>/dev/null |
while IFS='' read -r library; do

    # readelf is expensive, so keep track of updates with a timestamp file
    if [ ! -e "$library.stamp" ] || [ "$library" -nt "$library.stamp" ]; then

        # Get the library's current RPATH (RUNPATH)
        # Example output of `readelf -d ./build/build-qgroundcontrol-Desktop_Qt_5_15_2_GCC_64bit-Debug/staging/QGroundControl`:
        #  0x000000000000001d (RUNPATH)            Library runpath: [$ORIGIN/Qt/libs:/home/kbennett/storage/Qt/5.15.2/gcc_64/lib]
        #
        # It's possible there's no current rpath for a particular library, so turn
        # off pipefail to avoid grep causing it to die.
        # If you find a better way to do this, please fix.
        set +o pipefail
        current_rpath="$(
            # read the library, parsing its header
            # search for the RUNPATH field
            # filter out the human-readable text to leave only the RUNPATH value
            readelf -d "${library}" |
            grep -P '^ 0x[0-9a-f]+ +\(RUNPATH\) ' |
            sed -r 's/^ 0x[0-9a-f]+ +\(RUNPATH\) +Library runpath: \[(.*)\]$/\1/g'
        )"
        set -o pipefail

        # Get the directory containing the library
        library_dir="$(dirname "${library}")"

        # Get the relative path from the library's directory to the Qt/libs directory.
        our_rpath="$(realpath --relative-to "${library_dir}" "${RPATHDIR}")"

        # Calculate a new rpath with our library's rpath prefixed.
        # Note: '$ORIGIN' must not be expanded by the shell!
        # shellcheck disable=SC2016
        new_rpath='$ORIGIN/'"${our_rpath}"

        # If the library already had an rpath, then prefix ours to it.
        if [ -n "${current_rpath}" ]; then
            new_rpath="${new_rpath}:${current_rpath}"
        fi

        # patch the library's rpath
        patchelf --set-rpath "${new_rpath}" "${library}"

        touch "$library.stamp"
    fi
done