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