1#!/bin/sh
2
3CWD=`pwd`
4
5# Print an info-level message
6info() {
7	echo "$1"
8}
9
10fprintf() {
11	local fd="$1"
12	local msg="$2"
13	echo "${msg}" 1>&${fd}
14}
15
16# Print an error-level message to stderr
17error() {
18	fprintf 2 "ERROR: $1"
19}
20
21# Print a fatal message to stderr and exit
22fatal() {
23	error "$1"
24	exit 1
25}
26
27# Check for a non-zero error code
28# If non-zero, print the fatal error message
29check_error() {
30	if [ $? != 0 ]; then
31		fatal "$1"
32	fi
33}
34
35# Resolve a relative path
36abspath() {
37	local path="$1"
38	local var="$2"
39	case "${path}" in
40		/*)
41			# Nothing to do, absolute path
42			eval "${var}=\"${path}\""
43			;;
44		*)
45			eval "${var}=\"${CWD}/${path}\""
46			;;
47	esac
48}
49
50find_tool() {
51	local varname="$1"
52	shift
53	local names="$*"
54
55	while [ ! -z "${1}" ]; do
56		local path=`which "${1}"`
57		if [ ! -z "${path}" ]; then
58			eval "${varname}=\"${path}\""
59			return
60		fi
61		shift
62	done
63
64	fatal "Could not find required tool: ${names}"
65}
66
67safe_mkdir() {
68	local dir="$1"
69	mkdir -p "${dir}"
70	check_error "Failed to create directory: ${dir}"
71}
72
73safe_cp() {
74	local src="$1"
75	local dest="$2"
76	cp "${src}" "${dest}"
77	check_error "Failed to copy file"
78}
79
80# Push a new working directory and verify success
81safe_push_cwd() {
82	local dir="$1"
83	pushd "${dir}" >/dev/null
84	check_error "Failed to change to directory: ${dir}"
85}
86
87# Pop a working directory pushed via safe_push_cwd
88safe_pop_cwd() {
89	popd >/dev/null
90	check_error "Failed to change cwd"
91}
92
93# Create a hard link from arg2 (dest) to arg1 (source)
94safe_ln() {
95	local src="$1"
96	local dest="$2"
97
98	ln -f "${src}" "${dest}"
99	check_error "ln $1 $2 failed"
100}
101
102# Push a directory onto the PATH environment variable
103push_path() {
104	local dir="$1"
105	PATH="$1:$PATH"
106}
107
108# Pop a directory from the head of the PATH environment variable
109pop_path() {
110	export PATH="$(echo "${PATH}" | cut -d : -f 2-)"
111}
112
113# Append a value to a list and echo the result to stdout.
114list_append() {
115	local list="$1"
116	local value="$2"
117
118	# Use xargs to strip excess whitespace
119	echo "${list}" "${2}" | xargs
120}
121
122# Perform an autotools build
123run_autoconf() {
124	local tool="$1"
125	local objdir="$2"
126	local prefix="$3"
127
128	# Move the configure args to pos $1
129	shift;
130	shift;
131	shift;
132
133	local srcdir="${BUILDTOOLS}/$tool"
134
135	safe_mkdir "${objdir}"
136	safe_push_cwd "${objdir}"
137
138	# Skip reconfiguring if a config.log already exists
139	if [ ! -f "${objdir}/config.log" ]; then
140		"${srcdir}/configure" -C --prefix="${prefix}" $@ >> "${LOGFILE}" 2>&1
141		check_error "Configure failed"
142	fi
143}
144
145# Extract the arch from a host triple, and then map to a canonical arch
146triple_arch() {
147	local triple="$1"
148	local arch=$(echo "$triple" | awk -F '-' '{print $1}')
149	case "${arch}" in
150		x86)	echo "i386";;
151		i486)	echo "i386";;
152		i586)	echo "i386";;
153		i686)	echo "i386";;
154		*)		echo "${arch}";;
155	esac
156}
157
158# Extract the OS name from a host triple
159triple_system() {
160	local triple="$1"
161	echo "${triple}" | awk -F- '{print $3}' | sed 's/[0-9\.]*$//'
162}
163
164# Perform a cmake build
165run_cmake() {
166	local tool="$1"
167	local objdir="$2"
168	local prefix="$3"
169	local srcdir="${BUILDTOOLS}/$tool"
170	local builddir="$objdir"
171
172	safe_mkdir "${builddir}"
173	safe_push_cwd "${builddir}"
174
175	# Assemble the host architecture list for cmake
176	local cmake_archs_env=""
177	if [ ! -z "${HOSTS}" ]; then
178		for arch in ${HOSTS}; do
179			if [ ! -z "${cmake_archs_env}" ]; then
180				cmake_archs_env="${cmake_archs_env};"
181			fi
182
183			local cmake_archs_env="${cmake_archs_env}$(triple_arch ${arch})"
184		done
185
186		local cmake_archs_env="CMAKE_OSX_ARCHITECTURES=${cmake_archs_env}"
187	fi
188
189	env "${cmake_archs_env}" "${CMAKE}" \
190		"-DCMAKE_INSTALL_PREFIX:PATH=${prefix}" \
191		"${srcdir}" >> "${LOGFILE}" 2>&1
192
193	check_error "${CMAKE} failed"
194}
195
196# Install using gmake. Assumes that the Makefile supports DESTDIR, which should
197# be the case when using either autoconf or cmake
198run_gmake() {
199	local objdir="$1"
200
201	# Move the make args to pos $1
202	shift;
203
204	safe_push_cwd "${objdir}"
205
206	if [ -z "${DESTROOT}" ]; then
207		"${GMAKE}" ${JOBS} "$@" >> "${LOGFILE}" 2>&1
208	else
209		# Ensure that our caller can override DESTDIR by placing the caller's
210		# arguments after our own.
211		"${GMAKE}" ${JOBS} "DESTDIR=${DESTROOT}" "$@" >> "${LOGFILE}" 2>&1
212	fi
213
214	check_error "${GMAKE} failed"
215}
216
217# touch all info files in order to avoid the dependency on makeinfo
218# (which apparently doesn't work reliably on all the different host 
219# configurations and changes files which in turn appear as local changes
220# to the VCS).
221touch_mkinfo() {
222	local tool="$1"
223	local srcdir="${BUILDTOOLS}/$tool"
224
225	find "${srcdir}" -name \*.info -exec touch \{\} \;
226	check_error "touch_mkinfo() failed"
227}
228
229# copy headers used for the build
230copy_headers() {
231	local src=$1
232	local target=$2
233
234	headers="$(find "$src" -name \*\.h)"
235	check_error "searching for headers failed"
236
237	headers="$(echo $headers | sed -e "s@$src/@@g")"
238	check_error "sed failed"
239
240	for f in $headers; do
241		local header_dir="$target/$(dirname $f)"
242		safe_mkdir "$header_dir"
243		safe_cp "$src/$f" "$header_dir"
244	done
245}
246
247# Echo the object path to be used for building thin binaries for a given
248# host architecture
249thin_obj_path() {
250	local host_arch=$1
251
252	echo "${OBJROOT}/obj/${host_arch}/"
253}
254
255# Echo the DESTROOT path to be used for thin binaries for a given
256# product and host architecture. Exclude the architecture argument to
257# echo the top-level root path
258thin_destroot_path() {
259	local product="$1"
260	local host_arch="$2"
261
262	if [ -z "${host_arch}" ]; then
263		echo "${OBJROOT}/root/${product}/"
264	else
265		echo "${OBJROOT}/root/${product}/${host_arch}"
266	fi
267}
268
269# Glue together multiple binaries into a single fat binary
270host_fat_glue() {
271	local target="$1"
272	local bins="$2"
273
274	case "${HOST_FAT_TYPE}" in
275		elf)
276			"${DESTROOT}/${prefix}/bin/fatelf-glue" -r "${target}" ${bins}
277			;;
278		macho)
279			"${HAIKU_SRC}/build/scripts/macosx_merge_lipo.pl" ${bins} "${target}"
280			;;
281		none)
282			set -- "${bins}"
283			if [ $# > 1 ]; then
284				fatal "Fat binaries are not supported on this host"
285			fi
286			echo cp -R -p "${bins}" "${target}"
287			;;
288	esac
289}
290
291# The binutils/gcc build processes expect to find toolchain commands under
292# names like '<triplet>-<cmd>', but on fat architectures, only a single
293# non-prefixed command will be available. This function creates triplet-prefixed
294# wrappers.
295#
296# Some commands (ie, ranlib) change their behavior depending on the name under
297# which they're called, so we have to use a shell script for indirection.
298#
299# Two sets of tool wrappers are created:
300# - Host tool wrappers, used for HOSTS.
301# - Target tool wrappers, used for TARGETS.
302make_wrapper_tools() {
303	local arch="$1"
304	local use_host="$2"
305	local toolpath="$3"
306
307	safe_mkdir "${toolpath}"
308	for prog in ar nm ranlib strip ld as; do
309		local cmdname="${arch}-${prog}"
310		local cmdpath="${toolpath}/${cmdname}"
311
312		if [ "${use_host}" = "true" ]; then
313			if [ "${HOST_FAT_TYPE}" != "none" ]; then
314				local fat_support="true"
315				local realpath=$(which ${prog})
316			else
317				local fat_support="false"
318				local realpath="${prog}"
319			fi
320		else
321			local fat_support="true"
322			local realpath="${DESTROOT}/${PREFIX}/bin/${prog}"
323		fi
324
325
326		echo '#!/bin/sh' > "${cmdpath}" || exit 1
327
328		if [ "${prog}" = "as" ] && [ "${fat_support}" == "true" ]; then
329			echo "exec \"${realpath}\" -arch \"$(triple_arch ${arch})\" \$*" >> "${cmdpath}" || exit 1
330		else
331			echo "exec \"${realpath}\" \$*" >> "${cmdpath}" || exit 1
332		fi
333		chmod a+x "${cmdpath}" || exit 1
334	done
335}
336
337# Build all tools
338cmd_build() {
339	local binpath="${DESTROOT}/${PREFIX}/bin"
340	safe_mkdir "${binpath}"
341
342	# Native FatELF tools are required for all additional builds
343	# cmake builds are universal by default.
344	info "Building FatELF tools"
345	run_cmake "fatelf" "$(thin_obj_path universal)/fatelf" "${PREFIX}"
346
347	info "Installing FatELF tools"
348	run_gmake "$(thin_obj_path universal)/fatelf" install
349
350	# Create compatibility links for front-end compiler/assembler drivers
351	safe_ln "${binpath}/fatelf-gcc" "${binpath}/gcc"
352	safe_ln "${binpath}/fatelf-gcc" "${binpath}/g++"
353	safe_ln "${binpath}/fatelf-as" "${binpath}/as"
354
355	# Populate the '<triplet>-<cmd>' command wrappers
356	local target_toolpath="${OBJROOT}/target-wrapper-bin"
357	local host_toolpath="${OBJROOT}/host-wrapper-bin"
358
359	for arch in ${HOSTS}; do
360		make_wrapper_tools "${arch}" "true" "${host_toolpath}"
361	done
362
363	for arch in ${TARGETS}; do
364		make_wrapper_tools "${arch}" "false" "${target_toolpath}"
365	done
366
367	# Build binutils for all hosts/targets.
368	push_path "${host_toolpath}"
369	for host_arch in ${HOSTS}; do
370		# Generate portable binutils for all targets. We'll have to
371		# build as(1) seperately, as it can not support multiple
372		# targets.
373		eval set -- "${TARGETS}"
374		local binutils_target="$1"
375		local binutils_targets=`echo "${TARGETS}" | sed 's/ /,/g'`
376		local binutils_objdir="$(thin_obj_path ${host_arch})/binutils"
377
378		info "Building binutils for ${TARGETS} (host ${host_arch})"
379		touch_mkinfo "binutils"
380		run_autoconf "binutils" "${binutils_objdir}" "${PREFIX}" \
381			--program-prefix="" \
382			--without-gnu-as \
383			"${host_flag}" \
384			"--host=${host_arch}" \
385			"--target=${binutils_target}" \
386			"--enable-targets=${binutils_targets}"
387		run_gmake "${binutils_objdir}" tooldir="${PREFIX}"
388
389		info "Installing binutils for ${TARGETS} (host ${host_arch})"
390
391		local dest="$(thin_destroot_path binutils ${host_arch})"
392		safe_mkdir "${dest}"
393
394		run_gmake "${binutils_objdir}" \
395			DESTDIR="${dest}" \
396			tooldir="${PREFIX}" install
397
398		info "Copying binutils to ${DESTROOT}/${PREFIX} from destroots"
399		host_fat_glue "${DESTROOT}/" "$(thin_destroot_path binutils)/"*
400
401		# Build as(1) for each target
402		for arch in ${TARGETS}; do
403			info "Building assembler for ${arch} (host ${host_arch})"
404
405			local arch_prefix="${PREFIX}/${arch}"
406			local objdir="$(thin_obj_path ${host_arch})/binutils-as-${arch}"
407
408			run_autoconf "binutils" "${objdir}" "${arch_prefix}" \
409				--program-prefix="" \
410				"--host=${host_arch}" \
411				"--target=${arch}"
412
413			run_gmake "${objdir}" tooldir="${arch_prefix}" all
414
415			info "Installing assembler for ${arch} (host ${host_arch})"		
416			local dest="$(thin_destroot_path binutils-as-${arch} ${host_arch})"
417			safe_mkdir "${dest}"
418
419			run_gmake "${objdir}" \
420				DESTDIR="${dest}" \
421				tooldir="${arch_prefix}" install-gas
422				
423			info "Copying ${arch} assembler to ${DESTROOT}/${PREFIX} from destroots"
424			host_fat_glue "${DESTROOT}/" "$(thin_destroot_path binutils-as-${arch})/"*
425		done
426	done
427	pop_path
428
429
430	# Build the compiler for M:N hosts:targets
431	# Populate the object root
432	local sysinc="${OBJROOT}/sysincludes"
433	local syslib="${OBJROOT}/syslib"
434
435	safe_mkdir "${syslib}"
436	safe_mkdir "${sysinc}"
437
438	copy_headers "${HAIKU_SRC}/headers/config" "$sysinc/config"
439	copy_headers "${HAIKU_SRC}/headers/os" "$sysinc/os"
440	copy_headers "${HAIKU_SRC}/headers/posix" "$sysinc/posix"
441
442	touch_mkinfo "gcc"
443	push_path "${DESTROOT}/${target_toolpath}"
444	
445	for host_arch in ${HOSTS}; do
446		for arch in ${TARGETS}; do
447			info "Building compiler for ${arch}"
448			local objdir="$(thin_obj_path ${host_arch})/gcc-${arch}"
449
450			run_autoconf "gcc" "${objdir}" "${PREFIX}" \
451					--program-prefix="${arch}-" \
452					--target="${arch}" \
453					--with-headers="$sysinc" \
454					--with-libs="$syslib" \
455					--disable-nls \
456					--disable-shared \
457					--without-libiconv-prefix \
458					--disable-libstdcxx-pch \
459					--with-htmldir=html-docs --enable-lto \
460					--enable-frame-pointer
461			run_gmake "${objdir}"
462
463			info "Installing compiler for ${arch}"
464			local dest="$(thin_destroot_path gcc ${host_arch})"
465			safe_mkdir "${dest}"
466
467			run_gmake "${objdir}" \
468				DESTDIR="${dest}" \
469				install
470				# FATELF_TODO -- should only be enabled when host=target
471				# This will have to wait until we have a self-hosting toolchain
472				#install-target
473		done
474	done
475	pop_path
476
477	info "Copying gcc to ${DESTROOT}/${PREFIX} from destroots"
478	host_fat_glue "${DESTROOT}/" "$(thin_destroot_path gcc)/"*
479	
480	for arch in ${TARGETS}; do
481	    info "Populating GCC libexec symlinks for ${arch}"
482	    for dir in "${DESTROOT}/${PREFIX}/libexec/gcc/${arch}/"*; do
483	        safe_push_cwd "${dir}"
484	        ln -sf "../../../../bin/ld" "./ld"
485	        safe_pop_cwd
486	    done
487    done
488}
489
490# Clean the build output
491cmd_clean() {
492	info "Cleaning ${OBJROOT} ..."
493	rm -rf "${OBJROOT}"
494}
495
496# Clean the build output and destroot
497cmd_distclean() {
498	cmd_clean
499	if [ ! -z "${DESTROOT}" ]; then
500		info "Cleaning ${DESTROOT} ..."
501		rm -rf "${DESTROOT}"
502	fi
503}
504
505main() {
506	# Find required tools
507	find_tool GMAKE gmake make
508	find_tool CMAKE	cmake
509
510	info "Configured for targets: ${TARGETS}"
511	if [ ! -z "${HOSTS}" ]; then
512		info "Configured for hosts: ${HOSTS}"
513	fi
514
515	info "Logging to ${LOGFILE}"
516	safe_mkdir "`dirname "${LOGFILE}"`"
517	echo -n '' >"${LOGFILE}"
518
519	case "${COMMAND}" in
520		build)
521			cmd_build
522			;;
523		clean)
524			cmd_clean
525			;;
526		distclean)
527			cmd_distclean
528			;;
529		*)
530			fatal_usage "Unknown command: ${cmd}"
531			;;
532	esac
533}
534
535# Print usage
536usage() {
537	# Output to stderr if this occured due to an error, otherwise, stdout
538	# This allows users to page the output of --help, in which case there
539	# isn't an error, and usage is the expected output
540	local err="$1"
541	if [ -z "${err}" ]; then
542		local fd="1"
543	else
544		local fd="2"
545	fi
546
547	fprintf $fd "Usage: $0 <options> <command>"
548	fprintf $fd "Required Options: "
549	fprintf $fd "  --targets    The target triples for which cross tools will be built"
550	fprintf $fd "               (eg, i586-pc-haiku, i686-apple-darwin)"
551	fprintf $fd "  --prefix     The binary installation prefix (eg, /usr/local)."
552	fprintf $fd "  --objroot    The directory in which the tool builds will be performed"
553	fprintf $fd "  --buildtools The path to the Haiku buildtools checkout."
554	fprintf $fd "  --haiku      The path to the Haiku sources."
555
556	fprintf $fd ""
557	fprintf $fd "Options:"
558	fprintf $fd "  -jN          The number of concurrent build jobs. Passed to make."
559	fprintf $fd "  --destroot   The installation destroot. This is where the files will"
560	fprintf $fd "               actually be installed."
561	fprintf $fd "  --hosts      The host architectures for which the cross tools will be built."
562	fprintf $fd "               Accepts the same target triple values as --targets."
563	fprintf $fd "               This value is only supported when building a cross-compiler"
564	fprintf $fd "               on a Mach-O or FatELF host."
565
566	fprintf $fd ""
567	fprintf $fd "Commands: "
568	fprintf $fd "  clean		Clean any intermediate build output."
569	fprintf $fd "  distclean	Clean any intermediate build output, as well as the target"
570	fprintf $fd "               destroot (Caution!)."
571	fprintf $fd "  build		Build and install the cross tools (in the destroot, if"
572	fprintf $fd "               specified)."
573}
574
575# Print the provided message, usage, and then exit with an error
576fatal_usage() {
577	local msg="$1"
578	error "${msg}\n"
579	usage 1
580	exit 1
581}
582
583# Verify that a required option was supplied
584require_opt() {
585	local optname="$1"
586	local var="$2"
587	if [ -z "${var}" ]; then
588		fatal_usage "Missing required flag ${optname}"
589	fi
590}
591
592# Verify that a required option was supplied and points
593# to an existing directory.
594require_dir_opt() {
595	local optname="$1"
596	local dir="$2"
597
598	require_opt "${optname}" "${dir}"
599	if [ -f "${dir}" ]; then
600		fatal "Not a directory: ${dir}"
601	fi
602
603	if [ ! -d "${dir}" ]; then
604		fatal "No such directory: ${dir}"
605	fi
606}
607
608
609# Parse command line arguments
610while [ $# -gt 0 ]; do
611	case $1 in
612		--targets)
613			shift
614			TARGETS="$1"
615			shift
616			;;
617		--hosts)
618			shift
619			HOSTS="$1"
620			shift
621			;;
622		--prefix)
623			shift
624			abspath "$1" PREFIX
625			shift
626			;;
627		--objroot)
628			shift
629			abspath "$1" OBJROOT
630			shift
631			;;
632		--destroot)
633			shift
634			abspath "$1" DESTROOT
635			shift
636			;;
637		--buildtools)
638			shift
639			abspath "$1" BUILDTOOLS
640			shift
641			;;
642		--haiku)
643			shift
644			abspath "$1" HAIKU_SRC
645			shift
646			;;
647		-j)
648			shift
649			JOBS="-j$1"
650			shift
651			;;
652		-j*)
653			JOBS="$1"
654			shift
655			;;
656		-h|--help)
657			usage
658			exit 0
659			;;
660		-*)
661			fatal_usage "Unknown option $1"
662			;;
663		*)
664			# Check if command has already been specified
665			if [ ! -z "${COMMAND}" ]; then
666				fatal_usage "Unexpected command $1. Multiple commands were specified"
667			fi
668			COMMAND="${1}"
669			shift
670			;;
671	esac
672done
673
674if [ "${COMMAND}" != "clean" ] && [ "${COMMAND}"  != "distclean" ]; then
675	require_opt "--prefix" "${PREFIX}"
676	require_opt "--targets" "${TARGETS}"
677	require_dir_opt "--buildtools" "${BUILDTOOLS}"
678	require_dir_opt "--haiku" "${HAIKU_SRC}"
679fi
680
681require_opt "--objroot" "${OBJROOT}"
682
683if [ -z "${COMMAND}" ]; then
684	fatal_usage "No command specified (one of build, distclean, clean)"
685	exit 1
686fi
687
688# Determine the fat binary format to use for the given arch triples. If the
689# arch triples use systems with incompatible types, or multiple architectures
690# are specified on a non-fat system, fatal is called.
691#
692# We assume FatELF support on non-Darwin systems. FatELF is not actually
693# used unless multiple architectures are targeted.
694fat_type_for_archs() {
695	local archs="$1"
696
697	local type=""
698	local last_system=""
699	for arch in ${archs}; do
700		local system=$(triple_system "${arch}")
701		case "${system}" in
702			darwin) local new_type="macho" ;;
703			haiku) local new_type="elf" ;;
704			*) local new_type="none"
705		esac
706
707		# Enforce a single format. Mixing Mach-O/ELF is not possible.
708		if [ ! -z "${type}" ] && [ "${type}" != "${new_type}" ]; then
709			fatal "Specified multiple fat targets that use an incompatible fat binary format: ${archs}"
710		fi
711		local type="${new_type}"
712
713		# Mach-O does not support combining binaries for multiple operating
714		# systems in the same file, but FatELF does.
715		if [ ! -z "${last_system}" ] && [ "${last_system}" != "${system}" ]; then
716			if [ "${type}" = "macho" ]; then
717				fatal "Specified multiple fat systems: ${archs}"
718			fi
719		fi
720		local last_system="${system}"
721	done
722
723	if [ ! -z "${type}" ]; then
724		set -- ${archs}
725		if [ $# -gt 1 ] && [ "${type}" = "none" ]; then
726			fatal "Can't build for multiple hosts. Fat binaries are not supported for these architectures."
727		fi
728	fi
729
730	echo "${type}"
731}
732
733# Set a default host value if none set
734if [ -z "${HOSTS}" ]; then
735	HOSTS="$(${BUILDTOOLS}/gcc/config.guess)"
736fi
737
738# Determine which type of fat binaries the target(s) and host(s) support
739TARGET_FAT_TYPE="$(fat_type_for_archs "${TARGETS}")"
740check_error "Unsupported target architecture specification"
741
742HOST_FAT_TYPE="$(fat_type_for_archs "${HOSTS}")"
743check_error "Unsupported host architecture specification"
744
745LOGFILE="${OBJROOT}/build.log"
746
747# Run the main routine
748main