| 1 |
|
| 2 |
|
| 3 |
local svnrevision = string.match("$Revision$", "%d+") or "0" |
| 4 |
local svndate = string.match("$Date$", "[-%d]+") or "2009-12-04" |
| 5 |
local bannerstr = "runscript wrapper utility (rev. " .. |
| 6 |
svnrevision .. ", " .. svndate .. ")\n" .. |
| 7 |
"usage: runscript script-name [arguments]\n" .. |
| 8 |
"try -help [-v] for more information" |
| 9 |
|
| 10 |
local helpstr = [[ |
| 11 |
|
| 12 |
Script wrappers in TeX Live on Windows |
| 13 |
|
| 14 |
Rationale |
| 15 |
|
| 16 |
Wrappers enable use of scripts on Windows as regular programs. |
| 17 |
They are also required for some binary programs to set up the |
| 18 |
right environment for them. |
| 19 |
|
| 20 |
Batch scripts can be used for wrapping but they are not as universal |
| 21 |
as binaries (there are some odd cases where they don't work) and |
| 22 |
it is hard to make them robust and secure. Compiled binary wrappers |
| 23 |
don't suffer from these problems but they are harder to write, debug |
| 24 |
and maintain in comparison to scripts. For these reasons a hybrid |
| 25 |
approach is taken that combines a binary stub with a launcher script. |
| 26 |
|
| 27 |
Adding wrappers for user scripts |
| 28 |
|
| 29 |
The script wrapping machinery is not limited to scripts shipped with |
| 30 |
TeX Live. You can also use it for script programs from manually |
| 31 |
installed packages. This should minimize problems when using them |
| 32 |
with TeX Live. |
| 33 |
|
| 34 |
First, make sure that there is an interpreter program available on |
| 35 |
your system for the script you want to use. Interpreters for Perl |
| 36 |
and Lua are bundled with TeX Live, all others have to be installed |
| 37 |
independently. Lua scripts are the most efficient to run, so if you |
| 38 |
consider writing a new script, that would be the recommended choice. |
| 39 |
|
| 40 |
The following script types and their file extensions are currently |
| 41 |
supported and searched in that order: |
| 42 |
|
| 43 |
Lua (.tlu;.texlua;.lua) -- included |
| 44 |
Perl (.pl) -- included |
| 45 |
Ruby (.rb) -- requires installation |
| 46 |
Python (.py) -- requires installation |
| 47 |
Tcl (.tcl) -- requires installation |
| 48 |
Java (.jar) -- requires installation |
| 49 |
VBScript (.vbs) -- part of Windows |
| 50 |
JScript (.js) -- part of Windows |
| 51 |
Batch (.bat;.cmd) -- part of Windows |
| 52 |
|
| 53 |
Finally, Unix-style extensionless scripts are searched as last and |
| 54 |
the interpreter program is established based on the she-bang (#!) |
| 55 |
specification on the very first line of the script. This can be |
| 56 |
an arbitrary program but it must be present on the search path. |
| 57 |
|
| 58 |
Next, the script program needs to be installed somewhere below the |
| 59 |
'scripts' directory under one of the TEXMF trees (consult the |
| 60 |
documentation or texmf/web2c/texmf.cnf file for a list). You may |
| 61 |
need to update the file search database afterwards with: |
| 62 |
|
| 63 |
mktexlsr [TEXMFDIR] |
| 64 |
|
| 65 |
It is also possible to use scripts that are outside of TEXMF hierarchy |
| 66 |
by adjusting TEXMFSCRIPTS environment or kpathsea variable, see |
| 67 |
kpathsea documentation for more information on setting its variables. |
| 68 |
|
| 69 |
Test if the script can be located with: |
| 70 |
|
| 71 |
kpsewhich --format=texmfscripts <script-name>.<ext> |
| 72 |
|
| 73 |
This should output the full path to the script if everything is |
| 74 |
properly installed and configured. If this test is successful, |
| 75 |
the script can be run immediately with: |
| 76 |
|
| 77 |
runscript <script-name> [script arguments] |
| 78 |
|
| 79 |
If you prefer to call the script program simply by its name, copy |
| 80 |
and rename bin/win32/runscript.exe (or bin/win64/runscript.exe for |
| 81 |
64-bit Windows) to <script-name>.exe and put it somewhere on the |
| 82 |
search path.]] |
| 83 |
|
| 84 |
local docstr = [[ |
| 85 |
|
| 86 |
Wrapper structure |
| 87 |
|
| 88 |
Wrappers consist of small binary stubs and a common texlua script. |
| 89 |
The binary stubs are all the same, just different names (but CLI |
| 90 |
and GUI stubs differ, see below, and GUI stubs are actually all |
| 91 |
different due to different embedded icons). |
| 92 |
|
| 93 |
The job of the binary stub is twofold: (a) call the texlua launcher |
| 94 |
script 'runscript.tlu' from the same directory (or more precisely |
| 95 |
from the directory containing 'runscript.dll') and (b) pass to it |
| 96 |
argv[0] and the unparsed argument string as the last two arguments |
| 97 |
(after adding a sentinel argument, which ends with a new line |
| 98 |
character). Arbitrary C strings can be passed, because the script |
| 99 |
is executed by linking with luatex.dll and calling the lua |
| 100 |
interpreter internally rather than by spawning a new process. |
| 101 |
|
| 102 |
There are two flavours of the binary stub: one for CLI programs |
| 103 |
and another one for GUI programs. The GUI variant does not open |
| 104 |
a console window nor does it block the command prompt if started |
| 105 |
from there. It also uses a dialog box to display an error message |
| 106 |
in addition to outputting to stderr. |
| 107 |
|
| 108 |
The stubs are further split into a common DLL and EXE proxies |
| 109 |
to it. This is for maintenance reasons - updates can be done by |
| 110 |
replacement of a single DLL rather than all binary stubs. |
| 111 |
|
| 112 |
The launcher script knows, which variant has been used to invoke it |
| 113 |
based on the sentinel argument. The lack of this argument means |
| 114 |
that it was invoked in a standard way, i.e., through texlua.exe. |
| 115 |
|
| 116 |
All the hard work of locating a script/program to execute happens |
| 117 |
in the launcher script. The located script/program is always |
| 118 |
executed directly by spawning its interpreter (or binary) in a new |
| 119 |
process. The system shell (cmd.exe) is never called (except for |
| 120 |
batch scripts, of course). If the located script happens to be |
| 121 |
a (tex)lua script, it is loaded and called internally from within |
| 122 |
this script, i.e. no new process is spawned. Execution is done |
| 123 |
using a protected call, so any compile or runtime errors are catched. |
| 124 |
|
| 125 |
Source files |
| 126 |
|
| 127 |
runscript.tlu launcher script for locating and dispatching |
| 128 |
target scripts/programs |
| 129 |
runscript_dll.c common DLL part of the binary stubs; locates and |
| 130 |
calls the launcher script |
| 131 |
runscript_exe.c EXE proxy to the common DLL for CLI mode stubs |
| 132 |
wrunscript_exe.c EXE proxy to the common DLL for GUI mode stubs |
| 133 |
|
| 134 |
Compilation of binaries (requires luatex.dll in the same directory) |
| 135 |
|
| 136 |
with gcc (size optimized): |
| 137 |
|
| 138 |
gcc -Os -s -shared -o runscript.dll runscript_dll.c -L./ -lluatex |
| 139 |
gcc -Os -s -o runscript.exe runscript_exe.c -L./ -lrunscript |
| 140 |
gcc -mwindows -Os -s -o wrunscript.exe wrunscript_exe.c -L./ -lrunscript |
| 141 |
|
| 142 |
with tcc (extra small size): |
| 143 |
|
| 144 |
tiny_impdef luatex.dll |
| 145 |
tcc -shared -o runscript.dll runscript_dll.c luatex.def |
| 146 |
tcc -o runscript.exe runscript_exe.c runscript.def |
| 147 |
tcc -o wrunscript.exe wrunscript_exe.c runscript.def |
| 148 |
|
| 149 |
License |
| 150 |
|
| 151 |
Originally written in 2009 by Tomasz M. Trzeciak, Public Domain. |
| 152 |
|
| 153 |
Prior work: |
| 154 |
'tl-w32-wrapper.texlua' by Reinhard Kotucha and Norbert Preining. |
| 155 |
'tl-w32-wrapper.cmd' by Tomasz M. Trzeciak. |
| 156 |
|
| 157 |
Changelog |
| 158 |
|
| 159 |
2009/12/04 |
| 160 |
- initial version |
| 161 |
2009/12/15 |
| 162 |
- minor fixes for path & extension list parsing |
| 163 |
2010/01/09 |
| 164 |
- added support for GUI mode stubs |
| 165 |
2010/02/28 |
| 166 |
- enable GUI mode stubs for dviout, psv and texworks; |
| 167 |
- added generic handling of sys programs |
| 168 |
- added restricted repstopdf to alias_table |
| 169 |
2010/03/13 |
| 170 |
- added 'readme.txt' and changelog |
| 171 |
- added support and docs for calling user added scripts; |
| 172 |
(use path of 'runscript.dll' instead of .exe stub to |
| 173 |
locate 'runscript.tlu' script) |
| 174 |
- limit search for shell_escape_commands to system trees |
| 175 |
- added function for creating directory hierarchy |
| 176 |
- fixed directory creation for dviout & texworks aliases |
| 177 |
- fixed arg[0] of repstopdf & rpdfcrop |
| 178 |
2010/03/28 |
| 179 |
- restructured docs, added --help and --version options |
| 180 |
(available only when invoked under 'runscript' name) |
| 181 |
- use TEXMF_RESTRICTED_SCRIPTS kpse var for searching |
| 182 |
shell_escape_commands |
| 183 |
- changed command validation to handle a list of commands |
| 184 |
- prepend GUI mode command(s) to the command list |
| 185 |
- added support for .tcl scripts |
| 186 |
2010/03/31 |
| 187 |
- fixed fatal bug in extention_map definition for GUI mode |
| 188 |
2010/04/15 |
| 189 |
- encapsulated main chunk in a function to execute with |
| 190 |
pcall for more robustness and better error catching |
| 191 |
- added texdoctk to scripts4tlperl table |
| 192 |
- added tlgs and tlperl to alias_table; callable as e.g.: |
| 193 |
runscript tlperl ... |
| 194 |
- doc tweaks |
| 195 |
2010/04/22 |
| 196 |
- ensure only backslash is used in USERPROFILE variable |
| 197 |
(Adobe Reader crash case) |
| 198 |
- fixed argument processing for direct execution under texlua |
| 199 |
- more doc tweaks |
| 200 |
2010/05/30 |
| 201 |
- Windows XP or newer required to run TeXworks |
| 202 |
2010/06/04 |
| 203 |
- added support for Perl scripts starting with eval-exec-perl |
| 204 |
construct in place of she-bang (#!) |
| 205 |
2010/06/25 |
| 206 |
- run internal tlperl only with our Perl |
| 207 |
- added fontinst to alias_table |
| 208 |
- added support for all tex4ht commands from mk4ht.pl |
| 209 |
- removed some unsued aliases |
| 210 |
- some code refactoring and cleanup |
| 211 |
2010/12/28 |
| 212 |
- use of external Perl now requires kpathsea variable |
| 213 |
TEXLIVE_WINDOWS_TRY_EXTERNAL_PERL to be explicitly set to 1 |
| 214 |
- alias_table replaced with if-elseif-end tests to streamline |
| 215 |
special cases and to avoid hardcoding of texmf* file paths |
| 216 |
- added a2ping to special cases (requires -x switch to Perl) |
| 217 |
- set ASYMPTOTE_GS (for asy) to full path to tlgs |
| 218 |
2011/01/09 |
| 219 |
- removed tex4ht commands starting with ht from mk4ht aliases; |
| 220 |
they have their own scripts and mk4ht calls them internally, |
| 221 |
so aliasing results in an infinite recursion |
| 222 |
- removed alias for fontinst (no fontinst.exe any more) |
| 223 |
- fixed GUI-mode interpreter for Ruby |
| 224 |
2011/09/10 |
| 225 |
- added -dDisableFAPI=true to psview argument list. Needed by |
| 226 |
gs-9.xx |
| 227 |
2012/03/12 |
| 228 |
- added '-i', '.' to psview argument list (author's request) |
| 229 |
- added environment clean up from Perl specific variables |
| 230 |
(when not using external Perl) |
| 231 |
2012/08/05 |
| 232 |
- added alias for fmtutil |
| 233 |
2013/05/09 |
| 234 |
- added alias mkluatexfontdb -> luaotfload-tool |
| 235 |
2013/07/03 |
| 236 |
- fix for psview and UNC paths in unix-style |
| 237 |
- remove not needed is_abs_path function |
| 238 |
2013/08/07 |
| 239 |
- handle updmap-sys via updmap --sys |
| 240 |
2013/08/08 |
| 241 |
- allow overriding gs/gs_dll/gs_lib with kpse variables |
| 242 |
TEXLIVE_WINDOWS_EXTERNAL_GS, ..._GS_LIB, ..._GS_DLL |
| 243 |
2013/08/30 |
| 244 |
- do not pass -NULL to dviout, to allow users changing and |
| 245 |
saving settings. Patch by Yusuke KUROKI |
| 246 |
2013/09/22 |
| 247 |
- add TEXMFDIST/fonts to the GS_LIB path. Patch by Yusuke KUROKI |
| 248 |
2014/04/30 |
| 249 |
- fix for argument duplication in fmtutil |
| 250 |
2015/04/12 |
| 251 |
- handle fmtutil-sys via fmtutil --sys |
| 252 |
2015/09/10 |
| 253 |
- more slash flipping for the sake of vbscript and unc paths |
| 254 |
2015/12/11 |
| 255 |
- fix spurious arguments for updmap and fmtutil |
| 256 |
2016/04/22 |
| 257 |
- Warning if external perl is requested but missing |
| 258 |
2017/04/22 (exactly one year later ;-) |
| 259 |
- Cater for fmtutil-user and updmap-user => -user arg |
| 260 |
2017/04/24 |
| 261 |
- GS_LIB setting for tlgs. |
| 262 |
2017/05/06 |
| 263 |
- introduce sys_user_progs, make checks for updmap/fmtutil |
| 264 |
use sys_user_progs instead, add kanji-config-updmap |
| 265 |
2018/03/12 |
| 266 |
- introduce a new function gettexmfdist() for security. |
| 267 |
2018/04/06 |
| 268 |
- introduce a new function is_64bit_windows_os() to |
| 269 |
check Windows OS. |
| 270 |
2018/06/20 |
| 271 |
- support also scripts in trees other than TEXMFDIST: |
| 272 |
https://tug.org/pipermail/tex-live/2018-June/041922.html |
| 273 |
]] |
| 274 |
|
| 275 |
-- HELPER SUBROUTINES -- |
| 276 |
|
| 277 |
-- quotes string with spaces |
| 278 |
local function _q(str) |
| 279 |
str = string.gsub(str, '"', '') -- disallow embedded double quotes |
| 280 |
return string.find(str, "%s") and '"'..str..'"' or str |
| 281 |
end |
| 282 |
|
| 283 |
-- prepends directories to path if they are not already there |
| 284 |
local function prepend_path(path, ...) |
| 285 |
local pathcmp = string.lower(string.gsub(path, '/', '\\'))..';' |
| 286 |
for k = 1, select('#', ...) do |
| 287 |
local dir = string.lower(string.gsub(select(k, ...), '/', '\\'))..';' |
| 288 |
if not string.find(pathcmp, dir, 1, true) then path = dir..path end |
| 289 |
end |
| 290 |
return path |
| 291 |
end |
| 292 |
|
| 293 |
-- searches the PATH for a file |
| 294 |
local function search_path(fname, PATH, PATHEXT) |
| 295 |
if string.find(fname, '[/\\]') then |
| 296 |
return nil, "directory part not allowed for PATH search: "..fname |
| 297 |
end |
| 298 |
PATH = PATH or os.getenv('PATH') |
| 299 |
PATHEXT = PATHEXT or '\0' -- '\0' for no extension |
| 300 |
for dir in string.gmatch(PATH, '[^;]+') do |
| 301 |
local dirsep = (string.find(dir, '\\') and '\\' or '/') |
| 302 |
for ext in string.gmatch(PATHEXT, '[^;]+') do |
| 303 |
local f = dir..dirsep..fname..ext |
| 304 |
if lfs.isfile(f) then return f, ext end |
| 305 |
end |
| 306 |
end |
| 307 |
return nil, "file or program not on PATH: "..fname |
| 308 |
end |
| 309 |
|
| 310 |
-- tests for tex4ht command (as given in mk4ht.pl) |
| 311 |
-- except for commands starting with 'ht' (they have their own scripts) |
| 312 |
local function is_tex4ht_command(progname) |
| 313 |
local prefixes = 'xh uxh xhm mz oo es js jm tei teim db dbm w jh jh1' |
| 314 |
local formats = 'context latex tex texi xelatex xetex' |
| 315 |
for p in string.gmatch(prefixes, '%S+') do |
| 316 |
for q in string.gmatch(formats, '%S+') do |
| 317 |
if (progname == p..q) then |
| 318 |
-- we have a hit, but not all combinations are valid |
| 319 |
return (p ~= 'teim' and p ~= 'dbm') or (q ~= 'xelatex' and q~= 'xetex') |
| 320 |
end |
| 321 |
end |
| 322 |
end |
| 323 |
return false |
| 324 |
end |
| 325 |
|
| 326 |
-- checks whether an item is in an array |
| 327 |
local function contains (tab, val) |
| 328 |
for index, value in ipairs(tab) do |
| 329 |
if value == val then |
| 330 |
return true |
| 331 |
end |
| 332 |
end |
| 333 |
return false |
| 334 |
end |
| 335 |
|
| 336 |
-- locates texmfscript to execute |
| 337 |
local function find_texmfscript(progname, ext_list) |
| 338 |
ext_list = ext_list or '\0' |
| 339 |
for ext in string.gmatch(ext_list, '[^;]+') do |
| 340 |
local progfullname = kpse.find_file(progname..ext, 'texmfscripts') |
| 341 |
if progfullname then return progfullname, ext end |
| 342 |
end |
| 343 |
return nil, "no appropriate script or program found: "..progname |
| 344 |
end |
| 345 |
|
| 346 |
-- converts the #! line to arg table |
| 347 |
-- used for scripts w/o extension |
| 348 |
-- only the two most common cases are considered: |
| 349 |
-- #! /path/to/command [options] |
| 350 |
-- #! /usr/bin/env command [options] |
| 351 |
-- ([options] after the command are retained as well) |
| 352 |
local function shebang_to_argv(progfullname) |
| 353 |
local fid, errmsg = io.open(progfullname, 'r') |
| 354 |
if not fid then return nil, errmsg end |
| 355 |
local fstln = fid:read('*line') |
| 356 |
fid:close() |
| 357 |
if string.find(fstln, "eval.*exit.*exec.*perl") then |
| 358 |
-- special case of Perl's time-honoured "totally devious construct": |
| 359 |
-- eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' && eval 'exec perl -S $0 $argv:q' |
| 360 |
return {"perl"} |
| 361 |
elseif (string.sub(fstln, 1, 2) ~= '#!') then |
| 362 |
return nil, "don't know how to execute script: "..progfullname |
| 363 |
end |
| 364 |
local argv = string.explode( string.sub(fstln, 3) ) -- split on spaces |
| 365 |
argv[1] = string.match(argv[1], '[^/]+$') |
| 366 |
if (argv[1] == 'env') then table.remove(argv, 1) end |
| 367 |
return argv |
| 368 |
end |
| 369 |
|
| 370 |
-- checks if command exists on the path and returns its full path |
| 371 |
local function check_command(cmdlist, PATH) |
| 372 |
for cmd in string.gmatch(cmdlist, '%S+') do |
| 373 |
local cmdext = cmd..(string.find(cmd, '%.[^\\/.]*$') and '' or '.exe') |
| 374 |
local fullcmd = search_path(cmdext, PATH) |
| 375 |
if fullcmd then |
| 376 |
return fullcmd, cmd |
| 377 |
end |
| 378 |
end |
| 379 |
return nil, 'program not found (not part of TeX Live): '..cmdlist |
| 380 |
end |
| 381 |
|
| 382 |
-- creates directory or directory hierarchy |
| 383 |
local function mkdir_plus(dir) |
| 384 |
if lfs.mkdir(dir) then |
| 385 |
return true |
| 386 |
end |
| 387 |
-- try with system's mkdir in case we need to create intermediate dirs too |
| 388 |
local ret = os.spawn({[0]=search_path("cmd.exe"), |
| 389 |
string.format('cmd.exe /x /c mkdir "%s"', dir)}) |
| 390 |
if ret == 0 then |
| 391 |
return true |
| 392 |
else |
| 393 |
return nil, string.format("cannot create directory (error code %d): %s", ret, dir) |
| 394 |
end |
| 395 |
end |
| 396 |
|
| 397 |
-- |
| 398 |
-- return the TEXMFDIST directory in TeX Live |
| 399 |
-- |
| 400 |
local function gettexmfdist() |
| 401 |
local ffi = require("ffi") |
| 402 |
ffi.cdef[[ |
| 403 |
typedef void* HANDLE; |
| 404 |
typedef char* LPCSTR; |
| 405 |
int GetModuleFileNameA(HANDLE h, LPCSTR l, int i); |
| 406 |
HANDLE GetModuleHandleA(const char *a); |
| 407 |
]] |
| 408 |
local buffer = ffi.new("char[?]", 512) |
| 409 |
local runscripthandle = ffi.C.GetModuleHandleA("runscript.dll") |
| 410 |
if runscripthandle == nil then |
| 411 |
return nil |
| 412 |
end |
| 413 |
local err = ffi.C.GetModuleFileNameA(runscripthandle, buffer, 256) |
| 414 |
if err == 0 then |
| 415 |
return nil |
| 416 |
end |
| 417 |
local str = ffi.string(buffer) |
| 418 |
str = string.gsub(str, "\\","/") |
| 419 |
str = string.reverse(str) |
| 420 |
local a, b |
| 421 |
-- remove /runscript.dll |
| 422 |
a, b = string.find(str, '/', 1, true) |
| 423 |
str = string.sub(str,a+1) |
| 424 |
-- remove /win32 |
| 425 |
a, b = string.find(str, '/', 1, true) |
| 426 |
str = string.sub(str,a+1) |
| 427 |
-- remove /bin |
| 428 |
a, b = string.find(str, '/', 1, true) |
| 429 |
str = string.sub(str,a+1) |
| 430 |
str = string.reverse(str) |
| 431 |
str = str .. '/texmf-dist' |
| 432 |
return str |
| 433 |
end |
| 434 |
|
| 435 |
-- |
| 436 |
-- Is the Windows OS 64bit? |
| 437 |
-- |
| 438 |
local function is_64bit_windows_os() |
| 439 |
local return_val = false |
| 440 |
-- 32 bit binaries always work, thus the default is false |
| 441 |
local architecture = os.getenv('PROCESSOR_ARCHITECTURE') |
| 442 |
if architecture ~= nil then |
| 443 |
if architecture == 'x86' then |
| 444 |
local is_wow64 = os.getenv('PROCESSOR_ARCHITEW6432') |
| 445 |
if is_wow64 == nil then |
| 446 |
return_val = false |
| 447 |
else |
| 448 |
return_val = true |
| 449 |
end |
| 450 |
else |
| 451 |
return_val = true |
| 452 |
end |
| 453 |
end |
| 454 |
return return_val |
| 455 |
end |
| 456 |
|
| 457 |
-- MAIN_CHUNK -- encapsulated in a function for more robust execution with pcall |
| 458 |
|
| 459 |
local function MAIN_CHUNK() |
| 460 |
|
| 461 |
-- set the system-default value for LC_CTYPE |
| 462 |
-- http://tug.org/pipermail/tex-live/2018-May/041628.html |
| 463 |
os.setlocale("", "ctype") |
| 464 |
|
| 465 |
-- preprocess arguments |
| 466 |
|
| 467 |
local guimode = false |
| 468 |
local argline = '' |
| 469 |
-- check for the sentinel argment coming from the .exe stub |
| 470 |
if arg[#arg-2] and ( string.sub(arg[#arg-2], -1) == '\n' ) then |
| 471 |
-- argv[0] and unparsed argument line are passed |
| 472 |
-- from the .exe stub as the two last arguments |
| 473 |
-- pop them up from the arg table |
| 474 |
argline = table.remove(arg) -- pop unparsed arguments |
| 475 |
arg[0] = table.remove(arg) -- pop C stub's argv[0] |
| 476 |
guimode = (table.remove(arg) == 'GUI_MODE\n') -- pop sentinel argument |
| 477 |
else |
| 478 |
-- we must be called as: texlua runscript.tlu progname ... |
| 479 |
-- this is treated the same as: runscript[.exe] progname ... |
| 480 |
-- we don't have the unparsed arument line in this case, so construct one |
| 481 |
for k = #arg, 1, -1 do argline = _q(arg[k]) .. ' ' .. argline end |
| 482 |
end |
| 483 |
|
| 484 |
-- program name |
| 485 |
|
| 486 |
-- lower arg[0] : get file name part : remove extension |
| 487 |
local progname, substcount = string.lower(arg[0]):gsub('^.*[\\/]', ''):gsub('%.[^.]*$', '') |
| 488 |
-- special behaviour when called under 'runscript' name |
| 489 |
if (progname == 'runscript') then |
| 490 |
-- we are called as: runscript progname ... |
| 491 |
-- or as: runscript --help|-h|--version ... |
| 492 |
-- handle options first (only --help and --version) |
| 493 |
local opt, param = {}, nil |
| 494 |
while true do |
| 495 |
-- remove the first argument from the arg table and from the argline string |
| 496 |
-- (this argument should have no embedded spaces!) |
| 497 |
param = table.remove(arg, 1) |
| 498 |
if not param then break end |
| 499 |
argline = string.gsub(argline, '^%S+%s*', '') |
| 500 |
local optname = string.lower(param):match('^%-%-?(.*)$') |
| 501 |
if not optname then |
| 502 |
break |
| 503 |
elseif (optname == 'h') or (optname == 'help') then |
| 504 |
opt.help = true |
| 505 |
elseif (optname == 'v') then |
| 506 |
opt.v = true |
| 507 |
elseif (optname == 'version') then |
| 508 |
opt.version = true |
| 509 |
else |
| 510 |
error("unknown option: "..param.."\n"..bannerstr) |
| 511 |
end |
| 512 |
end |
| 513 |
if opt.help then |
| 514 |
print(helpstr) |
| 515 |
if opt.v then print(docstr) end |
| 516 |
os.exit(0) |
| 517 |
elseif opt.version or opt.v then |
| 518 |
print(bannerstr) |
| 519 |
os.exit(0) |
| 520 |
end |
| 521 |
-- make sure progname is valid |
| 522 |
arg[0] = assert(param, "not enough arguments!\n"..bannerstr) |
| 523 |
progname = string.lower(arg[0]):gsub('^.*[\\/]', ''):gsub('%.[^.]*$', '') |
| 524 |
assert(progname == string.lower(arg[0]), "bad command name: " .. arg[0]) |
| 525 |
end |
| 526 |
-- special case of sys programs |
| 527 |
progname, substcount = string.gsub(progname, '%-sys$', '') |
| 528 |
local sysprog = (substcount > 0) -- true if there was a -sys suffix removed |
| 529 |
-- special case of user programs |
| 530 |
-- we do not guard against programs foobar-user-sys ... we don't ship them |
| 531 |
progname, substcount = string.gsub(progname, '%-user$', '') |
| 532 |
local userprog = (substcount > 0) -- true if there was a -user suffix removed |
| 533 |
-- prevent recursive calls to this script |
| 534 |
assert(progname ~= 'runscript', "oops! wrapping the wrapper?") |
| 535 |
|
| 536 |
|
| 537 |
-- kpse and environment set-up |
| 538 |
|
| 539 |
-- init kpathsea |
| 540 |
local k = -1 |
| 541 |
while arg[k-1] do k = k - 1 end -- in case of a call: luatex --luaonly ... |
| 542 |
local lua_binary = arg[k] |
| 543 |
kpse.set_program_name(lua_binary, progname) |
| 544 |
|
| 545 |
-- various dir-vars |
| 546 |
local TEXDIR = kpse.var_value('SELFAUTOPARENT') |
| 547 |
-- local TEXMFDIST = kpse.var_value('TEXMFDIST') |
| 548 |
-- use a new function to obtain TEXMFDIST |
| 549 |
local TEXMFDIST = gettexmfdist() |
| 550 |
if TEXMFDIST == nil then |
| 551 |
TEXMFDIST = kpse.var_value('TEXMFDIST') |
| 552 |
end |
| 553 |
local BINDIR = kpse.var_value('SELFAUTOLOC') |
| 554 |
local PATH = os.getenv('PATH') or '' |
| 555 |
|
| 556 |
-- list of programs that have -sys and -user variants |
| 557 |
local sys_user_progs = { 'updmap', 'fmtutil', 'kanji-config-updmap' } |
| 558 |
|
| 559 |
-- restricted programs |
| 560 |
local shell_escape_commands = string.lower(kpse.var_value('shell_escape_commands') or '') |
| 561 |
local is_restricted_progname = string.find( ','..shell_escape_commands..',', |
| 562 |
','..progname..',', 1, true) |
| 563 |
if is_restricted_progname then |
| 564 |
-- limit search path to the restricted (system) trees |
| 565 |
-- (not really necessary for entries in the alias_table, |
| 566 |
-- because they are not searched for with kpathsea) |
| 567 |
os.setenv('TEXMFSCRIPTS', kpse.var_value('TEXMF_RESTRICTED_SCRIPTS')) |
| 568 |
end |
| 569 |
-- perl stuff |
| 570 |
local scripts4tlperl = { |
| 571 |
tlperl = true, |
| 572 |
updmap = true, |
| 573 |
fmtutil = true, |
| 574 |
['updmap-sys'] = true, |
| 575 |
['fmtutil-sys'] = true, |
| 576 |
} |
| 577 |
local try_extern_perl = (kpse.var_value('TEXLIVE_WINDOWS_TRY_EXTERNAL_PERL') == '1') and |
| 578 |
not (guimode or is_restricted_progname or scripts4tlperl[progname]) |
| 579 |
local PERLEXE = try_extern_perl and search_path('perl.exe', PATH) |
| 580 |
local extperl_warn |
| 581 |
if not PERLEXE then |
| 582 |
if try_extern_perl then extperl_warn = [[ |
| 583 |
External Perl missing or outdated. Please install a recent Perl, or configure |
| 584 |
TeX Live to always use the builtin Perl: |
| 585 |
tlmgr conf texmf TEXLIVE_WINDOWS_TRY_EXTERNAL_PERL 0 |
| 586 |
Meanwhile, continuing with built-in Perl... |
| 587 |
]] |
| 588 |
end -- if try_extern_perl |
| 589 |
PERLEXE = TEXDIR..'/tlpkg/tlperl/bin/perl.exe' |
| 590 |
os.setenv('PERL5LIB', TEXDIR..'/tlpkg/tlperl/lib') |
| 591 |
PATH = prepend_path(PATH, TEXDIR..'/tlpkg/tlperl/bin') |
| 592 |
local PERLENV = 'PERL5OPT;PERLIO;PERLIO_DEBUG;PERLLIB;PERL5DB;PERL5DB_THREADED;' .. |
| 593 |
'PERL5SHELL;PERL_ALLOW_NON_IFS_LSP;PERL_DEBUG_MSTATS;' .. |
| 594 |
'PERL_DESTRUCT_LEVEL;PERL_DL_NONLAZY;PERL_ENCODING;PERL_HASH_SEED;' .. |
| 595 |
'PERL_HASH_SEED_DEBUG;PERL_ROOT;PERL_SIGNALS;PERL_UNICODE' |
| 596 |
for var in string.gmatch(PERLENV, '[^;]+') do os.setenv(var, nil) end |
| 597 |
end |
| 598 |
-- gs stuff |
| 599 |
local override_gs |
| 600 |
if not is_restricted_progname then |
| 601 |
override_gs = kpse.var_value('TEXLIVE_WINDOWS_EXTERNAL_GS') |
| 602 |
end |
| 603 |
-- the full path to the executable |
| 604 |
local GSEXE |
| 605 |
-- the directory where the gs executable resides |
| 606 |
local GSDIR |
| 607 |
-- the name of the gs executable |
| 608 |
local GSNAME |
| 609 |
if override_gs then |
| 610 |
-- first check whether we got an absolute path or only executable name |
| 611 |
if string.find(override_gs, '[/\\]') then |
| 612 |
GSEXE = override_gs |
| 613 |
else |
| 614 |
-- search in the path |
| 615 |
GSEXE = search_path(override_gs, PATH) |
| 616 |
end |
| 617 |
end |
| 618 |
if GSEXE then |
| 619 |
-- split the dir and progname part so that we can set the path |
| 620 |
-- work on a string with all forward slashes |
| 621 |
local foo = string.lower(string.gsub(GSEXE, '\\', '/')) |
| 622 |
GSNAME = string.gsub(foo, '^.*[\\/]', '') |
| 623 |
GSDIR = string.gsub(foo, '^(.*)[\\/].*$', '%1') |
| 624 |
-- search also for a GS_DLL setting |
| 625 |
-- we do not need to check for is_restricted_progname, since |
| 626 |
-- GSEXE is only defined when it is not set |
| 627 |
local GSDLL = kpse.var_value('TEXLIVE_WINDOWS_EXTERNAL_GS_DLL') |
| 628 |
if GSDLL then |
| 629 |
os.setenv('GS_DLL', GSDLL) |
| 630 |
end |
| 631 |
local GSLIB = kpse.var_value('TEXLIVE_WINDOWS_EXTERNAL_GS_LIB') |
| 632 |
if GSLIB then |
| 633 |
os.setenv('GS_LIB', GSLIB) |
| 634 |
end |
| 635 |
else |
| 636 |
-- use built in gs |
| 637 |
os.setenv('GS_LIB', TEXDIR..'/tlpkg/tlgs/lib;' |
| 638 |
..TEXDIR..'/tlpkg/tlgs/fonts;' |
| 639 |
..TEXDIR..'/tlpkg/tlgs/Resource/Init;' |
| 640 |
..TEXDIR..'/tlpkg/tlgs/Resource;' |
| 641 |
..TEXDIR..'/tlpkg/tlgs/kanji;' |
| 642 |
..os.getenv('WINDIR')..'/Fonts;'..TEXMFDIST..'/fonts') |
| 643 |
os.setenv('GS_DLL', TEXDIR..'/tlpkg/tlgs/bin/gsdll32.dll') |
| 644 |
GSEXE = TEXDIR..'/tlpkg/tlgs/bin/gswin32c.exe' |
| 645 |
GSNAME = 'gswin32c.exe' |
| 646 |
GSDIR = TEXDIR..'/tlpkg/tlgs/bin' |
| 647 |
end |
| 648 |
-- now setup the path so that the gs program will be found |
| 649 |
PATH = prepend_path(PATH, GSDIR, BINDIR) |
| 650 |
os.setenv('PATH', PATH); |
| 651 |
|
| 652 |
-- sys stuff |
| 653 |
if (sysprog and not contains(sys_user_progs, progname)) then |
| 654 |
os.setenv('TEXMFVAR', kpse.var_value('TEXMFSYSVAR')) |
| 655 |
os.setenv('TEXMFCONFIG', kpse.var_value('TEXMFSYSCONFIG')) |
| 656 |
end |
| 657 |
-- Adobe Reader crash case: make sure USERPROFILE is not "slashed" |
| 658 |
os.setenv("USERPROFILE", os.getenv("USERPROFILE"):gsub('/', '\\')) |
| 659 |
|
| 660 |
-- extension to interpeter mapping |
| 661 |
|
| 662 |
-- the extension is mapped to argv table |
| 663 |
-- the command to execute is given as the first element of the table |
| 664 |
-- (it can be a whitespace separated list of names to try) |
| 665 |
local extension_map = { |
| 666 |
['.bat'] = {'cmd', '/c', 'call'}, |
| 667 |
['.jar'] = {'java.exe', '-jar'}, |
| 668 |
['.pl' ] = {'perl.exe'}, |
| 669 |
['.py' ] = {'python.exe'}, |
| 670 |
['.rb' ] = {'ruby.exe'}, |
| 671 |
['.tcl'] = {'tclsh.exe tclsh85.exe tclsh84.exe'}, |
| 672 |
['.vbs'] = {'cscript.exe', '-nologo'}, |
| 673 |
} |
| 674 |
if guimode then |
| 675 |
-- for GUI mode wrappers we try GUI mode interpeters where possible |
| 676 |
extension_map['.jar'][1] = 'javaw.exe ' .. extension_map['.jar'][1] |
| 677 |
extension_map['.pl' ][1] = 'wperl.exe ' .. extension_map['.pl' ][1] |
| 678 |
extension_map['.py' ][1] = 'pythonw.exe ' .. extension_map['.py' ][1] |
| 679 |
extension_map['.rb' ][1] = 'rubyw.exe ' .. extension_map['.rb' ][1] |
| 680 |
extension_map['.tcl'][1] = 'wish.exe wish85.exe wish84.exe ' .. extension_map['.tcl'][1] |
| 681 |
extension_map['.vbs'][1] = 'wscript.exe ' .. extension_map['.vbs'][1] |
| 682 |
end |
| 683 |
extension_map['.cmd'] = extension_map['.bat'] |
| 684 |
extension_map['.js'] = extension_map['.vbs'] |
| 685 |
|
| 686 |
-- set up argv table |
| 687 |
|
| 688 |
local ARGV = nil |
| 689 |
|
| 690 |
-- special cases (aliases) |
| 691 |
|
| 692 |
if is_tex4ht_command(progname) then |
| 693 |
argline = progname .. ' ' .. argline |
| 694 |
progname = 'mk4ht' |
| 695 |
elseif progname == 'a2ping' then |
| 696 |
table.insert(extension_map['.pl'], '-x') |
| 697 |
elseif contains(sys_user_progs, progname) then |
| 698 |
if sysprog then |
| 699 |
argline = ' --sys ' .. argline |
| 700 |
elseif userprog then |
| 701 |
argline = ' --user ' .. argline |
| 702 |
end |
| 703 |
-- do not guess input encoding in format creation for ptex |
| 704 |
-- and friends since guessing is not complete |
| 705 |
os.setenv('GUESS_INPUT_KANJI_ENCODING', '0') |
| 706 |
elseif progname == 'asy' then |
| 707 |
os.setenv('ASYMPTOTE_GS', GSEXE) |
| 708 |
os.setenv('CYGWIN', 'nodosfilewarning') |
| 709 |
if is_64bit_windows_os() then |
| 710 |
ARGV = {[0]=TEXDIR..'/tlpkg/asymptote64/asy.exe', 'asy'} |
| 711 |
else |
| 712 |
ARGV = {[0]=TEXDIR..'/tlpkg/asymptote/asy.exe', 'asy'} |
| 713 |
end |
| 714 |
elseif progname == 'dviout' then |
| 715 |
local fontsdir = kpse.var_value('TEXMFVAR') .. '/fonts' |
| 716 |
if (lfs.attributes(fontsdir, 'mode') ~= 'directory') then |
| 717 |
assert(mkdir_plus(fontsdir)) |
| 718 |
end |
| 719 |
local tfmpath = kpse.show_path('tfm') |
| 720 |
tfmpath = string.gsub(tfmpath, '!!', '') |
| 721 |
tfmpath = string.gsub(tfmpath, '/', '\\') |
| 722 |
local texrt = {} |
| 723 |
for d in string.gmatch(tfmpath, '([^;]+\\fonts)\\tfm[^;]*') do |
| 724 |
if (lfs.attributes(d, 'mode') == 'directory') then |
| 725 |
table.insert(texrt, d) |
| 726 |
end |
| 727 |
end |
| 728 |
local par = [["-gen=']] .. string.gsub(TEXDIR, '/', '\\') .. |
| 729 |
[[\tlpkg\dviout\gen_pk'" "-TEXROOT=']] .. |
| 730 |
table.concat(texrt, ';') .. [['" "-gsx=']] .. GSEXE .. [['"]]; |
| 731 |
ARGV = {[0]=TEXDIR..'/tlpkg/dviout/dviout.exe', 'dviout', par} |
| 732 |
elseif progname == 'mkluatexfontdb' then |
| 733 |
progname = 'luaotfload-tool' |
| 734 |
table.insert(arg, '--alias=mkluatexfontdb') |
| 735 |
elseif progname == 'psv' then |
| 736 |
argline = '-sINPUT='..argline |
| 737 |
ARGV = {[0]=TEXDIR..'/tlpkg/tlpsv/gswxlua.exe', 'gswxlua', |
| 738 |
'-dDisableFAPI=true', |
| 739 |
'-l', (_q(TEXDIR..'/tlpkg/tlpsv/psv.wx.lua'):gsub('/','\\')), |
| 740 |
'-p', (_q(TEXDIR..'/tlpkg/tlpsv/psv_view.ps'):gsub('/','\\')), |
| 741 |
'-i', '.'} |
| 742 |
elseif progname == 'repstopdf' or progname == 'rpdfcrop' then |
| 743 |
argline = '--restricted ' .. argline |
| 744 |
progname = string.sub(progname, 2, -1) |
| 745 |
elseif progname == 'texworks' then |
| 746 |
local winver = tonumber(string.match(os.uname().version, '%D*(%d+%.?%d*)')) |
| 747 |
assert(winver >= 5.01, "Windows XP or newer required to run TeXworks") |
| 748 |
local TW_LIBPATH = kpse.var_value('TW_LIBPATH') or |
| 749 |
kpse.var_value('TEXMFCONFIG')..'/texworks' |
| 750 |
local TW_INIPATH = kpse.var_value('TW_INIPATH') or TW_LIBPATH |
| 751 |
os.setenv('TW_LIBPATH', TW_LIBPATH) |
| 752 |
os.setenv('TW_INIPATH', TW_INIPATH) |
| 753 |
if (TW_INIPATH and lfs.attributes(TW_INIPATH, 'mode') ~= 'directory') then |
| 754 |
-- TeXworks needs directory holding its configuration to exist |
| 755 |
assert(mkdir_plus(TW_INIPATH)) |
| 756 |
end |
| 757 |
ARGV = {[0]=TEXDIR..'/tlpkg/texworks/texworks.exe', 'texworks'} |
| 758 |
elseif progname == 'tlgs' then |
| 759 |
ARGV = {[0]=GSEXE, GSNAME} |
| 760 |
elseif progname == 'tlperl' then |
| 761 |
ARGV = {[0]=PERLEXE, 'perl'} |
| 762 |
elseif progname == 'latexdef' then |
| 763 |
progname = 'texdef' |
| 764 |
argline = ' --tex latex ' .. argline |
| 765 |
end |
| 766 |
|
| 767 |
-- general case |
| 768 |
|
| 769 |
if not ARGV then |
| 770 |
os.setenv('TEXMF', TEXMFDIST) |
| 771 |
local extlist = '.tlu;.texlua;.pl;.lua;.rb;.py;.tcl;.jar;.vbs;.js;.bat;.cmd;\0' |
| 772 |
local progfullname = search_path(progname, BINDIR, '.tlu;.bat;.cmd') or |
| 773 |
find_texmfscript(progname, extlist) |
| 774 |
os.setenv('TEXMF', nil) |
| 775 |
if progfullname == nil then |
| 776 |
-- scripts in $TEXMFLOCAL etc. can't be found without the following |
| 777 |
-- line !! |
| 778 |
kpse.set_program_name('runscript') |
| 779 |
progfullname = assert(find_texmfscript(progname, extlist)) |
| 780 |
end |
| 781 |
local ext = string.match(string.lower(progfullname), '%.[^\\/.]*$') or '' |
| 782 |
if (ext == '.lua') or (ext == '.tlu') or (ext == '.texlua') then -- lua script |
| 783 |
arg[0] = progfullname |
| 784 |
else |
| 785 |
ARGV = extension_map[ext] or assert(shebang_to_argv(progfullname)) |
| 786 |
-- [w|c]script, for one, mistakes a forward-slashed UNC script path |
| 787 |
-- for an option even when quoted |
| 788 |
table.insert(ARGV, _q(progfullname:gsub('/','\\'))) |
| 789 |
if not ARGV[0] then |
| 790 |
ARGV[0], ARGV[1] = assert(check_command(ARGV[1], PATH)) |
| 791 |
end |
| 792 |
end |
| 793 |
end |
| 794 |
|
| 795 |
-- run the program/script |
| 796 |
|
| 797 |
if ARGV then |
| 798 |
table.insert(ARGV, argline) -- pass through original arguments |
| 799 |
if string.find (table.concat(ARGV, ' '), 'perl.exe') and extperl_warn then |
| 800 |
|
| 801 |
io.stderr:write(extperl_warn) |
| 802 |
end |
| 803 |
local ret = assert(os.spawn(ARGV)) |
| 804 |
if ret ~= 0 then |
| 805 |
local dbginfo = debug.getinfo(1) |
| 806 |
local errormsg = string.format("%s:%d: command failed with exit code %d:\n%s", |
| 807 |
dbginfo.short_src, dbginfo.currentline - 2, |
| 808 |
ret, table.concat(ARGV, ' ') ) |
| 809 |
os.setenv('RUNSCRIPT_ERROR_MESSAGE', errormsg) |
| 810 |
io.stderr:write(errormsg, '\n') |
| 811 |
end |
| 812 |
os.exit(ret) |
| 813 |
else -- must be a lua script |
| 814 |
dofile(arg[0]) |
| 815 |
end |
| 816 |
|
| 817 |
end -- MAIN_CHUNK |
| 818 |
|
| 819 |
-- execute MAIN_CHUNK with pcall to catch any runtime errors |
| 820 |
|
| 821 |
local success, errormsg = pcall(MAIN_CHUNK) |
| 822 |
if not success then |
| 823 |
os.setenv('RUNSCRIPT_ERROR_MESSAGE', errormsg) |
| 824 |
error(errormsg) |
| 825 |
end |
| 826 |
|
| 827 |
-- about RUNSCRIPT_ERROR_MESSAGE environment variable: |
| 828 |
-- it stores an error message that is caught and displayed |
| 829 |
-- in a message box on the C side at process exit |
| 830 |
-- (currently used only by gui mode stubs) |