Lately I've been using a Weirdindows machine, in which I installed MiKTeX. Finding my way through its automatic package installer I noticed that if I check the existence of a package file which I know for sure I don't have installed:

\IfFileExists{abntex2.cls}{Oh, no!}{Phew :)}

MikTeX will install that package and the test will return true. Of course, that is perfectly reasonable (the installation, not the package), since \IfFileExists opens the file for reading to test its existence, and once it opens the file MiKTeX will try to install it.

To avoid that I can use the --disable-installer option which, unsurprisingly, will disable the automatic package installer. However I'd like to know if there is (or if I can make) a macro-level interface which allows me to check if a package is installed or if it's a candidate for installation without triggering the automatic installation of the package. Preferably regardless of the current setting of the package manager. Is it possible?

  • I reckon, you'd need some form of flag to inquire against, so the check returns true or false in either case.
    – naphaneal
    Jun 1, 2019 at 15:45
  • 1
    @MarcelKrüger I have just installed some hyperbar package using \pdffilemoddate{hyperbar.sty}. Apparently Windows is excessive ;-) Apparently any file operation triggers the package manager... Jun 1, 2019 at 15:59
  • You can always use LuaTeX... Jun 1, 2019 at 16:11
  • 2
    @touhami Hm... Interesting point. Apparently if the full path is given then MiKTeX doesn't try to install the package. However this brings up the problem of knowing what is the full path to the file... Jun 1, 2019 at 18:28
  • 1
    The thing is that kpsewhich <package-file> will immediately trigger auto-install if for the package to which <package-file> belongs. It stands to reason that any TeX-operation on the file will trigger the auto-install. So Lua or some shell escape (but don't use kpsewhich) seem like the only options out. ...
    – moewe
    Jun 1, 2019 at 19:54

1 Answer 1


In this answer I will assume a Lua supporting engine (LuaTeX, HarfTeX, etc.)

MiKTeX is very eager to install packages, so you have to fool it: If the file you are looking for already exists in your current directory, MiKTeX is happy and does not install the package if you ask it for the file.

So that's the plan: Place a dummy file in the current working directory. Then ask MiKTeX, though the kpathsea emulation, for all known files matching the name. Of course the first one will be the local dummy file, but if another one is found, the package was installed. Then delete the dummy file in the end.

local truetok, falsetok = token.create'@firstoftwo', token.create'@secondoftwo'
lua.get_functions_table()[\the\filereallyexistsfuncid] = function()
  local filename = token.scan_string()
  local f = io.open(filename) % First test if the file exists in the current folder
  if f then
    return token.put_next(truetok) % In this case we are done, the file exists
  f = io.open(filename, "w") f:close() % Otherwise create it to stop MiKTeX from installing anything
  local _, f2 = kpse.lookup(filename,{all=true}) % Now we will always find the local file first and MiKTeX is happy. If the file existed before, it will be found too.
  os.remove(filename) % The local file did it's job, delete it
  token.put_next(f2 and truetok or falsetok) % And write the answer to TeX
\filereallyexists{hyperbar.sty}{AWESOME!}{How can you be so cruel?}

A remark about the more special Lua language constructs here: kpse.lookup returns, if all=true, one return value for every found file. So if only one file is found, only one value is returned. If two are found, two values, .... The line

local _, f2 = kpse.lookup(...)

stores the first two return values in the variables _ and f2. (In Lua _ is a regular variable but by convention _ is used if a value is to be ignored) The actual values stored there are the full absolute filenames of the found files. But what happens if only one file is found? Then there are no "first two return values", only one filename is returned. In cases like this, Lua sets the remaining variables to the special value nil.

In our code the only important question is: Is f2 a string or nil? In Lua, every string (including the empty string "") is considered true but nil is considered false. So we can act as if f2 was a boolean. Which brings us to the last line:

token.put_next(f2 and truetok or falsetok)

The a and b or c is the Lua equivalent to the tertiary conditional operator a ? b : c from C: If a (in our case f2) is true, we get b (truetok) otherwise c (falsetok). This works as long as b is never false (or nil) as a consequence of the short-circuiting of Lua's and and or: We have a and b or c which is seen as (a and b) or c. If a is considered true, then a and b if and only if b. So Lua just evaluates a and b to b without casting this to a boolean value. We assumed b to be true, so (a and b) or c === b or c is always true and c doesn't even have to be evaluated, so Lua just returns b, again without casting to booleans. So Lua's and and or always return the first argument which determines their truth value. (What happens for a false value a is left as an exercise to the reader ;-) )

  • AWESOME! Thanks ;-) Jun 1, 2019 at 18:05
  • 1
    I understood most of what you did there. What didn't quite sink in is the last line in the Lua code, namely the expression f2 and truetok or falsetok. If I understood correctly truetok and falsetok are the control sequences \@firstoftwo and \@secondoftwo. I didn't quite get what is f2 (a string, a bool, etc.), and how it makes that expression evaluate to truetok or falsetok. Would you mind explaining, please? Jun 1, 2019 at 19:37
  • 2
    @PhelypeOleinik It's basically the second hit of the search (with the first one being the local file) and the and/or comstruction is Lua's ternary operator, so outputs truetok if f2 is not nil and falsetok otherwise.
    – TeXnician
    Jun 1, 2019 at 19:55
  • I added some explanations, but it's exactly what TeXnician said. Jun 1, 2019 at 20:03
  • @TeXnician and Marcel -- Thank you both for the explanations! Jun 1, 2019 at 20:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.