#!/bin/sh
# $Id: fmtutil.sh 35626 2014-11-20 19:08:47Z karl $
# fmtutil - utility to maintain format files.
# Public domain. Originally written by Thomas Esser.
# Run with --help for usage.
# program history:
# further changes in texk/texlive/tl_scripts/ChangeLog.
# 2007-01-04 patch by JK to support $engine subdir (enabled by default)
# Fr Apr 8 19:15:05 CEST 2005 cleanup now has an argument for the return code
# Do Mar 02 10:42:31 CET 2006 add tmpdir to TEXFORMATS
# So Ma 27 18:52:06 CEST 2005 honor $TMPDIR, $TEMP and $TMP, not just $TMP
# Sa Jan 15 18:13:46 CET 2005 avoid multiple variable assignments in one statement
# Di Jan 11 11:42:36 CET 2005 fix --byhyphen with relative hyphenfile
# Fr Dez 31 16:51:29 CET 2004 option catcfg added (for being called by texconfig)
# Do Dez 30 21:53:27 CET 2004 rename variable verbose to verboseFlag
# Sa Dez 25 12:44:23 CET 2004 implementation adopted for teTeX-3.0 (tcfmgr)
# Do Okt 28 11:09:36 CEST 2004 added --refresh
# Fr Sep 17 19:25:28 CEST 2004 save $0 in a variable before calling a function
# Sun May 9 23:24:06 CEST 2004 changes for new web2c: format names
# are now *.fmt, nothing else, disable
# "plain" symlinks
# Thu May 6 14:16:19 CEST 2004: "mv .../dev/null \
&& { RUNNING_KSH=true; export RUNNING_KSH; exec /bin/ksh $0 ${1+"$@"}; }
unset RUNNING_KSH
test -f /bin/bsh && test -z "$RUNNING_BSH" \
&& { UNAMES=`uname -s`; test "x$UNAMES" = xAIX; } 2>/dev/null \
&& { RUNNING_BSH=true; export RUNNING_BSH; exec /bin/bsh $0 ${1+"$@"}; }
unset RUNNING_BSH
# hack around a bug in zsh:
test -n "${ZSH_VERSION+set}" && alias -g '${1+"$@"}'='"$@"'
# preferentially use subprograms from our own directory.
mydir=`echo "$0" | sed 's,/[^/]*$,,'`
mydir=`cd "$mydir" && pwd`
PATH="$mydir:$PATH"; export PATH
version='$Id: fmtutil.sh 35626 2014-11-20 19:08:47Z karl $'
progname=fmtutil
argv0=$0
cnf=fmtutil.cnf # name of the config file
###############################################################################
# cleanup ERRCODE, where ERRCODE=1 for failure and 0 for success.
# clean up the temp area and exit with proper exit status
#
cleanup()
{
rc=$1
# for debugging, exit $rc here so $tmpdir with its logs sticks around.
$needsCleanup && test -n "$tmpdir" && test -d "$tmpdir" \
&& { cd / && rm -rf "$tmpdir"; }
(exit $rc); exit $rc
}
###############################################################################
# help() and version() - display help resp. version message and exit 0.
#
help()
{
cat <
eof
cleanup 0
}
versionfunc()
{
cat </dev/null; then
ed "$file" >/dev/null 2>&1 <<-eof
/$pat/
c
$line
.
w
q
eof
else
echo "$line" >> $file
fi
}
###############################################################################
# setmatch(MATCH) - set the "match state" to MATCH (true or false).
# Used to see if there was at least one match.
#
setmatch()
{
match=$1
}
###############################################################################
# getmatch() - return success if there was at least one match.
#
getmatch()
{
test "x$match" = xtrue
}
###############################################################################
# initTexmfMain() - set $MT_TEXMFMAIN from $TEXMFMAIN, if not already set.
#
initTexmfMain()
{
case $MT_TEXMFMAIN in
"") MT_TEXMFMAIN=`kpsewhich --var-value=TEXMFMAIN`;;
esac
export MT_TEXMFMAIN
}
###############################################################################
# cache_vars()
# locate files / kpathsea variables and export variables to environment
# to speed up future calls to e.g. mktexupd.
#
cache_vars()
{
: ${MT_VARTEXFONTS=`kpsewhich --expand-var='$VARTEXFONTS' | sed 's%^!!%%'`}
: ${MT_MKTEXNAM=`kpsewhich --format='web2c files' mktexnam`}
: ${MT_MKTEXNAM_OPT=`kpsewhich --format='web2c files' mktexnam.opt`}
: ${MT_MKTEXDIR=`kpsewhich --format='web2c files' mktexdir`}
: ${MT_MKTEXDIR_OPT=`kpsewhich --format='web2c files' mktexdir.opt`}
: ${MT_MKTEXUPD=`kpsewhich --format='web2c files' mktexupd`}
: ${MT_MKTEX_CNF=`kpsewhich --format='web2c files' mktex.cnf`}
: ${MT_MKTEX_OPT=`kpsewhich --format='web2c files' mktex.opt`}
export MT_VARTEXFONTS MT_MKTEXNAM MT_MKTEXNAM_OPT MT_MKTEXDIR
export MT_MKTEXDIR_OPT MT_MKTEXUPD MT_MKTEX_CNF MT_MKTEX_OPT
}
###############################################################################
# abort(ERRMSG) print ERRMSG to stderr and exit with error code 1
#
abort()
{
echo "$progname: $1." >&2
cleanup 1
}
###############################################################################
# maybe_abort(ERRMSG) print ERRMSG to stderr; then, if
# noAbortFlag is set, do nothing; if not set, exit with error code 1.
#
maybe_abort()
{
echo "$progname: $1." >&2
$noAbortFlag || cleanup 1
}
###############################################################################
# verboseMsg(MSG) - print MSG to stderr if $verbose is true
#
verboseMsg() {
$verboseFlag && verbose echo ${1+"$@"}
}
###############################################################################
# flush_msg_buffers() - Called from byebye() to print accumulated
# error messages.
#
# global variable `flush_msg_buffers_called' is set true to detect
# recursive calls during error/trap processing. If the redirection of
# "cat" fails due to full file system, say, we get an error condition.
#
flush_msg_buffers()
{
if $flush_msg_buffers_called; then return; fi
flush_msg_buffers_called=true
if $has_errors; then
{
cat <&2
fi
}
###############################################################################
# byebye([RETURNCODE]) - report any failures and exit the program
#
# The argument RETURNCODE is optional. When byebye is called indirectly
# through trap processing, it is passed a RETURNCODE of 1, and the
# program then exits with status 1.
#
# If RETURNCODE is not given, the program exits with status 1 if
# log_failure has been called, and 0 otherwise.
#
# byebye invokes flush_msg_buffers to print the messages accumulated by
# the previous calls to log_failure. Thus, unless byebye is called,
# this flushing does not take place, and the messages are not reported.
#
byebye()
{
flush_msg_buffers # dump any accumulated output
# If we are passed an explicit non-zero error code, obey it.
force_error=false
test -n "$1" && test "x$1" != x0 && force_error=true
if $force_error; then
verboseMsg "$progname: Error(s) found, exiting unsuccessfully."
cleanup 1
else
verboseMsg "$progname: No errors, exiting successfully."
cleanup 0
fi
}
###############################################################################
# init_log_failure() - reset the list of failure messages
#
# Usage scenario:
# init_log_failure
# ...
# log_failure
# ...
# byebye (will flush the message and exit).
#
init_log_failure()
{
log_failure_msg=
has_errors=false
}
###############################################################################
# log_failure(FAILMSG) - report and save failure message FAILMSG
# and set global variable has_errors to true.
#
log_failure()
{
echo "Error: $@" >&2
if test -z "$log_failure_msg"; then
log_failure_msg="$@"
else
OLDIFS=$IFS; IFS=
log_failure_msg="$log_failure_msg
$@"
IFS=$OLDIFS
fi
has_errors=true
}
###############################################################################
# verbose(CMD) - execute CMD, redirecting output to stderr if $mktexfmtMode
# is true, to stdout otherwise.
#
# This is because when we are called as mktexfmt, we want to output one
# line and one line only to stdout: the name of the generated fmt file,
# which kpathsea can read and handle.
#
verbose()
{
if $mktexfmtMode; then
${1+"$@"} >&2
else
${1+"$@"}
fi
}
###############################################################################
# mktexdir(ARGS) - call mktexdir script with ARGS,
# disable all features (to prevent sticky directories)
#
mktexdir()
{
initTexmfMain
MT_FEATURES=none "$MT_TEXMFMAIN/web2c/mktexdir" "$@" >&2
}
###############################################################################
# tcfmgr(ARGS) - call tcfmgr script with ARGS.
#
tcfmgr()
{
initTexmfMain
"$MT_TEXMFMAIN/texconfig/tcfmgr" "$@"
}
###############################################################################
# mktexupd(ARGS) - call mktexupd script with ARGS
#
mktexupd()
{
initTexmfMain
"$MT_TEXMFMAIN/web2c/mktexupd" "$@"
}
###############################################################################
# main() - parse commandline arguments, initialize variables,
# switch into temp direcrory, execute desired command.
#
main()
{
destdir= # global variable: where do we put the format files?
cnf_file= # global variable: full name of the config file
cmd= # desired action from command line
needsCleanup=false
need_find_hyphenfile=false
cfgparam=
cfgmaint=
verboseFlag=true
noAbortFlag=false
# eradicate double slashes to avoid kpathsea expansion.
tmpdir=`echo ${TMPDIR-${TEMP-${TMP-/tmp}}}/$progname.$$ | sed s,//,/,g`
flush_msg_buffers_called=false # avoid recursion in error/trap processing
init_log_failure # must be before setupTmpDir since trap inside calls byebye
setupTmpDir # sets up trap for robust cleanup of tmpdir and more
# mktexfmtMode: if called as mktexfmt, set to true. Will echo the
# first generated filename after successful generation to stdout then
# (and nothing else), since kpathsea can only deal with one.
mktexfmtMode=false
case $argv0 in
mktexfmt|*/mktexfmt)
mktexfmtMode=true
fullfmt=$1; shift
case $fullfmt in
""|--help) help ;;
--version) versionfunc ;;
--*) abort "unknown option $fullfmt, try --help" ;;
*.fmt|*.mem|*.base)
set x --byfmt `echo $fullfmt | sed 's@\.[a-z]*$@@'` ${1+"$@"}
shift
;;
*.*) abort "unknown format type: $fullfmt" ;;
*) set x --byfmt $fullfmt; shift ;;
esac
;;
esac
use_engine_dir=true # whether to use web2c/$engine subdirs
while
case $1 in
--cnffile)
shift; cnf_file=$1; cfgparam=1;;
--cnffile=*)
cnf_file=`echo "$1" | sed 's/--cnffile=//'`; cfgparam=1; shift ;;
--fmtdir)
shift; destdir=$1;;
--fmtdir=*)
destdir=`echo "$1" | sed 's/--fmtdir=//'`; shift ;;
--no-engine-subdir)
use_engine_dir=false;;
--all|-a)
cmd=all;;
--edit|-e)
cmd=edit; cfgmaint=1;;
--missing|-m)
cmd=missing;;
--refresh|-r)
cmd=refresh;;
--byengine)
shift; cmd=byengine; arg=$1;;
--byengine=*)
cmd=byengine; arg=`echo "$1" | sed 's/--byengine=//'`; shift ;;
--byfmt|-f)
shift; cmd=byfmt; arg=$1;;
--byfmt=*)
cmd=byfmt; arg=`echo "$1" | sed 's/--byfmt=//'`; shift ;;
--byhyphen|-h)
shift; cmd=byhyphen; arg=$1;;
--byhyphen=*)
cmd=byhyphen; arg=`echo "$1" | sed 's/--byhyphen=//'`; shift ;;
--showhyphen|-s)
shift; cmd=showhyphen; arg=$1;;
--showhyphen=*)
cmd=showhyphen; arg=`echo "$1" | sed 's/--showhyphen=//'`; shift ;;
--help|-help)
cmd=help;;
--version)
cmd=version;;
--enablefmt)
shift; cmd=enablefmt; arg=$1; cfgmaint=1;;
--enablefmt=*)
cmd=enablefmt; arg=`echo "$1" | sed 's/--enablefmt=//'`; cfgmaint=1; shift;;
--disablefmt)
shift; cmd=disablefmt; arg=$1; cfgmaint=1;;
--disablefmt=*)
cmd=disablefmt; arg=`echo "$1" | sed 's/--disablefmt=//'`; cfgmaint=1; shift;;
--catcfg)
cmd=catcfg;;
--listcfg)
cmd=listcfg;;
--no-error-if-no-format)
noAbortFlag=true;;
--no-error-if-no-engine)
shift; noErrorEngines=$1;;
--no-error-if-no-engine=*)
noErrorEngines=`echo "$1" | sed 's/--no-error-if-no-engine=//'`;;
--quiet|-q|--silent)
verboseFlag=false;;
--test|--dolinks|--force)
;;
"") break;;
*) abort "unknown option \`$1'; try $progname --help if you need it";;
esac
do test $# -gt 0 && shift; done
case "$cmd" in
"") abort "missing command; try $progname --help if you need it";;
help) help;;
version) versionfunc;;
esac
if test -n "$cfgparam"; then
test -f "$cnf_file" || abort "config file \`$cnf_file' not found (ls-R missing?)"
fi
if test -n "$cfgmaint"; then
if test -z "$cfgparam"; then
co=`tcfmgr --tmp $tmpdir --cmd co --file $cnf`
test $? = 0 || cleanup 1
set x $co; shift
id=$1; cnf_file=$3; orig=$4
verboseMsg "$progname: initial config file is \`$orig'"
fi
else
if test -z "$cfgparam"; then
cnf_file=`tcfmgr --cmd find --file $cnf`
test -f "$cnf_file" || abort "config file \`$cnf' not found"
fi
fi
# these commands need no temp directory, so do them here:
case "$cmd" in
catcfg)
grep -v '^ *#' "$cnf_file" | sed 's@^ *@@; s@ *$@@' | grep . | sort
cleanup $? ;;
edit)
echo "$0: fmtutil --edit is disabled in TeX Live;" >&2
echo "$0: use a file fmtutil-local.cnf instead." >&2
echo "$0: See tlmgr --help or http://tug.org/texlive/doc/tlmgr.html." >&2
cleanup 0 ;;
enablefmt|disablefmt)
$cmd $arg ;; # does not return
listcfg)
listcfg_loop
cleanup $? ;;
showhyphen)
show_hyphen_file "$arg"
cleanup $? ;;
esac
if test -n "$cfgmaint"; then
if test -z "$cfgparam"; then
ci=`tcfmgr --tmp $tmpdir --cmd ci --id $id`
if test $? = 0; then
if test -n "$ci"; then
verboseMsg "$progname: configuration file updated: \`$ci'"
else
verboseMsg "$progname: configuration file unchanged."
fi
else
abort "failed to update configuration file."
fi
fi
cleanup $?
fi
# set up destdir:
if test -z "$destdir"; then
: ${MT_TEXMFVAR=`kpsewhich -var-value=TEXMFVAR`}
destdir=$MT_TEXMFVAR/web2c
fi
test -d "$destdir" || mktexdir "$destdir" >/dev/null 2>&1
test -d "$destdir" || abort "format directory \`$destdir' does not exist"
test -w "$destdir" || abort "format directory \`$destdir' is not writable"
thisdir=`pwd`
: ${KPSE_DOT=$thisdir}
export KPSE_DOT
# due to KPSE_DOT, we don't search the current directory, so include
# it explicitly for formats that \write and later on \read
TEXINPUTS="$tmpdir:$TEXINPUTS"; export TEXINPUTS
# for formats that load other formats (e.g., jadetex loads latex.fmt),
# add the current directory to TEXFORMATS, too. Currently unnecessary
# for MFBASES and MPMEMS.
TEXFORMATS="$tmpdir:$TEXFORMATS"; export TEXFORMATS
cd "$tmpdir" || cleanup 1
# make local paths absolute:
case "$destdir" in
/*) ;;
*) destdir="$thisdir/$destdir";;
esac
case "$cnf_file" in
/*) ;;
*) cnf_file="$thisdir/$cnf_file";;
esac
cache_vars
# execute the desired command:
case "$cmd" in
all)
recreate_all;;
missing)
create_missing;;
refresh)
recreate_existing;;
byengine)
recreate_by_engine "$arg";;
byfmt)
recreate_by_fmt "$arg";;
byhyphen)
recreate_by_hyphenfile "$arg";;
esac
byebye
}
###############################################################################
# parse_line(config_line) sets global variables:
# format: name of the format, e.g. pdflatex
# engine: name of the TeX engine, e.g. tex, etex, pdftex
# texargs: flags for initex and name of ini file (e.g. -mltex frlatex.ini)
# fmtfile: name of the format file (without directory, but with extension)
#
# Support for building internationalized formats sets:
# pool: base name of pool file (to support translated pool files)
# tcx: translation file used when creating the format
#
# Example (for fmtutil.cnf):
# mex-pl tex mexconf.tex nls=tex-pl,il2-pl mex.ini
#
# The nls parameter (pool,tcx) can only be specified as the first argument
# inside the 4th field in fmtutil.cnf.
#
# exit code: returns error code if the ini file is not installed
#
parse_line()
{
case $1 in
'#!') disabled=true; shift;;
*) disabled=false;;
esac
format=$1
engine=$2
hyphenation=$3
shift; shift; shift
# handle nls support: pool + tcx
pool=; tcx=
case $1 in
nls=*)
pool=`echo $1 | sed 's@nls=@@; s@,.*@@'`
tcx=`echo $1 | sed 's@nls=[^,]*@@; s@^,@@'`
shift # nls stuff is not handled by the engine directly,
# so we shift this away
;;
esac
texargs="$@"
case "$engine" in
mpost) fmtfile="$format.mem"; kpsefmt=mp; texengine=metapost;;
mf|mfw|mf-nowin) fmtfile="$format.base"; kpsefmt=mf; texengine=metafont;;
*) fmtfile="$format.fmt"; kpsefmt=tex; texengine=$engine;;
esac
# remove any * for the sake of the kpsewhich lookup.
eval lastarg=\$$#
inifile=`echo $lastarg | sed 's%^\*%%'`
# See if we can find $inifile for return code:
kpsewhich -progname=$format -format=$kpsefmt $inifile >/dev/null 2>&1
}
###############################################################################
# find_hyphenfile(FORMAT, HYPHENATION) - search for HYPHENATION
# along search path of FORMAT
# exit code: returns error code if file is not found
#
find_hyphenfile()
{
format="$1"
hyphenation="`echo $2 | sed 's/,/ /g'`"
case $hyphenation in
-) ;;
*) kpsewhich -progname="$format" -format=tex $hyphenation;;
esac
}
###############################################################################
# find_info_for_name(FORMAT) - look up config line for FORMAT
# and call parse_line to set global variables.
#
find_info_for_name()
{
format="$1"
# set x `awk '$1 == format {print; exit}' format="$format" "$cnf_file"`; shift
set x `egrep "^$format( | )" "$cnf_file" | sed q`; shift
test $# = 0 && abort "no info for format \`$format'"
parse_line "$@"
}
###############################################################################
# run_initex() - run initex.
# Assumes that global variables are set by parse_line.
#
run_initex()
{
# install a pool file and set tcx flag if requested in lang= option:
rm -f *.pool
poolfile=
tcxflag=
test -n "$pool" \
&& poolfile=`(kpsewhich -progname=$engine $pool.pool) 2>/dev/null`
if test -n "$poolfile" && test -f "$poolfile"; then
verboseMsg "$progname: attempting to create localized format using pool=$pool and tcx=$tcx."
cp "$poolfile" $engine.pool
test -n "$tcx" && tcxflag=-translate-file=$tcx
localpool=true
else
localpool=false
fi
jobswitch="-jobname=$format"
case "$format" in
metafun) prgswitch=-progname=mpost;;
mptopdf|cont-??) prgswitch=-progname=context;;
*) prgswitch=-progname=$format;;
esac
rm -f $fmtfile
# Check for infinite recursion before running the iniTeX:
# We do this check only if we are running in mktexfmt mode
# otherwise double format definitions will create an infinite loop, too
$mktexfmtMode || mktexfmt_loop=
case :$mktexfmt_loop: in
*:"$format/$engine":*)
abort "Infinite recursion detected, giving up!" ;;
esac
mktexfmt_loop=$mktexfmt_loop:$format/$engine
export mktexfmt_loop
# check for existence of engine and ignored setting
if `which $engine >/dev/null` ; then
: nothing to be done
else
# add additional commas at front and back and search for comma separated
# engine
if case ,$noErrorEngines, in *",$engine,"*) true ;; *) false;; esac ; then
verboseMsg "$progname: not building $format due to missing engine $engine."
return
fi
fi
verboseMsg "$progname: running \`$engine -ini $tcxflag $jobswitch $prgswitch $texargs' ..."
# run in a subshell to get a local effect of TEXPOOL manipulation:
(
# If necessary, set TEXPOOL. Use absolute path, because of KPSE_DOT.
$localpool && { TEXPOOL="`pwd`:$TEXPOOL"; export TEXPOOL; }
verbose $engine -ini $tcxflag $jobswitch $prgswitch $texargs
) /dev/null 2>&1 && log_failure \
"\`$engine -ini $tcxflag $jobswitch $prgswitch $texargs' had errors."
# Definitely avoid user interaction for the following mv/cp commands.
mv "$format.log" "$fulldestdir/$format.log" /dev/null"
# No match before the loop:
setmatch false
recreate_loop
# Now check if there was at least one match:
getmatch || maybe_abort "no format depends on hyphen file \`$hyphenfile'"
}
###############################################################################
# recreate_by_engine(ENGINE) - recreate formats based on ENGINE
#
recreate_by_engine()
{
enginename=$1
match_cmd="test x\$engine = x$enginename"
# No match before the loop:
setmatch false
recreate_loop
# Now check if there was at least one match:
getmatch || maybe_abort "no format depends on engine \`$enginename'"
}
###############################################################################
# show_hyphen_file(FORMAT) - print full name of the hyphenfile for FORMAT
# exit code: returns error code if the ini file is not installed or
# if the hyphen file cannot be found
#
show_hyphen_file()
{
fmtname=$1
find_info_for_name "$fmtname" || abort "no info for format \`$fmtname'"
if test "x$hyphenation" = x-; then
echo -
cleanup 0
fi
find_hyphenfile "$format" "$hyphenation" \
|| abort "hyphenfile \`$hyphenation' not found"
}
###############################################################################
# disablefmt(FORMAT) - disable FORMAT in configuration file
#
disablefmt()
{
grep "^$1[ ]" $cnf_file >/dev/null || { (exit 0); return 0; }
ed $cnf_file >/dev/null 2>&1 <<-eof
g/^$1[ ]/s/^/#! /
w
q
eof
(exit 0); return 0
}
###############################################################################
# enablefmt(FORMAT) - enable FORMAT in configuration file
#
enablefmt()
{
grep "^#![ ]*$1[ ]" $cnf_file >/dev/null || { (exit 0); return 0; }
ed $cnf_file >/dev/null 2>&1 <<-eof
g/^#![ ]*$1[ ]/s/..[ ]*//
w
q
eof
(exit 0); return 0
}
main ${1+"$@"}
# we should not get here
cleanup 0