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