| #!/bin/bash -p | |
| ############################################################################### | |
| # BRLTTY - A background process providing access to the console screen (when in | |
| # text mode) for a blind person using a refreshable braille display. | |
| # | |
| # Copyright (C) 1995-2023 by The BRLTTY Developers. | |
| # | |
| # BRLTTY comes with ABSOLUTELY NO WARRANTY. | |
| # | |
| # This is free software, placed under the terms of the | |
| # GNU Lesser General Public License, as published by the Free Software | |
| # Foundation; either version 2.1 of the License, or (at your option) any | |
| # later version. Please see the file LICENSE-LGPL for details. | |
| # | |
| # Web Page: http://brltty.app/ | |
| # | |
| # This software is maintained by Dave Mielke <dave@mielke.cc>. | |
| ############################################################################### | |
| set -e | |
| umask 022 | |
| shopt -s nullglob | |
| readonly autoconfOldVersion=2.13 | |
| readonly autoconfNewVersion=2.59 | |
| readonly automakeVersion=1.9.6 | |
| readonly crxVersion=2.04 | |
| readonly defaultArchivesSubdirectory="Archives" | |
| readonly defaultBuildSubdirectory="Build" | |
| readonly defaultInstallSubdirectory="Tools" | |
| readonly defaultTargetSystem="i586-pc-msdosdjgpp" | |
| function setVariable { | |
| local variable="${1}" | |
| local value="${2}" | |
| eval "${variable}"'="${value}"' | |
| } | |
| function setArrayElement { | |
| local array="${1}" | |
| local index="${2}" | |
| local value="${3}" | |
| setVariable "${array}[${index}]" "${value}" | |
| } | |
| function defineEnumeration { | |
| local array="${1}" | |
| shift 1 | |
| declare -g -i -A "${array}" | |
| local value=0 | |
| while [ "${#}" -gt 0 ] | |
| do | |
| setArrayElement "${array}" "${1}" $((value++)) | |
| shift 1 | |
| done | |
| readonly "${array}" | |
| } | |
| readonly programName="${0##*/}" | |
| readonly programDirectory="$(realpath "$(dirname "${0}")")" | |
| function programMessage { | |
| local message="${1}" | |
| echo >&2 "${programName}: ${message}" | |
| } | |
| defineEnumeration logLevels error warning task step detail | |
| declare -i logLevel="${logLevels[task]}" | |
| function logMore { | |
| ((logLevel += 1)) || : | |
| } | |
| function logLess { | |
| ((logLevel -= 1)) || : | |
| } | |
| function logMessage { | |
| local level="${1}" | |
| local message="${2}" | |
| local value="${logLevels[$level]}" | |
| [ -n "${value}" ] || { | |
| logWarning "undefined log level: ${level}" | |
| value=0 | |
| } | |
| [ "${value}" -gt "${logLevel}" ] || programMessage "${message}" | |
| } | |
| function syntaxError { | |
| local message="${1}" | |
| logError "${message}" | |
| exit 2 | |
| } | |
| function semanticError { | |
| local message="${1}" | |
| logError "${message}" | |
| exit 3 | |
| } | |
| function externalError { | |
| local message="${1}" | |
| logError "${message}" | |
| exit 4 | |
| } | |
| function showUsage { | |
| cat <<-END-OF-USAGE | |
| usage: ${programName} [-option ...] | |
| -h display a usage summary (this text), and then exit | |
| -q decrease output verbosity (may be specified more than once) | |
| -v increase output verbosity (may be specified more than once) | |
| -d directory specify the ROOT directory (default is the current directory) | |
| -a directory specify the archives directory (default is ROOT/${defaultArchivesSubdirectory}) | |
| -b directory specify the build directory (default is ROOT/${defaultBuildSubdirectory}) | |
| -i directory specify the install directory (default is ROOT/${defaultInstallSubdirectory}) | |
| -t system specify the target system (default is ${defaultTargetSystem}) | |
| -g version the gcc version to build (defaults if only one is archived) | |
| END-OF-USAGE | |
| exit 0 | |
| } | |
| function handleDirectoryOption { | |
| local variable="${1}" | |
| local default="${2}" | |
| local name="${3}" | |
| [ -n "${!variable}" ] || setVariable "${variable}" "${default}" | |
| setVariable "${variable}" "$(realpath "${!variable}")" | |
| readonly "${variable}" | |
| [ -z "${name}" ] || logMessage detail "${name} directory: ${!variable}" | |
| } | |
| function logArrayElements { | |
| local array="${1}" | |
| local name="${2}" | |
| message="${name} properties:" | |
| eval local 'indeces="${!'"${array}"'[*]}"' | |
| local index | |
| for index in ${indeces} | |
| do | |
| local variable="${array}[${index}]" | |
| message+=" ${index}=${!variable}" | |
| done | |
| logMessage detail "${message}" | |
| } | |
| function findHostCommand { | |
| local variable="${1}" | |
| shift 1 | |
| local command | |
| for command | |
| do | |
| local path="$(type -p "${command}")" | |
| [ -z "${path}" ] || { | |
| setVariable "${variable}" "${path}" | |
| export "${variable}" | |
| logMessage detail "host command location: ${variable} -> ${!variable}" | |
| return 0 | |
| } | |
| done | |
| semanticError "host command not found: ${variable} (${*})" | |
| } | |
| function pathChange { | |
| local newPath="${1}" | |
| export PATH="${newPath}" | |
| logMessage detail "host command search path: ${PATH}" | |
| } | |
| function pathPrepend { | |
| local directory="${1}" | |
| pathChange "${directory}:${PATH}" | |
| } | |
| function makeDirectory { | |
| local path="${1}" | |
| mkdir -p "${path}" | |
| } | |
| function emptyDirectory { | |
| local path="${1}" | |
| rm -f -r "${path}/"* | |
| } | |
| function initializeDirectory { | |
| local path="${1}" | |
| makeDirectory "${path}" | |
| emptyDirectory "${path}" | |
| } | |
| function verifyArchive { | |
| local array="${1}" | |
| local type="${2}" | |
| local prefix="${3}" | |
| local suffix="${4}" | |
| local version="${5}" | |
| declare -g -A "${array}" | |
| setArrayElement "${array}" type "${type}" | |
| setArrayElement "${array}" prefix "${prefix}" | |
| setArrayElement "${array}" suffix "${suffix}" | |
| local name="${prefix%-}" | |
| setArrayElement "${array}" name "${name}" | |
| local originalDirectory="${PWD}" | |
| cd "${archivesDirectory}" | |
| if [ -n "${version}" ] | |
| then | |
| local file="${prefix}${version}${suffix}" | |
| [ -f "${file}" ] || semanticError "${type} archive not found: ${file}" | |
| else | |
| local files=("${prefix}"*"${suffix}") | |
| local count="${#files[*]}" | |
| ((count > 0)) || semanticError "${type} archive not found: ${name}" | |
| ((count == 1)) || semanticError "${type} package with multiple archives: ${files[*]}" | |
| local file="${files[0]}" | |
| version="${file%${suffix}}" | |
| version="${version:${#prefix}}" | |
| fi | |
| cd "${originalDirectory}" | |
| [[ "${version}" =~ ^[0-9]{3}$ ]] && version="${version:0:1}.${version:1}" | |
| setArrayElement "${array}" version "${version}" | |
| setArrayElement "${array}" file "${file}" | |
| setArrayElement "${array}" path "${archivesDirectory}/${file}" | |
| local source="${name}-${version}" | |
| setArrayElement "${array}" source "${source}" | |
| readonly "${array}" | |
| logArrayElements "${array}" "archive" | |
| } | |
| function gnuVerifyArchive { | |
| local array="${1}" | |
| local name="${2}" | |
| local version="${3}" | |
| verifyArchive "${array}" Gnu "${name}-" ".tar.gz" "${version}" | |
| } | |
| function djgppVerifyArchive { | |
| local array="${1}" | |
| local name="${2}" | |
| local type="${3}" | |
| local version="${4}" | |
| verifyArchive "${array}" DJGPP "${name}" "${type}.zip" "${version//./}" | |
| } | |
| function logPackageTask { | |
| local array="${1}" | |
| local task="${2}" | |
| local nameVariable="${array}[name]" | |
| local versionVariable="${array}[version]" | |
| logMessage task "${task}: ${!nameVariable}-${!versionVariable}" | |
| } | |
| function unpackArchive { | |
| local array="${1}" | |
| local typeVariable="${array}[type]" | |
| local pathVariable="${array}[path]" | |
| logPackageTask "${array}" "unpacking ${!typeVariable} archive" | |
| "unpackArchive_${!typeVariable}" "${!pathVariable}" | |
| } | |
| function unpackArchive_Gnu { | |
| local path="${1}" | |
| tar xfz "${path}" | |
| } | |
| function unpackArchive_DJGPP { | |
| local path="${1}" | |
| unzip -q -a "${path}" | |
| } | |
| function changeScriptVariable { | |
| local script="${1}" | |
| local variable="${2}" | |
| local value="${3}" | |
| sed -e "/^ *${variable} *=/s%=.*%='${value}'%" -i "${script}" | |
| } | |
| function logBuildDirectory { | |
| logNote "build directory: ${PWD}" | |
| } | |
| function runBuildCommand { | |
| local logFile="${1}" | |
| shift 1 | |
| logNote "build command: ${*}" | |
| "${@}" >&"${logFile}" || externalError "build error: for details, see ${PWD}/${logFile}" | |
| } | |
| function configurePackage { | |
| local source="${1}" | |
| shift 1 | |
| runBuildCommand configure.log "${source}/configure" "${@}" | |
| } | |
| function makePackage { | |
| runBuildCommand make.log make "${@}" | |
| } | |
| function installPackage { | |
| runBuildCommand install.log make install "${@}" | |
| } | |
| function buildHostPackage { | |
| local array="${1}" | |
| shift 1 | |
| logPackageTask "${array}" "building host package" | |
| local sourceVariable="${array}[source]" | |
| local build="${!sourceVariable}-host" | |
| local prefix="$(realpath "${!sourceVariable}-install")" | |
| makeDirectory "${build}" | |
| cd "${build}" | |
| logBuildDirectory | |
| configurePackage "../${!sourceVariable}" --prefix="${prefix}" | |
| makePackage | |
| installPackage | |
| cd .. | |
| local bin="${prefix}/bin" | |
| pathPrepend "${bin}" | |
| while [ "${#}" -gt 0 ] | |
| do | |
| local command="${1}" | |
| local variable="${2}" | |
| shift 2 | |
| changeScriptVariable "${gccUnpackScript}" "${variable}" "${bin}/${command}" | |
| done | |
| } | |
| function buildHostArchive { | |
| local array="${1}" | |
| shift 1 | |
| unpackArchive "${array}" | |
| buildHostPackage "${array}" "${@}" | |
| } | |
| function buildHostAutoconf { | |
| local array="${1}" | |
| local autoconfVariable="${2}" | |
| local autoheaderVariable="${3}" | |
| buildHostArchive "${array}" autoconf "${autoconfVariable}" autoheader "${autoheaderVariable}" | |
| } | |
| function configureTargetPackage { | |
| local array="${1}" | |
| shift 1 | |
| local sourceVariable="${array}[source]" | |
| configurePackage "../${!sourceVariable}" \ | |
| "--prefix=${installDirectory}" \ | |
| "--target=${targetSystem}" \ | |
| "${@}" | |
| } | |
| function buildTargetPackage { | |
| local array="${1}" | |
| shift 1 | |
| logPackageTask "${array}" "building target package" | |
| cd gnu | |
| local sourceVariable="${array}[source]" | |
| local build="${!sourceVariable}-target" | |
| makeDirectory "${build}" | |
| cd "${build}" | |
| logBuildDirectory | |
| configureTargetPackage "${array}" "${@}" | |
| makePackage | |
| installPackage | |
| cd ../.. | |
| } | |
| rootDirectory="" | |
| archivesDirectory="" | |
| buildDirectory="" | |
| installDirectory="" | |
| targetSystem="" | |
| gccVersion="" | |
| while getopts ":hqvd:a:b:i:t:g:" option | |
| do | |
| case "${option}" | |
| in | |
| h) showUsage;; | |
| q) logLess;; | |
| v) logMore;; | |
| d) rootDirectory="${OPTARG}";; | |
| a) archivesDirectory="${OPTARG}";; | |
| b) buildDirectory="${OPTARG}";; | |
| i) installDirectory="${OPTARG}";; | |
| t) targetSystem="${OPTARG}";; | |
| g) gccVersion="${OPTARG}";; | |
| :) syntaxError "missing ooperand: -${OPTARG}";; | |
| \?) syntaxError "unknown option: -${OPTARG}";; | |
| *) syntaxError "unimplemented option: -${option}";; | |
| esac | |
| done | |
| shift $((OPTIND - 1)) | |
| [ "${#}" -eq 0 ] || syntaxError "too many parameters" | |
| handleDirectoryOption rootDirectory "${PWD}" "root" | |
| handleDirectoryOption archivesDirectory "${rootDirectory}/${defaultArchivesSubdirectory}" "archives" | |
| handleDirectoryOption buildDirectory "${rootDirectory}/${defaultBuildSubdirectory}" "build" | |
| handleDirectoryOption installDirectory "${rootDirectory}/${defaultInstallSubdirectory}" "install" | |
| [ -n "${targetSystem}" ] || targetSystem="${defaultTargetSystem}" | |
| readonly targetSystem | |
| pathChange "$(getconf PATH)" | |
| unset MAKEFLAGS | |
| logMessage task "finding host commands" | |
| findHostCommand CC cc gcc | |
| findHostCommand CXX c++ g++ cxx gxx | |
| findHostCommand LIBTOOL libtool | |
| logMessage task "verifying archives" | |
| gnuVerifyArchive gnuAutoconfOld autoconf "${autoconfOldVersion}" | |
| gnuVerifyArchive gnuAutoconfNew autoconf "${autoconfNewVersion}" | |
| gnuVerifyArchive gnuAutomake automake "${automakeVersion}" | |
| gnuVerifyArchive gnuBinutils binutils | |
| gnuVerifyArchive gnuGcc gcc "${gccVersion}" | |
| djgppVerifyArchive djgppGcc gcc s2 "${gnuGcc[version]}" | |
| djgppVerifyArchive djgppCrx djcrx "" "${crxVersion}" | |
| logMessage task "preparing build directory" | |
| initializeDirectory "${buildDirectory}" | |
| cd "${buildDirectory}" | |
| unpackArchive djgppCrx | |
| unpackArchive djgppGcc | |
| gccUnpackScript="unpack-gcc.sh" | |
| buildHostAutoconf gnuAutoconfOld AUTOCONF_OLD AUTOHEADER_OLD | |
| buildHostAutoconf gnuAutoconfNew AUTOCONF AUTOHEADER | |
| buildHostArchive gnuAutomake | |
| logMessage task "patching gcc source" | |
| logBuildDirectory | |
| chmod u=rwx,go=r "${gccUnpackScript}" | |
| runBuildCommand unpack-gcc.log "./${gccUnpackScript}" "$(realpath --relative-to=. "${gnuGcc["path"]}")" | |
| cd gnu | |
| unpackArchive gnuBinutils | |
| cd .. | |
| logMessage task "preparing install directory" | |
| initializeDirectory "${installDirectory}" | |
| pathPrepend "$${installDirectory}/bin" | |
| readonly targetDirectory="${installDirectory}/${targetSystem}" | |
| makeDirectory "${targetDirectory}" | |
| makeDirectory "${targetDirectory}/bin" | |
| cp -r lib "${targetDirectory}" | |
| cp -r include "${targetDirectory}" | |
| logMessage task "building stubify" | |
| cd src/stub | |
| logBuildDirectory | |
| runBuildCommand compile.log "${CC}" -O2 stubify.c -o "${targetDirectory}/bin/stubify" | |
| cd ../.. | |
| buildTargetPackage gnuBinutils | |
| buildTargetPackage djgppGcc --with-headers="${targetDirectory}/include" | |
| logMessage task "creating symbolic links" | |
| cd "${targetDirectory}/lib" | |
| ln -s libstdc++.a libstdcxx.a | |
| ln -s libsupc++.a libsupcxx.a | |
| logMessage task "cleaning up" | |
| cd / | |
| emptyDirectory "${buildDirectory}" | |
| logMessage task "done" | |
| exit 0 |