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