1#!/bin/bash
2#
3# Script that rsyncs a source/build tree to a remote server, performs a build,
4# and copies the result back
5#
6
7# This script is invoked instead of the initial recursive make(1) in ./Makefile.
8# First it must cache all binaries that might be used during the build by
9# calling "make print_exports" (any target would work) with an overriden xcrun(1)
10# which caches tools an SDKs into ./BUILD/obj/BuildTools. When the combined
11# source+build tree is rsync-ed to the remote server, we run a script to
12# re-initiate the build using an overriden xcrun(1) which hands back
13# cached tools in ./BUILD/obj/BuildTools instead of whatever Xcode tools are on
14# the remote system (or if no Xcode tools are installed remotely). Finally,
15# the build results are copied back locally.
16#
17
18function die() {
19    echo "$1" 1>&2
20    exit 1
21}
22
23
24TARGET=
25declare -a ARGS
26declare -a REMOTEARGS
27index=0
28for arg in "$@"; do
29    case $arg in
30	_REMOTEBUILD_TARGET=*)
31	    TARGET=`echo $arg | awk -F= '{print $2}'`
32	    continue
33	    ;;
34	_REMOTEBUILD_MAKE=*)
35	    MAKE=`echo $arg | awk -F= '{print $2}'`
36	    continue
37	    ;;
38	REMOTEBUILD=*)
39	    # Don't restart another remote build remotely
40	    ;;
41	SRCROOT=*)
42	    continue
43	    ;;
44	OBJROOT=*)
45	    continue
46	    ;;
47	SYMROOT=*)
48	    continue
49	    ;;
50	DSTROOT=*)
51	    continue
52	    ;;
53	CCHROOT=*)
54	    continue
55	    ;;
56	RC_XBS=*)
57	    # Remote build isn't chrooted or special in any way
58	    arg="VERBOSE=YES"
59	    continue
60	    ;;
61	VERBOSE=YES)
62	    set -x
63	    ;;
64    esac
65    ARGS[$index]="$arg"
66    REMOTEARGS[$index]="\"$arg\""
67    index=$(($index+1))
68done
69
70
71ARGS[$index]="REMOTEBUILD="
72REMOTEARGS[$index]="\"REMOTEBUILD=\""
73
74# For some targets like installsrc, we can't to a remote build
75SKIPREMOTE=0
76case $TARGET in
77    clean)
78	SKIPREMOTE=1
79	;;
80    installsrc)
81	SKIPREMOTE=1
82	;;
83    installopensource)
84	SKIPREMOTE=1
85	;;
86    cscope)
87	SKIPREMOTE=1
88	;;
89    tags)
90	SKIPREMOTE=1
91	;;
92    help)
93	SKIPREMOTE=1
94	;;
95esac
96
97if [ $SKIPREMOTE -eq 1 ]; then
98    exec "$MAKE" "$TARGET" "${ARGS[@]}"
99fi
100
101SRC="$(pwd -P)"
102SRCNAME="$(basename $SRC)"
103
104# Pick up build locations passed in the environment
105OBJROOT="${OBJROOT}"
106SYMROOT="${SYMROOT}"
107DSTROOT="${DSTROOT}"
108
109if [ -z "${OBJROOT}" ]; then
110    die "OBJROOT not set in environment"
111fi
112mkdir -p "${OBJROOT}" || die "Could not create ${OBJROOT}"
113
114if [ -z "${SYMROOT}" ]; then
115    die "SYMROOT not set in environment"
116fi
117mkdir -p "${SYMROOT}" || die "Could not create ${SYMROOT}"
118
119if [ -z "${DSTROOT}" ]; then
120    die "DSTROOT not set in environment"
121fi
122mkdir -p "${DSTROOT}" || die "Could not create ${DSTROOT}"
123
124if [ "$REMOTEBUILD" = "$SPECIALREMOTEBUILD" ]; then
125    :
126else
127    DOINSTALLSRC=0
128    REMOTE_SRCREL="./"
129    BUILDTOOLSDIR="$OBJROOT"
130    REMOTE_BUILDTOOLSREL="./BUILD/obj"
131    BUILDSCRIPTDIR="$OBJROOT"
132    REMOTE_BUILDSCRIPTREL="./BUILD/obj"
133    BUILDSCRIPTNAME="build.sh"
134    if [ ! -d "${OBJROOT}/SETUP" ]; then
135	RSYNC_ARGS="--delete-excluded"
136    else
137	RSYNC_ARGS=""
138    fi
139    if [ ! -e "${SYMROOT}/" ]; then
140	RSYNC_DELETE_SYMROOT=1
141    else
142	RSYNC_DELETE_SYMROOT=0
143    fi
144    if [ ! -e "${DSTROOT}/" ]; then
145	RSYNC_DELETE_DSTROOT=1
146    else
147	RSYNC_DELETE_DSTROOT=0
148    fi
149    TARBUILDDIRS=0
150fi
151
152echo "Caching build tools..." 1>&2
153mkdir -p "${BUILDTOOLSDIR}" || die "Could not create BUILDTOOLSDIR"
154$MAKE print_exports "${ARGS[@]}" XCRUN="${SRC}/tools/xcrun_cache.sh -c \"${BUILDTOOLSDIR}\"" >/dev/null || die "Could not cache build tools"
155
156# Cache the make(1) binary itself
157MAKE_SDKROOT=`"${SRC}/tools/xcrun_cache.sh" -u "${BUILDTOOLSDIR}" -sdk / -show-sdk-path`
158"${SRC}/tools/xcrun_cache.sh" -c "${BUILDTOOLSDIR}" -sdk "${MAKE_SDKROOT}" -find make >/dev/null || die "Could not cache make"
159
160# Create a canned build script that can restart the build on the remote server.
161mkdir -p "${BUILDSCRIPTDIR}" || die "Could not create BUILDSCRIPTDIR"
162cat > "${BUILDSCRIPTDIR}/${BUILDSCRIPTNAME}" <<EOF
163#!/bin/sh
164mkdir -p /private/tmp
165mkdir -p /private/var/tmp
166mkdir -p "\${TMPDIR}"
167cd "${REMOTE_SRCREL}"
168mkdir -p ./BUILD/obj
169mkdir -p ./BUILD/sym
170mkdir -p ./BUILD/dst
171MAKE=\`\$PWD/tools/xcrun_cache.sh -u "\$PWD/${REMOTE_BUILDTOOLSREL}" -sdk / -find make\`
172if [ -z "\${MAKE}" ]; then exit 1; fi
173\${MAKE} ${TARGET} ${REMOTEARGS[@]} XCRUN="\$PWD/tools/xcrun_cache.sh -u \"\$PWD/${REMOTE_BUILDTOOLSREL}\""
174ret=\$?
175if [ \$ret -eq 0 ]; then
176if [ ${TARBUILDDIRS} -eq 1 ]; then
177tar jcf ./BUILD/obj.tar.bz2 --exclude=\*.o --exclude=\*.cpo --exclude=\*.d --exclude=\*.cpd --exclude=\*.non_lto --exclude=\*.ctf --exclude=conf -C ./BUILD/obj . || exit 1
178tar jcf ./BUILD/sym.tar.bz2 -C ./BUILD/sym . || exit 1
179tar jcf ./BUILD/dst.tar.bz2 -C ./BUILD/dst . || exit 1
180fi
181fi
182exit \$ret
183EOF
184chmod a+x "${BUILDSCRIPTDIR}/${BUILDSCRIPTNAME}"
185#echo "Build script is:"
186#cat "${BUILDSCRIPTDIR}/${BUILDSCRIPTNAME}"
187
188mkdir -p "${BUILDTOOLSDIR}/empty"
189
190if [ "$REMOTEBUILD" = "$SPECIALREMOTEBUILD" ]; then
191    :
192else
193
194    REMOTEBUILD="$REMOTEBUILD"
195    REMOTEBUILDPATH="$REMOTEBUILDPATH"
196
197    if [ -z "$REMOTEBUILDPATH" ]; then
198	WHOAMI=`whoami`
199	case "${REMOTEBUILD}" in
200	    *@*)
201		WHOAMI=`echo "${REMOTEBUILD}" | awk -F@ '{print $1}'`
202		;;
203	esac
204	REMOTEBUILDPATH="/tmp/$WHOAMI"
205    fi
206
207# Construct a unique remote path
208    eval `stat -s "${SRC}"`
209
210    REMOTEBUILDPATH="${REMOTEBUILDPATH}/$st_ino/${SRCNAME}/"
211    echo "Remote path is ${REMOTEBUILD}:${REMOTEBUILDPATH}" 1>&2
212
213    ssh $REMOTEBUILD "mkdir -p \"${REMOTEBUILDPATH}/BUILD/\"{obj,sym,dst}" || die "Could not make remote build directory"
214
215    # Copy source only
216    rsync -azv --delete --exclude=\*~ --exclude=.svn --exclude=.git --exclude=/BUILD . $REMOTEBUILD:"${REMOTEBUILDPATH}" || die "Could not rsync source tree"
217
218    # Copy partial OBJROOT (just build tools and build script), and optionally delete everything else
219    rsync -azv --delete $RSYNC_ARGS --include=/build.sh --include=/BuildTools --include=/BuildTools/\*\* --exclude=\* "${OBJROOT}/" $REMOTEBUILD:"${REMOTEBUILDPATH}/BUILD/obj/" || die "Could not rsync build tree"
220
221    # Delete remote SYMROOT if it has been deleted locally
222    if [ "$RSYNC_DELETE_SYMROOT" -eq 1 ]; then
223	rsync -azv --delete "${BUILDTOOLSDIR}/empty/" $REMOTEBUILD:"${REMOTEBUILDPATH}/BUILD/sym/" || die "Could not rsync delete SYMROOT"
224    fi
225
226    # Delete remote DSTROOT if it has been deleted locally
227    if [ "$RSYNC_DELETE_DSTROOT" -eq 1 ]; then
228	rsync -azv --delete "${BUILDTOOLSDIR}/empty/" $REMOTEBUILD:"${REMOTEBUILDPATH}/BUILD/dst/" || die "Could not rsync delete DSTROOT"
229    fi
230
231    # Start the build
232    echo ssh $REMOTEBUILD "/bin/bash -c 'cd \"${REMOTEBUILDPATH}\" && ${REMOTE_BUILDSCRIPTREL}/${BUILDSCRIPTNAME}'" 1>&2
233    ssh $REMOTEBUILD "/bin/bash -c 'cd \"${REMOTEBUILDPATH}\" && ${REMOTE_BUILDSCRIPTREL}/${BUILDSCRIPTNAME}'" || die "Could not complete remote build"
234
235    # Copy back build results except for object files (which might be several GB)
236    echo "Copying results back..."
237    rsync -azv --no-o --no-g --exclude=\*.o --exclude=\*.cpo --exclude=\*.d --exclude=\*.cpd --exclude=\*.non_lto --exclude=\*.ctf --exclude=conf $REMOTEBUILD:"${REMOTEBUILDPATH}/BUILD/obj/" "${OBJROOT}/" || die "Could not rsync build results"
238    rsync -azv --no-o --no-g $REMOTEBUILD:"${REMOTEBUILDPATH}/BUILD/sym/" "${SYMROOT}/" || die "Could not rsync build results"
239    rsync -azv --no-o --no-g $REMOTEBUILD:"${REMOTEBUILDPATH}/BUILD/dst/" "${DSTROOT}/" || die "Could not rsync build results"
240
241fi
242
243exit 0
244