3 local svnrevision = string.match("$Revision: 39066 $", "%d+") or "0"
4 local svndate = string.match("$Date: 2015-12-11 05:51:32 +0900 (Fri, 11 Dec 2015) $", "[-%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"
12 Script wrappers in TeX Live on Windows
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.
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.
27 Adding wrappers for user scripts
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
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.
40 The following script types and their file extensions are currently
41 supported and searched in that order:
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
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.
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:
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.
69 Test if the script can be located with:
71 kpsewhich --format=texmfscripts <script-name>.<ext>
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:
77 runscript <script-name> [script arguments]
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
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).
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.
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.
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.
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.
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.
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
134 Compilation of binaries (requires luatex.dll in the same directory)
136 with gcc (size optimized):
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
142 with tcc (extra small size):
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
151 Originally written in 2009 by Tomasz M. Trzeciak, Public Domain.
154 'tl-w32-wrapper.texlua' by Reinhard Kotucha and Norbert Preining.
155 'tl-w32-wrapper.cmd' by Tomasz M. Trzeciak.
162 - minor fixes for path & extension list parsing
164 - added support for GUI mode stubs
166 - enable GUI mode stubs for dviout, psv and texworks;
167 - added generic handling of sys programs
168 - added restricted repstopdf to alias_table
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
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
187 - fixed fatal bug in extention_map definition for GUI mode
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.:
196 - ensure only backslash is used in USERPROFILE variable
197 (Adobe Reader crash case)
198 - fixed argument processing for direct execution under texlua
201 - Windows XP or newer required to run TeXworks
203 - added support for Perl scripts starting with eval-exec-perl
204 construct in place of she-bang (#!)
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
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
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
225 - added -dDisableFAPI=true to psview argument list. Needed by
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)
232 - added alias for fmtutil
234 - added alias mkluatexfontdb -> luaotfload-tool
236 - fix for psview and UNC paths in unix-style
237 - remove not needed is_abs_path function
239 - handle updmap-sys via updmap --sys
241 - allow overriding gs/gs_dll/gs_lib with kpse variables
242 TEXLIVE_WINDOWS_EXTERNAL_GS, ..._GS_LIB, ..._GS_DLL
244 - do not pass -NULL to dviout, to allow users changing and
245 saving settings. Patch by Yusuke KUROKI
247 - add TEXMFDIST/fonts to the GS_LIB path. Patch by Yusuke KUROKI
249 - fix for argument duplication in fmtutil
251 - handle fmtutil-sys via fmtutil --sys
253 - more slash flipping for the sake of vbscript and unc paths
255 - fix spurious arguments for updmap and fmtutil
258 -- HELPER SUBROUTINES --
260 -- quotes string with spaces
261 local function _q(str)
262 str = string.gsub(str, '"', '') -- disallow embedded double quotes
263 return string.find(str, "%s") and '"'..str..'"' or str
266 -- prepends directories to path if they are not already there
267 local function prepend_path(path, ...)
268 local pathcmp = string.lower(string.gsub(path, '/', '\\'))..';'
269 for k = 1, select('#', ...) do
270 local dir = string.lower(string.gsub(select(k, ...), '/', '\\'))..';'
271 if not string.find(pathcmp, dir, 1, true) then path = dir..path end
276 -- searches the PATH for a file
277 local function search_path(fname, PATH, PATHEXT)
278 if string.find(fname, '[/\\]') then
279 return nil, "directory part not allowed for PATH search: "..fname
281 PATH = PATH or os.getenv('PATH')
282 PATHEXT = PATHEXT or '\0' -- '\0' for no extension
283 for dir in string.gmatch(PATH, '[^;]+') do
284 local dirsep = (string.find(dir, '\\') and '\\' or '/')
285 for ext in string.gmatch(PATHEXT, '[^;]+') do
286 local f = dir..dirsep..fname..ext
287 if lfs.isfile(f) then return f, ext end
290 return nil, "file or program not on PATH: "..fname
293 -- tests for tex4ht command (as given in mk4ht.pl)
294 -- except for commands starting with 'ht' (they have their own scripts)
295 local function is_tex4ht_command(progname)
296 local prefixes = 'xh uxh xhm mz oo es js jm tei teim db dbm w jh jh1'
297 local formats = 'context latex tex texi xelatex xetex'
298 for p in string.gmatch(prefixes, '%S+') do
299 for q in string.gmatch(formats, '%S+') do
300 if (progname == p..q) then
301 -- we have a hit, but not all combinations are valid
302 return (p ~= 'teim' and p ~= 'dbm') or (q ~= 'xelatex' and q~= 'xetex')
309 -- locates texmfscript to execute
310 local function find_texmfscript(progname, ext_list)
311 ext_list = ext_list or '\0'
312 for ext in string.gmatch(ext_list, '[^;]+') do
313 local progfullname = kpse.find_file(progname..ext, 'texmfscripts')
314 if progfullname then return progfullname, ext end
316 return nil, "no appropriate script or program found: "..progname
319 -- converts the #! line to arg table
320 -- used for scripts w/o extension
321 -- only the two most common cases are considered:
322 -- #! /path/to/command [options]
323 -- #! /usr/bin/env command [options]
324 -- ([options] after the command are retained as well)
325 local function shebang_to_argv(progfullname)
326 local fid, errmsg = io.open(progfullname, 'r')
327 if not fid then return nil, errmsg end
328 local fstln = fid:read('*line')
330 if string.find(fstln, "eval.*exit.*exec.*perl") then
331 -- special case of Perl's time-honoured "totally devious construct":
332 -- eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' && eval 'exec perl -S $0 $argv:q'
334 elseif (string.sub(fstln, 1, 2) ~= '#!') then
335 return nil, "don't know how to execute script: "..progfullname
337 local argv = string.explode( string.sub(fstln, 3) ) -- split on spaces
338 argv[1] = string.match(argv[1], '[^/]+$')
339 if (argv[1] == 'env') then table.remove(argv, 1) end
343 -- checks if command exists on the path and returns its full path
344 local function check_command(cmdlist, PATH)
345 for cmd in string.gmatch(cmdlist, '%S+') do
346 local cmdext = cmd..(string.find(cmd, '%.[^\\/.]*$') and '' or '.exe')
347 local fullcmd = search_path(cmdext, PATH)
352 return nil, 'program not found (not part of TeX Live): '..cmdlist
355 -- creates directory or directory hierarchy
356 local function mkdir_plus(dir)
357 if lfs.mkdir(dir) then
360 -- try with system's mkdir in case we need to create intermediate dirs too
361 local ret = os.spawn({[0]=search_path("cmd.exe"),
362 string.format('cmd.exe /x /c mkdir "%s"', dir)})
366 return nil, string.format("cannot create directory (error code %d): %s", ret, dir)
370 -- MAIN_CHUNK -- encapsulated in a function for more robust execution with pcall
372 local function MAIN_CHUNK()
374 -- preprocess arguments
376 local guimode = false
378 -- check for the sentinel argment coming from the .exe stub
379 if arg[#arg-2] and ( string.sub(arg[#arg-2], -1) == '\n' ) then
380 -- argv[0] and unparsed argument line are passed
381 -- from the .exe stub as the two last arguments
382 -- pop them up from the arg table
383 argline = table.remove(arg) -- pop unparsed arguments
384 arg[0] = table.remove(arg) -- pop C stub's argv[0]
385 guimode = (table.remove(arg) == 'GUI_MODE\n') -- pop sentinel argument
387 -- we must be called as: texlua runscript.tlu progname ...
388 -- this is treated the same as: runscript[.exe] progname ...
389 -- we don't have the unparsed arument line in this case, so construct one
390 for k = #arg, 1, -1 do argline = _q(arg[k]) .. ' ' .. argline end
395 -- lower arg[0] : get file name part : remove extension
396 local progname, substcount = string.lower(arg[0]):gsub('^.*[\\/]', ''):gsub('%.[^.]*$', '')
397 -- special behaviour when called under 'runscript' name
398 if (progname == 'runscript') then
399 -- we are called as: runscript progname ...
400 -- or as: runscript --help|-h|--version ...
401 -- handle options first (only --help and --version)
402 local opt, param = {}, nil
404 -- remove the first argument from the arg table and from the argline string
405 -- (this argument should have no embedded spaces!)
406 param = table.remove(arg, 1)
407 if not param then break end
408 argline = string.gsub(argline, '^%S+%s*', '')
409 local optname = string.lower(param):match('^%-%-?(.*)$')
412 elseif (optname == 'h') or (optname == 'help') then
414 elseif (optname == 'v') then
416 elseif (optname == 'version') then
419 error("unknown option: "..param.."\n"..bannerstr)
424 if opt.v then print(docstr) end
426 elseif opt.version or opt.v then
430 -- make sure progname is valid
431 arg[0] = assert(param, "not enough arguments!\n"..bannerstr)
432 progname = string.lower(arg[0]):gsub('^.*[\\/]', ''):gsub('%.[^.]*$', '')
433 assert(progname == string.lower(arg[0]), "bad command name: " .. arg[0])
435 -- special case of sys programs
436 progname, substcount = string.gsub(progname, '%-sys$', '')
437 local sysprog = (substcount > 0) -- true if there was a -sys suffix removed
438 -- prevent recursive calls to this script
439 assert(progname ~= 'runscript', "oops! wrapping the wrapper?")
441 -- kpse and environment set-up
445 while arg[k-1] do k = k - 1 end -- in case of a call: luatex --luaonly ...
446 local lua_binary = arg[k]
447 kpse.set_program_name(lua_binary, progname)
449 local TEXDIR = kpse.var_value('SELFAUTOPARENT')
450 local TEXMFDIST = kpse.var_value('TEXMFDIST')
451 local BINDIR = kpse.var_value('SELFAUTOLOC')
452 local PATH = os.getenv('PATH') or ''
453 -- restricted programs
454 local shell_escape_commands = string.lower(kpse.var_value('shell_escape_commands') or '')
455 local is_restricted_progname = string.find( ','..shell_escape_commands..',',
456 ','..progname..',', 1, true)
457 if is_restricted_progname then
458 -- limit search path to the restricted (system) trees
459 -- (not really necessary for entries in the alias_table,
460 -- because they are not searched for with kpathsea)
461 os.setenv('TEXMFSCRIPTS', kpse.var_value('TEXMF_RESTRICTED_SCRIPTS'))
464 local scripts4tlperl = {
468 ['updmap-sys'] = true,
469 ['fmtutil-sys'] = true,
471 local try_extern_perl = (kpse.var_value('TEXLIVE_WINDOWS_TRY_EXTERNAL_PERL') == '1') and
472 not (guimode or is_restricted_progname or scripts4tlperl[progname])
473 local PERLEXE = try_extern_perl and search_path('perl.exe', PATH)
475 PERLEXE = TEXDIR..'/tlpkg/tlperl/bin/perl.exe'
476 os.setenv('PERL5LIB', TEXDIR..'/tlpkg/tlperl/lib')
477 PATH = prepend_path(PATH, TEXDIR..'/tlpkg/tlperl/bin')
478 local PERLENV = 'PERL5OPT;PERLIO;PERLIO_DEBUG;PERLLIB;PERL5DB;PERL5DB_THREADED;' ..
479 'PERL5SHELL;PERL_ALLOW_NON_IFS_LSP;PERL_DEBUG_MSTATS;' ..
480 'PERL_DESTRUCT_LEVEL;PERL_DL_NONLAZY;PERL_ENCODING;PERL_HASH_SEED;' ..
481 'PERL_HASH_SEED_DEBUG;PERL_ROOT;PERL_SIGNALS;PERL_UNICODE'
482 for var in string.gmatch(PERLENV, '[^;]+') do os.setenv(var, nil) end
486 if not is_restricted_progname then
487 override_gs = kpse.var_value('TEXLIVE_WINDOWS_EXTERNAL_GS')
489 -- the full path to the executable
491 -- the directory where the gs executable resides
493 -- the name of the gs executable
496 -- first check whether we got an absolute path or only executable name
497 if string.find(override_gs, '[/\\]') then
500 -- search in the path
501 GSEXE = search_path(override_gs, PATH)
505 -- split the dir and progname part so that we can set the path
506 -- work on a string with all forward slashes
507 local foo = string.lower(string.gsub(GSEXE, '\\', '/'))
508 GSNAME = string.gsub(foo, '^.*[\\/]', '')
509 GSDIR = string.gsub(foo, '^(.*)[\\/].*$', '%1')
510 -- search also for a GS_DLL setting
511 -- we do not need to check for is_restricted_progname, since
512 -- GSEXE is only defined when it is not set
513 local GSDLL = kpse.var_value('TEXLIVE_WINDOWS_EXTERNAL_GS_DLL')
515 os.setenv('GS_DLL', GSDLL)
517 local GSLIB = kpse.var_value('TEXLIVE_WINDOWS_EXTERNAL_GS_LIB')
519 os.setenv('GS_LIB', GSLIB)
523 os.setenv('GS_LIB', TEXDIR..'/tlpkg/tlgs/lib;'..TEXDIR..'/tlpkg/tlgs/fonts;'
524 ..os.getenv('WINDIR')..'/Fonts;'..TEXMFDIST..'/fonts')
525 os.setenv('GS_DLL', TEXDIR..'/tlpkg/tlgs/bin/gsdll32.dll')
526 GSEXE = TEXDIR..'/tlpkg/tlgs/bin/gswin32c.exe'
527 GSNAME = 'gswin32c.exe'
528 GSDIR = TEXDIR..'/tlpkg/tlgs/bin'
530 -- now setup the path so that the gs program will be found
531 PATH = prepend_path(PATH, GSDIR, BINDIR)
532 os.setenv('PATH', PATH);
535 if (sysprog and not (progname == 'updmap') and not (progname == 'fmtutil')) then
536 os.setenv('TEXMFVAR', kpse.var_value('TEXMFSYSVAR'))
537 os.setenv('TEXMFCONFIG', kpse.var_value('TEXMFSYSCONFIG'))
539 -- Adobe Reader crash case: make sure USERPROFILE is not "slashed"
540 os.setenv("USERPROFILE", os.getenv("USERPROFILE"):gsub('/', '\\'))
542 -- extension to interpeter mapping
544 -- the extension is mapped to argv table
545 -- the command to execute is given as the first element of the table
546 -- (it can be a whitespace separated list of names to try)
547 local extension_map = {
548 ['.bat'] = {'cmd', '/c', 'call'},
549 ['.jar'] = {'java.exe', '-jar'},
550 ['.pl' ] = {'perl.exe'},
551 ['.py' ] = {'python.exe'},
552 ['.rb' ] = {'ruby.exe'},
553 ['.tcl'] = {'tclsh.exe tclsh85.exe tclsh84.exe'},
554 ['.vbs'] = {'cscript.exe', '-nologo'},
557 -- for GUI mode wrappers we try GUI mode interpeters where possible
558 extension_map['.jar'][1] = 'javaw.exe ' .. extension_map['.jar'][1]
559 extension_map['.pl' ][1] = 'wperl.exe ' .. extension_map['.pl' ][1]
560 extension_map['.py' ][1] = 'pythonw.exe ' .. extension_map['.py' ][1]
561 extension_map['.rb' ][1] = 'rubyw.exe ' .. extension_map['.rb' ][1]
562 extension_map['.tcl'][1] = 'wish.exe wish85.exe wish84.exe ' .. extension_map['.tcl'][1]
563 extension_map['.vbs'][1] = 'wscript.exe ' .. extension_map['.vbs'][1]
565 extension_map['.cmd'] = extension_map['.bat']
566 extension_map['.js'] = extension_map['.vbs']
572 -- special cases (aliases)
574 if is_tex4ht_command(progname) then
575 argline = progname .. ' ' .. argline
577 elseif progname == 'a2ping' then
578 table.insert(extension_map['.pl'], '-x')
579 elseif progname == 'updmap' then
581 argline = ' --sys ' .. argline
583 elseif progname == 'fmtutil' then
585 argline = ' --sys ' .. argline
587 elseif progname == 'asy' then
588 os.setenv('ASYMPTOTE_GS', GSEXE)
589 os.setenv('CYGWIN', 'nodosfilewarning')
590 ARGV = {[0]=TEXDIR..'/tlpkg/asymptote/asy.exe', 'asy'}
591 elseif progname == 'dviout' then
592 local fontsdir = kpse.var_value('TEXMFVAR') .. '/fonts'
593 if (lfs.attributes(fontsdir, 'mode') ~= 'directory') then
594 assert(mkdir_plus(fontsdir))
596 local tfmpath = kpse.show_path('tfm')
597 tfmpath = string.gsub(tfmpath, '!!', '')
598 tfmpath = string.gsub(tfmpath, '/', '\\')
600 for d in string.gmatch(tfmpath, '([^;]+\\fonts)\\tfm[^;]*') do
601 if (lfs.attributes(d, 'mode') == 'directory') then
602 table.insert(texrt, d)
605 local par = [["-gen=']] .. string.gsub(TEXDIR, '/', '\\') ..
606 [[\tlpkg\dviout\gen_pk'" "-TEXROOT=']] ..
607 table.concat(texrt, ';') .. [['" "-gsx=']] .. GSEXE .. [['"]];
608 ARGV = {[0]=TEXDIR..'/tlpkg/dviout/dviout.exe', 'dviout', par}
609 elseif progname == 'mkluatexfontdb' then
610 progname = 'luaotfload-tool'
611 table.insert(arg, '--alias=mkluatexfontdb')
612 elseif progname == 'psv' then
613 argline = '-sINPUT='..argline
614 ARGV = {[0]=TEXDIR..'/tlpkg/tlpsv/gswxlua.exe', 'gswxlua',
615 '-dDisableFAPI=true',
616 '-l', (_q(TEXDIR..'/tlpkg/tlpsv/psv.wx.lua'):gsub('/','\\')),
617 '-p', (_q(TEXDIR..'/tlpkg/tlpsv/psv_view.ps'):gsub('/','\\')),
619 elseif progname == 'repstopdf' or progname == 'rpdfcrop' then
620 argline = '--restricted ' .. argline
621 progname = string.sub(progname, 2, -1)
622 elseif progname == 'texworks' then
623 local winver = tonumber(string.match(os.uname().version, '%D*(%d+%.?%d*)'))
624 assert(winver >= 5.01, "Windows XP or newer required to run TeXworks")
625 local TW_LIBPATH = kpse.var_value('TW_LIBPATH') or
626 kpse.var_value('TEXMFCONFIG')..'/texworks'
627 local TW_INIPATH = kpse.var_value('TW_INIPATH') or TW_LIBPATH
628 os.setenv('TW_LIBPATH', TW_LIBPATH)
629 os.setenv('TW_INIPATH', TW_INIPATH)
630 if (TW_INIPATH and lfs.attributes(TW_INIPATH, 'mode') ~= 'directory') then
631 -- TeXworks needs directory holding its configuration to exist
632 assert(mkdir_plus(TW_INIPATH))
634 ARGV = {[0]=TEXDIR..'/tlpkg/texworks/texworks.exe', 'texworks'}
635 elseif progname == 'tlgs' then
636 ARGV = {[0]=GSEXE, GSNAME}
637 elseif progname == 'tlperl' then
638 ARGV = {[0]=PERLEXE, 'perl'}
644 local extlist = '.tlu;.texlua;.lua;.pl;.rb;.py;.tcl;.jar;.vbs;.js;.bat;.cmd;\0'
645 local progfullname = search_path(progname, BINDIR, '.tlu;.bat;.cmd') or
646 assert(find_texmfscript(progname, extlist))
647 local ext = string.match(string.lower(progfullname), '%.[^\\/.]*$') or ''
648 if (ext == '.lua') or (ext == '.tlu') or (ext == '.texlua') then -- lua script
649 arg[0] = progfullname
651 ARGV = extension_map[ext] or assert(shebang_to_argv(progfullname))
652 -- [w|c]script, for one, mistakes a forward-slashed UNC script path
653 -- for an option even when quoted
654 table.insert(ARGV, _q(progfullname:gsub('/','\\')))
656 ARGV[0], ARGV[1] = assert(check_command(ARGV[1], PATH))
661 -- run the program/script
664 table.insert(ARGV, argline) -- pass through original arguments
665 local ret = assert(os.spawn(ARGV))
667 local dbginfo = debug.getinfo(1)
668 local errormsg = string.format("%s:%d: command failed with exit code %d:\n%s",
669 dbginfo.short_src, dbginfo.currentline - 2,
670 ret, table.concat(ARGV, ' ') )
671 os.setenv('RUNSCRIPT_ERROR_MESSAGE', errormsg)
672 io.stderr:write(errormsg, '\n')
675 else -- must be a lua script
681 -- execute MAIN_CHUNK with pcall to catch any runtime errors
683 local success, errormsg = pcall(MAIN_CHUNK)
685 os.setenv('RUNSCRIPT_ERROR_MESSAGE', errormsg)
689 -- about RUNSCRIPT_ERROR_MESSAGE environment variable:
690 -- it stores an error message that is catched and displayed
691 -- in a message box on the C side at process exit
692 -- (currently used only by gui mode stubs)