#!/usr/bin/env sh

# See hat @rpath is https://blog.krzyzanowskim.com/2018/12/05/rpath-what/ .

# This script replaces the occurrences of @rpath/ with the content of $(pwd)
# in all dylib occurrences in the directory in which the script is being run.

if [ "x${GETOPT}" = "x" ]; then
	GETOPT="getopt"
fi

USAGE="Usage:\n\t$0 [options] [directory]\n\n"
USAGE="${USAGE}Supported options:\n"
USAGE="${USAGE}\t-h, --help\n\t\tPrints help message.\n"

if ! "${GETOPT}" -l test: -u -o t: -- --test test > /dev/null; then
	echo "The default getopt does not support long options." >&2
	echo "You may provide such getopt version via the GETOPT variable e.g.:" >&2
	echo "GETOPT=/opt/local/bin/getopt $0" >&2
	exit 1
fi

# Parse rest of command line
set -- $("${GETOPT}" -l help -u -o h -- "$@")
if [ $# -lt 1 ]; then
	echo -e ${USAGE} >&2
	exit 1
fi

while [ $# -gt 0 ]; do
	OPTION="$1"
	PARAM="$2"
	case "${OPTION}" in
	-h|--help)
		echo -e ${USAGE}
		exit 0
		;;
	--)
		shift
		break
		;;
	-*|*)
		echo "Unknown option '${OPTION}'." >&2
		echo -e ${USAGE} >&2
		exit 1
		;;
	esac
	unset OPTION
	unset PARAM
	shift
done
# Remaining DIR.
DIR="$1"
if [ "x${DIR}" = "x" ]; then
	# No directory supplied. Running on current directory.
	DIR=$(pwd)
fi
shift
if [ $# -gt 0 ]; then
	echo -e "Unknown options: $@" >&2
	echo -en ${USAGE} >&2
	exit 1
fi

replace_id () {
	local NEW_ID="$1"
	local DYLIB="$2"

	echo "'${NEW_ID}' for '${DYLIB}'"

	echo install_name_tool -id "${NEW_ID}" "${DYLIB}"
	install_name_tool -id "${NEW_ID}" "${DYLIB}"
}

replace_rpath () {
	local OLD_PATH="$1"
	local NEW_PATH="$2"
	local DYLIB="$3"

	echo "'${OLD_PATH}' -> '${NEW_PATH}' in '${DYLIB}'"

	echo install_name_tool -change "${OLD_PATH}" "${NEW_PATH}" "${DYLIB}"
	install_name_tool -change "${OLD_PATH}" "${NEW_PATH}" "${DYLIB}"
}

replace_rpaths () {
	local DYLIB="$1"
	local NEW_PATH="${2}"

	if [ "x${DYLIB}" = "x" ]; then
		return 1
	fi
	if [ "x${NEW_PATH}" = "x" ]; then
		return 1
	fi

	local FOUND_ENTRIES=$(otool -L "${DYLIB}" | grep "@rpath")

	if [ "x${FOUND_ENTRIES}" = "x" ]; then
		echo "File '${DYLIB}' does not contain any '@rpath' entries."
		return 0
	fi

	cp "${DYLIB}" "${DYLIB}.bak"
	echo "Trying to replace '@rpath' entries in '${DYLIB}'."

	local FOUND_PATHS=$(otool -L "${DYLIB}" | grep "@rpath" | sed -e 's/^[^@]*//g' -e 's/^\(.*\.dylib\).*$/\1/g')

	for FOUND_PATH in ${FOUND_PATHS}; do
		local LIB_FILE=$(echo "${FOUND_PATH}" | sed -e 's/@rpath\///g')
		local DYLIB_FILE=$(echo "${DYLIB}" | sed -e 's/^.*\///g')

		if [ ! -f "${LIB_FILE}" ]; then
			echo "Cannot find file '${LIB_FILE}'." >&2
			return 1
		fi

		local OLD_PATH="${FOUND_PATH}"
		local NEW_PATH="${DIR}/${LIB_FILE}"

		if [ "${LIB_FILE}" != "${DYLIB_FILE}" ]; then
			local CANON_LIB_FILE=$(readlink -f "${LIB_FILE}")
			local CANON_DYLIB_FILE=$(readlink -f "${DYLIB_FILE}")
			if [ "${CANON_LIB_FILE}" != "${CANON_DYLIB_FILE}" ]; then
				replace_rpath "${OLD_PATH}" "${NEW_PATH}" "${DYLIB}" || return 0
			else
				# LIB_FILE is a symbolic link to DYLIB_FILE.
				replace_id "${NEW_PATH}" "${DYLIB}" || return 0
			fi
		else
			replace_id "${NEW_PATH}" "${DYLIB}" || return 0
		fi
	done

	return 0
}

if [ ! -d "${DIR}" ]; then
	echo "'${DIR}' isn't a directory." >&2
	exit 1
fi

# Change to specified directory.
pushd "${DIR}" /dev/null
DIR=$(pwd)

ALL_DYLIBS=""
for D in ${DIR}/*dylib; do
	if [ -f "${D}" ]; then
		ALL_DYLIBS="${ALL_DYLIBS} ${D}"
	fi
done
if [ "x${ALL_DYLIBS}" = "x" ]; then
	echo "No libraries found in directory '${DIR}'." >&2
	exit 1
fi

# List libraries using '@rpath'.
for D in ./*dylib; do
	if [ -f "${D}" ]; then
		RPATH_OUT=$(otool -L "${D}" | grep '@rpath')
		if [ "x${RPATH_OUT}" != "x" ]; then
			echo "${D}:\n${RPATH_OUT}"
		fi
		unset RPATH_OUT
	fi
done

for D in ./*dylib; do
	if [ ! -f "${D}" ]; then
		# Not a file.
		continue
	fi
	if [ -L "${D}" ]; then
		# File is a symbolic link.
		continue
	fi
	replace_rpaths "${D}" "${DIR}"
done

# Check that every '@rpath' occurrence has been replaced.
RPATH_OUT=$(otool -L ./*dylib | grep '@rpath')
if [ "x${RPATH_OUT}" = "x" ]; then
	echo "All '@rpath' occurences have been replaced in directory '${DIR}'."
else
	echo "The following libraries still use '@rpath':" >&2
	for D in ./*dylib; do
		RPATH_OUT=$(otool -L "${D}" | grep '@rpath')
		if [ "x${RPATH_OUT}" != "x" ]; then
			echo "${D}:\n${RPATH_OUT}" >&2
		fi
		unset RPATH_OUT
	done
	echo "Replacement of '@rpath' occurrences failed in directory '${DIR}'." >&2
	exit 1
fi
