summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/scripts/l3build
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2018-03-08 23:45:11 +0000
committerKarl Berry <karl@freefriends.org>2018-03-08 23:45:11 +0000
commit64ee185d962cd13195e329ab4ac8a9da6535b677 (patch)
tree652f96f04e1826a75ef1c3a38ab3d2a72ff9c067 /Master/texmf-dist/scripts/l3build
parenta502f321bf373ab83e239389e55d622e6c6980e2 (diff)
l3build now cmdline script (9mar18)
git-svn-id: svn://tug.org/texlive/trunk@46894 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/scripts/l3build')
-rw-r--r--Master/texmf-dist/scripts/l3build/l3build-arguments.lua270
-rw-r--r--Master/texmf-dist/scripts/l3build/l3build-aux.lua127
-rw-r--r--Master/texmf-dist/scripts/l3build/l3build-check.lua894
-rw-r--r--Master/texmf-dist/scripts/l3build/l3build-clean.lua56
-rw-r--r--Master/texmf-dist/scripts/l3build/l3build-ctan.lua245
-rw-r--r--Master/texmf-dist/scripts/l3build/l3build-file-functions.lua426
-rw-r--r--Master/texmf-dist/scripts/l3build/l3build-help.lua79
-rw-r--r--Master/texmf-dist/scripts/l3build/l3build-install.lua98
-rw-r--r--Master/texmf-dist/scripts/l3build/l3build-manifest-setup.lua364
-rw-r--r--Master/texmf-dist/scripts/l3build/l3build-manifest.lua280
-rw-r--r--Master/texmf-dist/scripts/l3build/l3build-stdmain.lua135
-rw-r--r--Master/texmf-dist/scripts/l3build/l3build-tagging.lua80
-rw-r--r--Master/texmf-dist/scripts/l3build/l3build-typesetting.lua249
-rw-r--r--Master/texmf-dist/scripts/l3build/l3build-unpack.lua99
-rw-r--r--Master/texmf-dist/scripts/l3build/l3build-variables.lua179
-rw-r--r--Master/texmf-dist/scripts/l3build/l3build.lua145
16 files changed, 3726 insertions, 0 deletions
diff --git a/Master/texmf-dist/scripts/l3build/l3build-arguments.lua b/Master/texmf-dist/scripts/l3build/l3build-arguments.lua
new file mode 100644
index 00000000000..499e2c4031e
--- /dev/null
+++ b/Master/texmf-dist/scripts/l3build/l3build-arguments.lua
@@ -0,0 +1,270 @@
+--[[
+
+File l3build-arguments.lua Copyright (C) 2018 The LaTeX3 Project
+
+It may be distributed and/or modified under the conditions of the
+LaTeX Project Public License (LPPL), either version 1.3c of this
+license or (at your option) any later version. The latest version
+of this license is in the file
+
+ http://www.latex-project.org/lppl.txt
+
+This file is part of the "l3build bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+-----------------------------------------------------------------------
+
+The development version of the bundle can be found at
+
+ https://github.com/latex3/l3build
+
+for those people who are interested.
+
+--]]
+
+local exit = os.exit
+local stderr = io.stderr
+
+local find = string.find
+local gmatch = string.gmatch
+local match = string.match
+local sub = string.sub
+
+local insert = table.insert
+
+-- Parse command line options
+
+option_list =
+ {
+ config =
+ {
+ desc = "Sets the config(s) used for running tests",
+ short = "c",
+ type = "table"
+ },
+ date =
+ {
+ desc = "Sets the date to insert into sources",
+ type = "string"
+ },
+ ["dry-run"] =
+ {
+ desc = "Dry run for install",
+ type = "boolean"
+ },
+ engine =
+ {
+ desc = "Sets the engine(s) to use for running test",
+ short = "e",
+ type = "table"
+ },
+ epoch =
+ {
+ desc = "Sets the epoch for tests and typesetting",
+ type = "string"
+ },
+ first =
+ {
+ desc = "Name of first test to run",
+ type = "string"
+ },
+ force =
+ {
+ desc = "Force tests to run if engine is not set up",
+ short = "f",
+ type = "boolean"
+ },
+ ["halt-on-error"] =
+ {
+ desc = "Stops running tests after the first failure",
+ short = "H",
+ type = "boolean"
+ },
+ help =
+ {
+ short = "h",
+ type = "boolean"
+ },
+ pdf =
+ {
+ desc = "Check/save PDF files",
+ short = "p",
+ type = "boolean"
+ },
+ quiet =
+ {
+ desc = "Suppresses TeX output when unpacking",
+ short = "q",
+ type = "boolean"
+ },
+ last =
+ {
+ desc = "Name of last test to run",
+ type = "string"
+ },
+ rerun =
+ {
+ desc = "Skip setup: simply rerun tests",
+ type = "boolean"
+ },
+ shuffle =
+ {
+ desc = "Shuffle order of tests",
+ type = "boolean"
+ },
+ texmfhome =
+ {
+ desc = "Location of user texmf tree",
+ type = "string"
+ }
+ }
+
+-- This is done as a function (rather than do ... end) as it allows early
+-- termination (break)
+local function argparse()
+ local result = { }
+ local names = { }
+ local long_options = { }
+ local short_options = { }
+ -- Turn long/short options into two lookup tables
+ for k,v in pairs(option_list) do
+ if v["short"] then
+ short_options[v["short"]] = k
+ end
+ long_options[k] = k
+ end
+ local args = args
+ -- arg[1] is a special case: must be a command or "-h"/"--help"
+ -- Deal with this by assuming help and storing only apparently-valid
+ -- input
+ local a = arg[1]
+ result["target"] = "help"
+ if a then
+ -- No options are allowed in position 1, so filter those out
+ if a == "--version" then
+ result["target"] = "version"
+ elseif not match(a, "^%-") then
+ result["target"] = a
+ end
+ end
+ -- Stop here if help or version is required
+ if result["target"] == "help" or result["target"] == "version" then
+ return result
+ end
+ -- An auxiliary to grab all file names into a table
+ local function remainder(num)
+ local names = { }
+ for i = num, #arg do
+ insert(names, arg[i])
+ end
+ return names
+ end
+ -- Examine all other arguments
+ -- Use a while loop rather than for as this makes it easier
+ -- to grab arg for optionals where appropriate
+ local i = 2
+ while i <= #arg do
+ local a = arg[i]
+ -- Terminate search for options
+ if a == "--" then
+ names = remainder(i + 1)
+ break
+ end
+ -- Look for optionals
+ local opt
+ local optarg
+ local opts
+ -- Look for and option and get it into a variable
+ if match(a, "^%-") then
+ if match(a, "^%-%-") then
+ opts = long_options
+ local pos = find(a, "=", 1, true)
+ if pos then
+ opt = sub(a, 3, pos - 1)
+ optarg = sub(a, pos + 1)
+ else
+ opt = sub(a, 3)
+ end
+ else
+ opts = short_options
+ opt = sub(a, 2, 2)
+ -- Only set optarg if it is there
+ if #a > 2 then
+ optarg = sub(a, 3)
+ end
+ end
+ -- Now check that the option is valid and sort out the argument
+ -- if required
+ local optname = opts[opt]
+ if optname then
+ -- Tidy up arguments
+ if option_list[optname]["type"] == "boolean" then
+ if optarg then
+ local opt = "-" .. (match(a, "^%-%-") and "-" or "") .. opt
+ stderr:write("Value not allowed for option " .. opt .."\n")
+ return {"help"}
+ end
+ else
+ if not optarg then
+ optarg = arg[i + 1]
+ if not optarg then
+ stderr:write("Missing value for option " .. a .."\n")
+ return {"help"}
+ end
+ i = i + 1
+ end
+ end
+ else
+ stderr:write("Unknown option " .. a .."\n")
+ return {"help"}
+ end
+ -- Store the result
+ if optarg then
+ if option_list[optname]["type"] == "string" then
+ result[optname] = optarg
+ else
+ local opts = result[optname] or { }
+ for hit in gmatch(optarg, "([^,%s]+)") do
+ insert(opts, hit)
+ end
+ result[optname] = opts
+ end
+ else
+ result[optname] = true
+ end
+ i = i + 1
+ end
+ if not opt then
+ names = remainder(i)
+ break
+ end
+ end
+ if next(names) then
+ result["names"] = names
+ end
+ return result
+end
+
+options = argparse()
+
+-- Sanity check
+function check_engines()
+ if options["engine"] and not options["force"] then
+ -- Make a lookup table
+ local t = { }
+ for _, engine in pairs(checkengines) do
+ t[engine] = true
+ end
+ for _, engine in pairs(options["engine"]) do
+ if not t[engine] then
+ print("\n! Error: Engine \"" .. engine .. "\" not set up for testing!")
+ print("\n Valid values are:")
+ for _, engine in ipairs(checkengines) do
+ print(" - " .. engine)
+ end
+ print("")
+ exit(1)
+ end
+ end
+ end
+end
diff --git a/Master/texmf-dist/scripts/l3build/l3build-aux.lua b/Master/texmf-dist/scripts/l3build/l3build-aux.lua
new file mode 100644
index 00000000000..773a17a40e1
--- /dev/null
+++ b/Master/texmf-dist/scripts/l3build/l3build-aux.lua
@@ -0,0 +1,127 @@
+--[[
+
+File l3build-aux.lua Copyright (C) 2018 The LaTeX3 Project
+
+It may be distributed and/or modified under the conditions of the
+LaTeX Project Public License (LPPL), either version 1.3c of this
+license or (at your option) any later version. The latest version
+of this license is in the file
+
+ http://www.latex-project.org/lppl.txt
+
+This file is part of the "l3build bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+-----------------------------------------------------------------------
+
+The development version of the bundle can be found at
+
+ https://github.com/latex3/l3build
+
+for those people who are interested.
+
+--]]
+
+local match = string.match
+
+local pairs = pairs
+local print = print
+
+--
+-- Auxiliary functions which are used by more than one main function
+--
+
+function normalise_epoch()
+ -- If given as an ISO date, turn into an epoch number
+ local y, m, d = match(epoch, "^(%d%d%d%d)-(%d%d)-(%d%d)$")
+ if y then
+ epoch =
+ os_time({year = y, month = m, day = d, hour = 0, sec = 0, isdst = nil}) -
+ os_time({year = 1970, month = 1, day = 1, hour = 0, sec = 0, isdst = nil})
+ elseif match(epoch, "^%d+$") then
+ epoch = tonumber(epoch)
+ else
+ epoch = 0
+ end
+end
+
+function setepoch()
+ return
+ os_setenv .. " SOURCE_DATE_EPOCH=" .. epoch
+ .. os_concat ..
+ os_setenv .. " SOURCE_DATE_EPOCH_TEX_PRIMITIVES=1"
+ .. os_concat ..
+ os_setenv .. " FORCE_SOURCE_DATE=1"
+ .. os_concat
+end
+
+local function getscriptname()
+ if match(arg[0], "l3build(%.lua)$") then
+ return "l3build.lua"
+ else
+ return "build.lua"
+ end
+end
+
+-- Do some subtarget for all modules in a bundle
+function call(dirs, target, opts)
+ -- Turn the option table into a string
+ local opts = opts or options
+ local s = ""
+ for k,v in pairs(opts) do
+ if k ~= "names" and k ~= "target" then -- Special cases
+ local t = option_list[k] or { }
+ local arg = ""
+ if t["type"] == "string" then
+ arg = arg .. "=" .. v
+ end
+ if t["type"] == "table" then
+ for _,a in pairs(v) do
+ if arg == "" then
+ arg = "=" .. a -- Add the initial "=" here
+ else
+ arg = arg .. "," .. a
+ end
+ end
+ end
+ s = s .. " --" .. k .. arg
+ end
+ end
+ if opts["names"] then
+ for _,v in pairs(opts["names"]) do
+ s = s .. " " .. v
+ end
+ end
+ local scriptname = getscriptname()
+ for _,i in ipairs(dirs) do
+ local text = " for module " .. i
+ if i == "." then
+ text = " with configuration " .. opts["config"][1]
+ end
+ print(
+ "Running script " .. scriptname .. " with target \"" .. target
+ .. "\"" .. text
+ )
+ local errorlevel = run(
+ i,
+ "texlua " .. scriptname .. " " .. target .. s
+ )
+ if errorlevel ~= 0 then
+ return errorlevel
+ end
+ end
+ return 0
+end
+
+-- Unpack files needed to support testing/typesetting/unpacking
+function depinstall(deps)
+ local errorlevel
+ for _,i in ipairs(deps) do
+ print("Installing dependency: " .. i)
+ errorlevel = run(i, "texlua " .. getscriptname() .. " unpack -q")
+ if errorlevel ~= 0 then
+ return errorlevel
+ end
+ end
+ return 0
+end
diff --git a/Master/texmf-dist/scripts/l3build/l3build-check.lua b/Master/texmf-dist/scripts/l3build/l3build-check.lua
new file mode 100644
index 00000000000..2239fe97a76
--- /dev/null
+++ b/Master/texmf-dist/scripts/l3build/l3build-check.lua
@@ -0,0 +1,894 @@
+--[[
+
+File l3build-check.lua Copyright (C) 2018 The LaTeX3 Project
+
+It may be distributed and/or modified under the conditions of the
+LaTeX Project Public License (LPPL), either version 1.3c of this
+license or (at your option) any later version. The latest version
+of this license is in the file
+
+ http://www.latex-project.org/lppl.txt
+
+This file is part of the "l3build bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+-----------------------------------------------------------------------
+
+The development version of the bundle can be found at
+
+ https://github.com/latex3/l3build
+
+for those people who are interested.
+
+--]]
+
+-- Local access to functions
+local open = io.open
+local close = io.close
+local write = io.write
+local output = io.output
+
+local rnd = math.random
+
+local luatex_version = status.luatex_version
+
+local len = string.len
+local char = string.char
+local format = string.format
+local gmatch = string.gmatch
+local gsub = string.gsub
+local match = string.match
+
+local insert = table.insert
+local sort = table.sort
+
+local utf8_char = unicode.utf8.char
+
+local exit = os.exit
+local execute = os.execute
+
+--
+-- Auxiliary functions which are used by more than one main function
+--
+
+-- Set up the check system files: needed for checking one or more tests and
+-- for saving the test files
+function checkinit()
+ cleandir(testdir)
+ depinstall(checkdeps)
+ -- Copy dependencies to the test directory itself: this makes the paths
+ -- a lot easier to manage, and is important for dealing with the log and
+ -- with file input/output tests
+ for _,i in ipairs(filelist(localdir)) do
+ cp(i, localdir, testdir)
+ end
+ bundleunpack({sourcefiledir, testfiledir})
+ for _,i in ipairs(installfiles) do
+ cp(i, unpackdir, testdir)
+ end
+ for _,i in ipairs(checkfiles) do
+ cp(i, unpackdir, testdir)
+ end
+ if direxists(testsuppdir) then
+ for _,i in ipairs(filelist(testsuppdir)) do
+ cp(i, testsuppdir, testdir)
+ end
+ end
+ for _,i in ipairs(checksuppfiles) do
+ cp(i, supportdir, testdir)
+ end
+ execute(os_ascii .. ">" .. testdir .. "/ascii.tcx")
+end
+
+-- Convert the raw log file into one for comparison/storage: keeps only
+-- the 'business' part from the tests and removes system-dependent stuff
+local function formatlog(logfile, newfile, engine, errlevels)
+ local maxprintline = maxprintline
+ if engine == "luatex" or engine == "luajittex" then
+ maxprintline = maxprintline + 1 -- Deal with an out-by-one error
+ end
+ local function killcheck(line)
+ -- Skip lines containing file dates
+ if match(line, "[^<]%d%d%d%d/%d%d/%d%d") then
+ return true
+ elseif
+ -- Skip \openin/\openout lines in web2c 7.x
+ -- As Lua doesn't allow "(in|out)", a slightly complex approach:
+ -- do a substitution to check the line is exactly what is required!
+ match(
+ gsub(line, "^\\openin", "\\openout"), "^\\openout%d%d? = "
+ ) then
+ return true
+ end
+ return false
+ end
+ -- Substitutions to remove some non-useful changes
+ local function normalize(line, lastline)
+ -- Zap line numbers from \show, \showbox, \box_show and the like:
+ -- do this before wrapping lines
+ line = gsub(line, "^l%.%d+ ", "l. ...")
+ -- Also from lua stack traces.
+ line = gsub(line, "lua:%d+: in function", "lua:...: in function")
+ -- Allow for wrapped lines: preserve the content and wrap
+ -- Skip lines that have an explicit marker for truncation
+ if len(line) == maxprintline and
+ not match(line, "%.%.%.$") then
+ return "", (lastline or "") .. line
+ end
+ local line = (lastline or "") .. line
+ lastline = ""
+ -- Zap ./ at begin of filename
+ line = gsub(line, "%(%.%/", "(")
+ -- Zap paths if places other than 'here' are accessible
+ if checksearch then
+ -- The pattern excludes < and > as the image part can have
+ -- several entries on one line
+ local pattern = "%w?:?/[^ %<%>]*/([^/%(%)]*%.%w*)"
+ -- Files loaded from TeX: all start ( -- )
+ line = gsub(line, "%(" .. pattern, "(../%1")
+ -- Images
+ line = gsub(line, "<" .. pattern .. ">", "<../%1>")
+ -- luaotfload files start with keywords
+ line = gsub(line, "from " .. pattern .. "%(", "from. ./%1(")
+ line = gsub(line, ": " .. pattern .. "%)", ": ../%1)")
+ -- Deal with XeTeX specials
+ if match(line, "^%.+\\XeTeX.?.?.?file") then
+ line = gsub(line, pattern, "../%1")
+ end
+ end
+ -- Deal with the fact that "(.aux)" may have still a leading space
+ line = gsub(line, "^ %(%.aux%)", "(.aux)")
+ -- Merge all of .fd data into one line so will be removed later
+ if match(line, "^ *%([%.%/%w]+%.fd[^%)]*$") then
+ lastline = (lastline or "") .. line
+ return "", (lastline or "") .. line
+ end
+ -- TeX90/XeTeX knows only the smaller set of dimension units
+ line = gsub(line,
+ "cm, mm, dd, cc, bp, or sp",
+ "cm, mm, dd, cc, nd, nc, bp, or sp")
+ -- On the other hand, (u)pTeX has some new units!
+ line = gsub(line,
+ "em, ex, zw, zh, in, pt, pc,",
+ "em, ex, in, pt, pc,")
+ line = gsub(line,
+ "cm, mm, dd, cc, bp, H, Q, or sp;",
+ "cm, mm, dd, cc, nd, nc, bp, or sp;")
+ -- Normalise a case where fixing a TeX bug changes the message text
+ line = gsub(line, "\\csname\\endcsname ", "\\csname\\endcsname")
+ -- Zap "on line <num>" and replace with "on line ..."
+ -- Two similar cases, Lua patterns mean we need to do them separately
+ line = gsub(line, "on line %d*", "on line ...")
+ line = gsub(line, "on input line %d*", "on input line ...")
+ -- Tidy up to ^^ notation
+ for i = 0, 31 do
+ line = gsub(line, char(i), "^^" .. char(64 + i))
+ end
+ -- Normalise register allocation to hard-coded numbers
+ -- No regex, so use a pattern plus lookup approach
+ local register_types = {
+ attribute = true,
+ box = true,
+ bytecode = true,
+ catcodetable = true,
+ count = true,
+ dimen = true,
+ insert = true,
+ language = true,
+ luabytecode = true,
+ luachunk = true,
+ luafunction = true,
+ marks = true,
+ muskip = true,
+ read = true,
+ skip = true,
+ toks = true,
+ whatsit = true,
+ write = true,
+ XeTeXcharclass = true
+ }
+ if register_types[match(line, "^\\[^%]]+=\\([a-z]+)%d+$")] then
+ line = gsub(line, "%d+$", "...")
+ end
+ -- Also deal with showing boxes
+ if match(line, "^> \\box%d+=$") or match(line, "^> \\box%d+=(void)$") then
+ line = gsub(line, "%d+=", "...=")
+ end
+ -- Remove 'normal' direction information on boxes with (u)pTeX
+ line = gsub(line, ",? yoko direction,?", "")
+ line = gsub(line, ",? yoko%(math%) direction,?", "")
+ -- Remove '\displace 0.0' lines in (u)pTeX
+ if match(line,"^%.*\\displace 0%.0$") then
+ return ""
+ end
+ -- Remove the \special line that in DVI mode keeps PDFs comparable
+ if match(line, "^%.*\\special%{pdf: docinfo << /Creator") then
+ return ""
+ end
+ -- Remove the \special line possibly present in DVI mode for paper size
+ if match(line, "^%.*\\special%{papersize") then
+ return ""
+ end
+ -- Remove ConTeXt stuff
+ if match(line, "^backend >") or
+ match(line, "^close source >") or
+ match(line, "^mkiv lua stats >") or
+ match(line, "^pages >") or
+ match(line, "^system >") or
+ match(line, "^used file >") or
+ match(line, "^used option >") or
+ match(line, "^used structure >") then
+ return ""
+ end
+ -- A tidy-up to keep LuaTeX and other engines in sync
+ line = gsub(line, utf8_char(127), "^^?")
+ -- Unicode engines display chars in the upper half of the 8-bit range:
+ -- tidy up to match pdfTeX if an ASCII engine is in use
+ if next(asciiengines) then
+ for i = 128, 255 do
+ line = gsub(line, utf8_char(i), "^^" .. format("%02x", i))
+ end
+ end
+ return line, lastline
+ end
+ local lastline = ""
+ local newlog = ""
+ local prestart = true
+ local skipping = false
+ -- Read the entire log file as a binary: deals with ^@/^[, etc.
+ local file = assert(open(logfile, "rb"))
+ local contents = gsub(file:read("*all") .. "\n", "\r\n", "\n")
+ close(file)
+ for line in gmatch(contents, "([^\n]*)\n") do
+ if line == "START-TEST-LOG" then
+ prestart = false
+ elseif line == "END-TEST-LOG" or
+ match(line, "^Here is how much of .?.?.?TeX\'s memory you used:") then
+ break
+ elseif line == "OMIT" then
+ skipping = true
+ elseif match(line, "^%)?TIMO$") then
+ skipping = false
+ elseif not prestart and not skipping then
+ line, lastline = normalize(line, lastline)
+ if not match(line, "^ *$") and not killcheck(line) then
+ newlog = newlog .. line .. os_newline
+ end
+ end
+ end
+ local newfile = open(newfile, "w")
+ output(newfile)
+ write(newlog)
+ if recordstatus then
+ write('***************\n')
+ for i = 1, checkruns do
+ write('Compilation ' .. i .. ' of test file completed with exit status ' .. errlevels[i] .. '\n')
+ end
+ end
+ close(newfile)
+end
+
+-- Additional normalization for LuaTeX
+local function formatlualog(logfile, newfile, luatex)
+ local function normalize(line, lastline, dropping)
+ -- Find \discretionary or \whatsit lines:
+ -- These may come back later
+ if match(line, "^%.+\\discretionary$") or
+ match(line, "^%.+\\discretionary %(penalty 50%)$") or
+ match(line, "^%.+\\discretionary50%|$") or
+ match(line, "^%.+\\discretionary50%| replacing $") or
+ match(line, "^%.+\\whatsit$") then
+ return "", line
+ end
+ -- For \mathon, we always need this line but the next
+ -- may be affected
+ if match(line, "^%.+\\mathon$") then
+ return line, line
+ end
+ -- LuaTeX has a flexible output box
+ line = gsub(line,"\\box\\outputbox", "\\box255")
+ -- LuaTeX identifies spaceskip glue
+ line = gsub(line,"%(\\spaceskip%) ", " ")
+ -- Remove 'display' at end of display math boxes:
+ -- LuaTeX omits this as it includes direction in all cases
+ line = gsub(line, "(\\hbox%(.*), display$", "%1")
+ -- Remove 'normal' direction information on boxes:
+ -- any bidi/vertical stuff will still show
+ line = gsub(line, ", direction TLT", "")
+ -- Find glue setting and round out the last place
+ local function round_digits(l, m)
+ return gsub(
+ l,
+ m .. " (%-?)%d+%.%d+",
+ m .. " %1"
+ .. format(
+ "%.3f",
+ match(line, m .. " %-?(%d+%.%d+)") or 0
+ )
+ )
+ end
+ if match(line, "glue set %-?%d+%.%d+") then
+ line = round_digits(line, "glue set")
+ end
+ if match(
+ line, "glue %-?%d+%.%d+ plus %-?%d+%.%d+ minus %-?%d+%.%d+$"
+ )
+ then
+ line = round_digits(line, "glue")
+ line = round_digits(line, "plus")
+ line = round_digits(line, "minus")
+ end
+ -- LuaTeX writes ^^M as a new line, which we lose
+ line = gsub(line, "%^%^M", "")
+ -- Remove U+ notation in the "Missing character" message
+ line = gsub(
+ line,
+ "Missing character: There is no (%^%^..) %(U%+(....)%)",
+ "Missing character: There is no %1"
+ )
+ -- The first time a new font is used, it shows up
+ -- as being cached
+ line = gsub(line, "(save cache:", "(load cache:")
+ -- LuaTeX from v1.07 logs kerns differently ...
+ -- This block only applies to the output of LuaTeX itself,
+ -- hence needing a flag to skip the case of the reference log
+ if luatex and
+ tonumber(luatex_version) >= 107 and
+ match(line, "^%.*\\kern") then
+ -- Re-insert the space in explicit kerns
+ if match(line, "kern%-?%d+%.%d+ *$") then
+ line = gsub(line, "kern", "kern ")
+ elseif match(line, "%(accent%)$") then
+ line = gsub(line, "kern", "kern ")
+ line = gsub(line, "%(accent%)$", "(for accent)")
+ elseif match(line, "%(italic%)$") then
+ line = gsub(line, "kern", "kern ")
+ line = gsub(line, " %(italic%)$", "")
+ else
+ line = gsub(line, " %(font%)$", "")
+ end
+ end
+ -- Changes in PDF specials
+ line = gsub(line, "\\pdfliteral origin", "\\pdfliteral")
+ -- A function to handle the box prefix part
+ local function boxprefix(s)
+ return gsub(match(s, "^(%.+)"), "%.", "%%.")
+ end
+ -- 'Recover' some discretionary data
+ if match(lastline, "^%.+\\discretionary %(penalty 50%)$") and
+ match(line, boxprefix(lastline) .. "%.= ") then
+ line = gsub(line," %(font%)$","")
+ return gsub(line, "%.= ", ""),""
+ end
+ -- Where the last line was a discretionary, looks for the
+ -- info one level in about what it represents
+ if match(lastline, "^%.+\\discretionary$") or
+ match(lastline, "^%.+\\discretionary %(penalty 50%)$") or
+ match(lastline, "^%.+\\discretionary50%|$") or
+ match(lastline, "^%.+\\discretionary50%| replacing $") then
+ local prefix = boxprefix(lastline)
+ if match(line, prefix .. "%.") or
+ match(line, prefix .. "%|") then
+ if match(lastline, " replacing $") and
+ not dropping then
+ -- Modify the return line
+ return gsub(line, "^%.", ""), lastline, true
+ else
+ return "", lastline, true
+ end
+ else
+ if dropping then
+ -- End of a \discretionary block
+ return line, ""
+ else
+ -- Not quite a normal discretionary
+ if match(lastline, "^%.+\\discretionary50%|$") then
+ lastline = gsub(lastline, "50%|$", "")
+ end
+ -- Remove some info that TeX90 lacks
+ lastline = gsub(lastline, " %(penalty 50%)$", "")
+ -- A normal (TeX90) discretionary:
+ -- add with the line break reintroduced
+ return lastline .. os_newline .. line, ""
+ end
+ end
+ end
+ -- Look for another form of \discretionary, replacing a "-"
+ pattern = "^%.+\\discretionary replacing *$"
+ if match(line, pattern) then
+ return "", line
+ else
+ if match(lastline, pattern) then
+ local prefix = boxprefix(lastline)
+ if match(line, prefix .. "%.\\kern") then
+ return gsub(line, "^%.", ""), lastline, true
+ elseif dropping then
+ return "", ""
+ else
+ return lastline .. os_newline .. line, ""
+ end
+ end
+ end
+ -- For \mathon, if the current line is an empty \hbox then
+ -- drop it
+ if match(lastline, "^%.+\\mathon$") then
+ local prefix = boxprefix(lastline)
+ if match(line, prefix .. "\\hbox%(0%.0%+0%.0%)x0%.0$") then
+ return "", ""
+ end
+ end
+ -- Various \local... things that other engines do not do:
+ -- Only remove the no-op versions
+ if match(line, "^%.+\\localpar$") or
+ match(line, "^%.+\\localinterlinepenalty=0$") or
+ match(line, "^%.+\\localbrokenpenalty=0$") or
+ match(line, "^%.+\\localleftbox=null$") or
+ match(line, "^%.+\\localrightbox=null$") then
+ return "", ""
+ end
+ -- Older LuaTeX versions set the above up as a whatsit
+ -- (at some stage this can therefore go)
+ if match(lastline, "^%.+\\whatsit$") then
+ local prefix = boxprefix(lastline)
+ if match(line, prefix .. "%.") then
+ return "", lastline, true
+ else
+ -- End of a \whatsit block
+ return line, ""
+ end
+ end
+ -- Wrap some cases that can be picked out
+ -- In some places LuaTeX does use max_print_line, then we
+ -- get into issues with different wrapping approaches
+ if len(line) == maxprintline then
+ return "", lastline .. line
+ elseif len(lastline) == maxprintline then
+ if match(line, "\\ETC%.%}$") then
+ -- If the line wrapped at \ETC we might have lost a space
+ return lastline
+ .. ((match(line, "^\\ETC%.%}$") and " ") or "")
+ .. line, ""
+ elseif match(line, "^%}%}%}$") then
+ return lastline .. line, ""
+ else
+ return lastline .. os_newline .. line, ""
+ end
+ -- Return all of the text for a wrapped (multi)line
+ elseif len(lastline) > maxprintline then
+ return lastline .. line, ""
+ end
+ -- Remove spaces at the start of lines: deals with the fact that LuaTeX
+ -- uses a different number to the other engines
+ return gsub(line, "^%s+", ""), ""
+ end
+ local newlog = ""
+ local lastline = ""
+ local dropping = false
+ -- Read the entire log file as a binary: deals with ^@/^[, etc.
+ local file = assert(open(logfile, "rb"))
+ local contents = gsub(file:read("*all") .. "\n", "\r\n", "\n")
+ close(file)
+ for line in gmatch(contents, "([^\n]*)\n") do
+ line, lastline, dropping = normalize(line, lastline, dropping)
+ if not match(line, "^ *$") then
+ newlog = newlog .. line .. os_newline
+ end
+ end
+ local newfile = open(newfile, "w")
+ output(newfile)
+ write(newlog)
+ close(newfile)
+end
+
+-- Run one test which may have multiple engine-dependent comparisons
+-- Should create a difference file for each failed test
+function runcheck(name, hide)
+ local checkengines = checkengines
+ if options["engine"] then
+ checkengines = options["engine"]
+ end
+ local errorlevel = 0
+ for _,i in ipairs(checkengines) do
+ -- Allow for luatex == luajittex for .tlg purposes
+ local engine = i
+ if i == "luajittex" then
+ engine = "luatex"
+ end
+ checkpdf = setup_check(name, engine)
+ runtest(name, i, hide, lvtext, checkpdf)
+ -- Generation of results depends on test type
+ local errlevel
+ if checkpdf then
+ errlevel = compare_pdf(name, engine)
+ else
+ errlevel = compare_tlg(name, engine)
+ end
+ if errlevel ~= 0 and options["halt-on-error"] then
+ showfaileddiff()
+ if errlevel ~= 0 then
+ return 1
+ end
+ end
+ if errlevel > errorlevel then
+ errorlevel = errlevel
+ end
+ end
+ return errorlevel
+end
+
+function setup_check(name, engine)
+ local testname = name .. "." .. engine
+ local pdffile = locate(
+ {testfiledir, unpackdir},
+ {testname .. pdfext, name .. pdfext}
+ )
+ local tlgfile = locate(
+ {testfiledir, unpackdir},
+ {testname .. tlgext, name .. tlgext}
+ )
+ -- Attempt to generate missing reference file from expectation
+ if not (pdffile or tlgfile) then
+ if not locate({unpackdir, testfiledir}, {name .. lveext}) then
+ print(
+ "Error: failed to find " .. pdfext .. ", " .. tlgext .. " or "
+ .. lveext .. " file for " .. name .. "!"
+ )
+ exit(1)
+ end
+ runtest(name, engine, true, lveext, true)
+ pdffile = testdir .. "/" .. testname .. pdfext
+ -- If a PDF is generated use it for comparisons
+ if not fileexists(pdffile) then
+ pdffile = nil
+ ren(testdir, testname .. logext, testname .. tlgext)
+ end
+ else
+ -- Install comparison files found
+ for _,v in pairs({pdffile, tlgfile}) do
+ if v then
+ cp(
+ match(v, ".*/(.*)"),
+ match(v, "(.*)/.*"),
+ testdir
+ )
+ end
+ end
+ end
+ if pdffile then
+ local pdffile = match(pdffile, ".*/(.*)")
+ ren(
+ testdir,
+ pdffile,
+ gsub(pdffile, pdfext .. "$", ".ref" .. pdfext)
+ )
+ return true
+ else
+ return false
+ end
+end
+
+function compare_pdf(name, engine)
+ local errorlevel
+ local testname = name .. "." .. engine
+ local cmpfile = testdir .. "/" .. testname .. os_cmpext
+ local pdffile = testdir .. "/" .. testname .. pdfext
+ local refpdffile = locate(
+ {testdir}, {testname .. ".ref" .. pdfext, name .. ".ref" .. pdfext}
+ )
+ if not refpdffile then
+ return
+ end
+ errorlevel = execute(
+ os_cmpexe .. " " .. normalize_path(refpdffile)
+ .. " " .. pdffile .. " > " .. cmpfile
+ )
+ if errorlevel == 0 then
+ os.remove(cmpfile)
+ end
+ return errorlevel
+end
+
+function compare_tlg(name, engine)
+ local errorlevel
+ local testname = name .. "." .. engine
+ local difffile = testdir .. "/" .. testname .. os_diffext
+ local logfile = testdir .. "/" .. testname .. logext
+ local tlgfile = locate({testdir}, {testname .. tlgext, name .. tlgext})
+ if not tlgfile then
+ return
+ end
+ -- Do additional log formatting if the engine is LuaTeX, there is no
+ -- LuaTeX-specific .tlg file and the default engine is not LuaTeX
+ if engine == "luatex"
+ and not match(tlgfile, "%.luatex" .. "%" .. tlgext)
+ and stdengine ~= "luatex"
+ and stdengine ~= "luajittex"
+ then
+ local luatlgfile = testdir .. "/" .. name .. ".luatex" .. tlgext
+ formatlualog(tlgfile, luatlgfile, false)
+ formatlualog(logfile, logfile, true)
+ -- This allows code sharing below: we only need the .tlg name in one place
+ tlgfile = luatlgfile
+ end
+ errorlevel = execute(os_diffexe .. " "
+ .. normalize_path(tlgfile .. " " .. logfile .. " > " .. difffile))
+ if errorlevel == 0 then
+ os.remove(difffile)
+ end
+ return errorlevel
+end
+
+-- Run one of the test files: doesn't check the result so suitable for
+-- both creating and verifying .tlg files
+function runtest(name, engine, hide, ext, makepdf)
+ local lvtfile = name .. (ext or lvtext)
+ cp(lvtfile, fileexists(testfiledir .. "/" .. lvtfile)
+ and testfiledir or unpackdir, testdir)
+ local engine = engine or stdengine
+ -- Set up the format file name if it's one ending "...tex"
+ local realengine = engine
+ local format
+ if
+ match(checkformat, "tex$") and
+ not match(engine, checkformat) then
+ format = " -fmt=" .. gsub(engine, "(.*)tex$", "%1") .. checkformat
+ else
+ format = ""
+ end
+ -- Special casing for e-LaTeX format
+ if
+ match(checkformat, "^latex$") and
+ match(engine, "^etex$") then
+ format = " -fmt=latex"
+ end
+ -- Special casing for (u)pTeX LaTeX formats
+ if
+ match(checkformat, "^latex$") and
+ match(engine, "^u?ptex$") then
+ realengine = "e" .. engine
+ end
+ -- Special casing for XeTeX engine
+ local checkopts = checkopts
+ if match(engine, "xetex") and not makepdf then
+ checkopts = checkopts .. " -no-pdf"
+ end
+ -- Special casing for ConTeXt
+ if match(checkformat, "^context$") then
+ format = ""
+ if engine == "luatex" or engine == "luajittex" then
+ realengine = "context"
+ elseif engine == "pdftex" then
+ realengine = "texexec"
+ elseif engine == "xetex" then
+ realengine = "texexec --xetex"
+ else
+ print("Engine incompatible with format")
+ exit(1)
+ end
+ end
+ local logfile = testdir .. "/" .. name .. logext
+ local newfile = testdir .. "/" .. name .. "." .. engine .. logext
+ local asciiopt = ""
+ for _,i in ipairs(asciiengines) do
+ if realengine == i then
+ asciiopt = "-translate-file ./ascii.tcx "
+ break
+ end
+ end
+ local errlevels = {}
+ for i = 1, checkruns do
+ errlevels[i] = run(
+ testdir,
+ -- No use of localdir here as the files get copied to testdir:
+ -- avoids any paths in the logs
+ os_setenv .. " TEXINPUTS=." .. (checksearch and os_pathsep or "")
+ .. os_concat ..
+ -- Avoid spurious output from (u)pTeX
+ os_setenv .. " GUESS_INPUT_KANJI_ENCODING=0"
+ .. os_concat ..
+ (forcecheckepoch and setepoch() or "") ..
+ -- Ensure lines are of a known length
+ os_setenv .. " max_print_line=" .. maxprintline
+ .. os_concat ..
+ realengine .. format .. " -jobname=" .. name .. " "
+ .. asciiopt .. " " .. checkopts .. " \"\\input " .. lvtfile .. "\" "
+ .. (hide and (" > " .. os_null) or "")
+ .. os_concat ..
+ runtest_tasks(jobname(lvtfile))
+ )
+ end
+ if makepdf and fileexists(testdir .. "/" .. name .. dviext) then
+ dvitopdf(name, testdir, engine, hide)
+ end
+ formatlog(logfile, newfile, engine, errlevels)
+ -- Store secondary files for this engine
+ for _,filetype in pairs(auxfiles) do
+ for _,file in pairs(filelist(testdir, filetype)) do
+ if match(file,"^" .. name .. ".[^.]+$") then
+ local ext = match(file, "%.[^.]+$")
+ if ext ~= lvtext and
+ ext ~= tlgext and
+ ext ~= lveext and
+ ext ~= logext then
+ ren(testdir, file, gsub(file, "(%.[^.]+)$", "." .. engine .. "%1"))
+ end
+ end
+ end
+ end
+end
+
+-- A hook to allow additional tasks to run for the tests
+runtest_tasks = runtest_tasks or function(name)
+ return ""
+end
+
+-- Look for a test: could be in the testfiledir or the unpackdir
+function testexists(test)
+ return(locate({testfiledir, unpackdir}, {test .. lvtext}))
+end
+
+-- Standard versions of the main targets for building modules
+
+function check(names)
+ local errorlevel = 0
+ if testfiledir ~= "" and direxists(testfiledir) then
+ if not options["rerun"] then
+ checkinit()
+ end
+ local hide = true
+ if names and next(names) then
+ hide = false
+ end
+ names = names or { }
+ -- No names passed: find all test files
+ if not next(names) then
+ for _,i in pairs(filelist(testfiledir, "*" .. lvtext)) do
+ insert(names, jobname(i))
+ end
+ for _,i in ipairs(filelist(unpackdir, "*" .. lvtext)) do
+ if fileexists(testfiledir .. "/" .. i) then
+ print("Duplicate test file: " .. i)
+ return 1
+ else
+ insert(names, jobname(i))
+ end
+ end
+ sort(names)
+ -- Deal limiting range of names
+ if options["first"] then
+ local allnames = names
+ local active = false
+ local firstname = options["first"]
+ names = { }
+ for _,name in ipairs(allnames) do
+ if name == firstname then
+ active = true
+ end
+ if active then
+ insert(names,name)
+ end
+ end
+ end
+ if options["last"] then
+ local allnames = names
+ local lastname = options["last"]
+ names = { }
+ for _,name in ipairs(allnames) do
+ insert(names,name)
+ if name == lastname then
+ break
+ end
+ end
+ end
+ end
+ -- https://stackoverflow.com/a/32167188
+ local function shuffle(tbl)
+ local len, random = #tbl, rnd
+ for i = len, 2, -1 do
+ local j = random(1, i)
+ tbl[i], tbl[j] = tbl[j], tbl[i]
+ end
+ return tbl
+ end
+ if options["shuffle"] then
+ names = shuffle(names)
+ end
+ -- Actually run the tests
+ print("Running checks on")
+ local i = 0
+ for _,name in ipairs(names) do
+ i = i + 1
+ print(" " .. name .. " (" .. i.. "/" .. #names ..")")
+ local errlevel = runcheck(name, hide)
+ -- Return value must be 1 not errlevel
+ if errlevel ~= 0 then
+ if options["halt-on-error"] then
+ return 1
+ else
+ errorlevel = 1
+ -- visually show that something has failed
+ print(" --> failed\n")
+ end
+ end
+ end
+ if errorlevel ~= 0 then
+ checkdiff()
+ else
+ print("\n All checks passed\n")
+ end
+ end
+ return errorlevel
+end
+
+-- A short auxiliary to print the list of differences for check
+function checkdiff()
+ print("\n Check failed with difference files")
+ for _,i in ipairs(filelist(testdir, "*" .. os_diffext)) do
+ print(" - " .. testdir .. "/" .. i)
+ end
+ for _,i in ipairs(filelist(testdir, "*" .. os_cmpext)) do
+ print(" - " .. testdir .. "/" .. i)
+ end
+ print("")
+end
+
+function showfaileddiff()
+ print("\nCheck failed with difference file")
+ for _,i in ipairs(filelist(testdir, "*" .. os_diffext)) do
+ print(" - " .. testdir .. "/" .. i)
+ print("")
+ local f = open(testdir .. "/" .. i,"r")
+ local content = f:read("*all")
+ close(f)
+ print("-----------------------------------------------------------------------------------")
+ print(content)
+ print("-----------------------------------------------------------------------------------")
+ end
+ for _,i in ipairs(filelist(testdir, "*" .. os_cmpext)) do
+ print(" - " .. testdir .. "/" .. i)
+ end
+end
+
+function save(names)
+ checkinit()
+ local engines = options["engine"] or {stdengine}
+ for _,name in pairs(names) do
+ local engine
+ for _,engine in pairs(engines) do
+ local tlgengine = ((engine == stdengine and "") or "." .. engine)
+ local tlgfile = name .. tlgengine .. tlgext
+ local spdffile = name .. tlgengine .. pdfext
+ local newfile = name .. "." .. engine .. logext
+ local pdffile = name .. "." .. engine .. pdfext
+ local refext = ((options["pdf"] and pdfext) or tlgext)
+ if testexists(name) then
+ print("Creating and copying " .. refext)
+ runtest(name, engine, false, lvtext, options["pdf"])
+ if options["pdf"] then
+ ren(testdir, pdffile, spdffile)
+ cp(spdffile, testdir, testfiledir)
+ else
+ ren(testdir, newfile, tlgfile)
+ cp(tlgfile, testdir, testfiledir)
+ end
+ if fileexists(unpackdir .. "/" .. tlgfile) then
+ print(
+ "Saved " .. tlgext
+ .. " file overrides unpacked version of the same name"
+ )
+ end
+ elseif locate({unpackdir, testfiledir}, {name .. lveext}) then
+ print(
+ "Saved " .. tlgext .. " file overrides a "
+ .. lveext .. " file of the same name"
+ )
+ else
+ print(
+ "Test input \"" .. testfiledir .. "/" .. name .. lvtext
+ .. "\" not found"
+ )
+ end
+ end
+ end
+end
+
diff --git a/Master/texmf-dist/scripts/l3build/l3build-clean.lua b/Master/texmf-dist/scripts/l3build/l3build-clean.lua
new file mode 100644
index 00000000000..3fd8396c9ce
--- /dev/null
+++ b/Master/texmf-dist/scripts/l3build/l3build-clean.lua
@@ -0,0 +1,56 @@
+--[[
+
+File l3build-clean.lua Copyright (C) 2018 The LaTeX3 Project
+
+It may be distributed and/or modified under the conditions of the
+LaTeX Project Public License (LPPL), either version 1.3c of this
+license or (at your option) any later version. The latest version
+of this license is in the file
+
+ http://www.latex-project.org/lppl.txt
+
+This file is part of the "l3build bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+-----------------------------------------------------------------------
+
+The development version of the bundle can be found at
+
+ https://github.com/latex3/l3build
+
+for those people who are interested.
+
+--]]
+
+-- Remove all generated files
+function clean()
+ -- To make sure that distribdir never contains any stray subdirs,
+ -- it is entirely removed then recreated rather than simply deleting
+ -- all of the files
+ local errorlevel =
+ rmdir(distribdir) +
+ mkdir(distribdir) +
+ cleandir(localdir) +
+ cleandir(testdir) +
+ cleandir(typesetdir) +
+ cleandir(unpackdir)
+ for _,i in ipairs(cleanfiles) do
+ for _,dir in pairs(remove_duplicates({maindir, sourcefiledir, docfiledir})) do
+ errorlevel = rm(dir, i) + errorlevel
+ end
+ end
+ return errorlevel
+end
+
+function bundleclean()
+ local errorlevel = call(modules, "clean")
+ for _,i in ipairs(cleanfiles) do
+ errorlevel = rm(maindir, i) + errorlevel
+ end
+ return (
+ errorlevel +
+ rmdir(ctandir) +
+ rmdir(tdsdir)
+ )
+end
+
diff --git a/Master/texmf-dist/scripts/l3build/l3build-ctan.lua b/Master/texmf-dist/scripts/l3build/l3build-ctan.lua
new file mode 100644
index 00000000000..875f50dc4fa
--- /dev/null
+++ b/Master/texmf-dist/scripts/l3build/l3build-ctan.lua
@@ -0,0 +1,245 @@
+--[[
+
+File l3build-ctan.lua Copyright (C) 2018 The LaTeX3 Project
+
+It may be distributed and/or modified under the conditions of the
+LaTeX Project Public License (LPPL), either version 1.3c of this
+license or (at your option) any later version. The latest version
+of this license is in the file
+
+ http://www.latex-project.org/lppl.txt
+
+This file is part of the "l3build bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+-----------------------------------------------------------------------
+
+The development version of the bundle can be found at
+
+ https://github.com/latex3/l3build
+
+for those people who are interested.
+
+--]]
+
+local gsub = string.gsub
+local match = string.match
+local insert = table.insert
+
+-- Copy files to the main CTAN release directory
+function copyctan()
+ mkdir(ctandir .. "/" .. ctanpkg)
+ local function copyfiles(files,source)
+ if source == currentdir or flatten then
+ for _,filetype in pairs(files) do
+ cp(filetype,source,ctandir .. "/" .. ctanpkg)
+ end
+ else
+ for _,filetype in pairs(files) do
+ for file,_ in pairs(tree(source,filetype)) do
+ local path = splitpath(file)
+ local ctantarget = ctandir .. "/" .. ctanpkg .. "/"
+ .. source .. "/" .. path
+ mkdir(ctantarget)
+ cp(file,source,ctantarget)
+ end
+ end
+ end
+ end
+ for _,tab in pairs(
+ {bibfiles,demofiles,docfiles,pdffiles,scriptmanfiles,typesetlist}) do
+ copyfiles(tab,docfiledir)
+ end
+ copyfiles(sourcefiles,sourcefiledir)
+ for _,file in pairs(textfiles) do
+ cp(file, currentdir, ctandir .. "/" .. ctanpkg)
+ end
+end
+
+-- Copy files to the correct places in the TDS tree
+function copytds()
+ local function install(source, dest, files, tool)
+ local moduledir = moduledir
+ -- For material associated with secondary tools (BibTeX, MakeIndex)
+ -- the structure needed is slightly different from those items going
+ -- into the tex/doc/source trees
+ if tool then
+ -- "base" is reserved for the tools themselves: make the assumption
+ -- in this case that the tdsroot name is the right place for stuff to
+ -- go (really just for the team)
+ if module == "base" then
+ moduledir = tdsroot
+ else
+ moduledir = module
+ end
+ end
+ -- Convert the file table(s) to a list of individual files
+ local filenames = { }
+ for _,i in ipairs(files) do
+ for _,j in ipairs(i) do
+ for file,_ in pairs(tree(source, j)) do
+ insert(filenames, file)
+ end
+ end
+ end
+ -- The target is only created if there are actual files to install
+ if next(filenames) ~= nil then
+ local installdir = tdsdir .. "/" .. dest .. "/" .. moduledir
+ mkdir(installdir)
+ for _,i in ipairs(filenames) do
+ cp(i, source, installdir)
+ end
+ end
+ end
+ install(
+ docfiledir,
+ "doc",
+ {bibfiles, demofiles, docfiles, pdffiles, textfiles, typesetlist}
+ )
+ install(unpackdir, "makeindex", {makeindexfiles}, true)
+ install(unpackdir, "bibtex/bst", {bstfiles}, true)
+ install(sourcefiledir, "source", {sourcelist})
+ install(unpackdir, "tex", {installfiles})
+ install(unpackdir, "scripts", {scriptfiles}, true)
+ -- Any script man files need special handling
+ for _,glob in pairs(scriptmanfiles) do
+ for file,_ in pairs(tree(docfiledir,glob)) do
+ -- Man files should have a single-digit extension: the type
+ local installdir = tdsdir .. "/doc/man/man" .. match(file,".$")
+ mkdir(installdir)
+ cp(file,docfiledir,installdir)
+ end
+ end
+end
+
+-- Standard versions of the main targets for building modules
+
+function ctan()
+ -- Always run tests for all engines
+ options["engine"] = nil
+ local function dirzip(dir, name)
+ local zipname = name .. ".zip"
+ local function tab_to_str(table)
+ local string = ""
+ for _,i in ipairs(table) do
+ string = string .. " " .. "\"" .. i .. "\""
+ end
+ return string
+ end
+ -- Convert the tables of files to quoted strings
+ local binfiles = tab_to_str(binaryfiles)
+ local exclude = tab_to_str(excludefiles)
+ -- First, zip up all of the text files
+ run(
+ dir,
+ zipexe .. " " .. zipopts .. " -ll ".. zipname .. " " .. "."
+ .. (
+ (binfiles or exclude) and (" -x" .. binfiles .. " " .. exclude)
+ or ""
+ )
+ )
+ -- Then add the binary ones
+ run(
+ dir,
+ zipexe .. " " .. zipopts .. " -g ".. zipname .. " " .. ". -i" ..
+ binfiles .. (exclude and (" -x" .. exclude) or "")
+ )
+ end
+ local errorlevel
+ local standalone = false
+ if bundle == "" then
+ standalone = true
+ end
+ if standalone then
+ errorlevel = check()
+ bundle = module
+ else
+ errorlevel = call(modules, "bundlecheck")
+ end
+ if errorlevel == 0 then
+ rmdir(ctandir)
+ mkdir(ctandir .. "/" .. ctanpkg)
+ rmdir(tdsdir)
+ mkdir(tdsdir)
+ if standalone then
+ errorlevel = bundlectan()
+ else
+ errorlevel = call(modules, "bundlectan")
+ end
+ else
+ print("\n====================")
+ print("Tests failed, zip stage skipped!")
+ print("====================\n")
+ return errorlevel
+ end
+ if errorlevel == 0 then
+ for _,i in ipairs(textfiles) do
+ for _,j in pairs({unpackdir, currentdir}) do
+ cp(i, j, ctandir .. "/" .. ctanpkg)
+ cp(i, j, tdsdir .. "/doc/" .. tdsroot .. "/" .. bundle)
+ end
+ end
+ dirzip(tdsdir, ctanpkg .. ".tds")
+ if packtdszip then
+ cp(ctanpkg .. ".tds.zip", tdsdir, ctandir)
+ end
+ dirzip(ctandir, ctanpkg)
+ cp(ctanpkg .. ".zip", ctandir, currentdir)
+ else
+ print("\n====================")
+ print("Typesetting failed, zip stage skipped!")
+ print("====================\n")
+ end
+ return errorlevel
+end
+
+function bundlectan()
+ -- Generate a list of individual file names excluding those in the second
+ -- argument: the latter is a table
+ local function excludelist(include, exclude, dir)
+ local include = include or { }
+ local exclude = exclude or { }
+ local dir = dir or currentdir
+ local includelist = { }
+ local excludelist = { }
+ for _,i in ipairs(exclude) do
+ for _,j in ipairs(i) do
+ for file,_ in pairs(tree(dir, j)) do
+ excludelist[file] = true
+ end
+ end
+ end
+ for _,i in ipairs(include) do
+ for file,_ in pairs(tree(dir, i)) do
+ if not excludelist[file] then
+ insert(includelist, file)
+ end
+ end
+ end
+ return includelist
+ end
+ unpack()
+ local errorlevel = doc()
+ if errorlevel == 0 then
+ -- Work out what PDF files are available
+ pdffiles = { }
+ for _,i in ipairs(typesetfiles) do
+ insert(pdffiles, (gsub(i, "%.%w+$", ".pdf")))
+ end
+ -- For the purposes here, any typesetting demo files need to be
+ -- part of the main typesetting list
+ local typesetfiles = typesetfiles
+ for _,v in pairs(typesetdemofiles) do
+ insert(typesetfiles, v)
+ end
+ typesetlist = excludelist(typesetfiles, {sourcefiles}, docfiledir)
+ sourcelist = excludelist(
+ sourcefiles, {bstfiles, installfiles, makeindexfiles, scriptfiles},
+ sourcefiledir
+ )
+ copyctan()
+ copytds()
+ end
+ return errorlevel
+end
+
diff --git a/Master/texmf-dist/scripts/l3build/l3build-file-functions.lua b/Master/texmf-dist/scripts/l3build/l3build-file-functions.lua
new file mode 100644
index 00000000000..a8c24356353
--- /dev/null
+++ b/Master/texmf-dist/scripts/l3build/l3build-file-functions.lua
@@ -0,0 +1,426 @@
+--[[
+
+File l3build-file-functions.lua Copyright (C) 2018 The LaTeX3 Project
+
+It may be distributed and/or modified under the conditions of the
+LaTeX Project Public License (LPPL), either version 1.3c of this
+license or (at your option) any later version. The latest version
+of this license is in the file
+
+ http://www.latex-project.org/lppl.txt
+
+This file is part of the "l3build bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+-----------------------------------------------------------------------
+
+The development version of the bundle can be found at
+
+ https://github.com/latex3/l3build
+
+for those people who are interested.
+
+--]]
+
+local pairs = pairs
+local print = print
+
+local open = io.open
+
+local attributes = lfs.attributes
+local currentdir = lfs.currentdir
+local chdir = lfs.chdir
+local lfs_dir = lfs.dir
+
+local execute = os.execute
+local getenv = os.getenv
+local remove = os.remove
+local os_time = os.time
+local os_type = os.type
+
+local luatex_revision = status.luatex_revision
+local luatex_version = status.luatex_version
+
+local match = string.match
+local sub = string.sub
+local gmatch = string.gmatch
+local gsub = string.gsub
+
+local insert = table.insert
+
+-- Convert a file glob into a pattern for use by e.g. string.gub
+-- Based on https://github.com/davidm/lua-glob-pattern
+-- Simplified substantially: "[...]" syntax not supported as is not
+-- required by the file patterns used by the team. Also note style
+-- changes to match coding approach in rest of this file.
+--
+-- License for original globtopattern
+--[[
+
+ (c) 2008-2011 David Manura. Licensed under the same terms as Lua (MIT).
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+ (end license)
+
+--]]
+local function glob_to_pattern(glob)
+
+ local pattern = "^" -- pattern being built
+ local i = 0 -- index in glob
+ local char -- char at index i in glob
+
+ -- escape pattern char
+ local function escape(char)
+ return match(char, "^%w$") and char or "%" .. char
+ end
+
+ -- Convert tokens.
+ while true do
+ i = i + 1
+ char = sub(glob, i, i)
+ if char == "" then
+ pattern = pattern .. "$"
+ break
+ elseif char == "?" then
+ pattern = pattern .. "."
+ elseif char == "*" then
+ pattern = pattern .. ".*"
+ elseif char == "[" then
+ -- Ignored
+ print("[...] syntax not supported in globs!")
+ elseif char == "\\" then
+ i = i + 1
+ char = sub(glob, i, i)
+ if char == "" then
+ pattern = pattern .. "\\$"
+ break
+ end
+ pattern = pattern .. escape(char)
+ else
+ pattern = pattern .. escape(char)
+ end
+ end
+ return pattern
+end
+
+-- Detect the operating system in use
+-- Support items are defined here for cases where a single string can cover
+-- both Windows and Unix cases: more complex situations are handled inside
+-- the support functions
+os_concat = ";"
+os_null = "/dev/null"
+os_pathsep = ":"
+os_setenv = "export"
+os_yes = "printf 'y\\n%.0s' {1..200}"
+
+os_ascii = "echo \"\""
+os_cmpexe = getenv("cmpexe") or "cmp"
+os_cmpext = getenv("cmpext") or ".cmp"
+os_diffext = getenv("diffext") or ".diff"
+os_diffexe = getenv("diffexe") or "diff -c --strip-trailing-cr"
+os_grepexe = "grep"
+os_newline = "\n"
+
+if os_type == "windows" then
+ os_ascii = "@echo."
+ os_cmpexe = getenv("cmpexe") or "fc /b"
+ os_cmpext = getenv("cmpext") or ".cmp"
+ os_concat = "&"
+ os_diffext = getenv("diffext") or ".fc"
+ os_diffexe = getenv("diffexe") or "fc /n"
+ os_grepexe = "findstr /r"
+ os_newline = "\n"
+ if tonumber(luatex_version) < 100 or
+ (tonumber(luatex_version) == 100
+ and tonumber(luatex_revision) < 4) then
+ os_newline = "\r\n"
+ end
+ os_null = "nul"
+ os_pathsep = ";"
+ os_setenv = "set"
+ os_yes = "for /l %I in (1,1,200) do @echo y"
+end
+
+-- Deal with the fact that Windows and Unix use different path separators
+local function unix_to_win(path)
+ return gsub(path, "/", "\\")
+end
+
+function normalize_path(path)
+ if os_type == "windows" then
+ return unix_to_win(path)
+ end
+ return path
+end
+
+-- Return an absolute path from a relative one
+function abspath(path)
+ local oldpwd = currentdir()
+ chdir(path)
+ local result = currentdir()
+ chdir(oldpwd)
+ return gsub(result, "\\", "/")
+end
+
+-- For cleaning out a directory, which also ensures that it exists
+function cleandir(dir)
+ local errorlevel = mkdir(dir)
+ if errorlevel ~= 0 then
+ return errorlevel
+ end
+ return rm(dir, "*")
+end
+
+-- Copy files 'quietly'
+function cp(glob, source, dest)
+ local errorlevel
+ for i,_ in pairs(tree(source, glob)) do
+ local source = source .. "/" .. i
+ if os_type == "windows" then
+ if attributes(source)["mode"] == "directory" then
+ errorlevel = execute(
+ 'xcopy /y /e /i "' .. unix_to_win(source) .. '" "'
+ .. unix_to_win(dest .. '/' .. i) .. '" > nul'
+ )
+ else
+ errorlevel = execute(
+ 'xcopy /y "' .. unix_to_win(source) .. '" "'
+ .. unix_to_win(dest .. '/') .. '" > nul'
+ )
+ end
+ else
+ errorlevel = execute("cp -RLf '" .. source .. "' '" .. dest .. "'")
+ end
+ if errorlevel ~=0 then
+ return errorlevel
+ end
+ end
+ return 0
+end
+
+-- OS-dependent test for a directory
+function direxists(dir)
+ local errorlevel
+ if os_type == "windows" then
+ errorlevel =
+ execute("if not exist \"" .. unix_to_win(dir) .. "\" exit 1")
+ else
+ errorlevel = execute("[ -d " .. dir .. " ]")
+ end
+ if errorlevel ~= 0 then
+ return false
+ end
+ return true
+end
+
+function fileexists(file)
+ local f = open(file, "r")
+ if f ~= nil then
+ f:close()
+ return true
+ else
+ return false
+ end
+end
+
+-- Generate a table containing all file names of the given glob or all files
+-- if absent
+function filelist(path, glob)
+ local files = { }
+ local pattern
+ if glob then
+ pattern = glob_to_pattern(glob)
+ end
+ if direxists(path) then
+ for entry in lfs_dir(path) do
+ if pattern then
+ if match(entry, pattern) then
+ insert(files, entry)
+ end
+ else
+ if entry ~= "." and entry ~= ".." then
+ insert(files, entry)
+ end
+ end
+ end
+ end
+ return files
+end
+
+-- Does what filelist does, but can also glob subdirectories. In the returned
+-- table, the keys are paths relative to the given starting path, the values
+-- are their counterparts relative to the current working directory.
+function tree(path, glob)
+ local function cropdots(path)
+ return gsub(gsub(path, "^%./", ""), "/%./", "/")
+ end
+ local function always_true()
+ return true
+ end
+ local function is_dir(file)
+ return attributes(file)["mode"] == "directory"
+ end
+ local dirs = {["."] = cropdots(path)}
+ for pattern, criterion in gmatch(cropdots(glob), "([^/]+)(/?)") do
+ local criterion = criterion == "/" and is_dir or always_true
+ function fill(path, dir, table)
+ for _, file in ipairs(filelist(dir, pattern)) do
+ local fullpath = path .. "/" .. file
+ if file ~= "." and file ~= ".." and
+ fullpath ~= builddir and
+ (sub(pattern, 1, 1) == "."
+ or sub(file, 1, 1) ~= ".")
+ then
+ local fulldir = dir .. "/" .. file
+ if criterion(fulldir) then
+ table[fullpath] = fulldir
+ end
+ end
+ end
+ end
+ local newdirs = {}
+ if pattern == "**" then
+ while true do
+ path, dir = next(dirs)
+ if not path then
+ break
+ end
+ dirs[path] = nil
+ newdirs[path] = dir
+ fill(path, dir, dirs)
+ end
+ else
+ for path, dir in pairs(dirs) do
+ fill(path, dir, newdirs)
+ end
+ end
+ dirs = newdirs
+ end
+ return dirs
+end
+
+function remove_duplicates(a)
+ -- Return array with duplicate entries removed from input array `a`.
+
+ local uniq = {}
+ local hash = {}
+
+ for _,v in ipairs(a) do
+ if (not hash[v]) then
+ hash[v] = true
+ uniq[#uniq+1] = v
+ end
+ end
+
+ return uniq
+end
+
+function mkdir(dir)
+ if os_type == "windows" then
+ -- Windows (with the extensions) will automatically make directory trees
+ -- but issues a warning if the dir already exists: avoid by including a test
+ local dir = unix_to_win(dir)
+ return execute(
+ "if not exist " .. dir .. "\\nul " .. "mkdir " .. dir
+ )
+ else
+ return execute("mkdir -p " .. dir)
+ end
+end
+
+-- Rename
+function ren(dir, source, dest)
+ local dir = dir .. "/"
+ if os_type == "windows" then
+ local source = gsub(source, "^%.+/", "")
+ local dest = gsub(dest, "^%.+/", "")
+ return execute("ren " .. unix_to_win(dir) .. source .. " " .. dest)
+ else
+ return execute("mv " .. dir .. source .. " " .. dir .. dest)
+ end
+end
+
+-- Remove file(s) based on a glob
+function rm(source, glob)
+ for _,i in ipairs(filelist(source, glob)) do
+ rmfile(source,i)
+ end
+ -- os.remove doesn't give a sensible errorlevel
+ return 0
+end
+
+-- Remove file
+function rmfile(source, file)
+ remove(source .. "/" .. file)
+ -- os.remove doesn't give a sensible errorlevel
+ return 0
+end
+
+-- Remove a directory tree
+function rmdir(dir)
+ -- First, make sure it exists to avoid any errors
+ mkdir(dir)
+ if os_type == "windows" then
+ return execute("rmdir /s /q " .. unix_to_win(dir))
+ else
+ return execute("rm -r " .. dir)
+ end
+end
+
+-- Run a command in a given directory
+function run(dir, cmd)
+ return execute("cd " .. dir .. os_concat .. cmd)
+end
+
+-- Split a path into file and directory component
+function splitpath(file)
+ local path, name = match(file, "^(.*)/([^/]*)$")
+ if path then
+ return path, name
+ else
+ return ".", file
+ end
+end
+
+-- Arguably clearer names
+function basename(file)
+ return(select(2, splitpath(file)))
+end
+
+function dirname(file)
+ return(select(1, splitpath(file)))
+end
+
+-- Strip the extension from a file name (if present)
+function jobname(file)
+ local name = match(basename(file), "^(.*)%.")
+ return name or file
+end
+
+-- Look for files, directory by directory, and return the first existing
+function locate(dirs, names)
+ for _,i in ipairs(dirs) do
+ for _,j in ipairs(names) do
+ local path = i .. "/" .. j
+ if fileexists(path) then
+ return path
+ end
+ end
+ end
+end
diff --git a/Master/texmf-dist/scripts/l3build/l3build-help.lua b/Master/texmf-dist/scripts/l3build/l3build-help.lua
new file mode 100644
index 00000000000..770022f254d
--- /dev/null
+++ b/Master/texmf-dist/scripts/l3build/l3build-help.lua
@@ -0,0 +1,79 @@
+--[[
+
+File l3build-help.lua Copyright (C) 2018 The LaTeX3 Project
+
+It may be distributed and/or modified under the conditions of the
+LaTeX Project Public License (LPPL), either version 1.3c of this
+license or (at your option) any later version. The latest version
+of this license is in the file
+
+ http://www.latex-project.org/lppl.txt
+
+This file is part of the "l3build bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+-----------------------------------------------------------------------
+
+The development version of the bundle can be found at
+
+ https://github.com/latex3/l3build
+
+for those people who are interested.
+
+--]]
+
+function version()
+ print(
+ "\n" ..
+ "l3build: A testing and building system for LaTeX\n\n" ..
+ "Release " .. release_date
+ )
+end
+
+function help()
+ print("usage: " .. arg[0] .. " <command> [<options>] [<names>]")
+ print("")
+ print("The most commonly used l3build commands are:")
+ if testfiledir ~= "" then
+ print(" check Run all automated tests")
+ end
+ print(" clean Clean out directory tree")
+ if module == "" or bundle == "" then
+ print(" ctan Create CTAN-ready archive")
+ end
+ print(" doc Typesets all documentation files")
+ print(" install Installs files into the local texmf tree")
+ if module ~= "" and testfiledir ~= "" then
+ print(" save Saves test validation log")
+ end
+ print(" tag Update release tags in files")
+ print(" uninstall Uninstalls files from the local texmf tree")
+ print(" unpack Unpacks the source files into the build tree")
+ print("")
+ print("Valid options are:")
+ local longest = 0
+ for k,v in pairs(option_list) do
+ if k:len() > longest then
+ longest = k:len()
+ end
+ end
+ -- Sort the options
+ local t = { }
+ for k,_ in pairs(option_list) do
+ table.insert(t, k)
+ end
+ table.sort(t)
+ for _,k in ipairs(t) do
+ local opt = option_list[k]
+ local filler = string.rep(" ", longest - k:len() + 1)
+ if opt["desc"] then -- Skip --help as it has no desc
+ if opt["short"] then
+ print(" --" .. k .. "|-" .. opt["short"] .. filler .. opt["desc"])
+ else
+ print(" --" .. k .. " " .. filler .. opt["desc"])
+ end
+ end
+ end
+ print("")
+ print("See l3build.pdf for further details.")
+end
diff --git a/Master/texmf-dist/scripts/l3build/l3build-install.lua b/Master/texmf-dist/scripts/l3build/l3build-install.lua
new file mode 100644
index 00000000000..c18677e4271
--- /dev/null
+++ b/Master/texmf-dist/scripts/l3build/l3build-install.lua
@@ -0,0 +1,98 @@
+--[[
+
+File l3build-install.lua Copyright (C) 2018 The LaTeX3 Project
+
+It may be distributed and/or modified under the conditions of the
+LaTeX Project Public License (LPPL), either version 1.3c of this
+license or (at your option) any later version. The latest version
+of this license is in the file
+
+ http://www.latex-project.org/lppl.txt
+
+This file is part of the "l3build bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+-----------------------------------------------------------------------
+
+The development version of the bundle can be found at
+
+ https://github.com/latex3/l3build
+
+for those people who are interested.
+
+--]]
+
+local pairs = pairs
+local print = print
+
+local set_program = kpse.set_program_name
+local var_value = kpse.var_value
+
+local function gethome()
+ set_program("latex")
+ return options["texmfhome"] or var_value("TEXMFHOME")
+end
+
+function uninstall()
+ local function uninstall_files(target)
+ local installdir = gethome() .. target
+ if options["dry-run"] then
+ print("\n" .. "Installation root: " .. installdir)
+ local files = filelist(installdir)
+ -- Deal with an empty directory
+ if next(files) then
+ print("\n" .. "Files for removal:")
+ for _,file in pairs(filelist(installdir)) do
+ print("- " .. file)
+ end
+ else
+ print("No files present")
+ end
+ return 0
+ else
+ return rmdir(installdir)
+ end
+ end
+ return uninstall_files("/tex/" .. moduledir)
+ + uninstall_files("/scripts/" .. module)
+end
+
+
+-- Locally install files: only deals with those extracted, not docs etc.
+function install()
+ local function install_files(files,target)
+ if not next(files) then
+ return 0
+ end
+ local installdir = gethome() .. target
+ if options["dry-run"] then
+ print("\n" .. "Installation root: " .. installdir
+ .. "\n" .. "Installation files:"
+ )
+ for _,filetype in pairs(files) do
+ for _,file in pairs(filelist(unpackdir,filetype)) do
+ print("- " .. file)
+ end
+ end
+ return 0
+ else
+ errorlevel = cleandir(installdir)
+ if errorlevel ~= 0 then
+ return errorlevel
+ end
+ for _,filetype in pairs(files) do
+ errorlevel = cp(filetype, unpackdir, installdir)
+ if errorlevel ~= 0 then
+ return errorlevel
+ end
+ end
+ end
+ return 0
+ end
+ local errorlevel = unpack()
+ if errorlevel ~= 0 then
+ return errorlevel
+ end
+ return install_files(installfiles, "/tex/" .. moduledir)
+ + install_files(scriptfiles, "/scripts/" .. module)
+end
diff --git a/Master/texmf-dist/scripts/l3build/l3build-manifest-setup.lua b/Master/texmf-dist/scripts/l3build/l3build-manifest-setup.lua
new file mode 100644
index 00000000000..fb46eb1d369
--- /dev/null
+++ b/Master/texmf-dist/scripts/l3build/l3build-manifest-setup.lua
@@ -0,0 +1,364 @@
+--[[
+
+File l3build-manifest-setup.lua Copyright (C) 2017-2018 The LaTeX3 Project
+
+It may be distributed and/or modified under the conditions of the
+LaTeX Project Public License (LPPL), either version 1.3c of this
+license or (at your option) any later version. The latest version
+of this license is in the file
+
+ http://www.latex-project.org/lppl.txt
+
+This file is part of the "l3build bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+-----------------------------------------------------------------------
+
+The development version of the bundle can be found at
+
+ https://github.com/latex3/l3build
+
+for those people who are interested.
+
+--]]
+
+
+--[[
+ L3BUILD MANIFEST SETUP
+ ======================
+ This file contains all of the code that is easily replaceable by the user.
+ Either create a copy of this file, rename, and include alongside your `build.lua`
+ script and load it with `dofile()`, or simply copy/paste the definitions below
+ into your `build.lua` script directly.
+--]]
+
+
+--[[
+ Setup of manifest "groups"
+ --------------------------
+
+ The grouping of manifest files is broken into three subheadings:
+
+ * The development repository
+ * The TDS structure from `ctan`
+ * The CTAN structure from `ctan`
+
+ The latter two will only be produced if the `manifest` target is run *after*
+ the `ctan` target. Contrarily, if you run `clean` before `manifest` then
+ only the first grouping will be printed.
+
+ If you want to omit the files in the development repository, essentially
+ producing a minimalist manifest with only the files included for distribution,
+ make a copy of the `manifest_setup` function and delete the groups under
+ the ‘Repository manifest’ subheading below.
+--]]
+
+
+manifest_setup = manifest_setup or function()
+ local groups = {
+ {
+ subheading = "Repository manifest",
+ description = [[
+The following groups list the files included in the development repository of the package.
+Files listed with a ‘†’ marker are included in the TDS but not CTAN files, and files listed
+with ‘‡’ are included in both.
+]],
+ },
+ {
+ name = "Source files",
+ description = [[
+These are source files for a number of purposes, including the `unpack` process which
+generates the installation files of the package. Additional files included here will also
+be installed for processing such as testing.
+]],
+ files = {sourcefiles},
+ dir = sourcefiledir or maindir, -- TODO: remove "or maindir" after rebasing onto master
+ },
+ {
+ name = "Typeset documentation source files",
+ description = [[
+These files are typeset using LaTeX to produce the PDF documentation for the package.
+]],
+ files = {typesetfiles,typesetsourcefiles,typesetdemofiles},
+ },
+ {
+ name = "Documentation files",
+ description = [[
+These files form part of the documentation but are not typeset. Generally they will be
+additional input files for the typeset documentation files listed above.
+]],
+ files = {docfiles},
+ dir = docfiledir or maindir, -- TODO: remove "or maindir" after rebasing onto master
+ },
+ {
+ name = "Text files",
+ description = [[
+Plain text files included as documentation or metadata.
+]],
+ files = {textfiles},
+ skipfiledescription = true,
+ },
+ {
+ name = "Demo files",
+ description = [[
+Files included to demonstrate package functionality. These files are *not*
+typeset or compiled in any way.
+]],
+ files = {demofiles},
+ },
+ {
+ name = "Bibliography and index files",
+ description = [[
+Supplementary files used for compiling package documentation.
+]],
+ files = {bibfiles,bstfiles,makeindexfiles},
+ },
+ {
+ name = "Derived files",
+ description = [[
+The files created by ‘unpacking’ the package sources. This typically includes
+`.sty` and `.cls` files created from DocStrip `.dtx` files.
+]],
+ files = {installfiles},
+ exclude = {excludefiles,sourcefiles},
+ dir = unpackdir,
+ skipfiledescription = true,
+ },
+ {
+ name = "Typeset documents",
+ description = [[
+The output files (PDF, essentially) from typesetting the various source, demo,
+etc., package files.
+]],
+ files = {typesetfiles,typesetsourcefiles,typesetdemofiles},
+ rename = {"%.%w+$", ".pdf"},
+ skipfiledescription = true,
+ },
+ {
+ name = "Support files",
+ description = [[
+These files are used for unpacking, typesetting, or checking purposes.
+]],
+ files = {unpacksuppfiles,typesetsuppfiles,checksuppfiles},
+ dir = supportdir,
+ },
+ {
+ name = "Checking-specific support files",
+ description = [[
+Support files for checking the test suite.
+]],
+ files = {"*.*"},
+ exclude = {{".",".."},excludefiles},
+ dir = testsuppdir,
+ },
+ {
+ name = "Test files",
+ description = [[
+These files form the test suite for the package. `.lvt` or `.lte` files are the individual
+unit tests, and `.tlg` are the stored output for ensuring changes to the package produce
+the same output. These output files are sometimes shared and sometime specific for
+different engines (pdfTeX, XeTeX, LuaTeX, etc.).
+]],
+ files = {"*"..lvtext,"*"..lveext,"*"..tlgext},
+ dir = testfiledir,
+ skipfiledescription = true,
+ },
+ {
+ subheading = "TDS manifest",
+ description = [[
+The following groups list the files included in the TeX Directory Structure used to install
+the package into a TeX distribution.
+]],
+ },
+ {
+ name = "Source files (TDS)",
+ description = "All files included in the `"..module.."/source` directory.\n",
+ dir = tdsdir.."/source/"..moduledir,
+ files = {"*.*"},
+ exclude = {".",".."},
+ flag = false,
+ skipfiledescription = true,
+ },
+ {
+ name = "TeX files (TDS)",
+ description = "All files included in the `"..module.."/tex` directory.\n",
+ dir = tdsdir.."/tex/"..moduledir,
+ files = {"*.*"},
+ exclude = {".",".."},
+ flag = false,
+ skipfiledescription = true,
+ },
+ {
+ name = "Doc files (TDS)",
+ description = "All files included in the `"..module.."/doc` directory.\n",
+ dir = tdsdir.."/doc/"..moduledir,
+ files = {"*.*"},
+ exclude = {".",".."},
+ flag = false,
+ skipfiledescription = true,
+ },
+ {
+ subheading = "CTAN manifest",
+ description = [[
+The following group lists the files included in the CTAN package.
+]],
+ },
+ {
+ name = "CTAN files",
+ dir = ctandir.."/"..module,
+ files = {"*.*"},
+ exclude = {".",".."},
+ flag = false,
+ skipfiledescription = true,
+ },
+ }
+ return groups
+end
+
+--[[
+ Sorting within groups
+ ---------------------
+--]]
+
+manifest_sort_within_match = manifest_sort_within_match or function(files)
+ local f = files
+ table.sort(f)
+ return f
+end
+
+manifest_sort_within_group = manifest_sort_within_group or function(files)
+ local f = files
+ --[[
+ -- no-op by default; make your own definition to customise. E.g.:
+ table.sort(f)
+ --]]
+ return f
+end
+
+--[[
+ Writing to file
+ ---------------
+--]]
+
+manifest_write_opening = manifest_write_opening or function(filehandle)
+
+ filehandle:write("# Manifest for " .. module .. "\n\n")
+ filehandle:write([[
+This file is a listing of all files considered to be part of this package.
+It is automatically generated with `texlua build.lua manifest`.
+]])
+
+end
+
+manifest_write_subheading = manifest_write_subheading or function(filehandle,heading,description)
+
+ filehandle:write("\n\n## " .. heading .. "\n\n")
+
+ if description then
+ filehandle:write(description)
+ end
+
+end
+
+manifest_write_group_heading = manifest_write_group_heading or function (filehandle,heading,description)
+
+ filehandle:write("\n### " .. heading .. "\n\n")
+
+ if description then
+ filehandle:write(description .. "\n")
+ end
+
+end
+
+manifest_write_group_file = manifest_write_group_file or function(filehandle,filename,param)
+ --[[
+ filehandle : write file object
+ filename : the count of the filename to be written
+
+ param.dir : the directory of the file
+ param.count : the name of the file to write
+ param.filemaxchar : the maximum number of chars of all filenames in this group
+ param.flag : false OR string for indicating CTAN/TDS location
+ param.ctanfile : (boolean) if file is in CTAN dir
+ param.tdsfile : (boolean) if file is in TDS dir
+ --]]
+
+ -- no file description: plain bullet list item:
+
+ flagstr = param.flag or ""
+ filehandle:write("* " .. filename .. " " .. flagstr .. "\n")
+
+ --[[
+ -- or if you prefer an enumerated list:
+ filehandle:write(param.count..". " .. filename .. "\n")
+ --]]
+
+
+end
+
+manifest_write_group_file_descr = manifest_write_group_file_descr or function(filehandle,filename,descr,param)
+ --[[
+ filehandle : write file object
+ filename : the name of the file to write
+ descr : description of the file to write
+
+ param.dir : the directory of the file
+ param.count : the count of the filename to be written
+ param.filemaxchar : the maximum number of chars of all filenames in this group
+ param.descmaxchar : the maximum number of chars of all descriptions in this group
+ param.flag : false OR string for indicating CTAN/TDS location
+ param.ctanfile : (boolean) if file is in CTAN dir
+ param.tdsfile : (boolean) if file is in TDS dir
+ --]]
+
+ -- filename+description: Github-flavoured Markdown table
+
+ filestr = string.format(" | %-"..param.filemaxchar.."s",filename)
+ flagstr = param.flag and string.format(" | %s",param.flag) or ""
+ descstr = string.format(" | %-"..param.descmaxchar.."s",descr)
+
+ filehandle:write(filestr..flagstr..descstr.." |\n")
+
+end
+
+--[[
+ Extracting ‘descriptions’ from source files
+ -------------------------------------------
+--]]
+
+manifest_extract_filedesc = manifest_extract_filedesc or function(filehandle)
+
+ -- no-op by default; two examples below
+
+end
+
+--[[
+
+-- From the first match of a pattern in a file:
+manifest_extract_filedesc = function(filehandle)
+
+ local all_file = filehandle:read("*all")
+ local matchstr = "\\section{(.-)}"
+
+ filedesc = string.match(all_file,matchstr)
+
+ return filedesc
+end
+
+-- From the match of the 2nd line (say) of a file:
+manifest_extract_filedesc = function(filehandle)
+
+ local end_read_loop = 2
+ local matchstr = "%%%S%s+(.*)"
+ local this_line = ""
+
+ for ii = 1, end_read_loop do
+ this_line = filehandle:read("*line")
+ end
+
+ filedesc = string.match(this_line,matchstr)
+
+ return filedesc
+end
+
+]]--
diff --git a/Master/texmf-dist/scripts/l3build/l3build-manifest.lua b/Master/texmf-dist/scripts/l3build/l3build-manifest.lua
new file mode 100644
index 00000000000..3b899d55fd8
--- /dev/null
+++ b/Master/texmf-dist/scripts/l3build/l3build-manifest.lua
@@ -0,0 +1,280 @@
+--[[
+
+File l3build-manifest.lua Copyright (C) 2017-2018 The LaTeX3 Project
+
+It may be distributed and/or modified under the conditions of the
+LaTeX Project Public License (LPPL), either version 1.3c of this
+license or (at your option) any later version. The latest version
+of this license is in the file
+
+ http://www.latex-project.org/lppl.txt
+
+This file is part of the "l3build bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+-----------------------------------------------------------------------
+
+The development version of the bundle can be found at
+
+ https://github.com/latex3/l3build
+
+for those people who are interested.
+
+--]]
+
+
+--[[
+ L3BUILD MANIFEST
+ ================
+ If desired this entire function can be replaced; if not, it uses a number of
+ auxiliary functions which are included in this file.
+
+ Additional setup can be performed by replacing the functions lists in the file
+ `l3build-manifest-setup.lua`.
+--]]
+
+manifest = manifest or function()
+
+ -- build list of ctan files
+ ctanfiles = {}
+ for _,f in ipairs(filelist(ctandir.."/"..ctanpkg,"*.*")) do
+ ctanfiles[f] = true
+ end
+ tdsfiles = {}
+ for _,subdir in ipairs({"/doc/","/source/","/tex/"}) do
+ for _,f in ipairs(filelist(tdsdir..subdir..moduledir,"*.*")) do
+ tdsfiles[f] = true
+ end
+ end
+
+ local manifest_entries = manifest_setup()
+
+ for ii,_ in ipairs(manifest_entries) do
+ manifest_entries[ii] = manifest_build_list(manifest_entries[ii])
+ end
+
+ manifest_write(manifest_entries)
+
+ printline = "Manifest written to " .. manifestfile
+ print((printline:gsub(".","*"))) print(printline) print((printline:gsub(".","*")))
+
+end
+
+--[[
+ Internal Manifest functions: build_list
+ ---------------------------------------
+--]]
+
+manifest_build_list = function(entry)
+
+ if not(entry.subheading) then
+
+ entry = manifest_build_init(entry)
+
+ -- build list of excluded files
+ for _,glob_list in ipairs(entry.exclude) do
+ for _,this_glob in ipairs(glob_list) do
+ for _,this_file in ipairs(filelist(maindir,this_glob)) do
+ entry.excludes[this_file] = true
+ end
+ end
+ end
+
+ -- build list of matched files
+ for _,glob_list in ipairs(entry.files) do
+ for _,this_glob in ipairs(glob_list) do
+
+ local these_files = filelist(entry.dir,this_glob)
+ these_files = manifest_sort_within_match(these_files)
+
+ for _,this_file in ipairs(these_files) do
+ entry = manifest_build_file(entry,this_file)
+ end
+
+ entry.files_ordered = manifest_sort_within_group(entry.files_ordered)
+
+ end
+ end
+
+ end
+
+ return entry
+
+end
+
+
+manifest_build_init = function(entry)
+
+ -- currently these aren't customisable; I guess they could be?
+ local manifest_group_defaults = {
+ skipfiledescription = false ,
+ rename = false ,
+ dir = maindir ,
+ exclude = {excludefiles} ,
+ flag = true ,
+ }
+
+ -- internal data added to each group in the table that needs to be initialised
+ local manifest_group_init = {
+ N = 0 , -- # matched files
+ ND = 0 , -- # descriptions
+ matches = {} ,
+ excludes = {} ,
+ files_ordered = {} ,
+ descr = {} ,
+ Nchar_file = 4 , -- TODO: generalise
+ Nchar_descr = 11 , -- TODO: generalise
+ }
+
+ -- copy default options to each group if necessary
+ for kk,ll in pairs(manifest_group_defaults) do
+ if entry[kk] == nil then
+ entry[kk] = ll
+ end
+ -- can't use "entry[kk] = entry[kk] or ll" because false/nil are indistinguishable!
+ end
+
+ -- initialisation for internal data
+ for kk,ll in pairs(manifest_group_init) do
+ entry[kk] = ll
+ end
+
+ -- allow nested tables by requiring two levels of nesting
+ if type(entry.files[1])=="string" then
+ entry.files = {entry.files}
+ end
+ if type(entry.exclude[1])=="string" then
+ entry.exclude = {entry.exclude}
+ end
+
+ return entry
+
+end
+
+
+manifest_build_file = function(entry,this_file)
+
+ if entry.rename then
+ this_file = this_file:gsub(entry.rename[1],entry.rename[2])
+ end
+
+ if not entry.excludes[this_file] then
+
+ entry.N = entry.N+1
+ if not(entry.matches[this_file]) then
+
+ entry.matches[this_file] = true -- store the file name
+ entry.files_ordered[entry.N] = this_file -- store the file order
+ entry.Nchar_file = math.max(entry.Nchar_file,this_file:len())
+
+ end
+
+ if not(entry.skipfiledescription) then
+
+ local ff = assert(io.open(entry.dir .. "/" .. this_file, "r"))
+ this_descr = manifest_extract_filedesc(ff,this_file)
+ ff:close()
+
+ if this_descr and this_descr ~= "" then
+ entry.descr[this_file] = this_descr
+ entry.ND = entry.ND+1
+ entry.Nchar_descr = math.max(entry.Nchar_descr,this_descr:len())
+ end
+
+ end
+ end
+
+ return entry
+
+end
+
+--[[
+ Internal Manifest functions: write
+ ----------------------------------
+--]]
+
+manifest_write = function(manifest_entries)
+
+ local f = assert(io.open(manifestfile, "w"))
+ manifest_write_opening(f)
+
+ for ii,vv in ipairs(manifest_entries) do
+ if manifest_entries[ii].subheading then
+ manifest_write_subheading(f,manifest_entries[ii].subheading,manifest_entries[ii].description)
+ elseif manifest_entries[ii].N > 0 then
+ manifest_write_group(f,manifest_entries[ii])
+ end
+ end
+
+ f:close()
+
+end
+
+
+manifest_write_group = function(f,entry)
+
+ manifest_write_group_heading(f,entry.name,entry.description)
+
+ if entry.ND > 0 then
+
+ for ii,file in ipairs(entry.files_ordered) do
+ local descr = entry.descr[file] or ""
+ local param = {
+ dir = entry.dir ,
+ count = ii ,
+ filemaxchar = entry.Nchar_file ,
+ descmaxchar = entry.Nchar_descr ,
+ ctanfile = ctanfiles[file] ,
+ tdsfile = tdsfiles[file] ,
+ flag = false ,
+ }
+
+ if entry.flag then
+ param.flag = " "
+ if tdsfiles[file] and not(ctanfiles[file]) then
+ param.flag = "† "
+ elseif ctanfiles[file] then
+ param.flag = "‡ "
+ end
+ end
+
+ if ii == 1 then
+ -- header of table
+ -- TODO: generalise
+ local p = {}
+ for k,v in pairs(param) do p[k] = v end
+ p.count = -1
+ p.flag = p.flag and "Flag"
+ manifest_write_group_file_descr(f,"File","Description",p)
+ p.flag = p.flag and "--- "
+ manifest_write_group_file_descr(f,"---","---",p)
+ end
+
+ manifest_write_group_file_descr(f,file,descr,param)
+ end
+
+ else
+
+ for ii,file in ipairs(entry.files_ordered) do
+ local param = {
+ dir = entry.dir ,
+ count = ii ,
+ filemaxchar = entry.Nchar_file ,
+ ctanfile = ctanfiles[file] ,
+ tdsfile = tdsfiles[file] ,
+ }
+ if entry.flag then
+ param.flag = ""
+ if tdsfiles[file] and not(ctanfiles[file]) then
+ param.flag = "†"
+ elseif ctanfiles[file] then
+ param.flag = "‡"
+ end
+ end
+ manifest_write_group_file(f,file,param)
+ end
+
+ end
+
+end
+
diff --git a/Master/texmf-dist/scripts/l3build/l3build-stdmain.lua b/Master/texmf-dist/scripts/l3build/l3build-stdmain.lua
new file mode 100644
index 00000000000..7dac27aa983
--- /dev/null
+++ b/Master/texmf-dist/scripts/l3build/l3build-stdmain.lua
@@ -0,0 +1,135 @@
+--[[
+
+File l3build-stdmain.lua Copyright (C) 2018 The LaTeX3 Project
+
+It may be distributed and/or modified under the conditions of the
+LaTeX Project Public License (LPPL), either version 1.3c of this
+license or (at your option) any later version. The latest version
+of this license is in the file
+
+ http://www.latex-project.org/lppl.txt
+
+This file is part of the "l3build bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+-----------------------------------------------------------------------
+
+The development version of the bundle can be found at
+
+ https://github.com/latex3/l3build
+
+for those people who are interested.
+
+--]]
+
+local exit = os.exit
+
+-- List all modules
+function listmodules()
+ local modules = { }
+ local exclmodules = exclmodules or { }
+ for entry in lfs.dir(".") do
+ if entry ~= "." and entry ~= ".." then
+ local attr = lfs.attributes(entry)
+ assert(type(attr) == "table")
+ if attr.mode == "directory" then
+ if not exclmodules[entry] then
+ table.insert(modules, entry)
+ end
+ end
+ end
+ end
+ return modules
+end
+
+--
+-- The overall main function
+--
+
+function stdmain(target, names)
+ local errorlevel
+ -- If the module name is empty, the script is running in a bundle:
+ -- apart from ctan all of the targets are then just mappings
+ if module == "" then
+ -- Detect all of the modules
+ modules = modules or listmodules()
+ if target == "doc" then
+ errorlevel = call(modules, "doc")
+ elseif target == "check" then
+ errorlevel = call(modules, "bundlecheck")
+ if errorlevel ~=0 then
+ print("There were errors: checks halted!\n")
+ end
+ elseif target == "clean" then
+ errorlevel = bundleclean()
+ elseif target == "ctan" then
+ errorlevel = ctan()
+ elseif target == "install" then
+ errorlevel = call(modules, "install")
+ elseif target == "tag" then
+ if options["names"] and #options["names"] == 1 then
+ errorlevel = call(modules,"tag")
+ -- Deal with any files in the bundle dir itself
+ if errorlevel == 0 then
+ errorlevel = tag(options["names"][1])
+ end
+ else
+ print("Tag name required")
+ help()
+ exit(1)
+ end
+ elseif target == "uninstall" then
+ errorlevel = call(modules, "uninstall")
+ elseif target == "unpack" then
+ errorlevel = call(modules, "bundleunpack")
+ else
+ help()
+ end
+ else
+ if target == "bundleunpack" then -- 'Hidden' as only needed 'higher up'
+ depinstall(unpackdeps)
+ errorlevel = bundleunpack()
+ elseif target == "bundlecheck" then
+ errorlevel = check()
+ elseif target == "bundlectan" then
+ errorlevel = bundlectan()
+ elseif target == "doc" then
+ errorlevel = doc(names)
+ elseif target == "check" then
+ errorlevel = check(names)
+ elseif target == "clean" then
+ errorlevel = clean()
+ elseif target == "ctan" then
+ errorlevel = ctan()
+ elseif target == "install" then
+ errorlevel = install()
+ elseif target == "manifest" then
+ errorlevel = manifest()
+ elseif target == "save" then
+ if next(names) then
+ errorlevel = save(names)
+ else
+ help()
+ end
+ elseif target == "tag" then
+ if options["names"] and #options["names"] == 1 then
+ errorlevel = tag(options["names"][1])
+ else
+ print("Tag name required")
+ help()
+ exit(1)
+ end
+ elseif target == "uninstall" then
+ errorlevel = uninstall()
+ elseif target == "unpack" then
+ errorlevel = unpack()
+ else
+ help()
+ end
+ end
+ if errorlevel ~= 0 then
+ exit(1)
+ else
+ exit(0)
+ end
+end
diff --git a/Master/texmf-dist/scripts/l3build/l3build-tagging.lua b/Master/texmf-dist/scripts/l3build/l3build-tagging.lua
new file mode 100644
index 00000000000..9a6f8cf5014
--- /dev/null
+++ b/Master/texmf-dist/scripts/l3build/l3build-tagging.lua
@@ -0,0 +1,80 @@
+--[[
+
+File l3build-tagging.lua Copyright (C) 2018 The LaTeX3 Project
+
+It may be distributed and/or modified under the conditions of the
+LaTeX Project Public License (LPPL), either version 1.3c of this
+license or (at your option) any later version. The latest version
+of this license is in the file
+
+ http://www.latex-project.org/lppl.txt
+
+This file is part of the "l3build bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+-----------------------------------------------------------------------
+
+The development version of the bundle can be found at
+
+ https://github.com/latex3/l3build
+
+for those people who are interested.
+
+--]]
+
+local pairs = pairs
+local open = io.open
+local os_date = os.date
+local match = string.match
+local gsub = string.gsub
+
+function update_tag(filename,content,tagname,tagdate)
+ return content
+end
+
+function tag_hook(tagname,tagdate)
+ return 0
+end
+
+local function update_file_tag(file,tagname,tagdate)
+ local filename = basename(file)
+ print("Tagging ".. filename)
+ local f = assert(open(file,"rb"))
+ local content = f:read("*all")
+ f:close()
+ -- Deal with Unix/Windows line endings
+ content = gsub(content .. (match(content,"\n$") and "" or "\n"),
+ "\r\n", "\n")
+ local updated_content = update_tag(filename,content,tagname,tagdate)
+ if content == updated_content then
+ return 0
+ else
+ local path = dirname(file)
+ ren(path,filename,filename .. ".bak")
+ local f = assert(open(file,"w"))
+ -- Convert line ends back if required during write
+ -- Watch for the second return value!
+ f:write((gsub(updated_content,"\n",os_newline)))
+ f:close()
+ rm(path,filename .. ".bak")
+ end
+ return 0
+end
+
+function tag(tagname)
+ local tagdate = options["date"] or os_date("%Y-%m-%d")
+ local dirs = remove_duplicates({currentdir, sourcefiledir, docfiledir})
+ local errorlevel = 0
+ for _,dir in pairs(dirs) do
+ for _,filetype in pairs(tagfiles) do
+ for file,_ in pairs(tree(dir,filetype)) do
+ errorlevel = update_file_tag(dir .. "/" .. file,tagname,tagdate)
+ if errorlevel ~= 0 then
+ return errorlevel
+ end
+ end
+ end
+ end
+ return tag_hook(tagname,tagdate)
+end
+
diff --git a/Master/texmf-dist/scripts/l3build/l3build-typesetting.lua b/Master/texmf-dist/scripts/l3build/l3build-typesetting.lua
new file mode 100644
index 00000000000..229745dafc6
--- /dev/null
+++ b/Master/texmf-dist/scripts/l3build/l3build-typesetting.lua
@@ -0,0 +1,249 @@
+--[[
+
+File l3build-typesetting.lua Copyright (C) 2018 The LaTeX3 Project
+
+It may be distributed and/or modified under the conditions of the
+LaTeX Project Public License (LPPL), either version 1.3c of this
+license or (at your option) any later version. The latest version
+of this license is in the file
+
+ http://www.latex-project.org/lppl.txt
+
+This file is part of the "l3build bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+-----------------------------------------------------------------------
+
+The development version of the bundle can be found at
+
+ https://github.com/latex3/l3build
+
+for those people who are interested.
+
+--]]
+
+--
+-- Auxiliary functions for typesetting: need to be generally available
+--
+
+local gsub = string.gsub
+local match = string.match
+
+function dvitopdf(name, dir, engine, hide)
+ if match(engine, "^u?ptex$") then
+ run(
+ dir,
+ (forcecheckepoch and setepoch() or "") ..
+ "dvipdfmx " .. name .. dviext
+ .. (hide and (" > " .. os_null) or "")
+ )
+ else
+ run(
+ dir,
+ (forcecheckepoch and setepoch() or "") ..
+ "dvips " .. name .. dviext
+ .. (hide and (" > " .. os_null) or "")
+ .. os_concat ..
+ "ps2pdf " .. name .. psext
+ .. (hide and (" > " .. os_null) or "")
+ )
+ end
+end
+
+-- An auxiliary used to set up the environmental variables
+function runtool(subdir, dir, envvar, command)
+ dir = dir or "."
+ return(
+ run(
+ typesetdir .. "/" .. subdir,
+ (forcedocepoch and setepoch() or "") ..
+ os_setenv .. " " .. envvar .. "=." .. os_pathsep
+ .. abspath(localdir) .. os_pathsep
+ .. abspath(dir .. "/" .. subdir)
+ .. (typesetsearch and os_pathsep or "")
+ .. os_concat ..
+ command
+ )
+ )
+end
+
+function biber(name, dir)
+ if fileexists(typesetdir .. "/" .. name .. ".bcf") then
+ local path, name = splitpath(name)
+ return(
+ runtool(path, dir, "BIBINPUTS", biberexe .. " " .. biberopts .. " " .. name)
+ )
+ end
+ return 0
+end
+
+function bibtex(name, dir)
+ if fileexists(typesetdir .. "/" .. name .. ".aux") then
+ -- LaTeX always generates an .aux file, so there is a need to
+ -- look inside it for a \citation line
+ local grep
+ if os_type == "windows" then
+ grep = "\\\\"
+ else
+ grep = "\\\\\\\\"
+ end
+ local path, name = splitpath(name)
+ if run(
+ typesetdir,
+ os_grepexe .. " \"^" .. grep .. "citation{\" " .. name .. ".aux > "
+ .. os_null
+ ) + run(
+ typesetdir,
+ os_grepexe .. " \"^" .. grep .. "bibdata{\" " .. name .. ".aux > "
+ .. os_null
+ ) == 0 then
+ return(
+ -- Cheat slightly as we need to set two variables
+ runtool(
+ path, dir,
+ "BIBINPUTS",
+ os_setenv .. " BSTINPUTS=." .. os_pathsep
+ .. abspath(localdir)
+ .. (typesetsearch and os_pathsep or "") ..
+ os_concat ..
+ bibtexexe .. " " .. bibtexopts .. " " .. name
+ )
+ )
+ end
+ end
+ return 0
+end
+
+function makeindex(name, dir, inext, outext, logext, style)
+ if fileexists(typesetdir .. "/" .. name .. inext) then
+ local path, name = splitpath(name)
+ if style == "" then style = nil end
+ return(
+ runtool(
+ path, dir,
+ "INDEXSTYLE",
+ makeindexexe .. " " .. makeindexopts
+ .. " -o " .. name .. outext
+ .. (style and (" -s " .. style) or "")
+ .. " -t " .. name .. " " .. name .. inext
+ )
+ )
+ end
+ return 0
+end
+
+function tex(file, dir)
+ local path, name = splitpath(file)
+ return(
+ runtool(
+ path, dir,
+ "TEXINPUTS",
+ typesetexe .. " " .. typesetopts .. " \"" .. typesetcmds
+ .. "\\input " .. name .. "\""
+ )
+ )
+end
+
+function typesetpdf(file, dir)
+ local name = gsub(file, "%.[^.]+$", "")
+ print("Typesetting " .. name)
+ local errorlevel = typeset(file, dir)
+ if errorlevel == 0 then
+ name = name .. ".pdf"
+ os.remove(jobname(name))
+ cp(name, typesetdir, docfiledir)
+ else
+ print(" ! Compilation failed")
+ end
+ return errorlevel
+end
+
+typeset = typeset or function(file, dir)
+ dir = dir or "."
+ local errorlevel = tex(file, dir)
+ if errorlevel ~= 0 then
+ return errorlevel
+ else
+ local name = jobname(file)
+ errorlevel = biber(name, dir) + bibtex(name, dir)
+ if errorlevel == 0 then
+ local function cycle(name, dir)
+ return(
+ makeindex(name, dir, ".glo", ".gls", ".glg", glossarystyle) +
+ makeindex(name, dir, ".idx", ".ind", ".ilg", indexstyle) +
+ tex(file, dir)
+ )
+ end
+ for i = 1, typesetruns do
+ errorlevel = cycle(name, dir)
+ if errorlevel ~= 0 then break end
+ end
+ end
+ return errorlevel
+ end
+end
+
+
+-- A hook to allow additional typesetting of demos
+typeset_demo_tasks = typeset_demo_tasks or function()
+ return 0
+end
+
+-- Typeset all required documents
+-- Uses a set of dedicated auxiliaries that need to be available to others
+function doc(files)
+ -- Set up
+ cleandir(typesetdir)
+ for _,filetype in pairs(
+ {bibfiles, docfiles, typesetfiles, typesetdemofiles}
+ ) do
+ for _,file in pairs(filetype) do
+ cp(file, docfiledir, typesetdir)
+ end
+ end
+ for _,file in pairs(sourcefiles) do
+ cp(file, sourcefiledir, typesetdir)
+ end
+ for _,file in pairs(typesetsuppfiles) do
+ cp(file, supportdir, typesetdir)
+ end
+ depinstall(typesetdeps)
+ unpack({sourcefiles, typesetsourcefiles}, {sourcefiledir, docfiledir})
+ -- Main loop for doc creation
+ local done = {}
+ local errorlevel = typeset_demo_tasks()
+ if errorlevel ~= 0 then
+ return errorlevel
+ end
+ for _, typesetfiles in ipairs({typesetdemofiles, typesetfiles}) do
+ for _,i in ipairs(typesetfiles) do
+ for _, dir in ipairs({unpackdir, typesetdir}) do
+ for j,_ in pairs(tree(dir, i)) do
+ if not done[j] then
+ j = gsub(j, "^%./", "")
+ -- Allow for command line selection of files
+ local typeset = true
+ if files and next(files) then
+ typeset = false
+ for _,k in ipairs(files) do
+ if k == gsub(j, "%.[^.]+$", "") then
+ typeset = true
+ break
+ end
+ end
+ end
+ if typeset then
+ local errorlevel = typesetpdf(j, dir)
+ if errorlevel ~= 0 then
+ return errorlevel
+ else
+ done[j] = true
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ return 0
+end
diff --git a/Master/texmf-dist/scripts/l3build/l3build-unpack.lua b/Master/texmf-dist/scripts/l3build/l3build-unpack.lua
new file mode 100644
index 00000000000..9779502c2fc
--- /dev/null
+++ b/Master/texmf-dist/scripts/l3build/l3build-unpack.lua
@@ -0,0 +1,99 @@
+--[[
+
+File l3build-unpack.lua Copyright (C) 2018 The LaTeX3 Project
+
+It may be distributed and/or modified under the conditions of the
+LaTeX Project Public License (LPPL), either version 1.3c of this
+license or (at your option) any later version. The latest version
+of this license is in the file
+
+ http://www.latex-project.org/lppl.txt
+
+This file is part of the "l3build bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+-----------------------------------------------------------------------
+
+The development version of the bundle can be found at
+
+ https://github.com/latex3/l3build
+
+for those people who are interested.
+
+--]]
+
+local execute = os.execute
+
+-- Unpack the package files using an 'isolated' system: this requires
+-- a copy of the 'basic' DocStrip program, which is used then removed
+function unpack(sources, sourcedirs)
+ local errorlevel = depinstall(unpackdeps)
+ if errorlevel ~= 0 then
+ return errorlevel
+ end
+ errorlevel = bundleunpack(sourcedirs, sources)
+ if errorlevel ~= 0 then
+ return errorlevel
+ end
+ for _,i in ipairs(installfiles) do
+ errorlevel = cp(i, unpackdir, localdir)
+ if errorlevel ~= 0 then
+ return errorlevel
+ end
+ end
+ return 0
+end
+
+-- Split off from the main unpack so it can be used on a bundle and not
+-- leave only one modules files
+bundleunpack = bundleunpack or function(sourcedirs, sources)
+ local errorlevel = mkdir(localdir)
+ if errorlevel ~=0 then
+ return errorlevel
+ end
+ errorlevel = cleandir(unpackdir)
+ if errorlevel ~=0 then
+ return errorlevel
+ end
+ for _,i in ipairs(sourcedirs or {sourcefiledir}) do
+ for _,j in ipairs(sources or {sourcefiles}) do
+ for _,k in ipairs(j) do
+ errorlevel = cp(k, i, unpackdir)
+ if errorlevel ~=0 then
+ return errorlevel
+ end
+ end
+ end
+ end
+ for _,i in ipairs(unpacksuppfiles) do
+ errorlevel = cp(i, supportdir, localdir)
+ if errorlevel ~=0 then
+ return errorlevel
+ end
+ end
+ for _,i in ipairs(unpackfiles) do
+ for j,_ in pairs(tree(unpackdir, i)) do
+ -- This 'yes' business is needed to pass a series of "y\n" to
+ -- TeX if \askforoverwrite is true
+ -- That is all done using a file as it's the only way on Windows and
+ -- on Unix the "yes" command can't be used inside execute (it never
+ -- stops, which confuses Lua)
+ execute(os_yes .. ">>" .. localdir .. "/yes")
+ local path, name = splitpath(j)
+ local localdir = abspath(localdir)
+ errorlevel = run(
+ unpackdir .. "/" .. path,
+ os_setenv .. " TEXINPUTS=." .. os_pathsep
+ .. localdir .. (unpacksearch and os_pathsep or "") ..
+ os_concat ..
+ unpackexe .. " " .. unpackopts .. " " .. name .. " < "
+ .. localdir .. "/yes"
+ .. (options["quiet"] and (" > " .. os_null) or "")
+ )
+ if errorlevel ~=0 then
+ return errorlevel
+ end
+ end
+ end
+ return 0
+end
diff --git a/Master/texmf-dist/scripts/l3build/l3build-variables.lua b/Master/texmf-dist/scripts/l3build/l3build-variables.lua
new file mode 100644
index 00000000000..5d4e3265c2a
--- /dev/null
+++ b/Master/texmf-dist/scripts/l3build/l3build-variables.lua
@@ -0,0 +1,179 @@
+--[[
+
+File l3build-variables.lua Copyright (C) 2018 The LaTeX3 Project
+
+It may be distributed and/or modified under the conditions of the
+LaTeX Project Public License (LPPL), either version 1.3c of this
+license or (at your option) any later version. The latest version
+of this license is in the file
+
+ http://www.latex-project.org/lppl.txt
+
+This file is part of the "l3build bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+-----------------------------------------------------------------------
+
+The development version of the bundle can be found at
+
+ https://github.com/latex3/l3build
+
+for those people who are interested.
+
+--]]
+
+local exit = os.exit
+
+-- "module" is a deprecated function in Lua 5.2: as we want the name
+-- for other purposes, and it should eventually be 'free', simply
+-- remove the built-in
+if type(module) == "function" then
+ module = nil
+end
+
+-- Ensure the module and bundle exist
+module = module or ""
+bundle = bundle or ""
+
+-- Directory structure for the build system
+-- Use Unix-style path separators
+currentdir = "."
+maindir = maindir or currentdir
+
+-- Substructure for file locations
+docfiledir = docfiledir or currentdir
+sourcefiledir = sourcefiledir or currentdir
+supportdir = supportdir or maindir .. "/support"
+testfiledir = testfiledir or currentdir .. "/testfiles"
+testsuppdir = testsuppdir or testfiledir .. "/support"
+
+-- Structure within a development area
+builddir = builddir or maindir .. "/build"
+distribdir = distribdir or builddir .. "/distrib"
+localdir = localdir or builddir .. "/local"
+testdir = testdir or builddir .. "/test"
+typesetdir = typesetdir or builddir .. "/doc"
+unpackdir = unpackdir or builddir .. "/unpacked"
+
+-- Substructure for CTAN release material
+ctandir = ctandir or distribdir .. "/ctan"
+tdsdir = tdsdir or distribdir .. "/tds"
+tdsroot = tdsroot or "latex"
+
+-- Location for installation on CTAN or in TEXMFHOME
+if bundle == "" then
+ moduledir = tdsroot .. "/" .. module
+ ctanpkg = ctanpkg or module
+else
+ moduledir = tdsroot .. "/" .. bundle .. "/" .. module
+ ctanpkg = ctanpkg or bundle
+end
+
+-- File types for various operations
+-- Use Unix-style globs
+-- All of these may be set earlier, so a initialised conditionally
+auxfiles = auxfiles or {"*.aux", "*.lof", "*.lot", "*.toc"}
+bibfiles = bibfiles or {"*.bib"}
+binaryfiles = binaryfiles or {"*.pdf", "*.zip"}
+bstfiles = bstfiles or {"*.bst"}
+checkfiles = checkfiles or { }
+checksuppfiles = checksuppfiles or { }
+cleanfiles = cleanfiles or {"*.log", "*.pdf", "*.zip"}
+demofiles = demofiles or { }
+docfiles = docfiles or { }
+excludefiles = excludefiles or {"*~"}
+installfiles = installfiles or {"*.sty","*.cls"}
+makeindexfiles = makeindexfiles or {"*.ist"}
+scriptfiles = scriptfiles or { }
+scriptmanfiles = scriptmanfiles or { }
+sourcefiles = sourcefiles or {"*.dtx", "*.ins"}
+tagfiles = tagfiles or {"*.dtx"}
+textfiles = textfiles or {"*.md", "*.txt"}
+typesetdemofiles = typesetdemofiles or { }
+typesetfiles = typesetfiles or {"*.dtx"}
+typesetsuppfiles = typesetsuppfiles or { }
+typesetsourcefiles = typesetsourcefiles or { }
+unpackfiles = unpackfiles or {"*.ins"}
+unpacksuppfiles = unpacksuppfiles or { }
+
+-- Roots which should be unpacked to support unpacking/testing/typesetting
+checkdeps = checkdeps or { }
+typesetdeps = typesetdeps or { }
+unpackdeps = unpackdeps or { }
+
+-- Executable names plus following options
+typesetexe = typesetexe or "pdflatex"
+unpackexe = unpackexe or "tex"
+zipexe = zipexe or "zip"
+
+checkopts = checkopts or "-interaction=nonstopmode"
+typesetopts = typesetopts or "-interaction=nonstopmode"
+unpackopts = unpackopts or ""
+zipopts = zipopts or "-v -r -X"
+
+-- Engines for testing
+checkengines = checkengines or {"pdftex", "xetex", "luatex"}
+checkformat = checkformat or "latex"
+stdengine = stdengine or "pdftex"
+
+-- Configs for testing
+checkconfigs = checkconfigs or {"build"}
+
+-- Enable access to trees outside of the repo
+-- As these may be set false, a more elaborate test than normal is needed
+if checksearch == nil then
+ checksearch = true
+end
+if typesetsearch == nil then
+ typesetsearch = true
+end
+if unpacksearch == nil then
+ unpacksearch = true
+end
+
+-- Additional settings to fine-tune typesetting
+glossarystyle = glossarystyle or "gglo.ist"
+indexstyle = indexstyle or "gind.ist"
+
+-- Supporting binaries and options
+biberexe = biberexe or "biber"
+biberopts = biberopts or ""
+bibtexexe = bibtexexe or "bibtex8"
+bibtexopts = bibtexopts or "-W"
+makeindexexe = makeindexexe or "makeindex"
+makeindexopts = makeindexopts or ""
+
+-- Forcing epoch
+if forcecheckepoch == nil then
+ forcecheckepoch = true
+end
+if forcedocepoch == nil then
+ forcedocepoch = false
+end
+
+-- Other required settings
+asciiengines = asciiengines or {"pdftex"}
+checkruns = checkruns or 1
+epoch = epoch or 1463734800
+if flatten == nil then
+ flatten = true
+end
+maxprintline = maxprintline or 79
+packtdszip = packtdszip or false
+typesetcmds = typesetcmds or ""
+typesetruns = typesetruns or 2
+versionform = versionform or ""
+recordstatus = recordstatus or false
+
+-- Extensions for various file types: used to abstract out stuff a bit
+bakext = bakext or ".bak"
+dviext = dviext or ".dvi"
+logext = logext or ".log"
+lveext = lveext or ".lve"
+lvtext = lvtext or ".lvt"
+pdfext = pdfext or ".pdf"
+psext = psext or ".ps"
+tlgext = tlgext or ".tlg"
+
+-- Manifest options
+manifestfile = manifestfile or "MANIFEST.md"
diff --git a/Master/texmf-dist/scripts/l3build/l3build.lua b/Master/texmf-dist/scripts/l3build/l3build.lua
new file mode 100644
index 00000000000..cb8a6d674cb
--- /dev/null
+++ b/Master/texmf-dist/scripts/l3build/l3build.lua
@@ -0,0 +1,145 @@
+--[[
+
+File l3build.lua Copyright (C) 2014-2017 The LaTeX3 Project
+
+It may be distributed and/or modified under the conditions of the
+LaTeX Project Public License (LPPL), either version 1.3c of this
+license or (at your option) any later version. The latest version
+of this license is in the file
+
+ http://www.latex-project.org/lppl.txt
+
+This file is part of the "l3build bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+-----------------------------------------------------------------------
+
+The development version of the bundle can be found at
+
+ https://github.com/latex3/l3build
+
+for those people who are interested.
+
+--]]
+
+-- Version information
+release_date = "2018-03-08"
+
+-- File operations are aided by the LuaFileSystem module
+local lfs = require("lfs")
+
+-- Local access to functions
+
+local assert = assert
+local ipairs = ipairs
+local lookup = kpse.lookup
+local match = string.match
+local next = next
+local print = print
+local select = select
+local tonumber = tonumber
+local exit = os.exit
+
+-- l3build setup and functions
+kpse.set_program_name("kpsewhich")
+build_kpse_path = string.match(lookup("l3build.lua"),"(.*[/])")
+local function build_require(s)
+ require(lookup("l3build-"..s..".lua", { path = build_kpse_path } ) )
+end
+
+-- Minimal code to do basic checks
+build_require("arguments")
+build_require("help")
+
+-- Filter out special cases early
+if options["target"] == "help" then
+ help()
+ exit(0)
+elseif options["target"] == "version" then
+ version()
+ exit(0)
+end
+
+build_require("file-functions")
+build_require("typesetting")
+build_require("aux")
+build_require("clean")
+build_require("check")
+build_require("ctan")
+build_require("install")
+build_require("unpack")
+build_require("manifest")
+build_require("manifest-setup")
+build_require("tagging")
+build_require("stdmain")
+
+-- Allow main function to be disabled 'higher up'
+main = main or stdmain
+
+-- Load configuration file if running as a script
+if match(arg[0], "l3build(%.lua)$") then
+ -- Look for some configuration details
+ if fileexists("build.lua") then
+ dofile("build.lua")
+ else
+ print("Error: Cannot find configuration build.lua")
+ exit(1)
+ end
+end
+
+-- Load standard settings for variables:
+-- comes after any user versions
+build_require("variables")
+
+-- Tidy up the epoch setting
+-- Force an epoch if set at the command line
+-- Must be done after loading variables, etc.
+if options["epoch"] then
+ epoch = options["epoch"]
+ forcecheckepoch = true
+ forcedocepoch = true
+end
+normalise_epoch()
+
+-- Sanity check
+check_engines()
+
+--
+-- Deal with multiple configs for tests
+--
+
+-- When we have specific files to deal with, only use explicit configs
+-- (or just the std one)
+if options["names"] then
+ checkconfigs = options["config"] or {stdconfig}
+else
+ checkconfigs = options["config"] or checkconfigs
+end
+
+if options["target"] == "check" then
+ if #checkconfigs > 1 then
+ local errorlevel = 0
+ local opts = options
+ for i = 1, #checkconfigs do
+ opts["config"] = {checkconfigs[i]}
+ errorlevel = call({"."}, "check", opts)
+ if errorlevel ~= 0 then exit(1) end
+ end
+ -- Avoid running the 'main' set of tests twice
+ exit(0)
+ end
+end
+if #checkconfigs == 1 and
+ checkconfigs[1] ~= "build" and
+ (options["target"] == "check" or options["target"] == "save") then
+ local config = "./" .. checkconfigs[1] .. ".lua"
+ if fileexists(config) then
+ dofile(config)
+ else
+ print("Error: Cannot find configuration " .. checkconfigs[1])
+ exit(1)
+ end
+end
+
+-- Call the main function
+main(options["target"], options["names"])