diff options
Diffstat (limited to 'src/luaotfload-tool.lua')
-rwxr-xr-x | src/luaotfload-tool.lua | 318 |
1 files changed, 310 insertions, 8 deletions
diff --git a/src/luaotfload-tool.lua b/src/luaotfload-tool.lua index 9e75944..47e7ccc 100755 --- a/src/luaotfload-tool.lua +++ b/src/luaotfload-tool.lua @@ -48,6 +48,7 @@ kpse.set_program_name "luatex" local ioopen = io.open local iowrite = io.write local kpsefind_file = kpse.find_file +local mathfloor = math.floor local next = next local osdate = os.date local ostype = os.type @@ -95,6 +96,7 @@ config = config or { } local config = config local luaotfloadconfig = config.luaotfload or { } config.luaotfload = luaotfloadconfig +luaotfloadconfig.bisect = false luaotfloadconfig.version = luaotfloadconfig.version or version luaotfloadconfig.names_dir = luaotfloadconfig.names_dir or "names" luaotfloadconfig.cache_dir = luaotfloadconfig.cache_dir or "fonts" @@ -113,8 +115,12 @@ config.lualibs.prefer_merged = true config.lualibs.load_extended = true require "lualibs" -local tabletohash = table.tohash +local iosavedata = io.savedata +local lfsisdir = lfs.isdir +local lfsisfile = lfs.isfile local stringsplit = string.split +local tableserialize = table.serialize +local tabletohash = table.tohash --[[doc-- \fileent{luatex-basics-gen.lua} calls functions from the @@ -256,7 +262,7 @@ local help_msg = function (version) names_gzip, names_bin, caches.getwritablepath ( - luaotfloadconfig.cache_dir))) + luaotfloadconfig.cache_dir))) end local about = [[ @@ -288,7 +294,7 @@ local head_adornchars = { } local textwidth = 80 -local wd_leftcolumn = math.floor(textwidth * .25) +local wd_leftcolumn = mathfloor(textwidth * .25) local key_fmt = stringformat([[%%%ds]], wd_leftcolumn) local val_fmt = [[%s]] local fieldseparator = ":" @@ -725,8 +731,8 @@ set. local action_sequence = { "loglevel", "help", "version", "diagnose", - "blacklist", "cache", "flush", "generate", - "list", "query", + "blacklist", "cache", "flush", "bisect", + "generate", "list", "query", } local action_pending = tabletohash(action_sequence, false) @@ -771,6 +777,297 @@ actions.generate = function (job) return false, false end +------------------------------------------------------------------------------- +--- bisect mode +------------------------------------------------------------------------------- + +local bisect_status_path = caches.getwritablepath "bisect" +local bisect_status_file = bisect_status_path .."/" .. "luaotfload-bisect-status.lua" +local bisect_status_fmt = [[ +--[==[------------------------------------------------------------------------- + This file is generated by Luaotfload. It can be safely deleted. + Creation date: %s. +-------------------------------------------------------------------------]==]-- + +%s + +--- vim:ft=lua:ts=8:et:sw=2 +]] + +--[[doc-- + + write_bisect_status -- Write the history of the current bisection to disk. + +--doc]]-- + +--- state list -> bool +local write_bisect_status = function (data) + local payload = tableserialize (data, true) + local status = stringformat (bisect_status_fmt, + osdate ("%Y-%m-d %H:%M:%S", os.time ()), + payload) + if status and iosavedata (bisect_status_file, status) then + report ("info", 4, "bisect", + "Bisection state written to %s.", bisect_status_file) + return true + end + report ("info", 0, "bisect", + "Failed to write bisection state to %s.", bisect_status_file) + return false +end + +--[[doc-- + + read_bisect_status -- Read the bisect log from disk. + +--doc]]-- + +--- unit -> state list +local read_bisect_status = function () + report ("info", 4, "bisect", "Testing for status file: %q.", bisect_status_file) + if not lfsisfile (bisect_status_file) then + report ("info", 2, "bisect", "No such file: %q.", bisect_status_file) + report ("info", 0, "bisect", "Not in bisect mode.") + return false + end + report ("info", 4, "bisect", "Reading status file: %q.", bisect_status_file) + local success, status = pcall (dofile, bisect_status_file) + if not success then + report ("info", 0, "bisect", "Could not read status file.") + return false + end + return status +end + +--[[doc-- + + bisect_start -- Begin a bisect session. Determines the number of + fonts and sets the initial high, low, and pivot values. + +--doc]]-- + +local bisect_start = function () + if lfsisfile (bisect_status_file) then + report ("info", 0, "bisect", + "Bisect session in progress.", + bisect_status_file) + report ("info", 0, "bisect", + "Use --bisect=stop to erase it before starting over.") + return false, false + end + report ("info", 2, "bisect", + "Starting bisection of font database %q.", bisect_status_file) + local n = names.count_font_files () + local pivot = mathfloor (n / 2) + local data = { { 1, n, pivot } } + report ("info", 0, "bisect", "Initializing pivot to %d.", pivot) + if write_bisect_status (data) then + return true, false + end + return false, false +end + +--[[doc-- + + bisect_stop -- Terminate bisection session by removing all state info. + +--doc]]-- + +local bisect_stop = function () + report ("info", 3, "bisect", "Erasing bisection state at %s.", bisect_status_file) + if lfsisfile (bisect_status_file) then + local success, msg = os.remove (bisect_status_file) + if not success then + report ("info", 2, "bisect", + "Failed to erase file %s (%s).", + bisect_status_file, msg) + end + end + if lfsisdir (bisect_status_path) then + local success, msg = os.remove (bisect_status_path) + if not success then + report ("info", 2, "bisect", + "Failed to erase directory %s (%s).", + bisect_status_path, msg) + end + end + if lfsisfile (bisect_status_file) then + return false, false + end + return true, false +end + +--[[doc-- + + bisect_terminate -- Wrap up a bisect session by printing the + offending font and removing the state file. + +--doc]]-- + +local bisect_terminate = function (nsteps, culprit) + report ("info", 1, "bisect", + "Bisection completed after %d steps.", nsteps) + report ("info", 0, "bisect", + "Bad file: %s.", names.nth_font_filename (culprit)) + report ("info", 0, "bisect", + "Run with --bisect=stop to finish bisection.") + return true, false +end + +--[[doc-- + + list_remainder -- Show remaining fonts in bisect slice. + +--doc]]-- + +local list_remainder = function (lo, hi) + local fonts = names.font_slice (lo, hi) + report ("info", 0, "bisect", "%d fonts left.", hi - lo + 1) + for i = 1, #fonts do + report ("info", 1, "bisect", " · %2d: %s", lo, fonts[i]) + lo = lo + 1 + end +end + +--[[doc-- + + bisect_set -- Prepare the next bisection step by setting high, low, + and pivot to new values. + + The “run” directive always picks the segment below the pivot so we + can rely on the “outcome parameter” to be referring to that. + +--doc]]-- + +local bisect_set = function (outcome) + local status = read_bisect_status () + if not status then + return false, false + end + + local nsteps = #status + local previous = status[nsteps] + if previous == true then + --- Bisection already completed; we exit early through + --- bisect_terminate() to avoid further writes to the + --- state files that mess up step counting. + nsteps = nsteps - 1 + return bisect_terminate (nsteps, status[nsteps][1]) + end + + local lo, hi, pivot = unpack (previous) + + report ("info", 3, "bisect", "Previous step %d: lo=%d, hi=%d, pivot=%d.", + nsteps, lo, hi, pivot) + + if outcome == "bad" then + hi = pivot + if lo >= hi then --- complete + status[nsteps + 1] = { lo, lo, lo } + status[nsteps + 1] = true + write_bisect_status (status) + return bisect_terminate (nsteps, lo) + end + pivot = mathfloor ((lo + hi) / 2) + report ("info", 0, "bisect", + "Continuing with the lower segment: lo=%d, hi=%d, pivot=%d.", + lo, hi, pivot) + elseif outcome == "good" then + lo = pivot + 1 + if lo >= hi then --- complete + status[nsteps + 1] = { lo, lo, lo } + write_bisect_status (status) + status[nsteps + 1] = true + return bisect_terminate (nsteps, lo) + end + pivot = mathfloor ((lo + hi) / 2) + report ("info", 0, "bisect", + "Continuing with the upper segment: lo=%d, hi=%d, pivot=%d.", + lo, hi, pivot) + else -- can’t happen + report ("info", 0, "bisect", "What the hell?", lo, hi, pivot) + return false, false + end + + status[nsteps + 1] = { lo, hi, pivot } + write_bisect_status (status) + if hi - lo <= 10 then + list_remainder (lo, hi) + end + return true, false +end + +--[[doc-- + + bisect_status -- Output information about the current bisect session. + +--doc]]-- + +local bisect_status = function () + local status = read_bisect_status () + if not status then + return false, false + end + local nsteps = #status + if nsteps > 1 then + for i = nsteps - 1, 1, -1 do + local step = status[i] + report ("info", 2, "bisect", "Step %d: lo=%d, hi=%d, pivot=%d.", + i, unpack (step)) + end + end + local current = status[nsteps] + report ("info", 0, "bisect", "Step %d: lo=%d, hi=%d, pivot=%d.", + nsteps, unpack (current)) + return true, false +end + +--[[doc-- + + bisect_run -- Run Luaotfload utilizing the current bisection state. + This should be combined with the --update mode, possibly with the + --force option. + + Luaotfload always tests the segment below the pivot first. + +--doc]]-- + +local bisect_run = function () + local status = read_bisect_status () + if not status then + return false, false + end + local nsteps = #status + local currentstep = nsteps + 1 + local current = status[nsteps] + local lo, hi, pivot = unpack (current) + report ("info", 3, "bisect", "Previous step %d: lo=%d, hi=%d, pivot=%d.", + nsteps, lo, hi, pivot) + report ("info", 1, "bisect", "Step %d: Testing fonts from %d to %d.", + currentstep, lo, pivot) + luaotfloadconfig.bisect = { lo, pivot } + return true, true +end + +local bisect_modes = { + start = bisect_start, + good = function () return bisect_set "good" end, + bad = function () return bisect_set "bad" end, + stop = bisect_stop, + status = bisect_status, + run = bisect_run, +} + +actions.bisect = function (job) + local mode = job.bisect + local runner = bisect_modes[mode] + if not runner then + report ("info", 0, "bisect", "Unknown directive %q.", mode) + return false, false + end + return runner (job) +end + actions.flush = function (job) local success = names.flush_lookup_cache() if success then @@ -1076,6 +1373,7 @@ local process_cmdline = function ( ) -- unit -> jobspec criterion = "", query = "", log_level = 0, --- 2 is approx. the old behavior + bisect = nil, } local long_options = { @@ -1097,6 +1395,7 @@ local process_cmdline = function ( ) -- unit -> jobspec ["local"] = "L", log = 1, ["max-fonts"] = 1, + ["bisect"] = 1, ["no-reload"] = "n", ["no-strip"] = 0, ["skip-read"] = "R", @@ -1211,6 +1510,9 @@ local process_cmdline = function ( ) -- unit -> jobspec luaotfloadconfig.max_fonts = n end end + elseif v == "bisect" then + result.bisect = optarg[n] + action_pending.bisect = true end end @@ -1239,16 +1541,16 @@ local main = function ( ) -- unit -> int if not success then report (false, 0, "util", - "Could not finish task", "%s", actionname) + "Failed to execute task.", "%s", actionname) retval = -1 exit = true elseif not continue then report (false, 3, "util", - "Task completed, exiting", "%s", actionname) + "Task completed, exiting.", "%s", actionname) exit = true else report (false, 3, "util", - "Task completed successfully", "%s", actionname) + "Task completed successfully.", "%s", actionname) end end if exit then break end |