yann@1: # This file contains some usefull common functions yann@1: # Copyright 2007 Yann E. MORIN yann@1: # Licensed under the GPL v2. See COPYING in the root of this package yann@1: yann@1: CT_OnError() { yann@1: ret=$? yann@1: CT_DoLog ERROR "Build failed in step \"${CT_STEP_MESSAGE[${CT_STEP_COUNT}]}\"" yann@1: for((step=(CT_STEP_COUNT-1); step>1; step--)); do yann@1: CT_DoLog ERROR " called in step \"${CT_STEP_MESSAGE[${step}]}\"" yann@1: done yann@1: CT_DoLog ERROR "Error happened in \"${BASH_SOURCE[1]}\" in function \"${FUNCNAME[1]}\" (line unknown, sorry)" yann@1: for((depth=2; ${BASH_LINENO[$((${depth}-1))]}>0; depth++)); do yann@1: CT_DoLog ERROR " called from \"${BASH_SOURCE[${depth}]}\" at line # ${BASH_LINENO[${depth}-1]} in function \"${FUNCNAME[${depth}]}\"" yann@1: done yann@1: CT_DoLog ERROR "Look at \"${CT_ACTUAL_LOG_FILE}\" for more info on this error." yann@1: exit $ret yann@1: } yann@1: trap CT_OnError ERR yann@1: yann@1: set -E yann@1: set -o pipefail yann@1: yann@1: # This is crosstool-ng-0.0.1 yann@1: CT_VERSION=ng-0.0.1 yann@1: yann@1: # The different log levels: yann@1: CT_LOG_LEVEL_ERROR=0 yann@1: CT_LOG_LEVEL_WARN=1 yann@1: CT_LOG_LEVEL_INFO=2 yann@1: CT_LOG_LEVEL_EXTRA=3 yann@1: CT_LOG_LEVEL_DEBUG=4 yann@1: yann@1: # Attributes yann@1: _A_NOR="\\033[0m" yann@1: _A_BRI="\\033[1m" yann@1: _A_DIM="\\033[2m" yann@1: _A_UND="\\033[4m" yann@1: _A_BRB="\\033[5m" yann@1: _A_REV="\\033[7m" yann@1: _A_HID="\\033[8m" yann@1: yann@1: # Fore colors yann@1: _F_BLK="\\033[30m" yann@1: _F_RED="\\033[31m" yann@1: _F_GRN="\\033[32m" yann@1: _F_YEL="\\033[33m" yann@1: _F_BLU="\\033[34m" yann@1: _F_MAG="\\033[35m" yann@1: _F_CYA="\\033[36m" yann@1: _F_WHI="\\033[37m" yann@1: yann@1: # A function to log what is happening yann@1: # Different log level are available: yann@1: # - ERROR: A serious, fatal error occurred yann@1: # - WARN: A non fatal, non serious error occurred, take your responsbility with the generated build yann@1: # - INFO: Informational messages yann@1: # - EXTRA: Extra informational messages yann@1: # - DEBUG: Debug messages yann@1: # Usage: CT_DoLog [message] yann@1: # If message is empty, then stdin will be logged. yann@1: CT_DoLog() { yann@47: local max_level LEVEL level cur_l cur_L yann@47: local l yann@1: eval max_level="\${CT_LOG_LEVEL_${CT_LOG_LEVEL_MAX}}" yann@1: # Set the maximum log level to DEBUG if we have none yann@1: [ -z ${max_level} ] && max_level=${CT_LOG_LEVEL_DEBUG} yann@1: yann@47: LEVEL="$1"; shift yann@1: eval level="\${CT_LOG_LEVEL_${LEVEL}}" yann@1: yann@1: if [ $# -eq 0 ]; then yann@1: cat - yann@1: else yann@1: echo "${1}" yann@1: fi |( IFS="\n" # We want the full lines, even leading spaces yann@1: cpt=0 yann@1: indent=$((2*CT_STEP_COUNT)) yann@1: while read line; do yann@47: case "${CT_LOG_SEE_TOOLS_WARN},${line}" in yann@47: y,*"warning:"*) cur_L=WARN; cur_l=${CT_LOG_LEVEL_WARN};; yann@47: *"error:"*) cur_L=ERROR; cur_l=${CT_LOG_LEVEL_ERROR};; yann@47: "make["?*"]:"*"Stop.") cur_L=ERROR; cur_l=${CT_LOG_LEVEL_ERROR};; yann@47: *) cur_L="${LEVEL}"; cur_l="${level}";; yann@47: esac yann@47: l="`printf \"[%-5s]%*s%s%s\" \"${cur_L}\" \"${indent}\" \" \" \"${line}\"`" yann@1: # There will always be a log file, be it /dev/null yann@1: echo -e "${l}" >>"${CT_ACTUAL_LOG_FILE}" yann@47: color="CT_${cur_L}_COLOR" yann@1: normal="CT_NORMAL_COLOR" yann@47: if [ ${cur_l} -le ${max_level} ]; then yann@1: echo -e "${!color}${l}${!normal}" yann@1: else yann@1: ${CT_PROG_BAR} yann@1: fi yann@1: done yann@1: ) yann@1: yann@1: return 0 yann@1: } yann@1: yann@1: # Abort the execution with a error message yann@1: # Usage: CT_Abort yann@1: CT_Abort() { yann@1: CT_DoLog ERROR "$1" >&2 yann@1: exit 1 yann@1: } yann@1: yann@1: # Test a condition, and print a message if satisfied yann@1: # Usage: CT_Test yann@1: CT_Test() { yann@1: local ret yann@1: local m="$1" yann@1: shift yann@1: test "$@" && CT_DoLog WARN "$m" yann@1: return 0 yann@1: } yann@1: yann@1: # Test a condition, and abort with an error message if satisfied yann@1: # Usage: CT_TestAndAbort yann@1: CT_TestAndAbort() { yann@1: local m="$1" yann@1: shift yann@1: test "$@" && CT_Abort "$m" yann@1: return 0 yann@1: } yann@1: yann@1: # Test a condition, and abort with an error message if not satisfied yann@1: # Usage: CT_TestAndAbort yann@1: CT_TestOrAbort() { yann@1: local m="$1" yann@1: shift yann@1: test "$@" || CT_Abort "$m" yann@1: return 0 yann@1: } yann@1: yann@1: # Test the presence of a tool, or abort if not found yann@1: # Usage: CT_HasOrAbort yann@1: CT_HasOrAbort() { yann@1: CT_TestAndAbort "\"${1}\" not found and needed for successfull toolchain build." -z "`which \"${1}\"`" yann@1: return 0 yann@1: } yann@1: yann@1: # Get current date with nanosecond precision yann@1: # On those system not supporting nanosecond precision, faked with rounding down yann@1: # to the highest entire second yann@1: # Usage: CT_DoDate yann@1: CT_DoDate() { yann@1: date "$1" |sed -r -e 's/%N$/000000000/;' yann@1: } yann@1: yann@1: CT_STEP_COUNT=1 yann@1: CT_STEP_MESSAGE[${CT_STEP_COUNT}]="" yann@1: # Memorise a step being done so that any error is caught yann@1: # Usage: CT_DoStep yann@1: CT_DoStep() { yann@1: local start=`CT_DoDate +%s%N` yann@1: CT_DoLog "$1" "=================================================================" yann@1: CT_DoLog "$1" "$2" yann@1: CT_STEP_COUNT=$((CT_STEP_COUNT+1)) yann@1: CT_STEP_LEVEL[${CT_STEP_COUNT}]="$1"; shift yann@1: CT_STEP_START[${CT_STEP_COUNT}]="${start}" yann@1: CT_STEP_MESSAGE[${CT_STEP_COUNT}]="$1" yann@1: return 0 yann@1: } yann@1: yann@1: # End the step just being done yann@1: # Usage: CT_EndStep yann@1: CT_EndStep() { yann@1: local stop=`CT_DoDate +%s%N` yann@1: local duration=`printf "%032d" $((stop-${CT_STEP_START[${CT_STEP_COUNT}]})) |sed -r -e 's/([[:digit:]]{2})[[:digit:]]{7}$/\.\1/; s/^0+//; s/^\./0\./;'` yann@1: local level="${CT_STEP_LEVEL[${CT_STEP_COUNT}]}" yann@1: local message="${CT_STEP_MESSAGE[${CT_STEP_COUNT}]}" yann@1: CT_STEP_COUNT=$((CT_STEP_COUNT-1)) yann@1: CT_DoLog "${level}" "${message}: done in ${duration}s" yann@1: return 0 yann@1: } yann@1: yann@1: # Pushes into a directory, and pops back yann@1: CT_Pushd() { yann@1: pushd "$1" >/dev/null 2>&1 yann@1: } yann@1: CT_Popd() { yann@1: popd >/dev/null 2>&1 yann@1: } yann@1: yann@1: # Makes a path absolute yann@1: # Usage: CT_MakeAbsolutePath path yann@1: CT_MakeAbsolutePath() { yann@1: # Try to cd in that directory yann@1: if [ -d "$1" ]; then yann@1: CT_Pushd "$1" yann@1: pwd yann@1: CT_Popd yann@1: else yann@1: # No such directory, fail back to guessing yann@1: case "$1" in yann@1: /*) echo "$1";; yann@1: *) echo "`pwd`/$1";; yann@1: esac yann@1: fi yann@1: yann@1: return 0 yann@1: } yann@1: yann@1: # Creates a temporary directory yann@1: # $1: variable to assign to yann@1: # Usage: CT_MktempDir foo yann@1: CT_MktempDir() { yann@1: # Some mktemp do not allow more than 6 Xs yann@1: eval "$1"="`mktemp -q -d \"${CT_BUILD_DIR}/.XXXXXX\"`" yann@1: CT_TestOrAbort "Could not make temporary directory" -n "${!1}" -a -d "${!1}" yann@1: } yann@1: yann@1: # Echoes the specified string on stdout until the pipe breaks. yann@1: # Doesn't fail yann@1: # $1: string to echo yann@1: # Usage: CT_DoYes "" |make oldconfig yann@1: CT_DoYes() { yann@1: yes "$1" || true yann@1: } yann@63: yann@63: # Download an URL using wget yann@63: # Usage: CT_DoGetFileWget yann@63: CT_DoGetFileWget() { yann@63: # Need to return true because it is legitimate to not find the tarball at yann@63: # some of the provided URLs (think about snapshots, different layouts for yann@63: # different gcc versions, etc...) yann@63: # Some (very old!) FTP server might not support the passive mode, thus yann@63: # retry without yann@63: # With automated download as we are doing, it can be very dangerous to use yann@63: # -c to continue the downloads. It's far better to simply overwrite the yann@63: # destination file yann@63: wget -nc --progress=dot:binary --tries=3 --passive-ftp "$1" || wget -nc --progress=dot:binary --tries=3 "$1" || true yann@63: } yann@63: yann@63: # Download an URL using curl yann@63: # Usage: CT_DoGetFileCurl yann@63: CT_DoGetFileCurl() { yann@63: # Note: comments about wget method are also valid here yann@63: # Plus: no good progreess indicator is available with curl, yann@63: # so output is consigned to oblivion yann@63: curl --ftp-pasv -O --retry 3 "$1" >/dev/null || curl -O --retry 3 "$1" >/dev/null || true yann@63: } yann@63: yann@63: # Wrapper function to call one of curl or wget yann@63: # Usage: CT_DoGetFile yann@63: CT_DoGetFile() { yann@63: local _wget=`which wget` yann@63: local _curl=`which curl` yann@63: case "${_wget},${_curl}" in yann@63: ,) CT_DoError "Could find neither wget nor curl";; yann@63: ,*) CT_DoGetFileCurl "$1";; yann@63: *) CT_DoGetFileWget "$1";; yann@63: esac yann@63: } yann@63: yann@63: # Download the file from one of the URLs passed as argument yann@63: # Usage: CT_GetFile [ ...] yann@63: CT_GetFile() { yann@63: local got_it yann@63: local ext yann@63: local url yann@63: local file="$1" yann@63: shift yann@63: yann@63: # Do we already have it? yann@63: ext=`CT_GetFileExtension "${file}"` yann@63: if [ -n "${ext}" ]; then yann@63: if [ "${CT_FORCE_DOWNLOAD}" = "y" ]; then yann@63: rm -f "${CT_TARBALLS_DIR}/${file}${ext}" yann@63: else yann@63: return 0 yann@63: fi yann@63: fi yann@63: yann@63: CT_DoLog EXTRA "Retrieving \"${file}\"" yann@63: CT_Pushd "${CT_TARBALLS_DIR}" yann@63: # File not yet downloaded, try to get it yann@63: got_it=0 yann@63: if [ "${got_it}" != "y" ]; then yann@63: # We'd rather have a bzip2'ed tarball, then gzipped, and finally plain tar. yann@63: for ext in .tar.bz2 .tar.gz .tgz .tar; do yann@63: # Try all urls in turn yann@63: for url in "$@"; do yann@63: case "${url}" in yann@63: *) CT_DoLog EXTRA "Trying \"${url}/${file}${ext}\"" yann@63: CT_DoGetFile "${url}/${file}${ext}" 2>&1 |CT_DoLog DEBUG yann@63: ;; yann@63: esac yann@63: [ -f "${file}${ext}" ] && got_it=1 && break 2 || true yann@63: done yann@63: done yann@63: fi yann@63: CT_Popd yann@63: yann@63: CT_TestAndAbort "Could not download \"${file}\", and not present in \"${CT_TARBALLS_DIR}\"" ${got_it} -eq 0 yann@63: } yann@63: yann@63: # Get the file name extension of a component yann@63: # Usage: CT_GetFileExtension yann@63: # If found, echoes the extension to stdout yann@63: # If not found, echoes nothing on stdout. yann@63: CT_GetFileExtension() { yann@63: local ext yann@63: local file="$1" yann@63: local got_it=1 yann@63: yann@63: CT_Pushd "${CT_TARBALLS_DIR}" yann@63: for ext in .tar.gz .tar.bz2 .tgz .tar; do yann@63: if [ -f "${file}${ext}" ]; then yann@63: echo "${ext}" yann@63: got_it=0 yann@63: break yann@63: fi yann@63: done yann@63: CT_Popd yann@63: yann@63: return 0 yann@63: } yann@63: yann@63: # Extract a tarball and patch the resulting sources if necessary. yann@63: # Some tarballs need to be extracted in specific places. Eg.: glibc addons yann@63: # must be extracted in the glibc directory; uCLibc locales must be extracted yann@63: # in the extra/locale sub-directory of uClibc. yann@63: CT_ExtractAndPatch() { yann@63: local file="$1" yann@63: local base_file=`echo "${file}" |cut -d - -f 1` yann@63: local ver_file=`echo "${file}" |cut -d - -f 2-` yann@63: local official_patch_dir yann@63: local custom_patch_dir yann@63: local libc_addon yann@63: local ext=`CT_GetFileExtension "${file}"` yann@63: CT_TestAndAbort "\"${file}\" not found in \"${CT_TARBALLS_DIR}\"" -z "${ext}" yann@63: local full_file="${CT_TARBALLS_DIR}/${file}${ext}" yann@63: yann@63: CT_Pushd "${CT_SRC_DIR}" yann@63: yann@63: # Add-ons need a little love, really. yann@63: case "${file}" in yann@63: glibc-[a-z]*-*) yann@63: CT_TestAndAbort "Trying to extract the C-library addon/locales \"${file}\" when C-library not yet extracted" ! -d "${CT_LIBC_FILE}" yann@63: cd "${CT_LIBC_FILE}" yann@63: libc_addon=y yann@63: [ -f ".${file}.extracted" ] && return 0 yann@63: touch ".${file}.extracted" yann@63: ;; yann@63: uClibc-locale-*) yann@63: CT_TestAndAbort "Trying to extract the C-library addon/locales \"${file}\" when C-library not yet extracted" ! -d "${CT_LIBC_FILE}" yann@63: cd "${CT_LIBC_FILE}/extra/locale" yann@63: libc_addon=y yann@63: [ -f ".${file}.extracted" ] && return 0 yann@63: touch ".${file}.extracted" yann@63: ;; yann@63: esac yann@63: yann@63: # If the directory exists, then consider extraction and patching done yann@63: [ -d "${file}" ] && return 0 yann@63: yann@63: CT_DoLog EXTRA "Extracting \"${file}\"" yann@63: case "${ext}" in yann@63: .tar.bz2) tar xvjf "${full_file}" |CT_DoLog DEBUG;; yann@63: .tar.gz|.tgz) tar xvzf "${full_file}" |CT_DoLog DEBUG;; yann@63: .tar) tar xvf "${full_file}" |CT_DoLog DEBUG;; yann@63: *) CT_Abort "Don't know how to handle \"${file}\": unknown extension" ;; yann@63: esac yann@63: yann@63: # Snapshots might not have the version number in the extracted directory yann@63: # name. This is also the case for some (old) packages, such as libfloat. yann@63: # Overcome this issue by symlink'ing the directory. yann@63: if [ ! -d "${file}" -a "${libc_addon}" != "y" ]; then yann@63: case "${ext}" in yann@63: .tar.bz2) base=`tar tjf "${full_file}" |head -n 1 |cut -d / -f 1 || true`;; yann@63: .tar.gz|.tgz) base=`tar tzf "${full_file}" |head -n 1 |cut -d / -f 1 || true`;; yann@63: .tar) base=`tar tf "${full_file}" |head -n 1 |cut -d / -f 1 || true`;; yann@63: esac yann@63: CT_TestOrAbort "There was a problem when extracting \"${file}\"" -d "${base}" -o "${base}" != "${file}" yann@63: ln -s "${base}" "${file}" yann@63: fi yann@63: yann@63: # Kludge: outside this function, we wouldn't know if we had just extracted yann@63: # a libc addon, or a plain package. Apply patches now. yann@63: CT_DoLog EXTRA "Patching \"${file}\"" yann@63: yann@63: # If libc addon, we're already in the correct place. yann@63: [ -z "${libc_addon}" ] && cd "${file}" yann@63: yann@63: [ "${CUSTOM_PATCH_ONLY}" = "y" ] || official_patch_dir="${CT_TOP_DIR}/patches/${base_file}/${ver_file}" yann@63: [ "${CT_CUSTOM_PATCH}" = "y" ] && custom_patch_dir="${CT_CUSTOM_PATCH_DIR}/${base_file}/${ver_file}" yann@63: for patch_dir in "${official_patch_dir}" "${custom_patch_dir}"; do yann@63: if [ -n "${patch_dir}" -a -d "${patch_dir}" ]; then yann@63: for p in "${patch_dir}"/*.patch; do yann@63: if [ -f "${p}" ]; then yann@63: CT_DoLog DEBUG "Applying patch \"${p}\"" yann@63: patch -g0 -F1 -p1 -f <"${p}" |CT_DoLog DEBUG yann@63: CT_TestAndAbort "Failed while applying patch file \"${p}\"" ${PIPESTATUS[0]} -ne 0 yann@63: fi yann@63: done yann@63: fi yann@63: done yann@63: yann@63: CT_Popd yann@63: } yann@63: yann@63: # Compute the target triplet from what is provided by the user yann@63: # Usage: CT_DoBuildTargetTriplet yann@63: # In fact this function takes the environment variables to build the target yann@63: # triplet. It is needed both by the normal build sequence, as well as the yann@63: # sample saving sequence. yann@63: CT_DoBuildTargetTriplet() { yann@63: case "${CT_ARCH_BE},${CT_ARCH_LE}" in yann@63: y,) target_endian_eb=eb; target_endian_el=;; yann@63: ,y) target_endian_eb=; target_endian_el=el;; yann@63: esac yann@63: case "${CT_ARCH}" in yann@63: arm) CT_TARGET="${CT_ARCH}${target_endian_eb}";; yann@63: mips) CT_TARGET="${CT_ARCH}${target_endian_el}";; yann@63: x86*) # Much love for this one :-( yann@63: # Ultimately, we should use config.sub to output the correct yann@63: # procesor name. Work for later... yann@63: arch="${CT_ARCH_ARCH}" yann@63: [ -z "${arch}" ] && arch="${CT_ARCH_TUNE}" yann@63: case "${CT_ARCH}" in yann@63: x86_64) CT_TARGET=x86_64;; yann@63: *) case "${arch}" in yann@63: "") CT_TARGET=i386;; yann@63: i386|i486|i586|i686) CT_TARGET="${arch}";; yann@63: winchip*) CT_TARGET=i486;; yann@63: pentium|pentium-mmx|c3*) CT_TARGET=i586;; yann@63: nocona|athlon*64|k8|athlon-fx|opteron) CT_TARGET=x86_64;; yann@63: pentiumpro|pentium*|athlon*) CT_TARGET=i686;; yann@63: *) CT_TARGET=i586;; yann@63: esac;; yann@63: esac;; yann@63: esac yann@63: case "${CT_TARGET_VENDOR}" in yann@63: "") CT_TARGET="${CT_TARGET}-unknown";; yann@63: *) CT_TARGET="${CT_TARGET}-${CT_TARGET_VENDOR}";; yann@63: esac yann@63: case "${CT_KERNEL}" in yann@63: linux*) CT_TARGET="${CT_TARGET}-linux";; yann@63: cygwin*) CT_TARGET="${CT_TARGET}-cygwin";; yann@63: esac yann@63: case "${CT_LIBC}" in yann@63: glibc) CT_TARGET="${CT_TARGET}-gnu";; yann@63: uClibc) CT_TARGET="${CT_TARGET}-uclibc";; yann@63: esac yann@63: case "${CT_ARCH_ABI}" in yann@63: eabi) CT_TARGET="${CT_TARGET}eabi";; yann@63: esac yann@63: CT_TARGET="`${CT_TOP_DIR}/tools/config.sub ${CT_TARGET}`" yann@63: }