summaryrefslogtreecommitdiffstats
path: root/mkspecs/features/data/mac
diff options
context:
space:
mode:
authorTor Arne Vestbø <tor.arne.vestbo@theqtcompany.com>2015-09-27 23:30:41 +0200
committerTor Arne Vestbø <tor.arne.vestbo@theqtcompany.com>2016-01-29 14:41:21 +0000
commitf24536f8a533a3001c3a142f9d5c4bc4036bc5bf (patch)
tree296fde80f7f8425033c3f596583ef3c7a40b8f79 /mkspecs/features/data/mac
parent50489f2e41b42ae391580a01e76f255ae98de1cf (diff)
Add tool to namespace Objective-C classes at link time
The feature is enabled by CONFIG += unsupported/objc_namespace, but can be easily integrated into other build systems such as CMake or native Xcode by modifying the LD and LDFLAGS equivalent for each build system. This is a less resource-intensive alternative to using multiple Qt builds with different -qtnamespace settings. Note: The feature is not supported in any way, and should be used with care. Change-Id: Ibb8ba1159db36efd7106c117cc2210c7e2e24784 Reviewed-by: Martin Smith <martin.smith@theqtcompany.com> Reviewed-by: Morten Johan Sørvig <morten.sorvig@theqtcompany.com> Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@theqtcompany.com>
Diffstat (limited to 'mkspecs/features/data/mac')
-rwxr-xr-xmkspecs/features/data/mac/objc_namespace.sh216
1 files changed, 216 insertions, 0 deletions
diff --git a/mkspecs/features/data/mac/objc_namespace.sh b/mkspecs/features/data/mac/objc_namespace.sh
new file mode 100755
index 0000000000..0c7faf18bf
--- /dev/null
+++ b/mkspecs/features/data/mac/objc_namespace.sh
@@ -0,0 +1,216 @@
+#!/bin/bash
+
+#############################################################################
+##
+## Copyright (C) 2015 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is the build configuration utility of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:LGPL21$
+## Commercial License Usage
+## Licensees holding valid commercial Qt licenses may use this file in
+## accordance with the commercial license agreement provided with the
+## Software or, alternatively, in accordance with the terms contained in
+## a written agreement between you and The Qt Company. For licensing terms
+## and conditions see http://www.qt.io/terms-conditions. For further
+## information use the contact form at http://www.qt.io/contact-us.
+##
+## GNU Lesser General Public License Usage
+## Alternatively, this file may be used under the terms of the GNU Lesser
+## General Public License version 2.1 or version 3 as published by the Free
+## Software Foundation and appearing in the file LICENSE.LGPLv21 and
+## LICENSE.LGPLv3 included in the packaging of this file. Please review the
+## following information to ensure the GNU Lesser General Public License
+## requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+## http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+##
+## As a special exception, The Qt Company gives you certain additional
+## rights. These rights are described in The Qt Company LGPL Exception
+## version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+script_argument_prefix="-Wobjc_namespace,--"
+
+required_arguments="target suffix original_ld"
+optional_arguments="exclude_list exclude_regex slient"
+
+for argument in $required_arguments $optional_arguments; do
+ declare "$argument="
+done
+
+declare -i silent=0
+declare -a linker_arguments
+
+for i in "$@"; do
+ case $1 in
+ $script_argument_prefix*)
+ declare "${1#$script_argument_prefix}"
+ ;;
+ -o)
+ if [ -n "$2" ]; then
+ target="$2"
+ fi
+ linker_arguments+=("$1")
+ ;;
+ *)
+ linker_arguments+=("$1")
+ ;;
+ esac
+ shift
+done
+
+get_entry() {
+ local map=$1 key=$2
+ local i="${map}_map_${2}"
+ printf '%s' "${!i}"
+}
+
+error() {
+ echo "$0: error: $*" >&2
+ exit 1
+}
+
+for argument in $required_arguments; do
+ if [ -z "${!argument}" ]; then
+ error "missing argument --${argument}"
+ fi
+done
+
+# Normalize suffix so we can use it as a bash variable
+suffix=${suffix//[-. ]/_}
+
+link_binary() {
+ (PS4=; test $silent -ne 1 && set -x; $original_ld "${linker_arguments[@]}" "$@") 2>&1 || exit 1
+}
+
+sanitize_address() {
+ local address="$1"
+ address=${address#0x} # Remove hex prefix
+ address=${address: ${#address} < 8 ? 0 : -8} # Limit to 32-bit
+ echo "0x$address"
+}
+
+read_binary() {
+ local address=$1
+ local length=$2
+
+ dd if="$target" bs=1 iseek=$address count=$length 2>|/dev/null
+}
+
+read_32bit_value() {
+ local address=$1
+ read_binary $address 4 | xxd -p | dd conv=swab 2>/dev/null | rev
+}
+
+inspect_binary() {
+ inspect_mode="$1"
+
+ echo -n "🔎 Inspecting binary '$target', "
+ if [ ! -f "$target" ]; then
+ echo "target does not exist!"
+ exit 1
+ fi
+
+ read -a mach_header <<< "$(otool -h "$target" -v | tail -n 1)"
+ if [ "${mach_header[1]}" != "X86_64" ]; then
+ echo "binary is not 64-bit, only 64-bit binaries are supported!"
+ exit 1
+ fi
+
+ classnames_section="__objc_classname"
+ classnames=$(otool -v -s __TEXT $classnames_section "$target" | tail -n +3)
+ while read -a classname; do
+ address=$(sanitize_address ${classname[0]})
+ name=${classname[1]}
+
+ declare "address_to_classname_map_$address=$name"
+ declare "classname_to_address_map_$name=$address"
+ done <<< "$classnames"
+
+ extra_classnames_file="$(mktemp -t ${classnames_section}_additions).S"
+
+ if [ "$inspect_mode" == "inject_classnames" ]; then
+ echo "class names have not been namespaced, adding suffix '$suffix'..."
+ printf ".section __TEXT,$classnames_section,cstring_literals,no_dead_strip\n" > $extra_classnames_file
+ elif [ "$inspect_mode" == "patch_classes" ]; then
+ echo "found namespaced class names, updating class entries..."
+ fi
+
+ classes=$(otool -o "$target" | grep class_ro_t)
+ while read -a class; do
+ address="$(sanitize_address ${class[1]})"
+
+ class_flags="0x$(read_32bit_value $address)"
+ if [ -z "$class_flags" ]; then
+ echo " 💥 failed to read class flags for class at $address"
+ continue
+ fi
+
+ is_metaclass=$(($class_flags & 0x1))
+
+ name_offset=$(($address + 24))
+ classname_address="0x$(read_32bit_value $name_offset)"
+ if [ -z "$classname_address" ]; then
+ echo " 💥 failed to read class name address for class at $address"
+ continue
+ fi
+
+ classname=$(get_entry address_to_classname $classname_address)
+ if [ -z "$classname" ]; then
+ echo " 💥 failed to resolve class name for address '$classname_address'"
+ continue
+ fi
+
+ if [[ $exclude_list =~ $classname || $classname =~ $exclude_regex ]]; then
+ if [ $is_metaclass -eq 1 ]; then
+ class_type="meta class"
+ else
+ class_type="class"
+ fi
+ echo " 🚽 skipping excluded $class_type '$classname'"
+ continue
+ fi
+
+ newclassname="${classname}_${suffix}"
+
+ if [ "$inspect_mode" == "inject_classnames" ]; then
+ if [ $is_metaclass -eq 1 ]; then
+ continue
+ fi
+
+ echo " 💉 injecting $classnames_section entry '$newclassname' for '$classname'"
+ printf ".asciz \"$newclassname\"\n" >> $extra_classnames_file
+
+ elif [ "$inspect_mode" == "patch_classes" ]; then
+ newclassname_address=$(get_entry classname_to_address ${newclassname})
+ if [ -z "$newclassname_address" ]; then
+ echo " 💥 failed to resolve class name address for class '$newclassname'"
+ continue
+ fi
+
+ if [ $is_metaclass -eq 1 ]; then
+ class_type="meta"
+ else
+ class_type="class"
+ fi
+
+ echo " 🔨 patching class_ro_t at $address ($class_type) from $classname_address ($classname) to $newclassname_address ($newclassname)"
+ echo ${newclassname_address: -8} | rev | dd conv=swab 2>/dev/null | xxd -p -r -seek $name_offset -l 4 - "$target"
+ fi
+ done <<< "$classes"
+}
+
+echo "🔩 Linking binary using '$original_ld'..."
+link_binary
+
+inspect_binary inject_classnames
+
+echo "🔩 Re-linking binary with extra __objc_classname section..."
+link_binary $extra_classnames_file
+
+inspect_binary patch_classes
+