#!/bin/sh CWD=`pwd` # Print an info-level message info() { echo "$1" } fprintf() { local fd="$1" local msg="$2" echo "${msg}" 1>&${fd} } # Print an error-level message to stderr error() { fprintf 2 "ERROR: $1" } # Print a fatal message to stderr and exit fatal() { error "$1" exit 1 } # Check for a non-zero error code # If non-zero, print the fatal error message check_error() { if [ $? != 0 ]; then fatal "$1" fi } # Resolve a relative path abspath() { local path="$1" local var="$2" case "${path}" in /*) # Nothing to do, absolute path eval "${var}=\"${path}\"" ;; *) eval "${var}=\"${CWD}/${path}\"" ;; esac } find_tool() { local varname="$1" shift local names="$*" while [ ! -z "${1}" ]; do local path=`which "${1}"` if [ ! -z "${path}" ]; then eval "${varname}=\"${path}\"" return fi shift done fatal "Could not find required tool: ${names}" } safe_mkdir() { local dir="$1" mkdir -p "${dir}" check_error "Failed to create directory: ${dir}" } safe_cp() { local src="$1" local dest="$2" cp "${src}" "${dest}" check_error "Failed to copy file" } # Push a new working directory and verify success safe_push_cwd() { local dir="$1" pushd "${dir}" >/dev/null check_error "Failed to change to directory: ${dir}" } # Pop a working directory pushed via safe_push_cwd safe_pop_cwd() { popd >/dev/null check_error "Failed to change cwd" } # Create a hard link from arg2 (dest) to arg1 (source) safe_ln() { local src="$1" local dest="$2" ln -f "${src}" "${dest}" check_error "ln $1 $2 failed" } # Push a directory onto the PATH environment variable push_path() { local dir="$1" PATH="$1:$PATH" } # Pop a directory from the head of the PATH environment variable pop_path() { export PATH="$(echo "${PATH}" | cut -d : -f 2-)" } # Append a value to a list and echo the result to stdout. list_append() { local list="$1" local value="$2" # Use xargs to strip excess whitespace echo "${list}" "${2}" | xargs } # Perform an autotools build run_autoconf() { local tool="$1" local objdir="$2" local prefix="$3" # Move the configure args to pos $1 shift; shift; shift; local srcdir="${BUILDTOOLS}/$tool" safe_mkdir "${objdir}" safe_push_cwd "${objdir}" # Skip reconfiguring if a config.log already exists if [ ! -f "${objdir}/config.log" ]; then "${srcdir}/configure" -C --prefix="${prefix}" $@ >> "${LOGFILE}" 2>&1 check_error "Configure failed" fi } # Extract the arch from a host triple, and then map to a canonical arch triple_arch() { local triple="$1" local arch=$(echo "$triple" | awk -F '-' '{print $1}') case "${arch}" in x86) echo "i386";; i486) echo "i386";; i586) echo "i386";; i686) echo "i386";; *) echo "${arch}";; esac } # Extract the OS name from a host triple triple_system() { local triple="$1" echo "${triple}" | awk -F- '{print $3}' | sed 's/[0-9\.]*$//' } # Perform a cmake build run_cmake() { local tool="$1" local objdir="$2" local prefix="$3" local srcdir="${BUILDTOOLS}/$tool" local builddir="$objdir" safe_mkdir "${builddir}" safe_push_cwd "${builddir}" # Assemble the host architecture list for cmake local cmake_archs_env="" if [ ! -z "${HOSTS}" ]; then for arch in ${HOSTS}; do if [ ! -z "${cmake_archs_env}" ]; then cmake_archs_env="${cmake_archs_env};" fi local cmake_archs_env="${cmake_archs_env}$(triple_arch ${arch})" done local cmake_archs_env="CMAKE_OSX_ARCHITECTURES=${cmake_archs_env}" fi env "${cmake_archs_env}" "${CMAKE}" \ "-DCMAKE_INSTALL_PREFIX:PATH=${prefix}" \ "${srcdir}" >> "${LOGFILE}" 2>&1 check_error "${CMAKE} failed" } # Install using gmake. Assumes that the Makefile supports DESTDIR, which should # be the case when using either autoconf or cmake run_gmake() { local objdir="$1" # Move the make args to pos $1 shift; safe_push_cwd "${objdir}" if [ -z "${DESTROOT}" ]; then "${GMAKE}" ${JOBS} "$@" >> "${LOGFILE}" 2>&1 else # Ensure that our caller can override DESTDIR by placing the caller's # arguments after our own. "${GMAKE}" ${JOBS} "DESTDIR=${DESTROOT}" "$@" >> "${LOGFILE}" 2>&1 fi check_error "${GMAKE} failed" } # touch all info files in order to avoid the dependency on makeinfo # (which apparently doesn't work reliably on all the different host # configurations and changes files which in turn appear as local changes # to the VCS). touch_mkinfo() { local tool="$1" local srcdir="${BUILDTOOLS}/$tool" find "${srcdir}" -name \*.info -exec touch \{\} \; check_error "touch_mkinfo() failed" } # copy headers used for the build copy_headers() { local src=$1 local target=$2 headers="$(find "$src" -name \*\.h)" check_error "searching for headers failed" headers="$(echo $headers | sed -e "s@$src/@@g")" check_error "sed failed" for f in $headers; do local header_dir="$target/$(dirname $f)" safe_mkdir "$header_dir" safe_cp "$src/$f" "$header_dir" done } # Echo the object path to be used for building thin binaries for a given # host architecture thin_obj_path() { local host_arch=$1 echo "${OBJROOT}/obj/${host_arch}/" } # Echo the DESTROOT path to be used for thin binaries for a given # product and host architecture. Exclude the architecture argument to # echo the top-level root path thin_destroot_path() { local product="$1" local host_arch="$2" if [ -z "${host_arch}" ]; then echo "${OBJROOT}/root/${product}/" else echo "${OBJROOT}/root/${product}/${host_arch}" fi } # Glue together multiple binaries into a single fat binary host_fat_glue() { local target="$1" local bins="$2" case "${HOST_FAT_TYPE}" in elf) "${DESTROOT}/${prefix}/bin/fatelf-glue" -r "${target}" ${bins} ;; macho) "${HAIKU_SRC}/build/scripts/macosx_merge_lipo.pl" ${bins} "${target}" ;; none) set -- "${bins}" if [ $# > 1 ]; then fatal "Fat binaries are not supported on this host" fi echo cp -R -p "${bins}" "${target}" ;; esac } # The binutils/gcc build processes expect to find toolchain commands under # names like '-', but on fat architectures, only a single # non-prefixed command will be available. This function creates triplet-prefixed # wrappers. # # Some commands (ie, ranlib) change their behavior depending on the name under # which they're called, so we have to use a shell script for indirection. # # Two sets of tool wrappers are created: # - Host tool wrappers, used for HOSTS. # - Target tool wrappers, used for TARGETS. make_wrapper_tools() { local arch="$1" local use_host="$2" local toolpath="$3" safe_mkdir "${toolpath}" for prog in ar nm ranlib strip ld as; do local cmdname="${arch}-${prog}" local cmdpath="${toolpath}/${cmdname}" if [ "${use_host}" = "true" ]; then if [ "${HOST_FAT_TYPE}" != "none" ]; then local fat_support="true" local realpath=$(which ${prog}) else local fat_support="false" local realpath="${prog}" fi else local fat_support="true" local realpath="${DESTROOT}/${PREFIX}/bin/${prog}" fi echo '#!/bin/sh' > "${cmdpath}" || exit 1 if [ "${prog}" = "as" ] && [ "${fat_support}" == "true" ]; then echo "exec \"${realpath}\" -arch \"$(triple_arch ${arch})\" \$*" >> "${cmdpath}" || exit 1 else echo "exec \"${realpath}\" \$*" >> "${cmdpath}" || exit 1 fi chmod a+x "${cmdpath}" || exit 1 done } # Build all tools cmd_build() { local binpath="${DESTROOT}/${PREFIX}/bin" safe_mkdir "${binpath}" # Native FatELF tools are required for all additional builds # cmake builds are universal by default. info "Building FatELF tools" run_cmake "fatelf" "$(thin_obj_path universal)/fatelf" "${PREFIX}" info "Installing FatELF tools" run_gmake "$(thin_obj_path universal)/fatelf" install # Create compatibility links for front-end compiler/assembler drivers safe_ln "${binpath}/fatelf-gcc" "${binpath}/gcc" safe_ln "${binpath}/fatelf-gcc" "${binpath}/g++" safe_ln "${binpath}/fatelf-as" "${binpath}/as" # Populate the '-' command wrappers local target_toolpath="${OBJROOT}/target-wrapper-bin" local host_toolpath="${OBJROOT}/host-wrapper-bin" for arch in ${HOSTS}; do make_wrapper_tools "${arch}" "true" "${host_toolpath}" done for arch in ${TARGETS}; do make_wrapper_tools "${arch}" "false" "${target_toolpath}" done # Build binutils for all hosts/targets. push_path "${host_toolpath}" for host_arch in ${HOSTS}; do # Generate portable binutils for all targets. We'll have to # build as(1) seperately, as it can not support multiple # targets. eval set -- "${TARGETS}" local binutils_target="$1" local binutils_targets=`echo "${TARGETS}" | sed 's/ /,/g'` local binutils_objdir="$(thin_obj_path ${host_arch})/binutils" info "Building binutils for ${TARGETS} (host ${host_arch})" touch_mkinfo "binutils" run_autoconf "binutils" "${binutils_objdir}" "${PREFIX}" \ --program-prefix="" \ --without-gnu-as \ "${host_flag}" \ "--host=${host_arch}" \ "--target=${binutils_target}" \ "--enable-targets=${binutils_targets}" run_gmake "${binutils_objdir}" tooldir="${PREFIX}" info "Installing binutils for ${TARGETS} (host ${host_arch})" local dest="$(thin_destroot_path binutils ${host_arch})" safe_mkdir "${dest}" run_gmake "${binutils_objdir}" \ DESTDIR="${dest}" \ tooldir="${PREFIX}" install info "Copying binutils to ${DESTROOT}/${PREFIX} from destroots" host_fat_glue "${DESTROOT}/" "$(thin_destroot_path binutils)/"* # Build as(1) for each target for arch in ${TARGETS}; do info "Building assembler for ${arch} (host ${host_arch})" local arch_prefix="${PREFIX}/${arch}" local objdir="$(thin_obj_path ${host_arch})/binutils-as-${arch}" run_autoconf "binutils" "${objdir}" "${arch_prefix}" \ --program-prefix="" \ "--host=${host_arch}" \ "--target=${arch}" run_gmake "${objdir}" tooldir="${arch_prefix}" all info "Installing assembler for ${arch} (host ${host_arch})" local dest="$(thin_destroot_path binutils-as-${arch} ${host_arch})" safe_mkdir "${dest}" run_gmake "${objdir}" \ DESTDIR="${dest}" \ tooldir="${arch_prefix}" install-gas info "Copying ${arch} assembler to ${DESTROOT}/${PREFIX} from destroots" host_fat_glue "${DESTROOT}/" "$(thin_destroot_path binutils-as-${arch})/"* done done pop_path # Build the compiler for M:N hosts:targets # Populate the object root local sysinc="${OBJROOT}/sysincludes" local syslib="${OBJROOT}/syslib" safe_mkdir "${syslib}" safe_mkdir "${sysinc}" copy_headers "${HAIKU_SRC}/headers/config" "$sysinc/config" copy_headers "${HAIKU_SRC}/headers/os" "$sysinc/os" copy_headers "${HAIKU_SRC}/headers/posix" "$sysinc/posix" touch_mkinfo "gcc" push_path "${DESTROOT}/${target_toolpath}" for host_arch in ${HOSTS}; do for arch in ${TARGETS}; do info "Building compiler for ${arch}" local objdir="$(thin_obj_path ${host_arch})/gcc-${arch}" run_autoconf "gcc" "${objdir}" "${PREFIX}" \ --program-prefix="${arch}-" \ --target="${arch}" \ --with-headers="$sysinc" \ --with-libs="$syslib" \ --disable-nls \ --disable-shared \ --without-libiconv-prefix \ --disable-libstdcxx-pch \ --with-htmldir=html-docs --enable-lto \ --enable-frame-pointer run_gmake "${objdir}" info "Installing compiler for ${arch}" local dest="$(thin_destroot_path gcc ${host_arch})" safe_mkdir "${dest}" run_gmake "${objdir}" \ DESTDIR="${dest}" \ install # FATELF_TODO -- should only be enabled when host=target # This will have to wait until we have a self-hosting toolchain #install-target done done pop_path info "Copying gcc to ${DESTROOT}/${PREFIX} from destroots" host_fat_glue "${DESTROOT}/" "$(thin_destroot_path gcc)/"* for arch in ${TARGETS}; do info "Populating GCC libexec symlinks for ${arch}" for dir in "${DESTROOT}/${PREFIX}/libexec/gcc/${arch}/"*; do safe_push_cwd "${dir}" ln -sf "../../../../bin/ld" "./ld" safe_pop_cwd done done } # Clean the build output cmd_clean() { info "Cleaning ${OBJROOT} ..." rm -rf "${OBJROOT}" } # Clean the build output and destroot cmd_distclean() { cmd_clean if [ ! -z "${DESTROOT}" ]; then info "Cleaning ${DESTROOT} ..." rm -rf "${DESTROOT}" fi } main() { # Find required tools find_tool GMAKE gmake make find_tool CMAKE cmake info "Configured for targets: ${TARGETS}" if [ ! -z "${HOSTS}" ]; then info "Configured for hosts: ${HOSTS}" fi info "Logging to ${LOGFILE}" safe_mkdir "`dirname "${LOGFILE}"`" echo -n '' >"${LOGFILE}" case "${COMMAND}" in build) cmd_build ;; clean) cmd_clean ;; distclean) cmd_distclean ;; *) fatal_usage "Unknown command: ${cmd}" ;; esac } # Print usage usage() { # Output to stderr if this occured due to an error, otherwise, stdout # This allows users to page the output of --help, in which case there # isn't an error, and usage is the expected output local err="$1" if [ -z "${err}" ]; then local fd="1" else local fd="2" fi fprintf $fd "Usage: $0 " fprintf $fd "Required Options: " fprintf $fd " --targets The target triples for which cross tools will be built" fprintf $fd " (eg, i586-pc-haiku, i686-apple-darwin)" fprintf $fd " --prefix The binary installation prefix (eg, /usr/local)." fprintf $fd " --objroot The directory in which the tool builds will be performed" fprintf $fd " --buildtools The path to the Haiku buildtools checkout." fprintf $fd " --haiku The path to the Haiku sources." fprintf $fd "" fprintf $fd "Options:" fprintf $fd " -jN The number of concurrent build jobs. Passed to make." fprintf $fd " --destroot The installation destroot. This is where the files will" fprintf $fd " actually be installed." fprintf $fd " --hosts The host architectures for which the cross tools will be built." fprintf $fd " Accepts the same target triple values as --targets." fprintf $fd " This value is only supported when building a cross-compiler" fprintf $fd " on a Mach-O or FatELF host." fprintf $fd "" fprintf $fd "Commands: " fprintf $fd " clean Clean any intermediate build output." fprintf $fd " distclean Clean any intermediate build output, as well as the target" fprintf $fd " destroot (Caution!)." fprintf $fd " build Build and install the cross tools (in the destroot, if" fprintf $fd " specified)." } # Print the provided message, usage, and then exit with an error fatal_usage() { local msg="$1" error "${msg}\n" usage 1 exit 1 } # Verify that a required option was supplied require_opt() { local optname="$1" local var="$2" if [ -z "${var}" ]; then fatal_usage "Missing required flag ${optname}" fi } # Verify that a required option was supplied and points # to an existing directory. require_dir_opt() { local optname="$1" local dir="$2" require_opt "${optname}" "${dir}" if [ -f "${dir}" ]; then fatal "Not a directory: ${dir}" fi if [ ! -d "${dir}" ]; then fatal "No such directory: ${dir}" fi } # Parse command line arguments while [ $# -gt 0 ]; do case $1 in --targets) shift TARGETS="$1" shift ;; --hosts) shift HOSTS="$1" shift ;; --prefix) shift abspath "$1" PREFIX shift ;; --objroot) shift abspath "$1" OBJROOT shift ;; --destroot) shift abspath "$1" DESTROOT shift ;; --buildtools) shift abspath "$1" BUILDTOOLS shift ;; --haiku) shift abspath "$1" HAIKU_SRC shift ;; -j) shift JOBS="-j$1" shift ;; -j*) JOBS="$1" shift ;; -h|--help) usage exit 0 ;; -*) fatal_usage "Unknown option $1" ;; *) # Check if command has already been specified if [ ! -z "${COMMAND}" ]; then fatal_usage "Unexpected command $1. Multiple commands were specified" fi COMMAND="${1}" shift ;; esac done if [ "${COMMAND}" != "clean" ] && [ "${COMMAND}" != "distclean" ]; then require_opt "--prefix" "${PREFIX}" require_opt "--targets" "${TARGETS}" require_dir_opt "--buildtools" "${BUILDTOOLS}" require_dir_opt "--haiku" "${HAIKU_SRC}" fi require_opt "--objroot" "${OBJROOT}" if [ -z "${COMMAND}" ]; then fatal_usage "No command specified (one of build, distclean, clean)" exit 1 fi # Determine the fat binary format to use for the given arch triples. If the # arch triples use systems with incompatible types, or multiple architectures # are specified on a non-fat system, fatal is called. # # We assume FatELF support on non-Darwin systems. FatELF is not actually # used unless multiple architectures are targeted. fat_type_for_archs() { local archs="$1" local type="" local last_system="" for arch in ${archs}; do local system=$(triple_system "${arch}") case "${system}" in darwin) local new_type="macho" ;; haiku) local new_type="elf" ;; *) local new_type="none" esac # Enforce a single format. Mixing Mach-O/ELF is not possible. if [ ! -z "${type}" ] && [ "${type}" != "${new_type}" ]; then fatal "Specified multiple fat targets that use an incompatible fat binary format: ${archs}" fi local type="${new_type}" # Mach-O does not support combining binaries for multiple operating # systems in the same file, but FatELF does. if [ ! -z "${last_system}" ] && [ "${last_system}" != "${system}" ]; then if [ "${type}" = "macho" ]; then fatal "Specified multiple fat systems: ${archs}" fi fi local last_system="${system}" done if [ ! -z "${type}" ]; then set -- ${archs} if [ $# -gt 1 ] && [ "${type}" = "none" ]; then fatal "Can't build for multiple hosts. Fat binaries are not supported for these architectures." fi fi echo "${type}" } # Set a default host value if none set if [ -z "${HOSTS}" ]; then HOSTS="$(${BUILDTOOLS}/gcc/config.guess)" fi # Determine which type of fat binaries the target(s) and host(s) support TARGET_FAT_TYPE="$(fat_type_for_archs "${TARGETS}")" check_error "Unsupported target architecture specification" HOST_FAT_TYPE="$(fat_type_for_archs "${HOSTS}")" check_error "Unsupported host architecture specification" LOGFILE="${OBJROOT}/build.log" # Run the main routine main