1if not modules then modules = { } end modules ['mtx-install'] = {
2 version = 1.002,
3 comment = "companion to mtxrun.lua",
4 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5 copyright = "PRAGMA ADE / ConTeXt Development Team",
6 license = "see context related readme files"
7}
8
9
10
11local helpinfo = [[
12<?xml version="1.0"?>
13<application>
14 <metadata>
15 <entry name="name">mtx-install</entry>
16 <entry name="detail">ConTeXt Installer</entry>
17 <entry name="version">2.01</entry>
18 </metadata>
19 <flags>
20 <category name="basic">
21 <subcategory>
22 <flag name="platform" value="string"><short>platform</short></flag>
23 <flag name="server" value="string"><short>repository url (rsync://contextgarden.net)</short></flag>
24 <flag name="modules" value="string"><short>extra modules (can be list or 'all')</short></flag>
25 <flag name="fonts" value="string"><short>additional fonts (can be list or 'all')</short></flag>
26 <flag name="goodies" value="string"><short>extra binaries (like scite and texworks)</short></flag>
27 <flag name="install"><short>install context</short></flag>
28 <flag name="update"><short>update context</short></flag>
29 <flag name="erase"><short>wipe the cache</short></flag>
30 <flag name="identify"><short>create list of files</short></flag>
31 <flag name="secure"><short>use curl for https</short></flag>
32 </subcategory>
33 </category>
34 </flags>
35</application>
36]]
37
38local type, tonumber = type, tonumber
39local gsub, find, escapedpattern = string.gsub, string.find, string.escapedpattern
40local round = math.round
41local savetable, loadtable, sortedhash = table.save, table.load, table.sortedhash
42local copyfile, joinfile, filesize, dirname, addsuffix, basename = file.copy, file.join, file.size, file.dirname, file.addsuffix, file.basename
43local isdir, isfile, walkdir, pushdir, popdir, currentdir = lfs.isdir, lfs.isfile, lfs.dir, dir.push, dir.pop, dir.current
44local mkdirs, globdir = dir.mkdirs, dir.glob
45local osremove, osexecute, ostype, resultof = os.remove, os.execute, os.type, os.resultof
46local savedata, loaddata = io.savedata, io.loaddata
47local formatters = string.formatters
48local httprequest = socket.http.request
49
50local usecurl = false
51local protocol = "http"
52
53local function checkcurl()
54 local s = resultof("curl --version")
55 return type(s) == "string" and find(s,"libcurl") and find(s,"rotocols")
56end
57
58local function fetch(url)
59 local data = nil
60 local detail = nil
61 if usecurl and find(url,"^https") then
62 data = resultof("curl " .. url)
63 else
64 data, detail = httprequest(url)
65 end
66 if type(data) ~= "string" then
67 data = false
68 detail = "download failed"
69 elseif #data == 0 then
70 data = false
71 detail = "download failed, zero length"
72 elseif #data < 2048 then
73 local n, t = find(data,"<head>%s*<title>%s*(%d+)%s(.-)</title>")
74 if tonumber(n) then
75 data = false
76 detail = n .. " " .. t
77 end
78 end
79 return data, detail
80end
81
82local application = logs.application {
83 name = "mtx-install",
84 banner = "ConTeXt Installer 2.01",
85 helpinfo = helpinfo,
86}
87
88local report = application.report
89
90scripts = scripts or { }
91scripts.install = scripts.install or { }
92local install = scripts.install
93
94local texformats = {
95 "cont-en",
96 "cont-nl",
97 "cont-cz",
98 "cont-de",
99 "cont-fa",
100 "cont-it",
101 "cont-ro",
102 "cont-uk",
103 "cont-pe",
104}
105
106local platforms = {
107
108
109
110
111
112 ["windows"] = "win64",
113 ["windows-64"] = "win64",
114 ["win64"] = "win64",
115
116
117
118
119
120 ["linux"] = "linux-64",
121 ["linux-64"] = "linux-64",
122 ["linux64"] = "linux-64",
123
124 ["linuxmusl"] = "linuxmusl",
125 ["linuxmusl-64"] = "linuxmusl-64",
126
127
128
129 ["openbsd"] = "openbsd-amd64",
130
131 ["openbsd-amd64"] = "openbsd-amd64",
132
133 ["freebsd"] = "freebsd-amd64",
134
135 ["freebsd-amd64"] = "freebsd-amd64",
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154 ["macosx"] = "osx-64",
155 ["osx"] = "osx-64",
156 ["osx-intel"] = "osx-64",
157 ["osx-64"] = "osx-64",
158 ["osx-arm"] = "osx-arm64",
159 ["osx-arm64"] = "osx-arm64",
160
161
162
163
164
165
166 ["unknown"] = "unknown",
167}
168
169function install.identify()
170
171
172
173
174 local hashdata = sha2 and sha2.HASH256 or md5.hex
175
176 local function collect(root,tree)
177
178 local path = root .. "/" .. tree
179
180 if isdir(path) then
181
182 local prefix = path .. "/"
183 local files = globdir(prefix .. "**")
184 local pattern = escapedpattern("^" .. prefix)
185
186 local details = { }
187 local total = 0
188
189 for i=1,#files do
190 local name = files[i]
191 local size = filesize(name)
192 local base = gsub(name,pattern,"")
193 local data = loaddata(name)
194 if data and #data > 0 then
195 local stamp = hashdata(data)
196 details[i] = { base, size, stamp }
197 total = total + size
198 else
199 report("%-24s : bad file %a",tree,name)
200 end
201 end
202 report("%-24s : %4i files, %3.0f MB",tree,#files,total/(1000*1000))
203
204 savetable(path .. ".tma",details)
205
206 end
207
208 end
209
210 local sourceroot = file.join(dir.current(),"tex")
211
212 for d in walkdir("./tex") do
213 if find(d,"%texmf") then
214 collect(sourceroot,d)
215 end
216 end
217
218 savetable("./tex/status.tma",{
219 name = "context",
220 version = "lmtx",
221 date = os.date("%Y-%m-%d"),
222 })
223
224 os.exit()
225
226end
227
228local function disclaimer()
229 report("ConTeXt LMTX with LuaMetaTeX is still experimental and when you get a crash this")
230 report("can be due to a mismatch between Lua bytecode and the engine. In that case you can")
231 report("try the following:")
232 report("")
233 report(" - wipe the texmf-cache directory")
234 report(" - run: mtxrun --generate")
235 report(" - run: context --make")
236 report("")
237 report("When that doesn't solve the problem, ask on the mailing list (ntg-context@ntg.nl).")
238end
239
240function install.update()
241
242 local hashdata = sha2 and sha2.HASH256 or md5.hex
243
244 local function validdir(d)
245 local ok = isdir(d)
246 if not ok then
247 mkdirs(d)
248 ok = isdir(d)
249 end
250 return ok
251 end
252
253 local function download(what,url,target,total,done,hash)
254 local data = fetch(url .. "/" .. target)
255 if type(data) == "string" and #data > 0 then
256 if total and done then
257 report("%-8s : %3i %% : %8i : %s",what,round(100*done/total),#data,target)
258 else
259 report("%-8s : %8i : %s",what,#data,target)
260 end
261 if hash and hash ~= hashdata(data) then
262 return "different hash value"
263 elseif not validdir(dirname(target)) then
264 return "wrong target directory"
265 else
266 savedata(target,data)
267 end
268 else
269 return "unable to download"
270 end
271 end
272
273 local function remove(what,target)
274 report("%-8s : %8i : %s",what,filesize(target),target)
275 osremove(target)
276 end
277
278 local function ispresent(target)
279 return isfile(target)
280 end
281
282 local function hashed(list)
283 local hash = { }
284 for i=1,#list do
285 local l = list[i]
286 hash[l[1]] = l
287 end
288 return hash
289 end
290
291 local function run(fmt,...)
292 local command = formatters[fmt](...)
293
294 report("running: %s",command)
295 osexecute(command)
296 end
297
298 local function prepare(tree)
299 tree = joinfile("tex",tree)
300 mkdirs(tree)
301 end
302
303 local function update(url,what,zipfile,skiplist)
304
305 local tree = joinfile("tex",what)
306
307 local ok = validdir(tree)
308 if not validdir(tree) then
309 report("invalid directory %a",tree)
310 return
311 end
312
313 local lua = tree .. ".tma"
314 local all = url .. "/" .. lua
315 local old = loadtable(lua)
316 local new = fetch(all)
317
318 if new then
319 new = loadstring(new)
320 if new then
321 new = new()
322 end
323 end
324
325 if not new then
326 report("invalid database %a",all)
327 return
328 end
329
330 local total = 0
331 local done = 0
332 local count = 0
333
334 if not old then
335
336 if zipfile then
337 zipfile = addsuffix(what,"zip")
338 end
339 if zipfile then
340 local zipurl = url .. "/" .. zipfile
341 report("fetching %a",zipurl)
342 local zipdata = fetch(zipurl)
343 if zipdata then
344 savedata(zipfile,zipdata)
345 else
346 osremove(zipfile)
347 zipfile = false
348 end
349 end
350
351 if type(zipfile) == "string" and isfile(zipfile) then
352
353
354
355 report("unzipping %a",zipfile)
356
357 local specification = {
358 zipname = zipfile,
359 path = ".",
360
361 verbose = "steps",
362 collect = true,
363 }
364
365 if utilities.zipfiles.unzipdir(specification) then
366 osremove(zipfile)
367 goto done
368 else
369 osremove(zipfile)
370 end
371 end
372
373 count = #new
374
375 report("installing %s, %i files",tree,count)
376
377 for i=1,count do
378 total = total + new[i][2]
379 end
380
381 for i=1,count do
382 local entry = new[i]
383 local name = entry[1]
384 local size = entry[2]
385 local target = joinfile(tree,name)
386 done = done + size
387 if not skiplist or not skiplist[basename(name)] then
388 local message = download("new",url,target,total,done)
389 if message then
390 report("%s",message)
391 end
392 else
393 report("skipping %s",target)
394 end
395 end
396
397 ::done::
398
399 else
400
401 report("updating %s, %i files",tree,#new)
402
403 local hold = hashed(old)
404 local hnew = hashed(new)
405 local todo = { }
406
407 for newname, newhash in sortedhash(hnew) do
408 local target = joinfile(tree,newname)
409 if not skiplist or not skiplist[basename(newname)] then
410 local oldhash = hold[newname]
411 local action = nil
412 if not oldhash then
413 action = "added"
414 elseif oldhash[3] ~= newhash[3] then
415 action = "changed"
416 elseif not ispresent(joinfile(tree,newname)) then
417 action = "missing"
418 end
419 if action then
420 local size = newhash[2]
421 total = total + size
422 todo[#todo+1] = {
423 action = action,
424 target = target,
425 size = size,
426 hash = newhash[3],
427 }
428 end
429 else
430 report("skipping %s",target)
431 end
432 end
433
434 count = #todo
435
436 for i=1,count do
437 local entry = todo[i]
438 for i=1,5 do
439 local target = entry.target
440 local message = download(entry.action,url,target,total,done,entry.hash)
441 if message then
442 if i == 5 then
443 report("%s, try again later: %s",target)
444 os.exit()
445 else
446 report("%s, trying again: %s",target)
447 os.sleep(2)
448 end
449 else
450 break
451 end
452 end
453 done = done + entry.size
454 end
455
456 for oldname, oldhash in sortedhash(hold) do
457 local newhash = hnew[oldname]
458 local target = joinfile(tree,oldname)
459 if not newhash and ispresent(target) then
460 remove("removed",target)
461 end
462 end
463
464 end
465
466 savetable(lua,new)
467
468 return { tree, count, done }
469
470 end
471
472 local targetroot = dir.current()
473
474 local server = environment.arguments.server or ""
475 local instance = environment.arguments.instance or ""
476 local osplatform = environment.arguments.platform or nil
477 local platform = platforms[osplatform or os.platform or ""]
478
479 if (platform == "unknown" or platform == "" or not platform) and osplatform then
480
481 platform = osplatform
482 elseif not osplatform then
483 osplatform = platform
484 end
485
486 if server == "" then
487 server = "lmtx.contextgarden.net,lmtx.pragma-ade.com,lmtx.pragma-ade.nl,dmz.pragma-ade.nl"
488 end
489 if instance == "" then
490 instance = "install-lmtx"
491 end
492 if not platform then
493 report("unknown platform")
494 return
495 end
496
497 local list = utilities.parsers.settings_to_array(server)
498 local server = false
499
500 for i=1,#list do
501 local host = list[i]
502 local data, status, detail = fetch(protocol .. "://" .. host .. "/" .. instance .. "/tex/status.tma")
503 if status == 200 and type(data) == "string" then
504 local t = loadstring(data)
505 if type(t) == "function" then
506 t = t()
507 end
508 if type(t) == "table" and t.name == "context" and t.version == "lmtx" then
509 server = host
510 break
511 end
512 end
513 end
514
515 if not server then
516 report("provide valid server and instance")
517 return
518 end
519
520 local url = protocol .. "://" .. server .. "/" .. instance .. "/"
521
522 local texmfplatform = "texmf-" .. platform
523
524 report("server : %s",server)
525 report("instance : %s",instance)
526 report("platform : %s",osplatform)
527 report("system : %s",ostype)
528
529 local status = { }
530 local skiplist = {
531 ["mtxrun"] = true,
532 ["context"] = true,
533 ["mtxrun.exe"] = true,
534 ["context.exe"] = true,
535 }
536
537 status[#status+1] = update(url,"texmf",true)
538 status[#status+1] = update(url,"texmf-context",true)
539 status[#status+1] = update(url,texmfplatform,false,skiplist)
540
541 prepare("texmf-cache")
542 prepare("texmf-project")
543 prepare("texmf-fonts")
544 prepare("texmf-local")
545 prepare("texmf-modules")
546
547 local binpath = joinfile(targetroot,"tex",texmfplatform,"bin")
548
549 local luametatex = "luametatex"
550 local luatex = "luatex"
551 local mtxrun = "mtxrun"
552 local context = "context"
553
554 if ostype == "windows" then
555 luametatex = addsuffix(luametatex,"exe")
556 luatex = addsuffix(luatex,"exe")
557 mtxrun = addsuffix(mtxrun,"exe")
558 context = addsuffix(context,"exe")
559 end
560
561 local luametatexbin = joinfile(binpath,luametatex)
562 local luatexbin = joinfile(binpath,luatex)
563 local mtxrunbin = joinfile(binpath,mtxrun)
564 local contextbin = joinfile(binpath,context)
565
566 local cdir = currentdir()
567 local pdir = pushdir(binpath)
568
569 report("current : %S",cdir or "<unable to check the curent path>")
570 report("target : %S",pdir or "<unable to change to the binary path>")
571
572 if pdir ~= cdir then
573
574 report("removing : %s",mtxrun)
575 report("removing : %s",context)
576
577 osremove(mtxrun)
578 osremove(context)
579
580 if isfile(luametatex) then
581 lfs.symlink(luametatex,mtxrun)
582 lfs.symlink(luametatex,context)
583 end
584
585 if isfile(mtxrun) then
586 report("linked : %s",mtxrun)
587 else
588 copyfile(luametatex,mtxrun)
589 report("copied : %s",mtxrun)
590 end
591 if isfile(context) then
592 report("linked : %s",context)
593 else
594 copyfile(luametatex,context)
595 report("copied : %s",context)
596 end
597
598 end
599
600 popdir()
601
602 if lfs.setexecutable(luametatexbin) then
603 report("xbit set : %s",luametatexbin)
604 else
605
606 end
607 if lfs.setexecutable(luatexbin) then
608 report("xbit set : %s",luatexbin)
609 else
610
611 end
612 if lfs.setexecutable(mtxrunbin) then
613 report("xbit set : %s",mtxrunbin)
614 else
615
616 end
617 if lfs.setexecutable(contextbin) then
618 report("xbit set : %s",contextbin)
619 else
620
621 end
622
623 run("%s --generate",mtxrunbin)
624 if environment.argument("erase") then
625 run("%s --script cache --erase",mtxrunbin)
626 run("%s --generate",mtxrunbin)
627 end
628 run("%s --make en", contextbin)
629
630
631
632 run("%s --luatex --generate",contextbin)
633 run("%s --luatex --make en", contextbin)
634
635
636
637 report("")
638 for i=1,#status do
639 report("%-20s : %4i files with %9i bytes installed",unpack(status[i]))
640 end
641 report("")
642 disclaimer()
643 report("")
644
645 report("update, done")
646
647end
648
649function install.modules()
650 report("--modules in not yet implemented")
651end
652
653function install.fonts()
654 report("--fonts in not yet implemented")
655end
656
657function install.goodies()
658 report("--goodies in not yet implemented")
659end
660
661if environment.argument("secure") then
662 usecurl = checkcurl()
663 if usecurl then
664 protocol = "https"
665 else
666 report("no curl installed, quitting")
667 os.exit()
668 end
669end
670
671io.output():setvbuf("no")
672
673if environment.argument("identify") then
674 install.identify()
675elseif environment.argument("install") then
676 install.update()
677elseif environment.argument("update") then
678 install.update()
679elseif environment.argument("modules") then
680 install.modules()
681elseif environment.argument("fonts") then
682 install.fonts()
683elseif environment.argument("goodies") then
684 install.goodies()
685elseif environment.argument("exporthelp") then
686 application.export(environment.argument("exporthelp"),environment.files[1])
687else
688 application.help()
689 report("")
690 disclaimer()
691end
692
693 |