scripts/functions
author "Yann E. MORIN" <yann.morin.1998@anciens.enib.fr>
Tue Nov 16 17:49:15 2010 +0100 (2010-11-16)
changeset 2204 ea1c9143e1e3
parent 2203 ac3e215141a1
child 2205 62b3f52315b3
permissions -rw-r--r--
scripts: call curl and wget in sequence

It can happen, in some circumpstances, than one can succeed where
the other would fail. Those cases involves convoluted enterprise
networks with proxies playing tricks.

Signed-off-by: "Yann E. MORIN" <yann.morin.1998@anciens.enib.fr>
     1 # This file contains some usefull common functions -*- sh -*-
     2 # Copyright 2007 Yann E. MORIN
     3 # Licensed under the GPL v2. See COPYING in the root of this package
     4 
     5 # Prepare the fault handler
     6 CT_OnError() {
     7     ret=$?
     8     # Bail out early in subshell, the upper level shell will act accordingly.
     9     [ ${BASH_SUBSHELL} -eq 0 ] || exit $ret
    10     CT_DoLog ERROR "Build failed in step '${CT_STEP_MESSAGE[${CT_STEP_COUNT}]}'"
    11     for((step=(CT_STEP_COUNT-1); step>1; step--)); do
    12         CT_DoLog ERROR "      called in step '${CT_STEP_MESSAGE[${step}]}'"
    13     done
    14     CT_DoLog ERROR "Error happened in '${BASH_SOURCE[1]}' in function '${FUNCNAME[1]}' (line unknown, sorry)"
    15     for((depth=2; ${BASH_LINENO[$((${depth}-1))]}>0; depth++)); do
    16         CT_DoLog ERROR "      called from '${BASH_SOURCE[${depth}]}' at line # ${BASH_LINENO[${depth}-1]} in function '${FUNCNAME[${depth}]}'"
    17     done
    18     [ "${CT_LOG_TO_FILE}" = "y" ] && CT_DoLog ERROR "Look at '${CT_LOG_FILE}' for more info on this error."
    19     CT_STEP_COUNT=1
    20     CT_DoEnd ERROR
    21     exit $ret
    22 }
    23 
    24 # Install the fault handler
    25 trap CT_OnError ERR
    26 
    27 # Inherit the fault handler in subshells and functions
    28 set -E
    29 
    30 # Make pipes fail on the _first_ failed command
    31 # Not supported on bash < 3.x, but we need it, so drop the obsoleting bash-2.x
    32 set -o pipefail
    33 
    34 # Don't hash commands' locations, and search every time it is requested.
    35 # This is slow, but needed because of the static/shared core gcc which shall
    36 # always match to shared if it exists, and only fallback to static if the
    37 # shared is not found
    38 set +o hashall
    39 
    40 # Log policy:
    41 #  - first of all, save stdout so we can see the live logs: fd #6
    42 exec 6>&1
    43 #  - then point stdout to the log file (temporary for now)
    44 tmp_log_file="${CT_TOP_DIR}/log.$$"
    45 exec >>"${tmp_log_file}"
    46 
    47 # The different log levels:
    48 CT_LOG_LEVEL_ERROR=0
    49 CT_LOG_LEVEL_WARN=1
    50 CT_LOG_LEVEL_INFO=2
    51 CT_LOG_LEVEL_EXTRA=3
    52 CT_LOG_LEVEL_CFG=4
    53 CT_LOG_LEVEL_FILE=5
    54 CT_LOG_LEVEL_STATE=6
    55 CT_LOG_LEVEL_ALL=7
    56 CT_LOG_LEVEL_DEBUG=8
    57 
    58 # Make it easy to use \n and !
    59 CR=$(printf "\n")
    60 BANG='!'
    61 
    62 # A function to log what is happening
    63 # Different log level are available:
    64 #   - ERROR:   A serious, fatal error occurred
    65 #   - WARN:    A non fatal, non serious error occurred, take your responsbility with the generated build
    66 #   - INFO:    Informational messages
    67 #   - EXTRA:   Extra informational messages
    68 #   - CFG:     Output of various "./configure"-type scripts
    69 #   - FILE:    File / archive unpacking.
    70 #   - STATE:   State save & restore
    71 #   - ALL:     Component's build messages
    72 #   - DEBUG:   Internal debug messages
    73 # Usage: CT_DoLog <level> [message]
    74 # If message is empty, then stdin will be logged.
    75 CT_DoLog() {
    76     local max_level LEVEL level cur_l cur_L
    77     local l
    78     eval max_level="\${CT_LOG_LEVEL_${CT_LOG_LEVEL_MAX}}"
    79     # Set the maximum log level to DEBUG if we have none
    80     [ -z "${max_level}" ] && max_level=${CT_LOG_LEVEL_DEBUG}
    81 
    82     LEVEL="$1"; shift
    83     eval level="\${CT_LOG_LEVEL_${LEVEL}}"
    84 
    85     if [ $# -eq 0 ]; then
    86         cat -
    87     else
    88         printf "%s\n" "${*}"
    89     fi |( IFS="${CR}" # We want the full lines, even leading spaces
    90           _prog_bar_cpt=0
    91           _prog_bar[0]='/'
    92           _prog_bar[1]='-'
    93           _prog_bar[2]='\'
    94           _prog_bar[3]='|'
    95           indent=$((2*CT_STEP_COUNT))
    96           while read line; do
    97               case "${CT_LOG_SEE_TOOLS_WARN},${line}" in
    98                 y,*"warning:"*)         cur_L=WARN; cur_l=${CT_LOG_LEVEL_WARN};;
    99                 y,*"WARNING:"*)         cur_L=WARN; cur_l=${CT_LOG_LEVEL_WARN};;
   100                 *"error:"*)             cur_L=ERROR; cur_l=${CT_LOG_LEVEL_ERROR};;
   101                 *"make["*"]: *** ["*)   cur_L=ERROR; cur_l=${CT_LOG_LEVEL_ERROR};;
   102                 *)                      cur_L="${LEVEL}"; cur_l="${level}";;
   103               esac
   104               # There will always be a log file (stdout, fd #1), be it /dev/null
   105               printf "[%-5s]%*s%s%s\n" "${cur_L}" "${indent}" " " "${line}"
   106               if [ ${cur_l} -le ${max_level} ]; then
   107                   # Only print to console (fd #6) if log level is high enough.
   108                   printf "\r[%-5s]%*s%s%s\n" "${cur_L}" "${indent}" " " "${line}" >&6
   109               fi
   110               if [ "${CT_LOG_PROGRESS_BAR}" = "y" ]; then
   111                   printf "\r[%02d:%02d] %s " $((SECONDS/60)) $((SECONDS%60)) "${_prog_bar[$((_prog_bar_cpt/10))]}" >&6
   112                   _prog_bar_cpt=$(((_prog_bar_cpt+1)%40))
   113               fi
   114           done
   115         )
   116 
   117     return 0
   118 }
   119 
   120 # Execute an action, and log its messages
   121 # Usage: [VAR=val...] CT_DoExecLog <level> <command [parameters...]>
   122 CT_DoExecLog() {
   123     local level="$1"
   124     shift
   125     CT_DoLog DEBUG "==> Executing: '${*}'"
   126     "${@}" 2>&1 |CT_DoLog "${level}"
   127 }
   128 
   129 # Tail message to be logged whatever happens
   130 # Usage: CT_DoEnd <level>
   131 CT_DoEnd()
   132 {
   133     local level="$1"
   134     CT_STOP_DATE=$(CT_DoDate +%s%N)
   135     CT_STOP_DATE_HUMAN=$(CT_DoDate +%Y%m%d.%H%M%S)
   136     if [ "${level}" != "ERROR" ]; then
   137         CT_DoLog "${level:-INFO}" "Build completed at ${CT_STOP_DATE_HUMAN}"
   138     fi
   139     elapsed=$((CT_STOP_DATE-CT_STAR_DATE))
   140     elapsed_min=$((elapsed/(60*1000*1000*1000)))
   141     elapsed_sec=$(printf "%02d" $(((elapsed%(60*1000*1000*1000))/(1000*1000*1000))))
   142     elapsed_csec=$(printf "%02d" $(((elapsed%(1000*1000*1000))/(10*1000*1000))))
   143     CT_DoLog ${level:-INFO} "(elapsed: ${elapsed_min}:${elapsed_sec}.${elapsed_csec})"
   144 }
   145 
   146 # Remove entries referring to . and other relative paths
   147 # Usage: CT_SanitizePath
   148 CT_SanitizePath() {
   149     local new
   150     local p
   151     local IFS=:
   152     for p in $PATH; do
   153         # Only accept absolute paths;
   154         # Note: as a special case the empty string in PATH is equivalent to .
   155         if [ -n "${p}" -a -z "${p%%/*}" ]; then
   156             new="${new}${new:+:}${p}"
   157         fi
   158     done
   159     PATH="${new}"
   160 }
   161 
   162 # Abort the execution with an error message
   163 # Usage: CT_Abort <message>
   164 CT_Abort() {
   165     CT_DoLog ERROR "$1"
   166     exit 1
   167 }
   168 
   169 # Test a condition, and print a message if satisfied
   170 # Usage: CT_Test <message> <tests>
   171 CT_Test() {
   172     local ret
   173     local m="$1"
   174     shift
   175     CT_DoLog DEBUG "Testing '! ( $* )'"
   176     test "$@" && CT_DoLog WARN "$m"
   177     return 0
   178 }
   179 
   180 # Test a condition, and abort with an error message if satisfied
   181 # Usage: CT_TestAndAbort <message> <tests>
   182 CT_TestAndAbort() {
   183     local m="$1"
   184     shift
   185     CT_DoLog DEBUG "Testing '! ( $* )'"
   186     test "$@" && CT_Abort "$m"
   187     return 0
   188 }
   189 
   190 # Test a condition, and abort with an error message if not satisfied
   191 # Usage: CT_TestAndAbort <message> <tests>
   192 CT_TestOrAbort() {
   193     local m="$1"
   194     shift
   195     CT_DoLog DEBUG "Testing '$*'"
   196     test "$@" || CT_Abort "$m"
   197     return 0
   198 }
   199 
   200 # Test the presence of a tool, or abort if not found
   201 # Usage: CT_HasOrAbort <tool>
   202 CT_HasOrAbort() {
   203     CT_TestAndAbort "'${1}' not found and needed for successful toolchain build." -z "$(CT_Which "${1}")"
   204     return 0
   205 }
   206 
   207 # Search a program: wrap "which" for those system where
   208 # "which" verbosely says there is no match (Mandriva is
   209 # such a sucker...)
   210 # Usage: CT_Which <filename>
   211 CT_Which() {
   212   which "$1" 2>/dev/null || true
   213 }
   214 
   215 # Get current date with nanosecond precision
   216 # On those system not supporting nanosecond precision, faked with rounding down
   217 # to the highest entire second
   218 # Usage: CT_DoDate <fmt>
   219 CT_DoDate() {
   220     date "$1" |sed -r -e 's/N$/000000000/;'
   221 }
   222 
   223 CT_STEP_COUNT=1
   224 CT_STEP_MESSAGE[${CT_STEP_COUNT}]="<none>"
   225 # Memorise a step being done so that any error is caught
   226 # Usage: CT_DoStep <loglevel> <message>
   227 CT_DoStep() {
   228     local start=$(CT_DoDate +%s%N)
   229     CT_DoLog "$1" "================================================================="
   230     CT_DoLog "$1" "$2"
   231     CT_STEP_COUNT=$((CT_STEP_COUNT+1))
   232     CT_STEP_LEVEL[${CT_STEP_COUNT}]="$1"; shift
   233     CT_STEP_START[${CT_STEP_COUNT}]="${start}"
   234     CT_STEP_MESSAGE[${CT_STEP_COUNT}]="$1"
   235     return 0
   236 }
   237 
   238 # End the step just being done
   239 # Usage: CT_EndStep
   240 CT_EndStep() {
   241     local stop=$(CT_DoDate +%s%N)
   242     local duration=$(printf "%032d" $((stop-${CT_STEP_START[${CT_STEP_COUNT}]})) |sed -r -e 's/([[:digit:]]{2})[[:digit:]]{7}$/\.\1/; s/^0+//; s/^\./0\./;')
   243     local elapsed=$(printf "%02d:%02d" $((SECONDS/60)) $((SECONDS%60)))
   244     local level="${CT_STEP_LEVEL[${CT_STEP_COUNT}]}"
   245     local message="${CT_STEP_MESSAGE[${CT_STEP_COUNT}]}"
   246     CT_STEP_COUNT=$((CT_STEP_COUNT-1))
   247     CT_DoLog "${level}" "${message}: done in ${duration}s (at ${elapsed})"
   248     return 0
   249 }
   250 
   251 # Pushes into a directory, and pops back
   252 CT_Pushd() {
   253     pushd "$1" >/dev/null 2>&1
   254 }
   255 CT_Popd() {
   256     popd >/dev/null 2>&1
   257 }
   258 
   259 # Creates a temporary directory
   260 # $1: variable to assign to
   261 # Usage: CT_MktempDir foo
   262 CT_MktempDir() {
   263     # Some mktemp do not allow more than 6 Xs
   264     eval "$1"=$(mktemp -q -d "${CT_BUILD_DIR}/tmp.XXXXXX")
   265     CT_TestOrAbort "Could not make temporary directory" -n "${!1}" -a -d "${!1}"
   266     CT_DoLog DEBUG "Made temporary directory '${!1}'"
   267     return 0
   268 }
   269 
   270 # Removes one or more directories, even if it is read-only, or its parent is
   271 # Usage: CT_DoForceRmdir dir [...]
   272 CT_DoForceRmdir() {
   273     local dir
   274     local mode
   275     for dir in "${@}"; do
   276         [ -d "${dir}" ] || continue
   277         case "$CT_SYS_OS" in
   278             Linux|CYGWIN*)
   279                 mode="$(stat -c '%a' "$(dirname "${dir}")")"
   280                 ;;
   281             Darwin|*BSD)
   282                 mode="$(stat -f '%Lp' "$(dirname "${dir}")")"
   283                 ;;
   284             *)
   285                 CT_Abort "Unhandled host OS $CT_SYS_OS"
   286                 ;;
   287         esac
   288         CT_DoExecLog ALL chmod u+w "$(dirname "${dir}")"
   289         CT_DoExecLog ALL chmod -R u+w "${dir}"
   290         CT_DoExecLog ALL rm -rf "${dir}"
   291         CT_DoExecLog ALL chmod ${mode} "$(dirname "${dir}")"
   292     done
   293 }
   294 
   295 # Echoes the specified string on stdout until the pipe breaks.
   296 # Doesn't fail
   297 # $1: string to echo
   298 # Usage: CT_DoYes "" |make oldconfig
   299 CT_DoYes() {
   300     yes "$1" || true
   301 }
   302 
   303 # Add the specified directory to LD_LIBRARY_PATH, and export it
   304 # If the specified patch is already present, just export
   305 # $1: path to add
   306 # $2: add as 'first' or 'last' path, 'first' is assumed if $2 is empty
   307 # Usage CT_SetLibPath /some/where/lib [first|last]
   308 CT_SetLibPath() {
   309     local path="$1"
   310     local pos="$2"
   311 
   312     case ":${LD_LIBRARY_PATH}:" in
   313         *:"${path}":*)  ;;
   314         *)  case "${pos}" in
   315                 last)
   316                     CT_DoLog DEBUG "Adding '${path}' at end of LD_LIBRARY_PATH"
   317                     LD_LIBRARY_PATH="${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}${path}"
   318                     ;;
   319                 first|"")
   320                     CT_DoLog DEBUG "Adding '${path}' at start of LD_LIBRARY_PATH"
   321                     LD_LIBRARY_PATH="${path}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"
   322                     ;;
   323                 *)
   324                     CT_Abort "Incorrect position '${pos}' to add '${path}' to LD_LIBRARY_PATH"
   325                     ;;
   326             esac
   327             ;;
   328     esac
   329     CT_DoLog DEBUG "==> LD_LIBRARY_PATH='${LD_LIBRARY_PATH}'"
   330     export LD_LIBRARY_PATH
   331 }
   332 
   333 # Get the file name extension of a component
   334 # Usage: CT_GetFileExtension <component_name-component_version> [extension]
   335 # If found, echoes the extension to stdout, and return 0
   336 # If not found, echoes nothing on stdout, and return !0.
   337 CT_GetFileExtension() {
   338     local ext
   339     local file="$1"
   340     shift
   341     local first_ext="$1"
   342 
   343     # we need to also check for an empty extension for those very
   344     # peculiar components that don't have one (such as sstrip from
   345     # buildroot).
   346     for ext in ${first_ext} .tar.gz .tar.bz2 .tgz .tar /.git ''; do
   347         if [ -e "${CT_TARBALLS_DIR}/${file}${ext}" ]; then
   348             echo "${ext}"
   349             exit 0
   350         fi
   351     done
   352 
   353     exit 1
   354 }
   355 
   356 # Try to retrieve the specified URL (HTTP or FTP)
   357 # Usage: CT_DoGetFile <URL>
   358 # This functions always returns true (0), as it can be legitimate not
   359 # to find the requested URL (think about snapshots, different layouts
   360 # for different gcc versions, etc...).
   361 CT_DoGetFile() {
   362     # OK, just look if we have them...
   363     # We are sure at least one is available, ./configure checked for it.
   364     local _curl=$(CT_Which curl)
   365     local _wget=$(CT_Which wget)
   366     _curl="${_curl:-false}"
   367     _wget="${_wget:-false}"
   368 
   369     # Some (very old!) FTP server might not support the passive mode, thus
   370     # retry without.
   371     # We also retry a few times, in case there is a transient error (eg. behind
   372     # a dynamic IP that changes during the transfer...)
   373     # With automated download as we are doing, it can be very dangerous to
   374     # continue the downloads. It's far better to simply overwrite the
   375     # destination file.
   376     # Some company networks have firewalls to connect to the internet, but it's
   377     # not easy to detect them, and wget does not timeout by default while
   378     # connecting, so force a global ${CT_CONNECT_TIMEOUT}-second timeout.
   379     # For curl, no good progress indicator is available. So, be silent.
   380        CT_DoExecLog ALL "${_curl}" --ftp-pasv    --retry 3 --connect-timeout ${CT_CONNECT_TIMEOUT} -L -f -s -O "$1" \
   381     || CT_DoExecLog ALL "${_curl}"               --retry 3 --connect-timeout ${CT_CONNECT_TIMEOUT} -L -f -s -O "$1" \
   382     || CT_DoExecLog ALL "${_wget}" --passive-ftp --tries=3 -T ${CT_CONNECT_TIMEOUT} -nc --progress=dot:binary  "$1" \
   383     || CT_DoExecLog ALL "${_wget}"               --tries=3 -T ${CT_CONNECT_TIMEOUT} -nc --progress=dot:binary  "$1" \
   384     || rm -f "${1##*/}"
   385 }
   386 
   387 # This function tries to retrieve a tarball form a local directory
   388 # Usage: CT_GetLocal <basename> [.extension]
   389 CT_GetLocal() {
   390     local basename="$1"
   391     local first_ext="$2"
   392     local ext
   393 
   394     # Do we already have it in *our* tarballs dir?
   395     if ext="$( CT_GetFileExtension "${basename}" ${first_ext} )"; then
   396         CT_DoLog DEBUG "Already have '${basename}'"
   397         return 0
   398     fi
   399 
   400     if [ -n "${CT_LOCAL_TARBALLS_DIR}" ]; then
   401         CT_DoLog DEBUG "Trying to retrieve an already downloaded copy of '${basename}'"
   402         # We'd rather have a bzip2'ed tarball, then gzipped tarball, plain tarball,
   403         # or, as a failover, a file without extension.
   404         for ext in ${first_ext} .tar.bz2 .tar.gz .tgz .tar ''; do
   405             CT_DoLog DEBUG "Trying '${CT_LOCAL_TARBALLS_DIR}/${basename}${ext}'"
   406             if [ -r "${CT_LOCAL_TARBALLS_DIR}/${basename}${ext}" -a \
   407                  "${CT_FORCE_DOWNLOAD}" != "y" ]; then
   408                 CT_DoLog DEBUG "Got '${basename}' from local storage"
   409                 CT_DoExecLog ALL ln -s "${CT_LOCAL_TARBALLS_DIR}/${basename}${ext}" "${CT_TARBALLS_DIR}/${basename}${ext}"
   410                 return 0
   411             fi
   412         done
   413     fi
   414     return 1
   415 }
   416 
   417 # This function saves the specified to local storage if possible,
   418 # and if so, symlinks it for later usage
   419 # Usage: CT_SaveLocal </full/path/file.name>
   420 CT_SaveLocal() {
   421     local file="$1"
   422     local basename="${file##*/}"
   423 
   424     if [ "${CT_SAVE_TARBALLS}" = "y" ]; then
   425         CT_DoLog EXTRA "Saving '${basename}' to local storage"
   426         # The file may already exist if downloads are forced: remove it first
   427         CT_DoExecLog ALL rm -f "${CT_LOCAL_TARBALLS_DIR}/${basename}"
   428         CT_DoExecLog ALL mv -f "${file}" "${CT_LOCAL_TARBALLS_DIR}"
   429         CT_DoExecLog ALL ln -s "${CT_LOCAL_TARBALLS_DIR}/${basename}" "${file}"
   430     fi
   431 }
   432 
   433 # Download the file from one of the URLs passed as argument
   434 # Usage: CT_GetFile <basename> [.extension] <url> [url ...]
   435 CT_GetFile() {
   436     local ext
   437     local url URLS LAN_URLS
   438     local file="$1"
   439     local first_ext
   440     shift
   441     # If next argument starts with a dot, then this is not an URL,
   442     # and we can consider that it is a preferred extension.
   443     case "$1" in
   444         .*) first_ext="$1"
   445             shift
   446             ;;
   447     esac
   448 
   449     # Does it exist localy?
   450     CT_GetLocal "${file}" ${first_ext} && return 0 || true
   451     # No, it does not...
   452 
   453     # Try to retrieve the file
   454     CT_DoLog EXTRA "Retrieving '${file}'"
   455     CT_Pushd "${CT_TARBALLS_DIR}"
   456 
   457     URLS="$@"
   458 
   459     # Add URLs on the LAN mirror
   460     LAN_URLS=
   461     if [ "${CT_USE_MIRROR}" = "y" ]; then
   462         CT_TestOrAbort "Please set the mirror base URL" -n "${CT_MIRROR_BASE_URL}"
   463         LAN_URLS="${LAN_URLS} ${CT_MIRROR_BASE_URL}/${file%-*}"
   464         LAN_URLS="${LAN_URLS} ${CT_MIRROR_BASE_URL}"
   465 
   466         if [ "${CT_PREFER_MIRROR}" = "y" ]; then
   467             CT_DoLog DEBUG "Pre-pending LAN mirror URLs"
   468             URLS="${LAN_URLS} ${URLS}"
   469         else
   470             CT_DoLog DEBUG "Appending LAN mirror URLs"
   471             URLS="${URLS} ${LAN_URLS}"
   472         fi
   473     fi
   474 
   475     # Scan all URLs in turn, and try to grab a tarball from there
   476     # Do *not* try git trees (ext=/.git), this is handled in a specific
   477     # wrapper, below
   478     for ext in ${first_ext} .tar.bz2 .tar.gz .tgz .tar ''; do
   479         # Try all urls in turn
   480         for url in ${URLS}; do
   481             CT_DoLog DEBUG "Trying '${url}/${file}${ext}'"
   482             CT_DoGetFile "${url}/${file}${ext}"
   483             if [ -f "${file}${ext}" ]; then
   484                 CT_DoLog DEBUG "Got '${file}' from the Internet"
   485                 CT_SaveLocal "${CT_TARBALLS_DIR}/${file}${ext}"
   486                 return 0
   487             fi
   488         done
   489     done
   490     CT_Popd
   491 
   492     CT_Abort "Could not retrieve '${file}'."
   493 }
   494 
   495 # Checkout from CVS, and build the associated tarball
   496 # The tarball will be called ${basename}.tar.bz2
   497 # Prerequisite: either the server does not require password,
   498 # or the user must already be logged in.
   499 # 'tag' is the tag to retrieve. Must be specified, but can be empty.
   500 # If dirname is specified, then module will be renamed to dirname
   501 # prior to building the tarball.
   502 # Usage: CT_GetCVS <basename> <url> <module> <tag> [dirname[=subdir]]
   503 # Note: if '=subdir' is given, then it is used instead of 'module'.
   504 CT_GetCVS() {
   505     local basename="$1"
   506     local uri="$2"
   507     local module="$3"
   508     local tag="${4:+-r ${4}}"
   509     local dirname="$5"
   510     local tmp_dir
   511 
   512     # Does it exist localy?
   513     CT_GetLocal "${basename}" && return 0 || true
   514     # No, it does not...
   515 
   516     CT_DoLog EXTRA "Retrieving '${basename}'"
   517 
   518     CT_MktempDir tmp_dir
   519     CT_Pushd "${tmp_dir}"
   520 
   521     CT_DoExecLog ALL cvs -z 9 -d "${uri}" co -P ${tag} "${module}"
   522     if [ -n "${dirname}" ]; then
   523         case "${dirname}" in
   524             *=*)
   525                 CT_DoExecLog DEBUG mv "${dirname#*=}" "${dirname%%=*}"
   526                 CT_DoExecLog ALL tar cjf "${CT_TARBALLS_DIR}/${basename}.tar.bz2" "${dirname%%=*}"
   527                 ;;
   528             *)
   529                 CT_DoExecLog ALL mv "${module}" "${dirname}"
   530                 CT_DoExecLog ALL tar cjf "${CT_TARBALLS_DIR}/${basename}.tar.bz2" "${dirname:-${module}}"
   531                 ;;
   532         esac
   533     fi
   534     CT_SaveLocal "${CT_TARBALLS_DIR}/${basename}.tar.bz2"
   535 
   536     CT_Popd
   537     CT_DoExecLog ALL rm -rf "${tmp_dir}"
   538 }
   539 
   540 # Check out from SVN, and build the associated tarball
   541 # The tarball will be called ${basename}.tar.bz2
   542 # Prerequisite: either the server does not require password,
   543 # or the user must already be logged in.
   544 # 'rev' is the revision to retrieve
   545 # Usage: CT_GetSVN <basename> <url> [rev]
   546 CT_GetSVN() {
   547     local basename="$1"
   548     local uri="$2"
   549     local rev="$3"
   550 
   551     # Does it exist localy?
   552     CT_GetLocal "${basename}" && return 0 || true
   553     # No, it does not...
   554 
   555     CT_DoLog EXTRA "Retrieving '${basename}'"
   556 
   557     CT_MktempDir tmp_dir
   558     CT_Pushd "${tmp_dir}"
   559 
   560     CT_DoExecLog ALL svn export ${rev:+-r ${rev}} "${uri}" "${basename}"
   561     CT_DoExecLog ALL tar cjf "${CT_TARBALLS_DIR}/${basename}.tar.bz2" "${basename}"
   562     CT_SaveLocal "${CT_TARBALLS_DIR}/${basename}.tar.bz2"
   563 
   564     CT_Popd
   565     CT_DoExecLog ALL rm -rf "${tmp_dir}"
   566 }
   567 
   568 # Clone a git tree
   569 # Tries the given URLs in turn until one can get cloned. No tarball will be created.
   570 # Prerequisites: either the server does not require password,
   571 # or the user has already taken any action to authenticate to the server.
   572 # The cloned tree will *not* be stored in the local tarballs dir!
   573 # Usage: CT_GetGit <basename> <url [url ...]>
   574 CT_GetGit() {
   575     local basename="$1"; shift
   576     local url
   577     local cloned=0
   578 
   579     # Do we have it in our tarballs dir?
   580     if [ -d "${CT_TARBALLS_DIR}/${basename}/.git" ]; then
   581         CT_DoLog EXTRA "Updating git tree '${basename}'"
   582         CT_Pushd "${CT_TARBALLS_DIR}/${basename}"
   583         CT_DoExecLog ALL git pull
   584         CT_Popd
   585     else
   586         CT_DoLog EXTRA "Retrieving git tree '${basename}'"
   587         for url in "${@}"; do
   588             CT_DoLog ALL "Trying to clone from '${url}'"
   589             CT_DoForceRmdir "${CT_TARBALLS_DIR}/${basename}"
   590             if git clone "${url}" "${CT_TARBALLS_DIR}/${basename}" 2>&1 |CT_DoLog ALL; then
   591                 cloned=1
   592                 break
   593             fi
   594         done
   595         CT_TestOrAbort "Could not clone '${basename}'" ${cloned} -ne 0
   596     fi
   597 }
   598 
   599 # Extract a tarball
   600 # Some tarballs need to be extracted in specific places. Eg.: glibc addons
   601 # must be extracted in the glibc directory; uCLibc locales must be extracted
   602 # in the extra/locale sub-directory of uClibc. This is taken into account
   603 # by the caller, that did a 'cd' into the correct path before calling us
   604 # and sets nochdir to 'nochdir'.
   605 # Note also that this function handles the git trees!
   606 # Usage: CT_Extract <basename> [nochdir] [options]
   607 # where 'options' are dependent on the source (eg. git branch/tag...)
   608 CT_Extract() {
   609     local nochdir="$1"
   610     local basename
   611     local ext
   612 
   613     if [ "${nochdir}" = "nochdir" ]; then
   614         shift
   615         nochdir="$(pwd)"
   616     else
   617         nochdir="${CT_SRC_DIR}"
   618     fi
   619 
   620     basename="$1"
   621     shift
   622 
   623     if ! ext="$(CT_GetFileExtension "${basename}")"; then
   624       CT_Abort "'${basename}' not found in '${CT_TARBALLS_DIR}'"
   625     fi
   626     local full_file="${CT_TARBALLS_DIR}/${basename}${ext}"
   627 
   628     # Check if already extracted
   629     if [ -e "${CT_SRC_DIR}/.${basename}.extracted" ]; then
   630         CT_DoLog DEBUG "Already extracted '${basename}'"
   631         return 0
   632     fi
   633 
   634     # Check if previously partially extracted
   635     if [ -e "${CT_SRC_DIR}/.${basename}.extracting" ]; then
   636         CT_DoLog ERROR "The '${basename}' sources were partially extracted."
   637         CT_DoLog ERROR "Please remove first:"
   638         CT_DoLog ERROR " - the source dir for '${basename}', in '${CT_SRC_DIR}'"
   639         CT_DoLog ERROR " - the file '${CT_SRC_DIR}/.${basename}.extracting'"
   640         CT_Abort "I'll stop now to avoid any carnage..."
   641     fi
   642     CT_DoExecLog DEBUG touch "${CT_SRC_DIR}/.${basename}.extracting"
   643 
   644     CT_Pushd "${nochdir}"
   645 
   646     CT_DoLog EXTRA "Extracting '${basename}'"
   647     case "${ext}" in
   648         .tar.bz2)     CT_DoExecLog FILE tar xvjf "${full_file}";;
   649         .tar.gz|.tgz) CT_DoExecLog FILE tar xvzf "${full_file}";;
   650         .tar)         CT_DoExecLog FILE tar xvf  "${full_file}";;
   651         /.git)        CT_ExtractGit "${basename}" "${@}";;
   652         *)            CT_Abort "Don't know how to handle '${basename}${ext}': unknown extension";;
   653     esac
   654 
   655     # Some tarballs have read-only files... :-(
   656     # Because of nochdir, we don't know where we are, so chmod all
   657     # the src tree
   658     CT_DoExecLog DEBUG chmod -R u+w "${CT_SRC_DIR}"
   659 
   660     # Don't mark as being extracted for git
   661     case "${ext}" in
   662         /.git)  ;;
   663         *)      CT_DoExecLog DEBUG touch "${CT_SRC_DIR}/.${basename}.extracted";;
   664     esac
   665     CT_DoExecLog DEBUG rm -f "${CT_SRC_DIR}/.${basename}.extracting"
   666 
   667     CT_Popd
   668 }
   669 
   670 # Create a working git clone
   671 # Usage: CT_ExtractGit <basename> [ref]
   672 # where 'ref' is the reference to use:
   673 #   the full name of a branch, like "remotes/origin/branch_name"
   674 #   a date as understandable by git, like "YYYY-MM-DD[ hh[:mm[:ss]]]"
   675 #   a tag name
   676 CT_ExtractGit() {
   677     local basename="${1}"
   678     local ref="${2}"
   679     local clone_dir
   680     local ref_type
   681 
   682     # pushd now to be able to get git revlist in case ref is a date
   683     clone_dir="${CT_TARBALLS_DIR}/${basename}"
   684     CT_Pushd "${clone_dir}"
   685 
   686     # What kind of reference is ${ref} ?
   687     if [ -z "${ref}" ]; then
   688         # Don't update the clone, keep as-is
   689         ref_type=none
   690     elif git tag |grep -E "^${ref}$" >/dev/null 2>&1; then
   691         ref_type=tag
   692     elif git branch -a --no-color |grep -E "^. ${ref}$" >/dev/null 2>&1; then
   693         ref_type=branch
   694     elif date -d "${ref}" >/dev/null 2>&1; then
   695         ref_type=date
   696         ref=$(git rev-list -n1 --before="${ref}")
   697     else
   698         CT_Abort "Reference '${ref}' is an incorrect git reference: neither tag, branch nor date"
   699     fi
   700 
   701     CT_DoExecLog DEBUG rm -f "${CT_SRC_DIR}/${basename}"
   702     CT_DoExecLog ALL ln -sf "${clone_dir}" "${CT_SRC_DIR}/${basename}"
   703 
   704     case "${ref_type}" in
   705         none)   ;;
   706         *)      CT_DoExecLog FILE git checkout "${ref}";;
   707     esac
   708 
   709     CT_Popd
   710 }
   711 
   712 # Patches the specified component
   713 # See CT_Extract, above, for explanations on 'nochdir'
   714 # Usage: CT_Patch [nochdir] <packagename> <packageversion>
   715 # If the package directory is *not* packagename-packageversion, then
   716 # the caller must cd into the proper directory first, and call us
   717 # with nochdir
   718 CT_Patch() {
   719     local nochdir="$1"
   720     local pkgname
   721     local version
   722     local pkgdir
   723     local base_file
   724     local ver_file
   725     local d
   726     local -a patch_dirs
   727     local bundled_patch_dir
   728     local local_patch_dir
   729 
   730     if [ "${nochdir}" = "nochdir" ]; then
   731         shift
   732         pkgname="$1"
   733         version="$2"
   734         pkgdir="${pkgname}-${version}"
   735         nochdir="$(pwd)"
   736     else
   737         pkgname="$1"
   738         version="$2"
   739         pkgdir="${pkgname}-${version}"
   740         nochdir="${CT_SRC_DIR}/${pkgdir}"
   741     fi
   742 
   743     # Check if already patched
   744     if [ -e "${CT_SRC_DIR}/.${pkgdir}.patched" ]; then
   745         CT_DoLog DEBUG "Already patched '${pkgdir}'"
   746         return 0
   747     fi
   748 
   749     # Check if already partially patched
   750     if [ -e "${CT_SRC_DIR}/.${pkgdir}.patching" ]; then
   751         CT_DoLog ERROR "The '${pkgdir}' sources were partially patched."
   752         CT_DoLog ERROR "Please remove first:"
   753         CT_DoLog ERROR " - the source dir for '${pkgdir}', in '${CT_SRC_DIR}'"
   754         CT_DoLog ERROR " - the file '${CT_SRC_DIR}/.${pkgdir}.extracted'"
   755         CT_DoLog ERROR " - the file '${CT_SRC_DIR}/.${pkgdir}.patching'"
   756         CT_Abort "I'll stop now to avoid any carnage..."
   757     fi
   758     touch "${CT_SRC_DIR}/.${pkgdir}.patching"
   759 
   760     CT_Pushd "${nochdir}"
   761 
   762     CT_DoLog EXTRA "Patching '${pkgdir}'"
   763 
   764     bundled_patch_dir="${CT_LIB_DIR}/patches/${pkgname}/${version}"
   765     local_patch_dir="${CT_LOCAL_PATCH_DIR}/${pkgname}/${version}"
   766 
   767     case "${CT_PATCH_ORDER}" in
   768         bundled)        patch_dirs=("${bundled_patch_dir}");;
   769         local)          patch_dirs=("${local_patch_dir}");;
   770         bundled,local)  patch_dirs=("${bundled_patch_dir}" "${local_patch_dir}");;
   771         local,bundled)  patch_dirs=("${local_patch_dir}" "${bundled_patch_dir}");;
   772         none)           patch_dirs=;;
   773     esac
   774 
   775     for d in "${patch_dirs[@]}"; do
   776         CT_DoLog DEBUG "Looking for patches in '${d}'..."
   777         if [ -n "${d}" -a -d "${d}" ]; then
   778             for p in "${d}"/*.patch; do
   779                 if [ -f "${p}" ]; then
   780                     CT_DoLog DEBUG "Applying patch '${p}'"
   781                     CT_DoExecLog ALL patch --no-backup-if-mismatch -g0 -F1 -p1 -f <"${p}"
   782                 fi
   783             done
   784             if [ "${CT_PATCH_SINGLE}" = "y" ]; then
   785                 break
   786             fi
   787         fi
   788     done
   789 
   790     if [ "${CT_OVERIDE_CONFIG_GUESS_SUB}" = "y" ]; then
   791         CT_DoLog ALL "Overiding config.guess and config.sub"
   792         for cfg in config_guess config_sub; do
   793             eval ${cfg}="${CT_LIB_DIR}/scripts/${cfg/_/.}"
   794             [ -e "${CT_TOP_DIR}/scripts/${cfg/_/.}" ] && eval ${cfg}="${CT_TOP_DIR}/scripts/${cfg/_/.}"
   795             # Can't use CT_DoExecLog because of the '{} \;' to be passed un-mangled to find
   796             find . -type f -name "${cfg/_/.}" -exec cp -v "${!cfg}" {} \; |CT_DoLog ALL
   797         done
   798     fi
   799 
   800     CT_DoExecLog DEBUG touch "${CT_SRC_DIR}/.${pkgdir}.patched"
   801     CT_DoExecLog DEBUG rm -f "${CT_SRC_DIR}/.${pkgdir}.patching"
   802 
   803     CT_Popd
   804 }
   805 
   806 # Two wrappers to call config.(guess|sub) either from CT_TOP_DIR or CT_LIB_DIR.
   807 # Those from CT_TOP_DIR, if they exist, will be be more recent than those from CT_LIB_DIR.
   808 CT_DoConfigGuess() {
   809     if [ -x "${CT_TOP_DIR}/scripts/config.guess" ]; then
   810         "${CT_TOP_DIR}/scripts/config.guess"
   811     else
   812         "${CT_LIB_DIR}/scripts/config.guess"
   813     fi
   814 }
   815 
   816 CT_DoConfigSub() {
   817     if [ -x "${CT_TOP_DIR}/scripts/config.sub" ]; then
   818         "${CT_TOP_DIR}/scripts/config.sub" "$@"
   819     else
   820         "${CT_LIB_DIR}/scripts/config.sub" "$@"
   821     fi
   822 }
   823 
   824 # Compute the target tuple from what is provided by the user
   825 # Usage: CT_DoBuildTargetTuple
   826 # In fact this function takes the environment variables to build the target
   827 # tuple. It is needed both by the normal build sequence, as well as the
   828 # sample saving sequence.
   829 CT_DoBuildTargetTuple() {
   830     # Set the endianness suffix, and the default endianness gcc option
   831     case "${CT_ARCH_BE},${CT_ARCH_LE}" in
   832         y,) target_endian_eb=eb
   833             target_endian_el=
   834             CT_ARCH_ENDIAN_CFLAG="-mbig-endian"
   835             CT_ARCH_ENDIAN_LDFLAG="-EB"
   836             ;;
   837         ,y) target_endian_eb=
   838             target_endian_el=el
   839             CT_ARCH_ENDIAN_CFLAG="-mlittle-endian"
   840             CT_ARCH_ENDIAN_LDFLAG="-EL"
   841             ;;
   842     esac
   843 
   844     # Build the default architecture tuple part
   845     CT_TARGET_ARCH="${CT_ARCH}"
   846 
   847     # Set defaults for the system part of the tuple. Can be overriden
   848     # by architecture-specific values.
   849     case "${CT_LIBC}" in
   850         *glibc) CT_TARGET_SYS=gnu;;
   851         uClibc) CT_TARGET_SYS=uclibc;;
   852         *)      CT_TARGET_SYS=elf;;
   853     esac
   854 
   855     # Set the default values for ARCH, ABI, CPU, TUNE, FPU and FLOAT
   856     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
   857     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
   858     [ "${CT_ARCH_ARCH}"     ] && { CT_ARCH_ARCH_CFLAG="-march=${CT_ARCH_ARCH}";  CT_ARCH_WITH_ARCH="--with-arch=${CT_ARCH_ARCH}"; }
   859     [ "${CT_ARCH_ABI}"      ] && { CT_ARCH_ABI_CFLAG="-mabi=${CT_ARCH_ABI}";     CT_ARCH_WITH_ABI="--with-abi=${CT_ARCH_ABI}";    }
   860     [ "${CT_ARCH_CPU}"      ] && { CT_ARCH_CPU_CFLAG="-mcpu=${CT_ARCH_CPU}";     CT_ARCH_WITH_CPU="--with-cpu=${CT_ARCH_CPU}";    }
   861     [ "${CT_ARCH_TUNE}"     ] && { CT_ARCH_TUNE_CFLAG="-mtune=${CT_ARCH_TUNE}";  CT_ARCH_WITH_TUNE="--with-tune=${CT_ARCH_TUNE}"; }
   862     [ "${CT_ARCH_FPU}"      ] && { CT_ARCH_FPU_CFLAG="-mfpu=${CT_ARCH_FPU}";     CT_ARCH_WITH_FPU="--with-fpu=${CT_ARCH_FPU}";    }
   863     [ "${CT_ARCH_FLOAT_SW}" ] && { CT_ARCH_FLOAT_CFLAG="-msoft-float";           CT_ARCH_WITH_FLOAT="--with-float=soft";          }
   864 
   865     # Build the default kernel tuple part
   866     CT_TARGET_KERNEL="${CT_KERNEL}"
   867 
   868     # Overide the default values with the components specific settings
   869     CT_DoArchTupleValues
   870     CT_DoKernelTupleValues
   871 
   872     # Finish the target tuple construction
   873     CT_TARGET="${CT_TARGET_ARCH}"
   874     CT_TARGET="${CT_TARGET}${CT_TARGET_VENDOR:+-${CT_TARGET_VENDOR}}"
   875     CT_TARGET="${CT_TARGET}${CT_TARGET_KERNEL:+-${CT_TARGET_KERNEL}}"
   876     CT_TARGET="${CT_TARGET}${CT_TARGET_SYS:+-${CT_TARGET_SYS}}"
   877 
   878     # Sanity checks
   879     __sed_alias=""
   880     if [ -n "${CT_TARGET_ALIAS_SED_EXPR}" ]; then
   881         __sed_alias=$(echo "${CT_TARGET}" |sed -r -e "${CT_TARGET_ALIAS_SED_EXPR}")
   882     fi
   883     case ":${CT_TARGET_VENDOR}:${CT_TARGET_ALIAS}:${__sed_alias}:" in
   884       :*" "*:*:*:) CT_Abort "Don't use spaces in the vendor string, it breaks things.";;
   885       :*"-"*:*:*:) CT_Abort "Don't use dashes in the vendor string, it breaks things.";;
   886       :*:*" "*:*:) CT_Abort "Don't use spaces in the target alias, it breaks things.";;
   887       :*:*:*" "*:) CT_Abort "Don't use spaces in the target sed transform, it breaks things.";;
   888     esac
   889 
   890     # Canonicalise it
   891     CT_TARGET=$(CT_DoConfigSub "${CT_TARGET}")
   892     # Prepare the target CFLAGS
   893     CT_ARCH_TARGET_CFLAGS="${CT_ARCH_TARGET_CFLAGS} ${CT_ARCH_ENDIAN_CFLAG}"
   894     CT_ARCH_TARGET_CFLAGS="${CT_ARCH_TARGET_CFLAGS} ${CT_ARCH_ARCH_CFLAG}"
   895     CT_ARCH_TARGET_CFLAGS="${CT_ARCH_TARGET_CFLAGS} ${CT_ARCH_ABI_CFLAG}"
   896     CT_ARCH_TARGET_CFLAGS="${CT_ARCH_TARGET_CFLAGS} ${CT_ARCH_CPU_CFLAG}"
   897     CT_ARCH_TARGET_CFLAGS="${CT_ARCH_TARGET_CFLAGS} ${CT_ARCH_TUNE_CFLAG}"
   898     CT_ARCH_TARGET_CFLAGS="${CT_ARCH_TARGET_CFLAGS} ${CT_ARCH_FPU_CFLAG}"
   899     CT_ARCH_TARGET_CFLAGS="${CT_ARCH_TARGET_CFLAGS} ${CT_ARCH_FLOAT_CFLAG}"
   900 
   901     # Now on for the target LDFLAGS
   902     CT_ARCH_TARGET_LDFLAGS="${CT_ARCH_TARGET_LDFLAGS} ${CT_ARCH_ENDIAN_LDFLAG}"
   903 }
   904 
   905 # This function does pause the build until the user strikes "Return"
   906 # Usage: CT_DoPause [optional_message]
   907 CT_DoPause() {
   908     local foo
   909     local message="${1:-Pausing for your pleasure}"
   910     CT_DoLog INFO "${message}"
   911     read -p "Press 'Enter' to continue, or Ctrl-C to stop..." foo >&6
   912     return 0
   913 }
   914 
   915 # This function creates a tarball of the specified directory, but
   916 # only if it exists
   917 # Usage: CT_DoTarballIfExists <dir> <tarball_basename> [extra_tar_options [...]]
   918 CT_DoTarballIfExists() {
   919     local dir="$1"
   920     local tarball="$2"
   921     shift 2
   922     local -a extra_tar_opts=( "$@" )
   923     local -a compress
   924 
   925     case "${CT_DEBUG_CT_SAVE_STEPS_GZIP}" in
   926         y)  compress=( gzip -c -3 - ); tar_ext=.gz;;
   927         *)  compress=( cat - );        tar_ext=;;
   928     esac
   929 
   930     if [ -d "${dir}" ]; then
   931         CT_DoLog DEBUG "  Saving '${dir}'"
   932         { tar c -C "${dir}" -v -f - "${extra_tar_opts[@]}" .    \
   933           |"${compress[@]}" >"${tarball}.tar${tar_ext}"         ;
   934         } 2>&1 |sed -r -e 's/^/    /;' |CT_DoLog STATE
   935     else
   936         CT_DoLog STATE "  Not saving '${dir}': does not exist"
   937     fi
   938 }
   939 
   940 # This function extracts a tarball to the specified directory, but
   941 # only if the tarball exists
   942 # Usage: CT_DoExtractTarballIfExists <tarball_basename> <dir> [extra_tar_options [...]]
   943 CT_DoExtractTarballIfExists() {
   944     local tarball="$1"
   945     local dir="$2"
   946     shift 2
   947     local -a extra_tar_opts=( "$@" )
   948     local -a uncompress
   949 
   950     case "${CT_DEBUG_CT_SAVE_STEPS_GZIP}" in
   951         y)  uncompress=( gzip -c -d ); tar_ext=.gz;;
   952         *)  uncompress=( cat );        tar_ext=;;
   953     esac
   954 
   955     if [ -f "${tarball}.tar${tar_ext}" ]; then
   956         CT_DoLog DEBUG "  Restoring '${dir}'"
   957         CT_DoForceRmdir "${dir}"
   958         CT_DoExecLog DEBUG mkdir -p "${dir}"
   959         { "${uncompress[@]}" "${tarball}.tar${tar_ext}"     \
   960           |tar x -C "${dir}" -v -f - "${extra_tar_opts[@]}" ;
   961         } 2>&1 |sed -r -e 's/^/    /;' |CT_DoLog STATE
   962     else
   963         CT_DoLog STATE "  Not restoring '${dir}': does not exist"
   964     fi
   965 }
   966 
   967 # This function saves the state of the toolchain to be able to restart
   968 # at any one point
   969 # Usage: CT_DoSaveState <next_step_name>
   970 CT_DoSaveState() {
   971 	[ "${CT_DEBUG_CT_SAVE_STEPS}" = "y" ] || return 0
   972     local state_name="$1"
   973     local state_dir="${CT_STATE_DIR}/${state_name}"
   974 
   975     # Log this to the log level required by the user
   976     CT_DoLog ${CT_LOG_LEVEL_MAX} "Saving state to restart at step '${state_name}'..."
   977 
   978     rm -rf "${state_dir}"
   979     mkdir -p "${state_dir}"
   980 
   981     CT_DoLog STATE "  Saving environment and aliases"
   982     # We must omit shell functions, and some specific bash variables
   983     # that break when restoring the environment, later. We could do
   984     # all the processing in the awk script, but a sed is easier...
   985     set |awk '
   986               BEGIN { _p = 1; }
   987               $0~/^[^ ]+ \(\)/ { _p = 0; }
   988               _p == 1
   989               $0 == "}" { _p = 1; }
   990               ' |sed -r -e '/^BASH_(ARGC|ARGV|LINENO|SOURCE|VERSINFO)=/d;
   991                            /^(UID|EUID)=/d;
   992                            /^(FUNCNAME|GROUPS|PPID|SHELLOPTS)=/d;' >"${state_dir}/env.sh"
   993 
   994     if [ "${CT_COMPLIBS_BACKUP}" = "y" ]; then
   995         CT_DoTarballIfExists "${CT_COMPLIBS_DIR}" "${state_dir}/complibs_dir"
   996     fi
   997     CT_DoTarballIfExists "${CT_CONFIG_DIR}" "${state_dir}/config_dir"
   998     CT_DoTarballIfExists "${CT_CC_CORE_STATIC_PREFIX_DIR}" "${state_dir}/cc_core_static_prefix_dir"
   999     CT_DoTarballIfExists "${CT_CC_CORE_SHARED_PREFIX_DIR}" "${state_dir}/cc_core_shared_prefix_dir"
  1000     CT_DoTarballIfExists "${CT_PREFIX_DIR}" "${state_dir}/prefix_dir" --exclude '*.log'
  1001 
  1002     if [ "${CT_LOG_TO_FILE}" = "y" ]; then
  1003         CT_DoLog STATE "  Saving log file"
  1004         exec >/dev/null
  1005         case "${CT_DEBUG_CT_SAVE_STEPS_GZIP}" in
  1006             y)  gzip -3 -c "${CT_LOG_FILE}"  >"${state_dir}/log.gz";;
  1007             *)  cat "${CT_LOG_FILE}" >"${state_dir}/log";;
  1008         esac
  1009         exec >>"${CT_LOG_FILE}"
  1010     fi
  1011 }
  1012 
  1013 # This function restores a previously saved state
  1014 # Usage: CT_DoLoadState <state_name>
  1015 CT_DoLoadState(){
  1016     local state_name="$1"
  1017     local state_dir="${CT_STATE_DIR}/${state_name}"
  1018     local old_RESTART="${CT_RESTART}"
  1019     local old_STOP="${CT_STOP}"
  1020 
  1021     CT_TestOrAbort "The previous build did not reach the point where it could be restarted at '${CT_RESTART}'" -d "${state_dir}"
  1022 
  1023     # We need to do something special with the log file!
  1024     if [ "${CT_LOG_TO_FILE}" = "y" ]; then
  1025         exec >"${state_dir}/tail.log"
  1026     fi
  1027 
  1028     # Log this to the log level required by the user
  1029     CT_DoLog ${CT_LOG_LEVEL_MAX} "Restoring state at step '${state_name}', as requested."
  1030 
  1031     CT_DoExtractTarballIfExists "${state_dir}/prefix_dir" "${CT_PREFIX_DIR}"
  1032     CT_DoExtractTarballIfExists "${state_dir}/cc_core_shared_prefix_dir" "${CT_CC_CORE_SHARED_PREFIX_DIR}"
  1033     CT_DoExtractTarballIfExists "${state_dir}/cc_core_static_prefix_dir" "${CT_CC_CORE_STATIC_PREFIX_DIR}"
  1034     CT_DoExtractTarballIfExists "${state_dir}/config_dir" "${CT_CONFIG_DIR}"
  1035     if [ "${CT_COMPLIBS_BACKUP}" = "y" ]; then
  1036         CT_DoExtractTarballIfExists "${state_dir}/complibs_dir" "${CT_COMPLIBS_DIR}"
  1037     fi
  1038 
  1039     # Restore the environment, discarding any error message
  1040     # (for example, read-only bash internals)
  1041     CT_DoLog STATE "  Restoring environment"
  1042     . "${state_dir}/env.sh" >/dev/null 2>&1 || true
  1043 
  1044     # Restore the new RESTART and STOP steps
  1045     CT_RESTART="${old_RESTART}"
  1046     CT_STOP="${old_STOP}"
  1047     unset old_stop old_restart
  1048 
  1049     if [ "${CT_LOG_TO_FILE}" = "y" ]; then
  1050         CT_DoLog STATE "  Restoring log file"
  1051         exec >/dev/null
  1052         case "${CT_DEBUG_CT_SAVE_STEPS_GZIP}" in
  1053             y)  zcat "${state_dir}/log.gz" >"${CT_LOG_FILE}";;
  1054             *)  cat "${state_dir}/log" >"${CT_LOG_FILE}";;
  1055         esac
  1056         cat "${state_dir}/tail.log" >>"${CT_LOG_FILE}"
  1057         exec >>"${CT_LOG_FILE}"
  1058         rm -f "${state_dir}/tail.log"
  1059     fi
  1060 }