#!/bin/bash
#
# Based upon Debian's locale-gen, fetched from glibc_2.3.6-7.diff.gz
#
unset POSIXLY_CORRECT IFS
umask 0022
argv0=${0##*/}
EPREFIX="@GENTOO_PORTAGE_EPREFIX@"
if [[ ${EPREFIX} == "@"GENTOO_PORTAGE_EPREFIX"@" ]] ; then
EPREFIX=""
fi
FUNCTIONS_SH="/lib/gentoo/functions.sh"
source "${EPREFIX}"${FUNCTIONS_SH} || {
echo "${argv0}: Could not source ${FUNCTIONS_SH}!" 1>&2
exit 1
}
show_usage() {
cat <<-EOF
Usage: ${HILITE}${argv0}${NORMAL} ${GOOD}[options]${NORMAL} -- ${GOOD}[localedef options]${NORMAL}
Generate locales based upon the config file /etc/locale.gen.
${HILITE}Options:${NORMAL}
${GOOD}-k, --keep${NORMAL} Don't nuke existing locales
${GOOD}-d, --destdir
${NORMAL} Use locale data in specified DESTDIR tree
${GOOD}-c, --config ${NORMAL} Use specified config instead of default locale.gen
${GOOD}-l, --list${NORMAL} List all the locales to be generated
${GOOD}-a, --ask${NORMAL} Ask before generating each locale
${GOOD}-A, --all${NORMAL} Pretend the locale list contains all locales
${GOOD}-u, --update${NORMAL} Only generate locales that are missing
${GOOD}-G, --generate ${NORMAL} Generate specified locale (one shot; implies -k -u)
${GOOD}-j, --jobs ${NORMAL} Number of locales to generate at a time (parallel)
${GOOD}-q, --quiet${NORMAL} Only show errors
${GOOD}-V, --version${NORMAL} Meaningless version information
${GOOD}-h, --help${NORMAL} Show this help cruft
${HILITE}Localedef Options:${NORMAL}
By default, ${GOOD}${LOCALEDEF_OPTS}${NORMAL} is passed to localedef.
For more info, see the ${HILITE}locale-gen${NORMAL}(1) and ${HILITE}locale.gen${NORMAL}(8) manpages.
EOF
[[ $# -eq 0 ]] && exit 0
echo ""
eerror "Unknown option '$1'"
exit 1
}
show_version() {
local b="(" a=")"
local cvsver="$Revision: 1.41 $b $Date: 2015/10/14 13:55:56 $a"
echo "locale-gen-${cvsver//: }"
exit 0
}
LOCALEDEF_OPTS="-c"
KEEP=""
DESTDIR=""
CONFIG=""
JUST_LIST=""
ASK=""
ALL=""
UPDATE=""
GENERATE=""
JOBS_MAX=""
QUIET=0
SET_X=""
LOCALE_ARCHIVE=true
while [[ $# -gt 0 ]] ; do
case $1 in
-k|--keep|--keep-existing) KEEP=$1;;
-d|--destdir) shift; DESTDIR=$1; unset ROOT;;
-c|--config) shift; CONFIG=$1;;
-l|--list) JUST_LIST=$1;;
-a|--ask) ASK=$1;;
-A|--all) ALL=$1;;
-u|--update) UPDATE=$1;;
-G|--generate) shift; GENERATE=$1;;
-j|--jobs) shift; JOBS_MAX=$(( $1 ));;
-j*) : $(( JOBS_MAX = ${1#-j} ));;
-q|--quiet) : $(( ++QUIET ));;
-x|--debug) SET_X="true";;
-V|--version) show_version;;
-h|--help) show_usage;;
--) shift; LOCALEDEF_OPTS=$*; break;;
*) show_usage $1;;
esac
shift
done
if [[ -z ${JOBS_MAX} ]] ; then
JOBS_MAX=$(getconf _NPROCESSORS_ONLN 2>/dev/null)
: ${JOBS_MAX:=1}
fi
[[ ${JOBS_MAX} -lt 1 ]] && JOBS_MAX=1
[[ -n ${SET_X} ]] && set -x
: ${KEEP:=${JUST_LIST}}
[[ -n ${GENERATE} ]] && UPDATE="true" && KEEP="true"
: ${ROOT:=/}
ROOT="${ROOT%/}/"
if [[ -n ${DESTDIR} ]] && [[ ${ROOT} != "/" ]] ; then
eerror "DESTDIR and ROOT are mutually exclusive options"
exit 1
fi
: ${EROOT:="${ROOT%/}${EPREFIX}/"}
if [[ ${EROOT} != "/" ]] ; then
einfo "Using locale.gen from ROOT ${EROOT}etc/"
fi
if [[ -n ${DESTDIR} ]] ; then
einfo "Building locales in DESTDIR '${DESTDIR}'"
else
DESTDIR=${EROOT}
fi
# XXX: should fix this ...
if [[ ${ROOT} != "/" ]] ; then
eerror "Sorry, but ROOT support is incomplete at this time."
exit 0
fi
: ${CONFIG:=${EROOT}etc/locale.gen}
LOCALES=${DESTDIR}usr/share/i18n/locales
CHARMAPS=${DESTDIR}usr/share/i18n/charmaps
SUPPORTED=${DESTDIR}usr/share/i18n/SUPPORTED
ALIAS=${DESTDIR}usr/share/locale/locale.alias
#
# Grab any user options in their config file
options=$(sed -n \
-e '/^[[:space:]]*#%/s:^[[:space:]]*#%[[:space:]]*::p'\
"${CONFIG}" 2>/dev/null
)
IFS=$'\n'
for option in ${options} ; do
case ${option} in
no-locale-archive)
LOCALE_ARCHIVE=false
;;
*)
ewarn "Unrecognized option '${option}'"
;;
esac
done
unset IFS
if ${LOCALE_ARCHIVE} ; then
if [[ ${JOBS_MAX} != 1 ]] ; then
ewarn "Generating locale-archive: forcing # of jobs to 1"
JOBS_MAX=1
fi
else
LOCALEDEF_OPTS="--no-archive ${LOCALEDEF_OPTS}"
fi
[[ -n ${ALL} ]] && CONFIG=${SUPPORTED}
# Extract the location of the locale dir on the fly as `localedef --help` has:
# locale path : /usr/lib64/locale:/usr/share/i18n
# For long paths, the line may get wrapped into two, in which case space (' ') is replaced
# by newline (\n).
LOCALEDIR=$(LC_ALL="C" "${DESTDIR}"usr/bin/localedef --help | sed -n -r '/locale path/{N;s|.*:[ \n](.*):/.*|\1|;p}')
LOCALEDIR="${DESTDIR}${LOCALEDIR#${EPREFIX}}"
if [[ $? -ne 0 ]] || [[ -z ${LOCALEDIR} ]] || [[ ${LOCALEDIR} != ${DESTDIR}/usr/lib*/locale ]] ; then
eerror "Unable to parse the output of your localedef utility." 1>&2
eerror "File a bug about this issue and include the output of 'localedef --help'." 1>&2
exit 1
fi
# Only generate locales the user specified before falling back to the config.
locales_to_generate=${GENERATE}
if [[ -z ${locales_to_generate} ]] && [[ -e ${CONFIG} ]] ; then
locales_to_generate=$(sed \
-e 's:#.*::' \
-e '/^[[:space:]]*$/d' \
"${CONFIG}" | sort)
# Sanity check to make sure people did not duplicate entries. #550884
# The first column must be unique specifically. #235555
dup_locales_to_generate=$(
echo "${locales_to_generate}" | \
awk '{ if ($1 == last) { print lastline; print; } else { lastline = $0; last = $1; } }')
if [[ -n ${dup_locales_to_generate} ]] ; then
ewarn "These locales have been duplicated in your config:\n${dup_locales_to_generate}"
ewarn "Some might be filtered, but you must fix it."
locales_to_generate=$(echo "${locales_to_generate}" | uniq)
fi
fi
if [[ -z ${locales_to_generate} ]] ; then
[[ ${QUIET} -eq 0 ]] && [[ -z ${JUST_LIST} ]] && \
ewarn "No locales found"
exit 0
fi
mkdir -p "${LOCALEDIR}"
if [[ -z ${KEEP} && -z ${UPDATE} ]] ; then
# Remove all old locale dir and locale-archive before generating new
# locale data. Scrubbing via update is done elsewhere.
rm -rf "${LOCALEDIR}"/* || true
fi
# Transform the name in locales.gen to the name used when storing
# the locale data in /usr/lib/locale/ ... this normalize algo is
# taken out of the glibc localedef source code ...
normalize() {
if [[ $1 == *.* ]] ; then
local ret=$(echo ${1##*.} | tr '[[:upper:]]' '[[:lower:]]')
echo ${1%%.*}.${ret//-}
else
echo $1
fi
}
# These funky sed's are based on the stuff in glibc's localedata/Makefile
# Basically we want to rewrite the display like so:
# .[@extra stuff after the @ in the locale]
# en_US ISO-8859-1 -> en_US.ISO-8859-1
# en_US.UTF-8 UTF-8 -> en_US.UTF-8
# de_DE@euro ISO-8859-15 -> de_DE.ISO-8859-15@euro
locales_disp=$(echo "${locales_to_generate}" | sed \
-e ' /@/ s:[[:space:]]*\([^@[:space:]]*\)\([^[:space:]]*\)[[:space:]]\+\([^[:space:]]*\):\1.\3\2:' \
-e '/^[^@]*$/s:[[:space:]]*\([^.[:space:]]*\)\([^[:space:]]*\)[[:space:]]\+\([^[:space:]]*\):\1.\3:')
eval declare -a locales_disp=(${locales_disp})
eval declare -a locales_to_generate=(${locales_to_generate})
total=$(( ${#locales_to_generate[*]} / 2 ))
[[ ${QUIET} -eq 0 ]] && [[ -z ${JUST_LIST} ]] && \
einfo "Generating ${total} locales (this might take a while) with ${JOBS_MAX} jobs"
if [[ -n ${UPDATE} ]] ; then
# normalize newlines into spaces
existing_locales=" $(echo $(locale -a 2>/dev/null)) "
fi
generate_locale() {
local output=""
if [[ -z ${ASK} ]] && [[ ${QUIET} -eq 0 ]] ; then
output=" (${cnt_fmt}/${total}) Generating ${disp}"
fi
if [[ $(( JOB_IDX_E - JOB_IDX_S )) == ${JOBS_MAX} ]] ; then
wait ${JOB_PIDS[$(( JOB_IDX_S++ ))]}
JOB_RETS+=( $? )
fi
(
# Accumulate all the output in one go so the parallel
# jobs don't tromp on each other
x=$(
[[ -n ${output} ]] && ebegin "${output}"
"${DESTDIR}"usr/bin/localedef ${LOCALEDEF_OPTS} \
-i "${input}" \
-f "${charmap}" \
-A "${ALIAS}" \
--prefix "${DESTDIR%${EPREFIX}/}/" \
"${locale}" 2>&1
ret=$?
[[ -n ${output} ]] && eend ${ret}
exit ${ret}
)
ret=$?
if [[ -n ${output} ]] ; then
echo "${x}"
elif [[ ${ret} -ne 0 ]] ; then
eerror "${disp}: ${x}"
fi
exit ${ret}
) &
JOB_PIDS+=( $! )
: $(( ++JOB_IDX_E ))
if [[ ${ret} != 0 && ${locale} == */* ]] ; then
ewarn "Perhaps you meant to use a space instead of a / in your config file ?"
fi
}
JOB_PIDS=()
JOB_RETS=()
JOB_IDX_S=0
JOB_IDX_E=0
cnt=0
lidx=0
while [[ -n ${locales_to_generate[${lidx}]} ]] ; do
: $(( ++cnt ))
locale=${locales_to_generate[$((lidx++))]}
charmap=${locales_to_generate[$((lidx++))]}
# XXX: if we wanted to, we could check existence of
# ${LOCALES}/${locale} and ${CHARMAPS}/${charmap}
# this would fail for things like "en_US.UTF8", but
# in that case we could fall back to checking the
# SUPPORTED file ... then again, the localedef
# below will abort nicely for us ...
if [[ -z ${locale} || -z ${charmap} ]] ; then
eerror "Bad entry in locale.gen: '${locale} ${charmap}'; skipping"
continue
fi
disp=${locales_disp[$(( cnt - 1 ))]}
if [[ -n ${UPDATE} ]] ; then
normalized_locale=$(normalize ${locale})
if [[ ${existing_locales} == *" ${normalized_locale} "* ]] ; then
existing_locales=${existing_locales/ ${normalized_locale} / }
if [[ ${QUIET} -eq 0 ]] ; then
cnt_fmt=$(printf "%${#total}i" ${cnt})
einfo " (${cnt_fmt}/${total}) Skipping ${disp}"
fi
continue
fi
fi
# If the locale is like 'en_US.UTF8', then we really want 'en_US'
if [[ -f ${LOCALES}/${locale} ]] ; then
input=${locale}
else
input=${locale%%.*}
fi
if [[ -z ${JUST_LIST} ]] ; then
# Format the output for the question/status
cnt_fmt=$(printf "%${#total}i" ${cnt})
if [[ -n ${ASK} ]] ; then
einfon " (${cnt_fmt}/${total}) Generate ${disp} ? (Y/n) "
read user_answer
[[ ${user_answer} == [nN]* ]] && continue
fi
generate_locale
else
echo "${disp}"
fi
done
for (( i = JOB_IDX_S; i < JOB_IDX_E; ++i )) ; do
wait ${JOB_PIDS[i]}
JOB_RETS+=( $? )
done
ret=$(( 0 ${JOB_RETS[@]/#/+} ))
[[ ${QUIET} -eq 0 ]] && [[ -z ${JUST_LIST} ]] && \
einfo "Generation complete"
# Remove locales that existed but were not requested
if [[ -n ${UPDATE} ]] ; then
# Ignore these pseudo locales
existing_locales=${existing_locales/ C / }
existing_locales=${existing_locales/ POSIX / }
if [[ -n ${existing_locales// } ]] ; then
if [[ -z ${KEEP} ]] ; then
[[ ${QUIET} -eq 0 ]] && einfo "Scrubbing old locales:"${existing_locales}
cd "${LOCALEDIR}" && rm -rf ${existing_locales}
else
[[ ${QUIET} -eq 0 ]] && einfo "Keeping old locales:"${existing_locales}
fi
fi
fi
exit ${ret}