From df516600663f95b4fb2f399744fff1ddc88d3b9c Mon Sep 17 00:00:00 2001
From: Iain Sandoe <iain@sandoe.co.uk>
Date: Thu, 17 Sep 2015 16:25:07 +0100
Subject: [PATCH] contrib: fat-gcc.sh

A script that mimics the Apple driver-driver command that builds multiple
objects and lipos them together.

***WARNING*** likely very incomplete
---
 contrib/fat-gcc.sh | 420 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 420 insertions(+)
 create mode 100755 contrib/fat-gcc.sh

diff --git a/contrib/fat-gcc.sh b/contrib/fat-gcc.sh
new file mode 100755
index 00000000000..b4f6dd2f5eb
--- /dev/null
+++ contrib/fat-gcc.sh
@@ -0,0 +1,420 @@
+#! /bin/sh
+#
+# (C) Iain Sandoe May 2015.
+#
+# Try to mimic enough of the behaviour of the apple "driverdriver" and clang
+# that we can build fat binaries using the normal GCC driver for most of the
+# work.
+#
+# Install this in your toolchain 'bin' directory - it finds the toolchain
+# pieces relative to its install.
+#
+# You would have (say):
+#  .../bin/fat-gcc-N.M (iff your gcc is installed with a numerical suffix)
+#  .../bin/fat-gcc (if not)
+#
+# Likewise:
+#
+#  .../fat-gfortran
+#  .../fat-g++ / fat-c++
+#
+# TODO: figure out how to install this instead of the non-canonical target
+#       compiler.
+#
+#
+# Mostly this is about building multiple objects and lipo-ing them together,
+# however it might also be necessary to partition arch-specific flags in some
+# cases.
+#
+# TODO: Handle different source types, handle arch-specific options.
+#
+# version 0.1 - May 2015 - basic support for ppc and x86
+# version 0.2 - July 2022 - add arm64.
+
+prog="${0##*/}"
+prog="${prog%.*}"
+homedir="${0%/*}"
+version="xtools - $prog 0.7.0"
+
+destprog="${prog#*fat-}"
+archs=0
+ppc=0
+ppc64=0
+x86=0
+x86_64=0
+arm64=0
+new_cl=
+linking=1
+non_link_opt_seen=0
+seen_e_or_s=
+seen_c=0
+b_path=
+output_name=
+seen_version=0
+seen_help=0
+seen_v=0
+seen_dylib=0
+seen_hashes=0
+min_vers=
+save_temps=
+seen_save_temps=0
+other_arg_count=0
+no_infiles=
+
+while [ $# -ne 0 ]; do
+   case $1 in
+	--version) seen_version=1; new_cl="${new_cl} $1"; no_infiles="${no_infiles} $1"; shift ;;
+	--help*) seen_help=1; new_cl="${new_cl} $1"; no_infiles="${no_infiles} $1"; shift ;;
+	-###) seen_hashes=1; new_cl="${new_cl} $1"; no_infiles="${no_infiles} $1"; shift ;;
+	-v) seen_v=1; new_cl="${new_cl} $1"; no_infiles="${no_infiles} $1"; shift;;
+	-save-temps*) seen_save_temps=1; save_temps=$1; shift;;
+	-arch)
+	    ((archs+=1)); shift
+	    case $1 in
+	        ppc64) ppc64=1;;
+	        ppc*) ppc=1; ppc_arch=$1;;
+	        i386) x86=1; x86_arch=$1;;
+	        i686) x86=1; x86_arch=$1;;
+	        x86_64) x86_64=1;;
+	        arm64 | aarch64) arm64=1;;
+	        *) echo "$prog : Unknown arch: $1 (ignored)";;
+	    esac
+	    shift
+	    ;;
+	-mmacosx-version-min=*)
+            min_vers="${1#*=}";
+            no_infiles="${no_infiles} $1";
+	    new_cl="${new_cl} $1";
+            shift
+            ;;
+        -S|-E)
+            seen_e_or_s=$1;
+            linking=0
+            no_infiles="${no_infiles} $1";
+	    new_cl="${new_cl} $1";
+	    shift
+	    ;;
+        -c)
+            seen_c=1;
+            linking=0
+            no_infiles="${no_infiles} $1";
+	    new_cl="${new_cl} $1";
+	    shift
+	    ;;
+	-B)
+	    # We care about picking up auxiliary commands (like lipo) from
+	    # overridden PATHs.
+	    new_cl="${new_cl} $1 $2";
+	    no_infiles="${no_infiles} $1 $2";
+	    b_path="$b_path$2:"; shift 2
+	    ;;
+	-B*)
+	    new_cl="${new_cl} $1";
+            no_infiles="${no_infiles} $1";
+	    b_path="$b_path${1#*B}:"; shift
+	    ;;
+	-o)
+	    output_name=$2;
+	    shift 2
+	    ;;
+	-dynamiclib | -shared)
+	    seen_dylib=1
+            no_infiles="${no_infiles} $1";
+	    new_cl="${new_cl} $1";
+	    shift
+	    ;;
+	-I | -L | -framework | -compatibility_version | -current_version | -install_name | -x)
+            no_infiles="${no_infiles} $1 $2";
+	    new_cl="${new_cl} $1 $2";
+	    shift 2
+	    ;;
+	-*)
+	    new_cl="${new_cl} $1";
+            no_infiles="${no_infiles} $1";
+	    shift
+	    ;;
+	*)
+	    # keep the remainder of the positional args.
+	    other_args[other_arg_count]="$1"
+	    (( other_arg_count += 1))
+	    new_cl="${new_cl} $1";
+	    shift
+	    ;;
+    esac
+done
+
+if [ $archs -le 1 ]; then
+  if [ $ppc -eq 1 ]; then new_cl="${new_cl} -arch $ppc_arch"; fi
+  if [ $x86 -eq 1 ]; then new_cl="${new_cl} -arch $x86_arch"; fi
+  if [ $ppc64 -eq 1 ]; then new_cl="${new_cl} -arch ppc64"; fi
+  if [ $x86_64 -eq 1 ]; then new_cl="${new_cl} -arch x86_64"; fi
+  if [ $arm64 -eq 1 ]; then new_cl="${new_cl} -arch arm64"; fi
+  if [ -n "$output_name" ]; then new_cl="${new_cl} -o $output_name"; fi
+  #echo "$prog : $destprog $new_cl $save_temps"
+  exec $destprog $new_cl
+fi
+
+# OK - so it's not a simple case, make sure we detect when we're cross-compiling
+# from a non-darwin host.
+
+is_darwin=0
+is_ppc=0
+is_arm64=0
+default_darwin_vers=9
+default_darwin_arch="i686"
+host_info=`uname -srm`
+read sys vers narch <<< "$host_info"
+case $sys in
+    Darwin)
+        is_darwin=1;
+        case $narch in
+           # We don't get anything to indicate a powerpc64 platform (because there
+           # never was - a public - one).
+           Power*) default_darwin_arch="powerpc"; is_ppc=0;;
+           x86_64) default_darwin_arch="x86_64"; is_x86=1 ;;
+           i386) default_darwin_arch="i686"; is_x86=1;;
+           Arm64) default_darwin_arch="arm64"; is_arm64=1;;
+           *) echo "$prog : didn't recognise the arch from $narch";;
+        esac
+        # Drop any micro version
+        default_darwin_vers="${vers%.*}"
+        # Drop any mini version
+        default_darwin_vers="${default_darwin_vers%.*}"
+	;;
+    *) ;;
+esac
+
+if [ -n "$min_vers" ]; then
+   maj="${min_vers#darwin.}"
+   maj="${maj%.*}"
+   if [ $maj -le 10 ]; then
+     # Lose the pointless 10.
+     darwin="${min_vers#*10.}"
+     # Lose the minor,
+     darwin="${darwin%.*}"
+     (( darwin += 4 ))
+   else
+     (( darwin += 9 ))
+   fi
+elif [ $is_arm64 -eq 1 ]; then
+    darwin=20
+else
+    darwin=$default_darwin_vers
+fi
+
+
+#echo "sys $sys version $vers native arch $narch (darwin vers $darwin)"
+
+case $seen_e_or_s in
+    -S)
+        echo "$prog : error: cannot use 'assembler' output with multiple -arch options"
+        exit 1
+        ;;
+    -E)
+        echo "$prog : error: cannot use 'c++-cpp-output' output with multiple -arch options"
+        exit 1
+        ;;
+    *) ;;
+esac
+
+# So now we want to build a per arch preferred toolname.
+# This is a bit icky, we're spawning commands to check if we can find compilers,
+# I suppose we could just assume that they're installed and fail at runtime.
+
+arch_idx=0
+if [ $ppc -eq 1 ] || [ $ppc64 -eq 1 ]; then
+    if [ $ppc -eq 1 ]; then
+        arch_list[$arch_idx]="$ppc_arch";
+        arch_type[$arch_idx]=0;
+        (( arch_idx += 1 ))
+    fi
+    if [ $ppc64 -eq 1 ]; then
+        arch_list[$arch_idx]="$ppc64";
+        arch_type[$arch_idx]=0;
+        (( arch_idx += 1 ))
+    fi
+    ppc_cmd="$homedir/powerpc-apple-darwin$darwin-$destprog"
+    if ! which $ppc_cmd >/dev/null ; then
+        ppc_cmd="$homedir/powerpc64-apple-darwin$darwin-$destprog"
+        if ! which $ppc_cmd >/dev/null ; then
+            echo "$prog : didn't find powerpc{,64}-apple-darwin$darwin-$destprog"
+            if [ $is_darwin -eq 1 ] && [ ! $is_x86 ]; then
+                echo "$prog : trying $homedir/$destprog"
+                ppc_cmd="$homedir/$destprog"
+            else
+                # Fall back to most likely.
+                ppc_cmd="powerpc-apple-darwin9-$destprog"
+                echo "$prog : trying $ppc_cmd"
+            fi
+        fi
+    fi
+fi
+
+if [ $x86 -eq 1 ] || [ $x86_64 -eq 1 ]; then
+    if [ $x86 -eq 1 ]; then
+        arch_list[$arch_idx]="$x86_arch";
+        arch_type[$arch_idx]=1;
+        (( arch_idx += 1 ))
+    fi
+    if [ $x86_64 -eq 1 ]; then
+        arch_list[$arch_idx]="x86_64";
+        arch_type[$arch_idx]=1;
+        (( arch_idx += 1 ))
+    fi
+    x86_cmd="$homedir/x86_64-apple-darwin$darwin-$destprog"
+    if ! which $x86_cmd >/dev/null ; then
+        x86_cmd="$homedir/i686-apple-darwin$darwin-$destprog"
+        if ! which $x86_cmd >/dev/null ; then
+            echo "$prog : didn't find {x86_64,i686}-apple-darwin$darwin-$destprog"
+            if [ $is_darwin -eq 1 ] && [ $is_x86 ]; then
+                echo "$prog : trying $homedir/$destprog"
+                x86_cmd="$homedir/$destprog"
+            fi
+        fi
+    fi
+fi
+
+if [ $arm64 -eq 1 ]; then
+    arch_list[$arch_idx]="arm64";
+    arch_type[$arch_idx]=2;
+    (( arch_idx += 1 ))
+    arm64_cmd="$homedir/aarch64-apple-darwin$darwin-$destprog"
+    if ! which $arm64_cmd >/dev/null ; then
+        echo "$prog : didn't find aarch64-apple-darwin$darwin-$destprog"
+    fi
+fi
+
+# The next easiest case is when we only need to worry about linking the result.
+if [ -z $output_name ]; then
+    output_name="a.out"
+fi
+idx=0
+while [ $idx -lt $arch_idx ]; do
+    if [ $seen_save_temps -eq 1 ]; then
+        tdir="./$prog.$$.${arch_list[idx]}"
+        save_temps="-save-temps=obj"
+    else
+        tdir="/tmp/$prog.$$.${arch_list[idx]}"
+	save_temps=
+    fi
+    tdirs[idx]="$tdir"
+    if [ $seen_hashes -eq 0 ]; then
+        if ! mkdir -p $tdir ; then
+            echo "failed to make $tdir"
+        fi
+    fi
+    (( idx += 1 ))
+done
+
+if [ $linking -ne 0 ]; then
+    extra_link_opts=
+    if [ $seen_dylib -ne 0 ]; then
+        extra_link_opts="-Wl,-arch_multiple,-final_output,$output_name"
+    fi
+    object_list=
+    idx=0
+    while [ $idx -lt $arch_idx ]; do
+        object_arch="${tdirs[idx]}/$output_name"
+        object_list="${object_list} $object_arch"
+        cl=" -arch ${arch_list[idx]} $new_cl $extra_link_opts -o $object_arch $save_temps"
+        if [ ${arch_type[idx]} -eq 0 ]; then
+            #echo "$ppc_cmd $cl"
+            if ! $ppc_cmd $cl ; then
+                echo "$ppc_cmd $cl failed"
+            fi
+        elif [ ${arch_type[idx]} -eq 2 ]; then
+            #echo "$ppc_cmd $cl"
+            if ! $arm64_cmd $cl ; then
+                echo "$arm64_cmd $cl failed"
+            fi
+        else
+            #echo "$x86_cmd $cl"
+            if ! $x86_cmd $cl ; then
+                echo "$x86_cmd $cl failed"
+            fi
+        fi
+        (( idx += 1 ))
+    done
+    if [ $seen_version -eq 1 ] || [ $seen_v -eq 1 ]; then
+        echo "$version"
+        if [ $seen_version -eq 1 ]; then exit 0; fi
+    fi
+    if [ $seen_hashes -eq 1 ]; then
+        lipo_to_use=`which lipo`
+        echo "\"$lipo_to_use\" -create -o $output_name $object_list"
+        exit 0
+    fi
+    if ! lipo -create -o $output_name $object_list ; then
+        echo "lipo -create -o $output_name $object_list ... failed"
+        exit 1
+    fi
+    if [ $seen_hashes -eq 0 ] && [ $seen_save_temps -eq 0 ]; then
+        idx=0
+        while [ $idx -lt $arch_idx ]; do
+            #echo "removing : ${tdirs[idx]}"
+            rm -r "${tdirs[idx]}"
+            (( idx += 1 ))
+        done
+    fi
+    exit 0
+fi
+
+# Now the longest-winded of them all.
+if [ $seen_c -ne 0 ]; then
+    idx=0
+    while [ $idx -lt $other_arg_count ]; do
+        name="${other_args[idx]}"
+        name="${name##*/}"
+        oname="${name%.*}"
+        oname="${oname}.o"
+        #echo "source to build : ${other_args[idx]} => $name => $oname"
+        aidx=0
+        object_list=
+        while [ $aidx -lt $arch_idx ]; do
+            object_arch="${tdirs[aidx]}/$oname"
+            object_list="${object_list} $object_arch"
+            cl=" -arch ${arch_list[aidx]} $no_infiles $extra_link_opts -o $object_arch $save_temps ${other_args[idx]}"
+            if [ ${arch_type[aidx]} -eq 0 ]; then
+                 #echo "$ppc_cmd $cl"
+                 if ! $ppc_cmd $cl ; then
+                     echo "$ppc_cmd $cl failed"
+                fi
+            elif [ ${arch_type[aidx]} -eq 2 ]; then
+                 #echo "$arm64_cmd $cl"
+                 if ! $arm64_cmd $cl ; then
+                     echo "$arm64_cmd $cl failed"
+                fi
+            else
+                #echo "$x86_cmd $cl"
+                if ! $x86_cmd $cl ; then
+                    echo "$x86_cmd $cl failed"
+                fi
+            fi
+           (( aidx += 1 ))
+         done
+         if [ $seen_hashes -eq 1 ]; then
+            lipo_to_use=`which lipo`
+            echo "\"$lipo_to_use\" -create -o $oname $object_list"
+         elif ! lipo -create -o $oname $object_list ; then
+            echo "lipo -create -o $oname $object_list ... failed"
+         fi
+#         if [ $seen_hashes -eq 0 ] && [ $seen_save_temps -eq 0 ]; then
+#            rm -r "$object_list"
+#         fi
+        (( idx += 1))
+    done
+fi
+if [ $seen_hashes -eq 0 ] && [ $seen_save_temps -eq 0 ]; then
+    idx=0
+    while [ $idx -lt $arch_idx ]; do
+        #echo "removing : ${tdirs[idx]}"
+        rm -r "${tdirs[idx]}"
+        (( idx += 1 ))
+    done
+fi
+
+#echo "$prog : #archs : $archs : ppc=$ppc_arch x86=$x86_arch ppc64=$ppc64 x86_64=$x86_64 arm64=$arm64"
+#echo "$prog : Bpaths : $b_path : min_vers $min_vers linking $linking"
+#echo "$prog : new commandline : $new_cl (arcs $arch_idx archlist : $arch_list)"
+
+exit 0
