crosstool-ng/scripts/functions
Yann E. MORIN" 3910899c77 scripts/functions: test for decompressors before use
./configure does check for the presence of gz and bzip2, so we can
safely use them in the build scripts.

On the other hand, more recent formats (eg. XZ) are not yet widely
available, and we do not want, and can't, force the user to install
them as a pre-requisite.

So, build up a list of allowed tarball formats based on the available
decompressors. For no, this is a static list, but the upcoming XZ
support will conditionnaly add to this list.

Signed-off-by: "Yann E. MORIN" <yann.morin.1998@anciens.enib.fr>
2011-05-04 00:04:23 +02:00

1160 lines
40 KiB
Bash

# This file contains some usefull common functions -*- sh -*-
# Copyright 2007 Yann E. MORIN
# Licensed under the GPL v2. See COPYING in the root of this package
# Prepare the fault handler
CT_OnError() {
local ret=$?
local intro
local file
local line
local func
local step_depth
# Bail out early in subshell, the upper level shell will act accordingly.
[ ${BASH_SUBSHELL} -eq 0 ] || exit $ret
# Print steps backtrace
step_depth=${CT_STEP_COUNT}
CT_STEP_COUNT=2
CT_DoLog ERROR ""
intro="Build failed"
for((step=step_depth; step>1; step--)); do
CT_DoLog ERROR ">> ${intro} in step '${CT_STEP_MESSAGE[${step}]}'"
intro=" called"
done
# Print functions backtrace
intro="Error happened in"
offset=1
CT_DoLog ERROR ">>"
for((depth=1; ${BASH_LINENO[$((${depth}-1))]}>0; depth++)); do
file="${BASH_SOURCE[${depth}]#${CT_LIB_DIR}/}"
case "${depth}" in
1) line="";;
*) line="@${BASH_LINENO[${depth}-1]}"
esac
func="${FUNCNAME[${depth}]}"
CT_DoLog ERROR ">> ${intro}: ${func}[${file}${line}]"
intro=" called from"
done
# Help diagnose the error
CT_DoLog ERROR ">>"
if [ "${CT_LOG_TO_FILE}" = "y" ]; then
CT_DoLog ERROR ">> For more info on this error, look at the file: '${tmp_log_file#${CT_TOP_DIR}/}'"
fi
CT_DoLog ERROR ">> There is a list of known issues, some with workarounds, in:"
CT_DoLog ERROR ">> '${CT_DOC_DIR#${CT_TOP_DIR}/}/B - Known issues.txt'"
CT_DoLog ERROR ""
CT_DoLog ERROR "Build failed in step '${CT_STEP_MESSAGE[${CT_STEP_COUNT}]}'"
CT_DoLog ERROR ""
CT_DoEnd ERROR
exit $ret
}
# Install the fault handler
trap CT_OnError ERR
# Inherit the fault handler in subshells and functions
set -E
# Make pipes fail on the _first_ failed command
# Not supported on bash < 3.x, but we need it, so drop the obsoleting bash-2.x
set -o pipefail
# Don't hash commands' locations, and search every time it is requested.
# This is slow, but needed because of the static/shared core gcc which shall
# always match to shared if it exists, and only fallback to static if the
# shared is not found
set +o hashall
# Log policy:
# - first of all, save stdout so we can see the live logs: fd #6
exec 6>&1
# - then point stdout to the log file
tmp_log_file="${CT_TOP_DIR}/build.log"
rm -f "${tmp_log_file}"
exec >>"${tmp_log_file}"
# The different log levels:
CT_LOG_LEVEL_ERROR=0
CT_LOG_LEVEL_WARN=1
CT_LOG_LEVEL_INFO=2
CT_LOG_LEVEL_EXTRA=3
CT_LOG_LEVEL_CFG=4
CT_LOG_LEVEL_FILE=5
CT_LOG_LEVEL_STATE=6
CT_LOG_LEVEL_ALL=7
CT_LOG_LEVEL_DEBUG=8
# Make it easy to use \n and !
CR=$(printf "\n")
BANG='!'
# A function to log what is happening
# Different log level are available:
# - ERROR: A serious, fatal error occurred
# - WARN: A non fatal, non serious error occurred, take your responsbility with the generated build
# - INFO: Informational messages
# - EXTRA: Extra informational messages
# - CFG: Output of various "./configure"-type scripts
# - FILE: File / archive unpacking.
# - STATE: State save & restore
# - ALL: Component's build messages
# - DEBUG: Internal debug messages
# Usage: CT_DoLog <level> [message]
# If message is empty, then stdin will be logged.
CT_DoLog() {
local max_level LEVEL level cur_l cur_L
local l
eval max_level="\${CT_LOG_LEVEL_${CT_LOG_LEVEL_MAX}}"
# Set the maximum log level to DEBUG if we have none
[ -z "${max_level}" ] && max_level=${CT_LOG_LEVEL_DEBUG}
LEVEL="$1"; shift
eval level="\${CT_LOG_LEVEL_${LEVEL}}"
if [ $# -eq 0 ]; then
cat -
else
printf "%s\n" "${*}"
fi |( IFS="${CR}" # We want the full lines, even leading spaces
_prog_bar_cpt=0
_prog_bar[0]='/'
_prog_bar[1]='-'
_prog_bar[2]='\'
_prog_bar[3]='|'
indent=$((2*CT_STEP_COUNT))
while read line; do
case "${CT_LOG_SEE_TOOLS_WARN},${line}" in
y,*"warning:"*) cur_L=WARN; cur_l=${CT_LOG_LEVEL_WARN};;
y,*"WARNING:"*) cur_L=WARN; cur_l=${CT_LOG_LEVEL_WARN};;
*"error:"*) cur_L=ERROR; cur_l=${CT_LOG_LEVEL_ERROR};;
*"make["*"]: *** ["*) cur_L=ERROR; cur_l=${CT_LOG_LEVEL_ERROR};;
*) cur_L="${LEVEL}"; cur_l="${level}";;
esac
# There will always be a log file (stdout, fd #1), be it /dev/null
printf "[%-5s]%*s%s%s\n" "${cur_L}" "${indent}" " " "${line}"
if [ ${cur_l} -le ${max_level} ]; then
# Only print to console (fd #6) if log level is high enough.
printf "${CT_LOG_PROGRESS_BAR:+\r}[%-5s]%*s%s%s\n" "${cur_L}" "${indent}" " " "${line}" >&6
fi
if [ "${CT_LOG_PROGRESS_BAR}" = "y" ]; then
printf "\r[%02d:%02d] %s " $((SECONDS/60)) $((SECONDS%60)) "${_prog_bar[$((_prog_bar_cpt/10))]}" >&6
_prog_bar_cpt=$(((_prog_bar_cpt+1)%40))
fi
done
)
return 0
}
# Execute an action, and log its messages
# It is possible to even log local variable assignments (a-la: var=val ./cmd opts)
# Usage: CT_DoExecLog <level> [VAR=val...] <command> [parameters...]
CT_DoExecLog() {
local level="$1"
shift
(
for i in "$@"; do
tmp_log+="'${i}' "
done
while true; do
case "${1}" in
*=*) eval export "'${1}'"; shift;;
*) break;;
esac
done
CT_DoLog DEBUG "==> Executing: ${tmp_log}"
"${@}" 2>&1 |CT_DoLog "${level}"
)
# Catch failure of the sub-shell
[ $? -eq 0 ]
}
# Tail message to be logged whatever happens
# Usage: CT_DoEnd <level>
CT_DoEnd()
{
local level="$1"
CT_STOP_DATE=$(CT_DoDate +%s%N)
CT_STOP_DATE_HUMAN=$(CT_DoDate +%Y%m%d.%H%M%S)
if [ "${level}" != "ERROR" ]; then
CT_DoLog "${level:-INFO}" "Build completed at ${CT_STOP_DATE_HUMAN}"
fi
elapsed=$((CT_STOP_DATE-CT_STAR_DATE))
elapsed_min=$((elapsed/(60*1000*1000*1000)))
elapsed_sec=$(printf "%02d" $(((elapsed%(60*1000*1000*1000))/(1000*1000*1000))))
elapsed_csec=$(printf "%02d" $(((elapsed%(1000*1000*1000))/(10*1000*1000))))
CT_DoLog ${level:-INFO} "(elapsed: ${elapsed_min}:${elapsed_sec}.${elapsed_csec})"
}
# Remove entries referring to . and other relative paths
# Usage: CT_SanitizePath
CT_SanitizePath() {
local new
local p
local IFS=:
for p in $PATH; do
# Only accept absolute paths;
# Note: as a special case the empty string in PATH is equivalent to .
if [ -n "${p}" -a -z "${p%%/*}" ]; then
new="${new}${new:+:}${p}"
fi
done
PATH="${new}"
}
# Sanitise the directory name contained in the variable passed as argument:
# - remove duplicate /
# Usage: CT_SanitiseVarDir CT_PREFIX_DIR
CT_SanitiseVarDir() {
local var
local old_dir
local new_dir
for var in "$@"; do
eval "old_dir=\"\${${var}}\""
new_dir="$( printf "${old_dir}" \
|sed -r -e 's:/+:/:g;' \
)"
eval "${var}=\"${new_dir}\""
CT_DoLog DEBUG "Sanitised '${var}': '${old_dir}' -> '${new_dir}'"
done
}
# Abort the execution with an error message
# Usage: CT_Abort <message>
CT_Abort() {
CT_DoLog ERROR "$1"
exit 1
}
# Test a condition, and print a message if satisfied
# Usage: CT_Test <message> <tests>
CT_Test() {
local ret
local m="$1"
shift
CT_DoLog DEBUG "Testing '! ( $* )'"
test "$@" && CT_DoLog WARN "$m"
return 0
}
# Test a condition, and abort with an error message if satisfied
# Usage: CT_TestAndAbort <message> <tests>
CT_TestAndAbort() {
local m="$1"
shift
CT_DoLog DEBUG "Testing '! ( $* )'"
test "$@" && CT_Abort "$m"
return 0
}
# Test a condition, and abort with an error message if not satisfied
# Usage: CT_TestAndAbort <message> <tests>
CT_TestOrAbort() {
local m="$1"
shift
CT_DoLog DEBUG "Testing '$*'"
test "$@" || CT_Abort "$m"
return 0
}
# Test the presence of a tool, or abort if not found
# Usage: CT_HasOrAbort <tool>
CT_HasOrAbort() {
CT_TestAndAbort "'${1}' not found and needed for successful toolchain build." -z "$(CT_Which "${1}")"
return 0
}
# Search a program: wrap "which" for those system where
# "which" verbosely says there is no match (Mandriva is
# such a sucker...)
# Usage: CT_Which <filename>
CT_Which() {
which "$1" 2>/dev/null || true
}
# Get current date with nanosecond precision
# On those system not supporting nanosecond precision, faked with rounding down
# to the highest entire second
# Usage: CT_DoDate <fmt>
CT_DoDate() {
date "$1" |sed -r -e 's/%?N$/000000000/;'
}
CT_STEP_COUNT=1
CT_STEP_MESSAGE[${CT_STEP_COUNT}]="<none>"
# Memorise a step being done so that any error is caught
# Usage: CT_DoStep <loglevel> <message>
CT_DoStep() {
local start=$(CT_DoDate +%s%N)
CT_DoLog "$1" "================================================================="
CT_DoLog "$1" "$2"
CT_STEP_COUNT=$((CT_STEP_COUNT+1))
CT_STEP_LEVEL[${CT_STEP_COUNT}]="$1"; shift
CT_STEP_START[${CT_STEP_COUNT}]="${start}"
CT_STEP_MESSAGE[${CT_STEP_COUNT}]="$1"
return 0
}
# End the step just being done
# Usage: CT_EndStep
CT_EndStep() {
local stop=$(CT_DoDate +%s%N)
local duration=$(printf "%032d" $((stop-${CT_STEP_START[${CT_STEP_COUNT}]})) |sed -r -e 's/([[:digit:]]{2})[[:digit:]]{7}$/\.\1/; s/^0+//; s/^\./0\./;')
local elapsed=$(printf "%02d:%02d" $((SECONDS/60)) $((SECONDS%60)))
local level="${CT_STEP_LEVEL[${CT_STEP_COUNT}]}"
local message="${CT_STEP_MESSAGE[${CT_STEP_COUNT}]}"
CT_STEP_COUNT=$((CT_STEP_COUNT-1))
CT_DoLog "${level}" "${message}: done in ${duration}s (at ${elapsed})"
return 0
}
# Pushes into a directory, and pops back
CT_Pushd() {
pushd "$1" >/dev/null 2>&1
}
CT_Popd() {
popd >/dev/null 2>&1
}
# Creates a temporary directory
# $1: variable to assign to
# Usage: CT_MktempDir foo
CT_MktempDir() {
# Some mktemp do not allow more than 6 Xs
eval "$1"=$(mktemp -q -d "${CT_BUILD_DIR}/tmp.XXXXXX")
CT_TestOrAbort "Could not make temporary directory" -n "${!1}" -a -d "${!1}"
CT_DoLog DEBUG "Made temporary directory '${!1}'"
return 0
}
# Removes one or more directories, even if it is read-only, or its parent is
# Usage: CT_DoForceRmdir dir [...]
CT_DoForceRmdir() {
local dir
local mode
for dir in "${@}"; do
[ -d "${dir}" ] || continue
case "$CT_SYS_OS" in
Linux|CYGWIN*)
mode="$(stat -c '%a' "$(dirname "${dir}")")"
;;
Darwin|*BSD)
mode="$(stat -f '%Lp' "$(dirname "${dir}")")"
;;
*)
CT_Abort "Unhandled host OS $CT_SYS_OS"
;;
esac
CT_DoExecLog ALL chmod u+w "$(dirname "${dir}")"
CT_DoExecLog ALL chmod -R u+w "${dir}"
CT_DoExecLog ALL rm -rf "${dir}"
CT_DoExecLog ALL chmod ${mode} "$(dirname "${dir}")"
done
}
# Echoes the specified string on stdout until the pipe breaks.
# Doesn't fail
# $1: string to echo
# Usage: CT_DoYes "" |make oldconfig
CT_DoYes() {
yes "$1" || true
}
# Add the specified directory to LD_LIBRARY_PATH, and export it
# If the specified patch is already present, just export
# $1: path to add
# $2: add as 'first' or 'last' path, 'first' is assumed if $2 is empty
# Usage CT_SetLibPath /some/where/lib [first|last]
CT_SetLibPath() {
local path="$1"
local pos="$2"
case ":${LD_LIBRARY_PATH}:" in
*:"${path}":*) ;;
*) case "${pos}" in
last)
CT_DoLog DEBUG "Adding '${path}' at end of LD_LIBRARY_PATH"
LD_LIBRARY_PATH="${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}${path}"
;;
first|"")
CT_DoLog DEBUG "Adding '${path}' at start of LD_LIBRARY_PATH"
LD_LIBRARY_PATH="${path}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"
;;
*)
CT_Abort "Incorrect position '${pos}' to add '${path}' to LD_LIBRARY_PATH"
;;
esac
;;
esac
CT_DoLog DEBUG "==> LD_LIBRARY_PATH='${LD_LIBRARY_PATH}'"
export LD_LIBRARY_PATH
}
# Build up the list of allowed tarball extensions
# Add them in the prefered order; most preferred comes first
CT_DoListTarballExt() {
printf ".tar.bz2\n"
printf ".tar.gz\n.tgz\n"
printf ".tar\n"
}
# Get the file name extension of a component
# Usage: CT_GetFileExtension <component_name-component_version> [extension]
# If found, echoes the extension to stdout, and return 0
# If not found, echoes nothing on stdout, and return !0.
CT_GetFileExtension() {
local ext
local file="$1"
shift
local first_ext="$1"
# we need to also check for an empty extension for those very
# peculiar components that don't have one (such as sstrip from
# buildroot).
for ext in ${first_ext} $(CT_DoListTarballExt) /.git ''; do
if [ -e "${CT_TARBALLS_DIR}/${file}${ext}" ]; then
echo "${ext}"
exit 0
fi
done
exit 1
}
# Try to retrieve the specified URL (HTTP or FTP)
# Usage: CT_DoGetFile <URL>
# This functions always returns true (0), as it can be legitimate not
# to find the requested URL (think about snapshots, different layouts
# for different gcc versions, etc...).
CT_DoGetFile() {
local url="${1}"
local dest="${CT_TARBALLS_DIR}/${url##*/}"
local tmp="${dest}.tmp-dl"
# OK, just look if we have them...
# We are sure at least one is available, ./configure checked for it.
local _curl=$(CT_Which curl)
local _wget=$(CT_Which wget)
_curl="${_curl:-false}"
_wget="${_wget:-false}"
# Remove potential left-over from a previous run
rm -f "${tmp}"
# Some (very old!) FTP server might not support the passive mode, thus
# retry without.
# We also retry a few times, in case there is a transient error (eg. behind
# a dynamic IP that changes during the transfer...)
# With automated download as we are doing, it can be very dangerous to
# continue the downloads. It's far better to simply overwrite the
# destination file.
# Some company networks have firewalls to connect to the internet, but it's
# not easy to detect them, and wget does not timeout by default while
# connecting, so force a global ${CT_CONNECT_TIMEOUT}-second timeout.
# For curl, no good progress indicator is available. So, be silent.
if CT_DoExecLog ALL "${_curl}" --ftp-pasv --retry 3 --connect-timeout ${CT_CONNECT_TIMEOUT} -L -f -s -o "${tmp}" "${url}" \
|| CT_DoExecLog ALL "${_curl}" --retry 3 --connect-timeout ${CT_CONNECT_TIMEOUT} -L -f -s -o "${tmp}" "${url}" \
|| CT_DoExecLog ALL "${_wget}" --passive-ftp --tries=3 -T ${CT_CONNECT_TIMEOUT} -nc --progress=dot:binary -O "${tmp}" "${url}" \
|| CT_DoExecLog ALL "${_wget}" --tries=3 -T ${CT_CONNECT_TIMEOUT} -nc --progress=dot:binary -O "${tmp}" "${url}" \
; then
# One of them succeeded, good!
mv "${tmp}" "${dest}"
else
# Woops...
rm -f "${tmp}"
fi
}
# This function tries to retrieve a tarball form a local directory
# Usage: CT_GetLocal <basename> [.extension]
CT_GetLocal() {
local basename="$1"
local first_ext="$2"
local ext
# Do we already have it in *our* tarballs dir?
if ext="$( CT_GetFileExtension "${basename}" ${first_ext} )"; then
CT_DoLog DEBUG "Already have '${basename}'"
return 0
fi
if [ -n "${CT_LOCAL_TARBALLS_DIR}" ]; then
CT_DoLog DEBUG "Trying to retrieve an already downloaded copy of '${basename}'"
# We'd rather have a bzip2'ed tarball, then gzipped tarball, plain tarball,
# or, as a failover, a file without extension.
for ext in ${first_ext} $(CT_DoListTarballExt) ''; do
CT_DoLog DEBUG "Trying '${CT_LOCAL_TARBALLS_DIR}/${basename}${ext}'"
if [ -r "${CT_LOCAL_TARBALLS_DIR}/${basename}${ext}" -a \
"${CT_FORCE_DOWNLOAD}" != "y" ]; then
CT_DoLog DEBUG "Got '${basename}' from local storage"
CT_DoExecLog ALL ln -s "${CT_LOCAL_TARBALLS_DIR}/${basename}${ext}" "${CT_TARBALLS_DIR}/${basename}${ext}"
return 0
fi
done
fi
return 1
}
# This function saves the specified to local storage if possible,
# and if so, symlinks it for later usage
# Usage: CT_SaveLocal </full/path/file.name>
CT_SaveLocal() {
local file="$1"
local basename="${file##*/}"
if [ "${CT_SAVE_TARBALLS}" = "y" ]; then
CT_DoLog EXTRA "Saving '${basename}' to local storage"
# The file may already exist if downloads are forced: remove it first
CT_DoExecLog ALL rm -f "${CT_LOCAL_TARBALLS_DIR}/${basename}"
CT_DoExecLog ALL mv -f "${file}" "${CT_LOCAL_TARBALLS_DIR}"
CT_DoExecLog ALL ln -s "${CT_LOCAL_TARBALLS_DIR}/${basename}" "${file}"
fi
}
# Download the file from one of the URLs passed as argument
# Usage: CT_GetFile <basename> [.extension] <url> [url ...]
CT_GetFile() {
local ext
local -a URLS
local url
local file="$1"
local first_ext
shift
# If next argument starts with a dot, then this is not an URL,
# and we can consider that it is a preferred extension.
case "$1" in
.*) first_ext="$1"
shift
;;
esac
# Does it exist localy?
if CT_GetLocal "${file}" ${first_ext}; then
return 0
fi
# No, it does not...
# Try to retrieve the file
CT_DoLog EXTRA "Retrieving '${file}'"
# Add URLs on the LAN mirror
if [ "${CT_USE_MIRROR}" = "y" ]; then
CT_TestOrAbort "Please set the mirror base URL" -n "${CT_MIRROR_BASE_URL}"
URLS+=( "${CT_MIRROR_BASE_URL}/${file%-*}" )
URLS+=( "${CT_MIRROR_BASE_URL}" )
fi
if [ "${CT_FORBID_DOWNLOAD}" != "y" ]; then
URLS+=( "${@}" )
fi
# Scan all URLs in turn, and try to grab a tarball from there
# Do *not* try git trees (ext=/.git), this is handled in a specific
# wrapper, below
for ext in ${first_ext} $(CT_DoListTarballExt) ''; do
# Try all urls in turn
for url in "${URLS[@]}"; do
[ -n "${url}" ] || continue
CT_DoLog DEBUG "Trying '${url}/${file}${ext}'"
CT_DoGetFile "${url}/${file}${ext}"
if [ -f "${CT_TARBALLS_DIR}/${file}${ext}" ]; then
CT_DoLog DEBUG "Got '${file}' from the Internet"
CT_SaveLocal "${CT_TARBALLS_DIR}/${file}${ext}"
return 0
fi
done
done
# Just return error, someone may want to catch and handle the error
# (eg. glibc/eglibc add-ons can be missing).
return 1
}
# Checkout from CVS, and build the associated tarball
# The tarball will be called ${basename}.tar.bz2
# Prerequisite: either the server does not require password,
# or the user must already be logged in.
# 'tag' is the tag to retrieve. Must be specified, but can be empty.
# If dirname is specified, then module will be renamed to dirname
# prior to building the tarball.
# Usage: CT_GetCVS <basename> <url> <module> <tag> [dirname[=subdir]]
# Note: if '=subdir' is given, then it is used instead of 'module'.
CT_GetCVS() {
local basename="$1"
local uri="$2"
local module="$3"
local tag="${4:+-r ${4}}"
local dirname="$5"
local tmp_dir
# First try locally, then the mirror
if CT_GetFile "${basename}"; then
# Got it! Return early! :-)
return 0
fi
if [ "${CT_FORBID_DOWNLOAD}" = "y" ]; then
CT_DoLog WARN "Downloads forbidden, not trying cvs retrieval"
return 1
fi
CT_MktempDir tmp_dir
CT_Pushd "${tmp_dir}"
CT_DoExecLog ALL cvs -z 9 -d "${uri}" co -P ${tag} "${module}"
if [ -n "${dirname}" ]; then
case "${dirname}" in
*=*)
CT_DoExecLog DEBUG mv "${dirname#*=}" "${dirname%%=*}"
CT_DoExecLog ALL tar cjf "${CT_TARBALLS_DIR}/${basename}.tar.bz2" "${dirname%%=*}"
;;
*)
CT_DoExecLog ALL mv "${module}" "${dirname}"
CT_DoExecLog ALL tar cjf "${CT_TARBALLS_DIR}/${basename}.tar.bz2" "${dirname:-${module}}"
;;
esac
fi
CT_SaveLocal "${CT_TARBALLS_DIR}/${basename}.tar.bz2"
CT_Popd
CT_DoExecLog ALL rm -rf "${tmp_dir}"
}
# Check out from SVN, and build the associated tarball
# The tarball will be called ${basename}.tar.bz2
# Prerequisite: either the server does not require password,
# or the user must already be logged in.
# 'rev' is the revision to retrieve
# Usage: CT_GetSVN <basename> <url> [rev]
CT_GetSVN() {
local basename="$1"
local uri="$2"
local rev="$3"
# First try locally, then the mirror
if CT_GetFile "${basename}"; then
# Got it! Return early! :-)
return 0
fi
if [ "${CT_FORBID_DOWNLOAD}" = "y" ]; then
CT_DoLog WARN "Downloads forbidden, not trying svn retrieval"
return 1
fi
CT_MktempDir tmp_dir
CT_Pushd "${tmp_dir}"
if ! CT_DoExecLog ALL svn export ${rev:+-r ${rev}} "${uri}" "${basename}"; then
CT_DoLog WARN "Could not retrieve '${basename}'"
return 1
fi
CT_DoExecLog ALL tar cjf "${CT_TARBALLS_DIR}/${basename}.tar.bz2" "${basename}"
CT_SaveLocal "${CT_TARBALLS_DIR}/${basename}.tar.bz2"
CT_Popd
CT_DoExecLog ALL rm -rf "${tmp_dir}"
}
# Clone a git tree
# Tries the given URLs in turn until one can get cloned. No tarball will be created.
# Prerequisites: either the server does not require password,
# or the user has already taken any action to authenticate to the server.
# The cloned tree will *not* be stored in the local tarballs dir!
# Usage: CT_GetGit <basename> <url [url ...]>
CT_GetGit() {
local basename="$1"; shift
local url
local cloned=0
if [ "${CT_FORBID_DOWNLOAD}" = "y" ]; then
CT_DoLog WARN "Downloads forbidden, not trying git retrieval"
return 1
fi
# Do we have it in our tarballs dir?
if [ -d "${CT_TARBALLS_DIR}/${basename}/.git" ]; then
CT_DoLog EXTRA "Updating git tree '${basename}'"
CT_Pushd "${CT_TARBALLS_DIR}/${basename}"
CT_DoExecLog ALL git pull
CT_Popd
else
CT_DoLog EXTRA "Retrieving git tree '${basename}'"
for url in "${@}"; do
CT_DoLog ALL "Trying to clone from '${url}'"
CT_DoForceRmdir "${CT_TARBALLS_DIR}/${basename}"
if git clone "${url}" "${CT_TARBALLS_DIR}/${basename}" 2>&1 |CT_DoLog ALL; then
cloned=1
break
fi
done
CT_TestOrAbort "Could not clone '${basename}'" ${cloned} -ne 0
fi
}
# Extract a tarball
# Some tarballs need to be extracted in specific places. Eg.: glibc addons
# must be extracted in the glibc directory; uCLibc locales must be extracted
# in the extra/locale sub-directory of uClibc. This is taken into account
# by the caller, that did a 'cd' into the correct path before calling us
# and sets nochdir to 'nochdir'.
# Note also that this function handles the git trees!
# Usage: CT_Extract <basename> [nochdir] [options]
# where 'options' are dependent on the source (eg. git branch/tag...)
CT_Extract() {
local nochdir="$1"
local basename
local ext
local -a tar_opts
if [ "${nochdir}" = "nochdir" ]; then
shift
nochdir="$(pwd)"
else
nochdir="${CT_SRC_DIR}"
fi
basename="$1"
shift
if ! ext="$(CT_GetFileExtension "${basename}")"; then
CT_DoLog WARN "'${basename}' not found in '${CT_TARBALLS_DIR}'"
return 1
fi
local full_file="${CT_TARBALLS_DIR}/${basename}${ext}"
# Check if already extracted
if [ -e "${CT_SRC_DIR}/.${basename}.extracted" ]; then
CT_DoLog DEBUG "Already extracted '${basename}'"
return 0
fi
# Check if previously partially extracted
if [ -e "${CT_SRC_DIR}/.${basename}.extracting" ]; then
CT_DoLog ERROR "The '${basename}' sources were partially extracted."
CT_DoLog ERROR "Please remove first:"
CT_DoLog ERROR " - the source dir for '${basename}', in '${CT_SRC_DIR}'"
CT_DoLog ERROR " - the file '${CT_SRC_DIR}/.${basename}.extracting'"
CT_Abort "I'll stop now to avoid any carnage..."
fi
CT_DoExecLog DEBUG touch "${CT_SRC_DIR}/.${basename}.extracting"
CT_Pushd "${nochdir}"
CT_DoLog EXTRA "Extracting '${basename}'"
CT_DoExecLog FILE mkdir -p "${basename}"
tar_opts=( "--strip-components=1" )
tar_opts+=( "-C" "${basename}" )
tar_opts+=( "-xv" )
case "${ext}" in
.tar.bz2) CT_DoExecLog FILE tar "${tar_opts[@]}" -j -f "${full_file}";;
.tar.gz|.tgz) CT_DoExecLog FILE tar "${tar_opts[@]}" -z -f "${full_file}";;
.tar) CT_DoExecLog FILE tar "${tar_opts[@]}" -f "${full_file}";;
/.git) CT_ExtractGit "${basename}" "${@}";;
*) CT_DoLog WARN "Don't know how to handle '${basename}${ext}': unknown extension"
return 1
;;
esac
# Don't mark as being extracted for git
case "${ext}" in
/.git) ;;
*) CT_DoExecLog DEBUG touch "${CT_SRC_DIR}/.${basename}.extracted";;
esac
CT_DoExecLog DEBUG rm -f "${CT_SRC_DIR}/.${basename}.extracting"
CT_Popd
}
# Create a working git clone
# Usage: CT_ExtractGit <basename> [ref]
# where 'ref' is the reference to use:
# the full name of a branch, like "remotes/origin/branch_name"
# a date as understandable by git, like "YYYY-MM-DD[ hh[:mm[:ss]]]"
# a tag name
CT_ExtractGit() {
local basename="${1}"
local ref="${2}"
local clone_dir
local ref_type
# pushd now to be able to get git revlist in case ref is a date
clone_dir="${CT_TARBALLS_DIR}/${basename}"
CT_Pushd "${clone_dir}"
# What kind of reference is ${ref} ?
if [ -z "${ref}" ]; then
# Don't update the clone, keep as-is
ref_type=none
elif git tag |grep -E "^${ref}$" >/dev/null 2>&1; then
ref_type=tag
elif git branch -a --no-color |grep -E "^. ${ref}$" >/dev/null 2>&1; then
ref_type=branch
elif date -d "${ref}" >/dev/null 2>&1; then
ref_type=date
ref=$(git rev-list -n1 --before="${ref}")
else
CT_Abort "Reference '${ref}' is an incorrect git reference: neither tag, branch nor date"
fi
CT_DoExecLog DEBUG rm -f "${CT_SRC_DIR}/${basename}"
CT_DoExecLog ALL ln -sf "${clone_dir}" "${CT_SRC_DIR}/${basename}"
case "${ref_type}" in
none) ;;
*) CT_DoExecLog FILE git checkout "${ref}";;
esac
CT_Popd
}
# Patches the specified component
# See CT_Extract, above, for explanations on 'nochdir'
# Usage: CT_Patch [nochdir] <packagename> <packageversion>
# If the package directory is *not* packagename-packageversion, then
# the caller must cd into the proper directory first, and call us
# with nochdir
CT_Patch() {
local nochdir="$1"
local pkgname
local version
local pkgdir
local base_file
local ver_file
local d
local -a patch_dirs
local bundled_patch_dir
local local_patch_dir
if [ "${nochdir}" = "nochdir" ]; then
shift
pkgname="$1"
version="$2"
pkgdir="${pkgname}-${version}"
nochdir="$(pwd)"
else
pkgname="$1"
version="$2"
pkgdir="${pkgname}-${version}"
nochdir="${CT_SRC_DIR}/${pkgdir}"
fi
# Check if already patched
if [ -e "${CT_SRC_DIR}/.${pkgdir}.patched" ]; then
CT_DoLog DEBUG "Already patched '${pkgdir}'"
return 0
fi
# Check if already partially patched
if [ -e "${CT_SRC_DIR}/.${pkgdir}.patching" ]; then
CT_DoLog ERROR "The '${pkgdir}' sources were partially patched."
CT_DoLog ERROR "Please remove first:"
CT_DoLog ERROR " - the source dir for '${pkgdir}', in '${CT_SRC_DIR}'"
CT_DoLog ERROR " - the file '${CT_SRC_DIR}/.${pkgdir}.extracted'"
CT_DoLog ERROR " - the file '${CT_SRC_DIR}/.${pkgdir}.patching'"
CT_Abort "I'll stop now to avoid any carnage..."
fi
touch "${CT_SRC_DIR}/.${pkgdir}.patching"
CT_Pushd "${nochdir}"
CT_DoLog EXTRA "Patching '${pkgdir}'"
bundled_patch_dir="${CT_LIB_DIR}/patches/${pkgname}/${version}"
local_patch_dir="${CT_LOCAL_PATCH_DIR}/${pkgname}/${version}"
case "${CT_PATCH_ORDER}" in
bundled) patch_dirs=("${bundled_patch_dir}");;
local) patch_dirs=("${local_patch_dir}");;
bundled,local) patch_dirs=("${bundled_patch_dir}" "${local_patch_dir}");;
local,bundled) patch_dirs=("${local_patch_dir}" "${bundled_patch_dir}");;
none) patch_dirs=;;
esac
for d in "${patch_dirs[@]}"; do
CT_DoLog DEBUG "Looking for patches in '${d}'..."
if [ -n "${d}" -a -d "${d}" ]; then
for p in "${d}"/*.patch; do
if [ -f "${p}" ]; then
CT_DoLog DEBUG "Applying patch '${p}'"
CT_DoExecLog ALL patch --no-backup-if-mismatch -g0 -F1 -p1 -f <"${p}"
fi
done
if [ "${CT_PATCH_SINGLE}" = "y" ]; then
break
fi
fi
done
if [ "${CT_OVERIDE_CONFIG_GUESS_SUB}" = "y" ]; then
CT_DoLog ALL "Overiding config.guess and config.sub"
for cfg in config_guess config_sub; do
eval ${cfg}="${CT_LIB_DIR}/scripts/${cfg/_/.}"
[ -e "${CT_TOP_DIR}/scripts/${cfg/_/.}" ] && eval ${cfg}="${CT_TOP_DIR}/scripts/${cfg/_/.}"
# Can't use CT_DoExecLog because of the '{} \;' to be passed un-mangled to find
find . -type f -name "${cfg/_/.}" -exec cp -v "${!cfg}" {} \; |CT_DoLog ALL
done
fi
CT_DoExecLog DEBUG touch "${CT_SRC_DIR}/.${pkgdir}.patched"
CT_DoExecLog DEBUG rm -f "${CT_SRC_DIR}/.${pkgdir}.patching"
CT_Popd
}
# Two wrappers to call config.(guess|sub) either from CT_TOP_DIR or CT_LIB_DIR.
# Those from CT_TOP_DIR, if they exist, will be be more recent than those from CT_LIB_DIR.
CT_DoConfigGuess() {
if [ -x "${CT_TOP_DIR}/scripts/config.guess" ]; then
"${CT_TOP_DIR}/scripts/config.guess"
else
"${CT_LIB_DIR}/scripts/config.guess"
fi
}
CT_DoConfigSub() {
if [ -x "${CT_TOP_DIR}/scripts/config.sub" ]; then
"${CT_TOP_DIR}/scripts/config.sub" "$@"
else
"${CT_LIB_DIR}/scripts/config.sub" "$@"
fi
}
# Compute the target tuple from what is provided by the user
# Usage: CT_DoBuildTargetTuple
# In fact this function takes the environment variables to build the target
# tuple. It is needed both by the normal build sequence, as well as the
# sample saving sequence.
CT_DoBuildTargetTuple() {
# Set the endianness suffix, and the default endianness gcc option
case "${CT_ARCH_BE},${CT_ARCH_LE}" in
y,) target_endian_eb=eb
target_endian_el=
CT_ARCH_ENDIAN_CFLAG="-mbig-endian"
CT_ARCH_ENDIAN_LDFLAG="-EB"
;;
,y) target_endian_eb=
target_endian_el=el
CT_ARCH_ENDIAN_CFLAG="-mlittle-endian"
CT_ARCH_ENDIAN_LDFLAG="-EL"
;;
esac
# Build the default architecture tuple part
CT_TARGET_ARCH="${CT_ARCH}"
# Set defaults for the system part of the tuple. Can be overriden
# by architecture-specific values.
case "${CT_LIBC}" in
*glibc) CT_TARGET_SYS=gnu;;
uClibc) CT_TARGET_SYS=uclibc;;
*) CT_TARGET_SYS=elf;;
esac
# Set the default values for ARCH, ABI, CPU, TUNE, FPU and FLOAT
unset CT_ARCH_ARCH_CFLAG CT_ARCH_ABI_CFLAG CT_ARCH_CPU_CFLAG CT_ARCH_TUNE_CFLAG CT_ARCH_FPU_CFLAG CT_ARCH_FLOAT_CFLAG
unset CT_ARCH_WITH_ARCH CT_ARCH_WITH_ABI CT_ARCH_WITH_CPU CT_ARCH_WITH_TUNE CT_ARCH_WITH_FPU CT_ARCH_WITH_FLOAT
[ "${CT_ARCH_ARCH}" ] && { CT_ARCH_ARCH_CFLAG="-march=${CT_ARCH_ARCH}"; CT_ARCH_WITH_ARCH="--with-arch=${CT_ARCH_ARCH}"; }
[ "${CT_ARCH_ABI}" ] && { CT_ARCH_ABI_CFLAG="-mabi=${CT_ARCH_ABI}"; CT_ARCH_WITH_ABI="--with-abi=${CT_ARCH_ABI}"; }
[ "${CT_ARCH_CPU}" ] && { CT_ARCH_CPU_CFLAG="-mcpu=${CT_ARCH_CPU}"; CT_ARCH_WITH_CPU="--with-cpu=${CT_ARCH_CPU}"; }
[ "${CT_ARCH_TUNE}" ] && { CT_ARCH_TUNE_CFLAG="-mtune=${CT_ARCH_TUNE}"; CT_ARCH_WITH_TUNE="--with-tune=${CT_ARCH_TUNE}"; }
[ "${CT_ARCH_FPU}" ] && { CT_ARCH_FPU_CFLAG="-mfpu=${CT_ARCH_FPU}"; CT_ARCH_WITH_FPU="--with-fpu=${CT_ARCH_FPU}"; }
[ "${CT_ARCH_FLOAT_SW}" ] && { CT_ARCH_FLOAT_CFLAG="-msoft-float"; CT_ARCH_WITH_FLOAT="--with-float=soft"; }
# Build the default kernel tuple part
CT_TARGET_KERNEL="${CT_KERNEL}"
# Overide the default values with the components specific settings
CT_DoArchTupleValues
CT_DoKernelTupleValues
# Finish the target tuple construction
CT_TARGET="${CT_TARGET_ARCH}"
CT_TARGET="${CT_TARGET}${CT_TARGET_VENDOR:+-${CT_TARGET_VENDOR}}"
CT_TARGET="${CT_TARGET}${CT_TARGET_KERNEL:+-${CT_TARGET_KERNEL}}"
CT_TARGET="${CT_TARGET}${CT_TARGET_SYS:+-${CT_TARGET_SYS}}"
# Sanity checks
__sed_alias=""
if [ -n "${CT_TARGET_ALIAS_SED_EXPR}" ]; then
__sed_alias=$(echo "${CT_TARGET}" |sed -r -e "${CT_TARGET_ALIAS_SED_EXPR}")
fi
case ":${CT_TARGET_VENDOR}:${CT_TARGET_ALIAS}:${__sed_alias}:" in
:*" "*:*:*:) CT_Abort "Don't use spaces in the vendor string, it breaks things.";;
:*"-"*:*:*:) CT_Abort "Don't use dashes in the vendor string, it breaks things.";;
:*:*" "*:*:) CT_Abort "Don't use spaces in the target alias, it breaks things.";;
:*:*:*" "*:) CT_Abort "Don't use spaces in the target sed transform, it breaks things.";;
esac
# Canonicalise it
CT_TARGET=$(CT_DoConfigSub "${CT_TARGET}")
# Prepare the target CFLAGS
CT_ARCH_TARGET_CFLAGS="${CT_ARCH_TARGET_CFLAGS} ${CT_ARCH_ENDIAN_CFLAG}"
CT_ARCH_TARGET_CFLAGS="${CT_ARCH_TARGET_CFLAGS} ${CT_ARCH_ARCH_CFLAG}"
CT_ARCH_TARGET_CFLAGS="${CT_ARCH_TARGET_CFLAGS} ${CT_ARCH_ABI_CFLAG}"
CT_ARCH_TARGET_CFLAGS="${CT_ARCH_TARGET_CFLAGS} ${CT_ARCH_CPU_CFLAG}"
CT_ARCH_TARGET_CFLAGS="${CT_ARCH_TARGET_CFLAGS} ${CT_ARCH_TUNE_CFLAG}"
CT_ARCH_TARGET_CFLAGS="${CT_ARCH_TARGET_CFLAGS} ${CT_ARCH_FPU_CFLAG}"
CT_ARCH_TARGET_CFLAGS="${CT_ARCH_TARGET_CFLAGS} ${CT_ARCH_FLOAT_CFLAG}"
# Now on for the target LDFLAGS
CT_ARCH_TARGET_LDFLAGS="${CT_ARCH_TARGET_LDFLAGS} ${CT_ARCH_ENDIAN_LDFLAG}"
}
# This function does pause the build until the user strikes "Return"
# Usage: CT_DoPause [optional_message]
CT_DoPause() {
local foo
local message="${1:-Pausing for your pleasure}"
CT_DoLog INFO "${message}"
read -p "Press 'Enter' to continue, or Ctrl-C to stop..." foo >&6
return 0
}
# This function creates a tarball of the specified directory, but
# only if it exists
# Usage: CT_DoTarballIfExists <dir> <tarball_basename> [extra_tar_options [...]]
CT_DoTarballIfExists() {
local dir="$1"
local tarball="$2"
shift 2
local -a extra_tar_opts=( "$@" )
local -a compress
case "${CT_DEBUG_CT_SAVE_STEPS_GZIP}" in
y) compress=( gzip -c -3 - ); tar_ext=.gz;;
*) compress=( cat - ); tar_ext=;;
esac
if [ -d "${dir}" ]; then
CT_DoLog DEBUG " Saving '${dir}'"
{ tar c -C "${dir}" -v -f - "${extra_tar_opts[@]}" . \
|"${compress[@]}" >"${tarball}.tar${tar_ext}" ;
} 2>&1 |sed -r -e 's/^/ /;' |CT_DoLog STATE
else
CT_DoLog STATE " Not saving '${dir}': does not exist"
fi
}
# This function extracts a tarball to the specified directory, but
# only if the tarball exists
# Usage: CT_DoExtractTarballIfExists <tarball_basename> <dir> [extra_tar_options [...]]
CT_DoExtractTarballIfExists() {
local tarball="$1"
local dir="$2"
shift 2
local -a extra_tar_opts=( "$@" )
local -a uncompress
case "${CT_DEBUG_CT_SAVE_STEPS_GZIP}" in
y) uncompress=( gzip -c -d ); tar_ext=.gz;;
*) uncompress=( cat ); tar_ext=;;
esac
if [ -f "${tarball}.tar${tar_ext}" ]; then
CT_DoLog DEBUG " Restoring '${dir}'"
CT_DoForceRmdir "${dir}"
CT_DoExecLog DEBUG mkdir -p "${dir}"
{ "${uncompress[@]}" "${tarball}.tar${tar_ext}" \
|tar x -C "${dir}" -v -f - "${extra_tar_opts[@]}" ;
} 2>&1 |sed -r -e 's/^/ /;' |CT_DoLog STATE
else
CT_DoLog STATE " Not restoring '${dir}': does not exist"
fi
}
# This function saves the state of the toolchain to be able to restart
# at any one point
# Usage: CT_DoSaveState <next_step_name>
CT_DoSaveState() {
[ "${CT_DEBUG_CT_SAVE_STEPS}" = "y" ] || return 0
local state_name="$1"
local state_dir="${CT_STATE_DIR}/${state_name}"
# Log this to the log level required by the user
CT_DoLog ${CT_LOG_LEVEL_MAX} "Saving state to restart at step '${state_name}'..."
rm -rf "${state_dir}"
mkdir -p "${state_dir}"
CT_DoLog STATE " Saving environment and aliases"
# We must omit shell functions, and some specific bash variables
# that break when restoring the environment, later. We could do
# all the processing in the awk script, but a sed is easier...
set |awk '
BEGIN { _p = 1; }
$0~/^[^ ]+ \(\)/ { _p = 0; }
_p == 1
$0 == "}" { _p = 1; }
' |sed -r -e '/^BASH_(ARGC|ARGV|LINENO|SOURCE|VERSINFO)=/d;
/^(UID|EUID)=/d;
/^(FUNCNAME|GROUPS|PPID|SHELLOPTS)=/d;' >"${state_dir}/env.sh"
CT_DoTarballIfExists "${CT_BUILDTOOLS_PREFIX_DIR}" "${state_dir}/buildtools_dir"
CT_DoTarballIfExists "${CT_COMPLIBS_DIR}" "${state_dir}/complibs_dir"
CT_DoTarballIfExists "${CT_CONFIG_DIR}" "${state_dir}/config_dir"
CT_DoTarballIfExists "${CT_CC_CORE_STATIC_PREFIX_DIR}" "${state_dir}/cc_core_static_prefix_dir"
CT_DoTarballIfExists "${CT_CC_CORE_SHARED_PREFIX_DIR}" "${state_dir}/cc_core_shared_prefix_dir"
CT_DoTarballIfExists "${CT_PREFIX_DIR}" "${state_dir}/prefix_dir" --exclude '*.log'
CT_DoLog STATE " Saving log file"
exec >/dev/null
case "${CT_DEBUG_CT_SAVE_STEPS_GZIP}" in
y) gzip -3 -c "${tmp_log_file}" >"${state_dir}/log.gz";;
*) cat "${tmp_log_file}" >"${state_dir}/log";;
esac
exec >>"${tmp_log_file}"
}
# This function restores a previously saved state
# Usage: CT_DoLoadState <state_name>
CT_DoLoadState(){
local state_name="$1"
local state_dir="${CT_STATE_DIR}/${state_name}"
local old_RESTART="${CT_RESTART}"
local old_STOP="${CT_STOP}"
CT_TestOrAbort "The previous build did not reach the point where it could be restarted at '${CT_RESTART}'" -d "${state_dir}"
# We need to do something special with the log file!
if [ "${CT_LOG_TO_FILE}" = "y" ]; then
exec >"${state_dir}/tail.log"
fi
# Log this to the log level required by the user
CT_DoLog ${CT_LOG_LEVEL_MAX} "Restoring state at step '${state_name}', as requested."
CT_DoExtractTarballIfExists "${state_dir}/prefix_dir" "${CT_PREFIX_DIR}"
CT_DoExtractTarballIfExists "${state_dir}/cc_core_shared_prefix_dir" "${CT_CC_CORE_SHARED_PREFIX_DIR}"
CT_DoExtractTarballIfExists "${state_dir}/cc_core_static_prefix_dir" "${CT_CC_CORE_STATIC_PREFIX_DIR}"
CT_DoExtractTarballIfExists "${state_dir}/config_dir" "${CT_CONFIG_DIR}"
CT_DoExtractTarballIfExists "${state_dir}/complibs_dir" "${CT_COMPLIBS_DIR}"
CT_DoExtractTarballIfExists "${state_dir}/buildtools_dir" "${CT_BUILDTOOLS_PREFIX_DIR}"
# Restore the environment, discarding any error message
# (for example, read-only bash internals)
CT_DoLog STATE " Restoring environment"
. "${state_dir}/env.sh" >/dev/null 2>&1 || true
# Restore the new RESTART and STOP steps
CT_RESTART="${old_RESTART}"
CT_STOP="${old_STOP}"
unset old_stop old_restart
CT_DoLog STATE " Restoring log file"
exec >/dev/null
case "${CT_DEBUG_CT_SAVE_STEPS_GZIP}" in
y) zcat "${state_dir}/log.gz" >"${tmp_log_file}";;
*) cat "${state_dir}/log" >"${tmp_log_file}";;
esac
cat "${state_dir}/tail.log" >>"${tmp_log_file}"
exec >>"${tmp_log_file}"
rm -f "${state_dir}/tail.log"
}