crosstool-ng/bootstrap
Alexey Neyman 4bd6d5f560 Fix bootstrap to work with bash 4.3
Signed-off-by: Alexey Neyman <stilor@att.net>
2017-07-08 10:57:56 -07:00

657 lines
16 KiB
Bash
Executable File

#!/bin/bash
########################################
# Common meta-language implementation. Syntax:
#
# The template file is processed line by line, with @@VAR@@ placeholders
# being replaced with a value of the VAR variable.
# Special lines start with '#!' and a keyword:
#
# #!//
# Comment, the rest of the line is ignored
# #!if COND
# Conditional: the lines until the matching #!end-if are processed
# only if the conditional COND evaluates to true.
# #!foreach NAME
# Iterate over NAME entities (the iterator must be set up first
# using the set_iter function), processing the lines until the matching
# #!end-foreach line.
declare -A info
debug()
{
if [ -n "${DEBUG}" ]; then
echo "DEBUG :: $@" >&2
fi
}
msg()
{
if [ -z "${QUIET}" ]; then
echo "INFO :: $@" >&2
fi
}
warn()
{
echo "WARN :: $@" >&2
}
error()
{
echo "ERROR :: $@" >&2
exit 1
}
find_end()
{
local token="${1}"
local count=1
# Skip first line, we know it has the proper '#!' command on it
endline=$[l + 1]
while [ "${endline}" -le "${end}" ]; do
case "${tlines[${endline}]}" in
"#!${token} "*)
count=$[count + 1]
;;
"#!end-${token}")
count=$[count - 1]
;;
esac
if [ "${count}" = 0 ]; then
return
fi
endline=$[endline + 1]
done
error "line ${l}: '${token}' token is unpaired"
}
set_iter()
{
local name="${1}"
if [ "${info[iter_${name}]+set}" = "set" ]; then
error "Iterator over '${name}' is already set up"
fi
shift
debug "Setting iterator over '${name}' to '$*'"
info[iter_${name}]="$*"
}
run_if()
{
local cond="${1}"
local endline
find_end "if"
if eval "${cond}"; then
debug "True conditional '${cond}' at lines ${l}..${endline}"
run_lines $[l + 1] $[endline - 1]
else
debug "False conditional '${cond}' at lines ${l}..${endline}"
fi
lnext=$[endline + 1]
debug "Continue at line ${lnext}"
}
do_foreach()
{
local var="${1}"
local -A saveinfo
local v k
shift
if [ "`type -t enter_${var}`" != "function" ]; then
error "No parameter setup routine for iterator over '${var}'"
fi
for v in ${info[iter_${var}]}; do
# This works in bash 4.4, but not in bash 4.3:
# local saveinfo=`declare -p info`
# ...
# eval "${saveinfo}"
# Therefore, need to save key-by-key
saveinfo=()
for k in "${!info[@]}"; do
saveinfo["${k}"]=${info["${k}"]}
done
eval "enter_${var} ${v}"
eval "$@"
info=()
for k in "${!saveinfo[@]}"; do
info["${k}"]=${saveinfo["${k}"]}
done
done
}
run_foreach()
{
local var="${1}"
local endline
if [ "${info[iter_${var}]+set}" != "set" ]; then
error "line ${l}: iterator over '${var}' is not defined"
fi
find_end "foreach"
debug "Loop over '${var}', lines ${l}..${endline}"
do_foreach ${var} run_lines $[l + 1] $[endline - 1]
lnext=$[endline + 1]
debug "Continue at line ${lnext}"
}
run_lines()
{
local start="${1}"
local end="${2}"
local l lnext s s1 v
debug "Running lines ${start}..${end}"
l=${start}
while [ "${l}" -le "${end}" ]; do
lnext=$[l+1]
s="${tlines[${l}]}"
# Expand @@foo@@ to ${info[foo]}. First escape variables/backslashes for evals below.
s="${s//\\/\\\\}"
s="${s//\$/\\\$}"
s1=
while [ -n "${s}" ]; do
case "${s}" in
*@@*@@*)
v="${s#*@@}"
v="${v%%@@*}"
if [ "${info[${v}]+set}" != "set" ]; then
error "line ${l}: reference to undefined variable '${v}'"
fi
s1="${s1}${s%%@@*}\${info[${v}]}"
s="${s#*@@*@@}"
;;
*@@*)
error "line ${l}: non-paired @@ markers"
;;
*)
s1="${s1}${s}"
break
;;
esac
done
s=${s1}
debug "Evaluate: ${s}"
case "${s}" in
"#!if "*)
run_if "${s#* }"
;;
"#!foreach "*)
run_foreach "${s#* }"
;;
"#!//"*)
# Comment, do nothing
;;
"#!"*)
error "line ${l}: unrecognized command"
;;
*)
# Not a special command
eval "echo \"${s//\"/\\\"}\""
;;
esac
l=${lnext}
done
}
run_template()
{
local -a tlines
local src="${1}"
if [ ! -r "${src}" ]; then
error "Template '${src}' not found"
fi
debug "Running template ${src}"
mapfile -O 1 -t tlines < "${src}"
run_lines 1 ${#tlines[@]}
}
########################################
# Convert the argument to a Kconfig-style macro
kconfigize()
{
local v="${1}"
v=${v//[^0-9A-Za-z_]/_}
echo "${v^^}"
}
# Helper for cmp_versions: compare an upstream/debian portion of
# a version. Returns 0 if equal, otherwise echoes "-1" or "1" and
# returns 1.
equal_versions()
{
local v1="${1}"
local v2="${2}"
local p1 p2
# Compare alternating non-numerical/numerical portions, until
# non-equal portion is found or either string is exhausted.
while [ -n "${v1}" -a -n "${v2}" ]; do
# Find non-numerical portions and compare lexicographically
p1="${v1%%[0-9]*}"
p2="${v2%%[0-9]*}"
v1="${v1#${p1}}"
v2="${v2#${p2}}"
#debug "lex [${p1}] v [${p2}]"
if [ "${p1}" \< "${p2}" ]; then
echo "-1"
return 1
elif [ "${p1}" \> "${p2}" ]; then
echo "1"
return 1
fi
#debug "rem [${v1}] v [${v2}]"
# Find numerical portions and compare numerically
p1="${v1%%[^0-9]*}"
p2="${v2%%[^0-9]*}"
v1="${v1#${p1}}"
v2="${v2#${p2}}"
#debug "num [${p1}] v [${p2}]"
if [ "${p1:-0}" -lt "${p2:-0}" ]; then
echo "-1"
return 1
elif [ "${p1:-0}" -gt "${p2:-0}" ]; then
echo "1"
return 1
fi
#debug "rem [${v1}] v [${v2}]"
done
if [ -n "${v1}" ]; then
echo "1"
return 1
elif [ -n "${v2}" ]; then
echo "-1"
return 1
fi
return 0
}
# Compare two version strings, similar to sort -V. But we don't
# want to depend on GNU sort availability on the host.
# See http://www.debian.org/doc/debian-policy/ch-controlfields.html
# for description of what the version is expected to be.
# Returns "-1", "0" or "1" if first version is earlier, same or
# later than the second.
cmp_versions()
{
local v1="${1}"
local v2="${2}"
local e1=0 e2=0 u1 u2 d1=0 d2=0
# Case-insensitive comparison
v1="${v1^^}"
v2="${v2^^}"
# Find if the versions contain epoch part
case "${v1}" in
*:*)
e1="${v1%%:*}"
v1="${v1#*:}"
;;
esac
case "${v2}" in
*:*)
e2="${v2%%:*}"
v2="${v2#*:}"
;;
esac
# Compare epochs numerically
if [ "${e1}" -lt "${e2}" ]; then
echo "-1"
return
elif [ "${e1}" -gt "${e2}" ]; then
echo "1"
return
fi
# Find if the version contains a "debian" part.
# v1/v2 will now contain "upstream" part.
case "${v1}" in
*-*)
d1=${v1##*-}
v1=${v1%-*}
;;
esac
case "${v2}" in
*-*)
d2=${v2##*-}
v2=${v2%-*}
;;
esac
# Compare upstream
if equal_versions "${v1}" "${v2}" && equal_versions "${d1}" "${d2}"; then
echo "0"
fi
}
# Sort versions, descending
sort_versions()
{
local sorted
local remains="$*"
local next_remains
local v vx found
while [ -n "${remains}" ]; do
#debug "Sorting [${remains}]"
for v in ${remains}; do
found=yes
next_remains=
#debug "Candidate ${v}"
for vx in ${remains}; do
#debug "${v} vs ${vx} :: `cmp_versions ${v} ${vx}`"
case `cmp_versions ${v} ${vx}` in
1)
next_remains+=" ${vx}"
;;
0)
;;
-1)
found=no
#debug "Bad: earlier than ${vx}"
break
;;
esac
done
if [ "${found}" = "yes" ]; then
# $v is less than all other members in next_remains
sorted+=" ${v}"
remains="${next_remains}"
#debug "Good candidate ${v} sorted [${sorted}] remains [${remains}]"
break
fi
done
done
echo "${sorted}"
}
read_file()
{
local l p
while read l; do
l="${p}${l}"
p=
case "${l}" in
"")
continue
;;
*\\)
p="${l%\\}"
continue
;;
"#"*)
continue
;;
*=*)
echo "info[${l%%=*}]=${l#*=}"
;;
*)
error "syntax error in '${1}': '${l}'"
;;
esac
done < "${1}"
}
read_package_desc()
{
read_file "packages/${1}/package.desc"
}
read_version_desc()
{
read_file "packages/${1}/${2}/version.desc"
}
find_forks()
{
local -A info
info[preferred]=${1}
eval `read_package_desc ${1}`
if [ -n "${info[master]}" ]; then
pkg_nforks[${info[master]}]=$[pkg_nforks[${info[master]}]+1]
pkg_forks[${info[master]}]+=" ${1} "
else
pkg_preferred[${1}]=${info[preferred]}
pkg_nforks[${1}]=$[pkg_nforks[${1}]+1]
pkg_forks[${1}]+=" ${1} "
pkg_milestones[${1}]=`sort_versions ${info[milestones]}`
pkg_masters+=( "${1}" )
fi
# Keep sorting so that preferred fork is first
if [ -n "${pkg_preferred[${1}]}" ]; then
pkg_forks[${1}]="${pkg_preferred[${1}]} ${pkg_forks[${1}]##* ${pkg_preferred[${1}]} } ${pkg_forks[${1}]%% ${pkg_preferred[${1}]} *}"
fi
}
check_obsolete_experimental()
{
[ -z "${info[obsolete]}" ] && only_obsolete=
[ -z "${info[experimental]}" ] && only_experimental=
}
enter_fork()
{
local fork="${1}"
local versions
local only_obsolete only_experimental
# Set defaults
info[obsolete]=
info[experimental]=
info[repository]=
info[repository_branch]=
info[repository_cset]=
info[repository_subdir]=
info[bootstrap]=
info[fork]=${fork}
info[name]=${fork}
info[mirrors]=
info[archive_filename]='@{pkg_name}-@{version}'
info[archive_dirname]='@{pkg_name}-@{version}'
eval `read_package_desc ${fork}`
info[pfx]=`kconfigize ${fork}`
info[originpfx]=`kconfigize ${info[origin]}`
if [ -r "packages/${info[origin]}.help" ]; then
info[originhelp]=`sed 's/^/ /' "packages/${info[origin]}.help"`
else
info[originhelp]=" ${info[master]} from ${info[origin]}."
fi
if [ -n "${info[repository]}" ]; then
info[vcs]=${info[repository]%% *}
info[repository_url]=${info[repository]#* }
fi
info[versionlocked]=`kconfigize "${info[versionlocked]}"`
versions=`cd packages/${fork} && \
for f in */version.desc; do [ -r "${f}" ] && echo "${f%/version.desc}"; done`
versions=`sort_versions ${versions}`
set_iter version ${versions}
info[all_versions]=${versions}
# If a fork does not define any versions at all ("rolling release"), do not
# consider it obsolete/experimental unless it is so marked in the fork's
# description.
if [ -n "${versions}" ]; then
only_obsolete=yes
only_experimental=yes
do_foreach version check_obsolete_experimental
info[only_obsolete]=${only_obsolete}
info[only_experimental]=${only_experimental}
else
info[only_obsolete]=${info[obsolete]}
info[only_experimental]=${info[experimental]}
fi
}
enter_version()
{
local -A ver_postfix=( \
[,yes,,]=" (OBSOLETE)" \
[,,yes,]=" (EXPERIMENTAL)" \
[,yes,yes,]=" (OBSOLETE,EXPERIMENTAL)" )
local version="${1}"
eval `read_version_desc ${info[fork]} ${version}`
info[ver]=${version}
info[kcfg]=`kconfigize ${version}`
info[ver_postfix]=${ver_postfix[,${info[obsolete]},${info[experimental]},]}
}
enter_milestone()
{
local ms="${1}"
local cmp
info[ms]=${ms}
info[ms_kcfg]=`kconfigize ${ms}`
if [ -n "${info[ver]}" ]; then
info[version_cmp_milestone]=`cmp_versions ${info[ver]} ${info[ms]}`
fi
}
gen_packages()
{
local -A pkg_forks pkg_milestones pkg_nforks
local -a pkg_masters pkg_all pkg_preferred
pkg_all=( `cd packages && \
ls */package.desc 2>/dev/null | \
while read f; do [ -r "${f}" ] && echo "${f%/package.desc}"; done | \
xargs echo` )
debug "Packages: ${pkg_all[@]}"
# We need to group forks of the same package into the same
# config file. Discover such relationships and only iterate
# over "master" packages at the top.
for p in "${pkg_all[@]}"; do
find_forks "${p}"
done
msg "Master packages: ${pkg_masters[@]}"
# Now for each master, create its kconfig file with version
# definitions.
for p in "${pkg_masters[@]}"; do
msg "Generating '${config_versions_dir}/${p}.in'"
exec >"${config_versions_dir}/${p}.in"
# Base definitions for the whole config file
info=( \
[master]=${p} \
[masterpfx]=`kconfigize ${p}` \
[nforks]=${pkg_nforks[${p}]} \
[all_milestones]=${pkg_milestones[${p}]} \
)
set_iter fork ${pkg_forks[${p}]}
set_iter milestone ${pkg_milestones[${p}]}
run_template "maintainer/kconfig-versions.template"
done
}
msg "*** Generating package version descriptions"
config_versions_dir=config/versions
rm -rf "${config_versions_dir}"
mkdir -p "${config_versions_dir}"
gen_packages
get_components()
{
local dir="${1}"
local f b
for f in ${dir}/*.in; do
b=${f#${dir}/}
echo ${b%.in}
done
}
enter_choice()
{
local choice="${1}"
local l
# TBD generate sourcing of versions/$component.in automatically - and add a comment that versions must
# TBD generated first? [what to do with glibc/glibc-ports]
info[choice]="${choice}"
info[kcfg_choice]=`kconfigize "${choice}"`
# Not local, we need these arrays be set in enter_dependency/enter_help
deplines=( )
helplines=( )
while read l; do
case "${l}" in
"## help "*)
helplines+=( "${l#* help }" )
;;
"## depends "*|"## select "*)
deplines+=( "${l#* }" )
;;
esac
done < "config/${info[dir]}/${choice}.in"
set_iter dependency "${!deplines[@]}"
set_iter help "${!helplines[@]}"
}
enter_dependency()
{
info[depline]="${deplines[${1}]}"
}
enter_help()
{
info[helpline]="${helplines[${1}]}"
}
gen_selection()
{
local type="${1}"
local dir="${2}"
local label="${3}"
msg "Generating ${dir}.in and ${dir}.in.2"
exec >"${config_gen_dir}/${dir}.in"
info=( \
[prefix]=`kconfigize ${dir}` \
[dir]=${dir} \
[label]="${label}" \
)
set_iter choice `get_components config/${dir}`
run_template "maintainer/kconfig-${type}.template"
}
msg "*** Generating menu/choice selections"
config_gen_dir=config/gen
rm -rf "${config_gen_dir}"
mkdir -p "${config_gen_dir}"
gen_selection choice arch "Target Architecture"
gen_selection choice kernel "Target OS"
gen_selection choice cc "Compiler"
gen_selection choice binutils "Binutils"
gen_selection choice libc "C library"
gen_selection menu debug "Debug facilities"
gen_selection menu comp_tools "Companion tools"
msg "*** Running autoconf"
autoconf -Wall --force
msg "*** Done!"