11

I'm working on a package that has to work on both TeXlive 2009 (Ubuntu, so updating TeXlive is a nonstarter) and later versions, and I'd like to be able to use fontspec with LuaLaTeX if we're on TeXlive 2010 or later, but \RequirePackage{fontspec}[2008/08/10] (Ubuntu's TeXlive 2009 ships with a fontspec.sty from 2008/08/09) still tries to load fontspec.sty and the date argument only serves to make LaTeX issue a warning that it's too old - after barfing out errors because fontspec.sty has \RequireXeTeX in it.

Is there a way to abort loading a package if it's too old, or do I have to do something borderline insane involving (temporarily) redefining \ProvidesPackage. I have the following example cooked up, but I'd love to replace it with something saner:

\listfiles
\documentclass{article}
%\usepackage{trace}
\def\XID#1{\begingroup\ifdefined #1\aftergroup#1\fi\endgroup}
\XID\traceon
\let\OPP\ProvidesPackage
\def\ProvidesPackage#1[#2/#3]{%
  \let\ProvidesPackage\OPP
  \let\OPP\undefined
  \begingroup
    \ifnum #2 < 2009 % if fontspec is older than 2009, abort
      \aftergroup\endinput
      \XID\traceoff
    \fi
  \endgroup
  \XID\traceoff
  \ProvidesPackage#1[#2/#3]}

\usepackage{fontspec}
\ifdefined\setmainfont
  \message{^^J*** \string\setmainfont\space defined! ***^^J}
\else
  \message{^^J*** \string\setmainfont\space not defined! ***^^J}
\fi
\begin{document}
\end{document}

EDIT: After some fairly intense hacking, I came up with this shorter and saner replacement:

\documentclass{minimal}
\makeatletter
\def\IfNewer#1#2#3#4{% we chain into #5 if true and #6 if false using \@{first,second}oftwo.
  \begingroup %that we might forget the craziness that is to follow...
  \def\NeedsTeXFormat##1{\@ifnextchar[\relax\relax}
  % This is rather brittle and should probably use \@ifnextchar[
  \def\ProvidesPackage##1[##2/##3/##4 ##5]{%
    \newif\if@goahead % defaults to false, obviously.
    % Ugly test, but it works
    \ifnum ##2 < #2 % Year too old
    \else\ifnum ##2 = #2\ifnum ##3 < #3 % Year OK, month too old
    \else\ifnum ##2 = #2\ifnum ##3 = #3\ifnum ##4 < #4 % Year & month OK, day too old
    \else 
      \@goaheadtrue
    \fi\fi\fi\fi\fi\fi
    \endinput}
  \input #1.sty % use TeX's \input to avoid #1.sty being in \listfiles
  \if@goahead
    \endgroup % \if@goahead and our redifinitions disappear in a puff of logic.
    \expandafter\@firstoftwo
  \else
    \endgroup
    \expandafter\@secondoftwo
  \fi}
\makeatother
\IfNewer{fontspec}{2010}{01}{01}
  {\message{^^J*** New enough! ***^^J}}
  {\message{^^J*** Too old! ***^^J}}
\begin{document}
\end{document}
Martin Scharrer
  • 262,582
kahen
  • 2,165

2 Answers2

10

After looking through the related LaTeX core code I don't see any other method than redefining \ProvidesPackage. It is the earliest place where the version is known. However, I would reuse the existing macros as much as possible to keep it compatible.

The following code reads the two arguments of, feeds them to the original and then checks the package version as normal, aborting the loading if required. Note that the package version number still gets defined (globally!) and can be used afterwards as well. You should set it to \relax as shown to mark the package as not loaded.

\documentclass{article}

\makeatletter

\let\origProvidesPackage\ProvidesPackage
\def\ProvidesPackage#1{%
    \@testopt{\@ProvidesPackage{#1}}{}%
}
\def\@ProvidesPackage#1[#2]{%
    \let\ProvidesPackage\origProvidesPackage
    \ifx\\#2\\
        \ProvidesPackage{#1}%
    \else
        \ProvidesPackage{#1}[#2]%
    \fi
    \@ifpackagelater{fontspec}{2008/08/10}{%
    }{%
        \endinput
    }%
}


\usepackage{fontspec}
\@ifpackagelater{fontspec}{2008/08/10}{%
    \expandafter\let\csname ver@fontspec.sty\endcsname\relax%   Mark package as not loaded; this must be after `\usepackage` because of internal LaTeX core code.
}{}%

\makeatother


\begin{document}


\end{document}
Moriambar
  • 11,466
Martin Scharrer
  • 262,582
  • What does \ifx\\#2\\ do? I thought the syntax was \ifx\cs@ne\cstw@ to compare two control sequences. – kahen Nov 06 '11 at 10:34
  • @kahen: That's a common coding template to test if #2 is empty, if so the expression stands for \ifx\\\\, i.e. it is tested if \\ is identical to itself, i.e. is always true, otherwise (if #2 doesn't start with \\ is it false and everything until the \else (or \fi if there is no \else) is ignored anyway. – Martin Scharrer Nov 06 '11 at 10:41
  • \tracingall \ifx\\ \@empty \\ \fi shows that it takes the false branch. Clearly I don't understand what's going on here. Wouldn't it be simpler to just \ifx#2\@empty? – kahen Nov 06 '11 at 10:53
  • 1
    No, it doesn't test if #2 is a macro which is empty, but if #2 does expand to nothing, then \ifx\\#2\\ is equal to \ifx\\\\ (true), not \ifx\\\@empty\\ (false). Because this is actually an often ask question I posted it here: http://tex.stackexchange.com/questions/33823/what-does-ifx-1-stand-for – Martin Scharrer Nov 06 '11 at 11:43
  • I ended up using this technique to create a macro \RequirePackageIfLater#1 that takes a date in yyyy/mm/dd format as its argument and curries into \RequirePackage after setting up \ProvidesPackage. Thanks a lot for the help – kahen Nov 06 '11 at 12:00
  • 1
    This makes me sad: it's apparently impossible to undefine \ver@\@currname.sty while in the middle of executing \RequirePackage. It causes stuff to break all over the place. – kahen Nov 10 '11 at 10:17
  • @kahen: Yes, I figured that out as well (see my code comment in the solution). The reason for that is that \ver@... is tested after the file is read to check if the version is new enough. If the macro is not expandable at this stage an error is raised. – Martin Scharrer Nov 10 '11 at 10:40
2

Sorry, I did not understand the question - Werner explained it me.

Ok, after you loaded package, you can check its version, for example

\@ifpackagelater{amsmath}{1999/12/20}{\typeout{OK}}{\typeout{BAD}}

See this example:

\documentclass{article}
\RequirePackage{amsmath}
\makeatletter
\@ifpackagelater{amsmath}{1999/12/20}{\typeout{OK}}{\typeout{BAD}}
\@ifpackagelater{amsmath}{2013/12/20}{\typeout{OK}}{\typeout{BAD}}
\makeatother

\begin{document}
Text
\end{document}

The problem here is that you need first to load the package, and only then its version becomes known. Which is ok if you want to kill the compilation for older distributions, but might pose problems otherwise.

In the comments I suggested to write whether the package is ok to a special file, and on the next run to check it before loading the package. Probably first one needs to redefine \RequreXeTeX to make the first run end.

Boris
  • 38,129
  • 1
    But isn't this exactly what the OP mentioned didn't work? If not, please elaborate on why this works. Perhaps it is OS-dependent? – Werner Nov 06 '11 at 05:19
  • Thanks, I did not understand the question. The OP propably needed @ifpackagelater{PACKAGE}{DATE}{yes branch}{no branch} – Boris Nov 06 '11 at 05:36
  • So you have to succesfully \usepackage{fontspec} before you can test whether it was OK?! That doesn't help since lualatex <some file using fontspec> fails on TeXlive 2009 (it runs smack into a \RequireXeTeX right after \ProvidesPackage) but succeeds on TeXlive 2010. Seems there is no way around redefining \ProvidesPackage then – kahen Nov 06 '11 at 05:52
  • The problem is, tex needs to read the file to get its version. Here is a hack for you: write the version of the package to the special file, and on the next run read it from this file rather than from the package. – Boris Nov 06 '11 at 06:01