diff options
| author | ruki <[email protected]> | 2025-10-30 17:48:35 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-10-30 17:48:35 +0800 |
| commit | fe201a2122485acc09e5c5bda75cdb14a7d4d50e (patch) | |
| tree | fe8a6eabbec8412f135b00fe3b7c3e1ec12d21ad | |
| parent | ca24c32ea55d64c99d8cb911e03a02272b9350c4 (diff) | |
| parent | 1a6b82357bb1ba437984fc46c77aa812873a169c (diff) | |
Merge pull request #6974 from xmake-io/progress
Support for multi-row refresh progress output
24 files changed, 475 insertions, 172 deletions
diff --git a/tests/apis/custom_toolchain/xmake/modules/core/tools/cl6x.lua b/tests/apis/custom_toolchain/xmake/modules/core/tools/cl6x.lua index ba6bc6e5e..37ceec0b8 100644 --- a/tests/apis/custom_toolchain/xmake/modules/core/tools/cl6x.lua +++ b/tests/apis/custom_toolchain/xmake/modules/core/tools/cl6x.lua @@ -183,10 +183,7 @@ function compile(self, sourcefile, objectfile, dependinfo, flags, opt) -- print some warnings if warnings and #warnings > 0 and policy.build_warnings(opt) then - if progress.showing_without_scroll() then - print("") - end - cprint("${color.warning}%s", table.concat(table.slice(warnings:split('\n'), 1, 8), '\n')) + progress.show_output("${color.warning}%s", table.concat(table.slice(warnings:split('\n'), 1, 8), '\n')) end -- generate the dependent includes diff --git a/xmake/actions/build/deprecated/build.lua b/xmake/actions/build/deprecated/build.lua index 336a7bb0d..dd6698b96 100644 --- a/xmake/actions/build/deprecated/build.lua +++ b/xmake/actions/build/deprecated/build.lua @@ -273,8 +273,8 @@ function main(targets_root, opt) local curdir = os.curdir() runjobs("build", batchjobs, {on_exit = function (errors) import("utils.progress") - if errors and progress.showing_without_scroll() then - print("") + if errors then + progress.show_output("") end end, comax = option.get("jobs") or 1, curdir = curdir, distcc = distcc}) os.cd(curdir) diff --git a/xmake/core/base/io.lua b/xmake/core/base/io.lua index d58d46297..f35c01ab3 100644 --- a/xmake/core/base/io.lua +++ b/xmake/core/base/io.lua @@ -254,6 +254,10 @@ end -- this file is a tty? function _file:isatty() + local isatty_cached = self._ISATTY + if isatty_cached ~= nil then + return isatty_cached + end -- ensure opened local ok, errors = self:_ensure_opened() @@ -266,6 +270,7 @@ function _file:isatty() if ok == nil and errors then errors = string.format("%s: %s", self, errors) end + self._ISATTY = ok return ok, errors end diff --git a/xmake/modules/async/runjobs.lua b/xmake/modules/async/runjobs.lua index e43286a13..be1cc2ad5 100644 --- a/xmake/modules/async/runjobs.lua +++ b/xmake/modules/async/runjobs.lua @@ -196,6 +196,7 @@ function _consume_jobs_loop(state, run_in_remote) end -- run job + co_running:data_set("runjobs.running", true) local job_index = state.finished_count + 1 state.running_jobs_indices[job_index] = job_index if job_func then @@ -206,6 +207,7 @@ function _consume_jobs_loop(state, run_in_remote) job_func(job_index, total, {progress = state.progress_wrapper}) end state.running_jobs_indices[job_index] = nil + co_running:data_set("runjobs.running", false) end, catch { diff --git a/xmake/modules/core/tools/armasm.lua b/xmake/modules/core/tools/armasm.lua index 33b77d915..2324e7d16 100644 --- a/xmake/modules/core/tools/armasm.lua +++ b/xmake/modules/core/tools/armasm.lua @@ -128,10 +128,7 @@ function compile(self, sourcefile, objectfile, dependinfo, flags, opt) -- print some warnings if warnings and #warnings > 0 and policy.build_warnings(opt) then - if progress.showing_without_scroll() then - print("") - end - cprint("${color.warning}%s", table.concat(table.slice(warnings:split('\n'), 1, 8), '\n')) + progress.show_output("${color.warning}%s", table.concat(table.slice(warnings:split('\n'), 1, 8), '\n')) end end } diff --git a/xmake/modules/core/tools/armasm_msvc.lua b/xmake/modules/core/tools/armasm_msvc.lua index 3afac67f4..f62820ce2 100644 --- a/xmake/modules/core/tools/armasm_msvc.lua +++ b/xmake/modules/core/tools/armasm_msvc.lua @@ -122,10 +122,7 @@ function compile(self, sourcefile, objectfile, dependinfo, flags, opt) -- print some warnings if warnings and #warnings > 0 and policy.build_warnings(opt) then - if progress.showing_without_scroll() then - print("") - end - cprint("${color.warning}%s", table.concat(table.slice(warnings:split('\n'), 1, 8), '\n')) + progress.show_output("${color.warning}%s", table.concat(table.slice(warnings:split('\n'), 1, 8), '\n')) end end } diff --git a/xmake/modules/core/tools/armcc.lua b/xmake/modules/core/tools/armcc.lua index 93d59aa8d..5179796c7 100644 --- a/xmake/modules/core/tools/armcc.lua +++ b/xmake/modules/core/tools/armcc.lua @@ -177,10 +177,7 @@ function compile(self, sourcefile, objectfile, dependinfo, flags, opt) -- print some warnings if warnings and #warnings > 0 and policy.build_warnings(opt) then - if progress.showing_without_scroll() then - print("") - end - cprint("${color.warning}%s", table.concat(table.slice(warnings:split('\n'), 1, 8), '\n')) + progress.show_output("${color.warning}%s", table.concat(table.slice(warnings:split('\n'), 1, 8), '\n')) end -- generate the dependent includes diff --git a/xmake/modules/core/tools/armlink.lua b/xmake/modules/core/tools/armlink.lua index 8b8639754..7d5184a50 100644 --- a/xmake/modules/core/tools/armlink.lua +++ b/xmake/modules/core/tools/armlink.lua @@ -116,10 +116,7 @@ function link(self, objectfiles, targetkind, targetfile, flags, opt) lines = table.slice(lines, 1, (#lines > 16 and 16 or #lines)) end local warnings = table.concat(lines, "\n") - if progress.showing_without_scroll() then - print("") - end - cprint("${color.warning}%s", warnings) + progress.show_output("${color.warning}%s", warnings) end end diff --git a/xmake/modules/core/tools/c51.lua b/xmake/modules/core/tools/c51.lua index 0df971f8d..87a602fcc 100644 --- a/xmake/modules/core/tools/c51.lua +++ b/xmake/modules/core/tools/c51.lua @@ -119,10 +119,7 @@ function compile(self, sourcefile, objectfile, dependinfo, flags, opt) lines = table.slice(lines, 1, (#lines > 16 and 16 or #lines)) end local warnings = table.concat(lines, "\n") - if progress.showing_without_scroll() then - print("") - end - cprint("${color.warning}%s", warnings) + progress.show_output("${color.warning}%s", warnings) end end end diff --git a/xmake/modules/core/tools/cl.lua b/xmake/modules/core/tools/cl.lua index 0a6416ded..fa88ab5a4 100644 --- a/xmake/modules/core/tools/cl.lua +++ b/xmake/modules/core/tools/cl.lua @@ -764,10 +764,7 @@ function compile(self, sourcefile, objectfile, dependinfo, flags, opt) lines = table.slice(lines, 1, (#lines > 16 and 16 or #lines)) end local warnings = table.concat(lines, "\r\n") - if progress.showing_without_scroll() then - print("") - end - cprint("${color.warning}%s", warnings) + progress.show_output("${color.warning}%s", warnings) end end end diff --git a/xmake/modules/core/tools/cl2000.lua b/xmake/modules/core/tools/cl2000.lua index 8b8f1f563..b977bc947 100644 --- a/xmake/modules/core/tools/cl2000.lua +++ b/xmake/modules/core/tools/cl2000.lua @@ -183,10 +183,7 @@ function compile(self, sourcefile, objectfile, dependinfo, flags, opt) -- print some warnings if warnings and #warnings > 0 and policy.build_warnings(opt) then - if progress.showing_without_scroll() then - print("") - end - cprint("${color.warning}%s", table.concat(table.slice(warnings:split('\n'), 1, 8), '\n')) + progress.show_output("${color.warning}%s", table.concat(table.slice(warnings:split('\n'), 1, 8), '\n')) end -- generate the dependent includes diff --git a/xmake/modules/core/tools/cl6x.lua b/xmake/modules/core/tools/cl6x.lua index ba6bc6e5e..37ceec0b8 100644 --- a/xmake/modules/core/tools/cl6x.lua +++ b/xmake/modules/core/tools/cl6x.lua @@ -183,10 +183,7 @@ function compile(self, sourcefile, objectfile, dependinfo, flags, opt) -- print some warnings if warnings and #warnings > 0 and policy.build_warnings(opt) then - if progress.showing_without_scroll() then - print("") - end - cprint("${color.warning}%s", table.concat(table.slice(warnings:split('\n'), 1, 8), '\n')) + progress.show_output("${color.warning}%s", table.concat(table.slice(warnings:split('\n'), 1, 8), '\n')) end -- generate the dependent includes diff --git a/xmake/modules/core/tools/gcc.lua b/xmake/modules/core/tools/gcc.lua index f4cd684e0..6d5f59fac 100644 --- a/xmake/modules/core/tools/gcc.lua +++ b/xmake/modules/core/tools/gcc.lua @@ -1058,10 +1058,7 @@ function compile(self, sourcefile, objectfile, dependinfo, flags, opt) lines = table.slice(lines, 1, (#lines > 16 and 16 or #lines)) end local warnings = table.concat(lines, "\n") - if progress.showing_without_scroll() then - print("") - end - cprint("${color.warning}%s", warnings) + progress.show_output("${color.warning}%s", warnings) end end diff --git a/xmake/modules/core/tools/iccarm.lua b/xmake/modules/core/tools/iccarm.lua index 50f9260cb..c1b763550 100644 --- a/xmake/modules/core/tools/iccarm.lua +++ b/xmake/modules/core/tools/iccarm.lua @@ -141,10 +141,7 @@ function compile(self, sourcefile, objectfile, dependinfo, flags, opt) -- print some warnings if warnings and #warnings > 0 and not warnings:find("Warnings: none", 1, true) and policy.build_warnings(opt) then - if progress.showing_without_scroll() then - print("") - end - cprint("${color.warning}%s", table.concat(table.slice(warnings:split('\n'), 1, 8), '\n')) + progress.show_output("${color.warning}%s", table.concat(table.slice(warnings:split('\n'), 1, 8), '\n')) end end } diff --git a/xmake/modules/core/tools/nvcc.lua b/xmake/modules/core/tools/nvcc.lua index aab088bf4..27f0340f5 100644 --- a/xmake/modules/core/tools/nvcc.lua +++ b/xmake/modules/core/tools/nvcc.lua @@ -450,10 +450,7 @@ function compile(self, sourcefile, objectfile, dependinfo, flags, opt) -- print some warnings if warnings and #warnings > 0 and policy.build_warnings(opt) then - if progress.showing_without_scroll() then - print("") - end - cprint("${color.warning}%s", table.concat(table.slice(warnings:split('\n', {plain = true}), 1, 8), '\n')) + progress.show_output("${color.warning}%s", table.concat(table.slice(warnings:split('\n', {plain = true}), 1, 8), '\n')) end -- generate the dependent includes diff --git a/xmake/modules/core/tools/sdasstm8.lua b/xmake/modules/core/tools/sdasstm8.lua index 883d31345..b9ad0e153 100644 --- a/xmake/modules/core/tools/sdasstm8.lua +++ b/xmake/modules/core/tools/sdasstm8.lua @@ -83,13 +83,8 @@ function compile(self, sourcefile, objectfile, dependinfo, flags, opt) finally { function (ok, warnings) - - -- print some warnings if warnings and #warnings > 0 and policy.build_warnings(opt) then - if progress.showing_without_scroll() then - print("") - end - cprint("${color.warning}%s", table.concat(table.slice(warnings:split('\n'), 1, 8), '\n')) + progress.show_output("${color.warning}%s", table.concat(table.slice(warnings:split('\n'), 1, 8), '\n')) end end } diff --git a/xmake/modules/core/tools/sdcc.lua b/xmake/modules/core/tools/sdcc.lua index b6461cafe..ebf8ef519 100644 --- a/xmake/modules/core/tools/sdcc.lua +++ b/xmake/modules/core/tools/sdcc.lua @@ -234,10 +234,7 @@ function compile(self, sourcefile, objectfile, dependinfo, flags, opt) -- show warnings if warnings and #warnings > 0 and policy.build_warnings(opt) then - if progress.showing_without_scroll() then - print("") - end - cprint("${color.warning}%s", table.concat(table.slice(warnings:split('\n'), 1, 8), '\n')) + progress.show_output("${color.warning}%s", table.concat(table.slice(warnings:split('\n'), 1, 8), '\n')) end -- generate the dependent includes diff --git a/xmake/modules/private/action/build/target.lua b/xmake/modules/private/action/build/target.lua index 19d4db853..315feab61 100644 --- a/xmake/modules/private/action/build/target.lua +++ b/xmake/modules/private/action/build/target.lua @@ -787,8 +787,8 @@ function run_targetjobs(targets_root, opt) local curdir = os.curdir() async_runjobs(job_kind, jobgraph, {on_exit = function (errors) import("utils.progress") - if errors and progress.showing_without_scroll() then - print("") + if errors then + progress.show_output("") end end, comax = opt.jobs or option.get("jobs") or 1, curdir = curdir, @@ -807,8 +807,8 @@ function run_filejobs(targets_root, opt) local curdir = os.curdir() async_runjobs(job_kind, jobgraph, {on_exit = function (errors) import("utils.progress") - if errors and progress.showing_without_scroll() then - print("") + if errors then + progress.show_output("") end end, comax = opt.jobs or option.get("jobs") or 1, curdir = curdir, diff --git a/xmake/modules/private/utils/batchcmds.lua b/xmake/modules/private/utils/batchcmds.lua index 268f34154..378f4f0f5 100644 --- a/xmake/modules/private/utils/batchcmds.lua +++ b/xmake/modules/private/utils/batchcmds.lua @@ -35,48 +35,20 @@ import("utils.binary.rpath", {alias = "rpath_utils"}) -- define module local batchcmds = batchcmds or object { _init = {"_TARGET", "_CMDS", "_DEPINFO", "_tip"}} --- show text -function _show(showtext, progress) - if option.get("verbose") then - cprint(showtext) - else - local is_scroll = _g.is_scroll - if is_scroll == nil then - is_scroll = theme.get("text.build.progress_style") == "scroll" - _g.is_scroll = is_scroll - end - if is_scroll then - cprint(showtext) - else - tty.erase_line_to_start().cr() - local msg = showtext - local msg_plain = colors.translate(msg, {plain = true}) - local maxwidth = os.getwinsize().width - if #msg_plain <= maxwidth then - cprintf(msg) - else - -- windows width is too small? strip the partial message in middle - local partlen = math.floor(maxwidth / 2) - 3 - local sep = msg_plain:sub(partlen + 1, #msg_plain - partlen - 1) - local split = msg:split(sep, {plain = true, strict = true}) - cprintf(table.concat(split, "...")) - end - if math.floor(progress:percent()) == 100 then - print("") - _g.showing_without_scroll = false - else - _g.showing_without_scroll = true - end - io.flush() - end +-- run command: show +function _runcmd_show(cmd, opt) + local format = cmd.format + if format then + cprint(format, table.unpack(cmd.argv)) end end --- run command: show -function _runcmd_show(cmd, opt) - local showtext = cmd.showtext - if showtext then - _show(showtext, cmd.progress) +-- run command: show_progress +function _runcmd_show_progress(cmd, opt) + local format = cmd.format + local progress = cmd.progress + if format and progress then + progress_utils.show(progress, format, table.unpack(cmd.argv)) end end @@ -242,24 +214,25 @@ function _runcmd(cmd, opt) if not maps then maps = { - show = _runcmd_show, - runv = _runcmd_runv, - vrunv = _runcmd_vrunv, - execv = _runcmd_execv, - vexecv = _runcmd_vexecv, - lua = _runcmd_lua, - vlua = _runcmd_vlua, - mkdir = _runcmd_mkdir, - rmdir = _runcmd_rmdir, - cd = _runcmd_cd, - rm = _runcmd_rm, - cp = _runcmd_cp, - mv = _runcmd_mv, - ln = _runcmd_ln, - clean_rpath = _runcmd_clean_rpath, - insert_rpath = _runcmd_insert_rpath, - remove_rpath = _runcmd_remove_rpath, - change_rpath = _runcmd_change_rpath + show = _runcmd_show, + show_progress = _runcmd_show_progress, + runv = _runcmd_runv, + vrunv = _runcmd_vrunv, + execv = _runcmd_execv, + vexecv = _runcmd_vexecv, + lua = _runcmd_lua, + vlua = _runcmd_vlua, + mkdir = _runcmd_mkdir, + rmdir = _runcmd_rmdir, + cd = _runcmd_cd, + rm = _runcmd_rm, + cp = _runcmd_cp, + mv = _runcmd_mv, + ln = _runcmd_ln, + clean_rpath = _runcmd_clean_rpath, + insert_rpath = _runcmd_insert_rpath, + remove_rpath = _runcmd_remove_rpath, + change_rpath = _runcmd_change_rpath } _g.maps = maps end @@ -470,8 +443,12 @@ end -- add command: show function batchcmds:show(format, ...) - local showtext = string.format(format, ...) - table.insert(self:cmds(), {kind = "show", showtext = showtext}) + table.insert(self:cmds(), {kind = "show", format = format, argv = table.pack(...)}) +end + +-- add command: show progress +function batchcmds:show_progress(progress, format, ...) + table.insert(self:cmds(), {kind = "show_progress", progress = progress, format = format, argv = table.pack(...)}) end -- add command: clean rpath @@ -499,14 +476,6 @@ function batchcmds:rawcmd(kind, rawstr) table.insert(self:cmds(), {kind = kind, rawstr = rawstr}) end --- add command: show progress -function batchcmds:show_progress(progress, format, ...) - if progress then - local showtext = progress_utils.text(progress, format, ...) - table.insert(self:cmds(), {kind = "show", showtext = showtext, progress = progress}) - end -end - -- get depinfo function batchcmds:depinfo() return self._DEPINFO diff --git a/xmake/modules/utils/progress.lua b/xmake/modules/utils/progress.lua index 264ab3bfd..a29d7cd2b 100644 --- a/xmake/modules/utils/progress.lua +++ b/xmake/modules/utils/progress.lua @@ -23,11 +23,17 @@ import("core.base.option") import("core.base.object") import("core.base.colors") import("core.base.tty") +import("core.base.scheduler") import("core.theme.theme") -- define module local progress = progress or object { _init = { "_RUNNING", "_INDEX", "_STREAM", "_OPT" } } +-- cache color strings +local COLOR_SUPERSLOW = "${color.build.progress_superslow}" +local COLOR_VERYSLOW = "${color.build.progress_veryslow}" +local COLOR_SLOW = "${color.build.progress_slow}" + -- stop the progress indicator, clear written frames function progress:stop() if self._RUNNING ~= 0 then @@ -68,54 +74,312 @@ function progress:running() return self._RUNNING and true or false end --- showing progress line without scroll? -function showing_without_scroll() - return _g.showing_without_scroll +-- is scroll output? +function _is_scroll() + local is_scroll = _g.is_scroll + if is_scroll == nil then + local style = theme.get("text.build.progress_style") or "scroll" + if style == "scroll" then + is_scroll = true + end + _g.is_scroll = is_scroll + end + return is_scroll +end + +-- is multi-row refresh output? +function _is_multirow_refresh() + local is_multirow_refresh = _g.is_multirow_refresh + if is_multirow_refresh == nil then + local style = theme.get("text.build.progress_style") + if style == "multirow_refresh" and tty.has_vtansi() and io.isatty() then + is_multirow_refresh = true + end + _g.is_multirow_refresh = is_multirow_refresh + end + return is_multirow_refresh +end + +-- is single-row refresh output? +function _is_singlerow_refresh() + local is_singlerow_refresh = _g.is_singlerow_refresh + if is_singlerow_refresh == nil then + local style = theme.get("text.build.progress_style") + if style == "singlerow_refresh" and tty.has_vtansi() and io.isatty() then + is_singlerow_refresh = true + end + _g.is_singlerow_refresh = is_singlerow_refresh + end + return is_singlerow_refresh +end + +-- get progress prefix +function _get_progress_prefix() + if not _g.progress_prefix then + _g.progress_prefix = "${color.build.progress}" .. theme.get("text.build.progress_format") .. ":${clear} " + end + return _g.progress_prefix +end + +-- strip progress line +function _strip_progress_line(msg, maxwidth) + local msg_plain = colors.translate(msg, {plain = true}) + if #msg_plain > maxwidth then + -- windows width is too small? strip the partial message in middle + local partlen = math.floor(maxwidth / 2) - 3 + local sep = msg_plain:sub(partlen + 1, #msg_plain - partlen - 1) + local split = msg:split(sep, {plain = true, strict = true}) + msg = table.concat(split, "...") + end + return msg +end + +-- show progress with verbose information +function _show_progress_with_verbose(progress, format, ...) + cprint(_get_progress_prefix() .. "${dim}" .. format, progress, ...) +end + +-- show progress with scroll +function _show_progress_with_scroll(progress, format, ...) + cprint(_get_progress_prefix() .. format, progress, ...) +end + +-- build ordered subprocess line infos from progress_lineinfos (internal helper) +function _build_ordered_subprocess_lineinfos(maxwidth, current_time) + local progress_lineinfos = _g.progress_lineinfos + if not progress_lineinfos then + return {} + end + + local order_lineinfos = {} + for _, progress_lineinfo in pairs(progress_lineinfos) do + local progress_msg = progress_lineinfo.progress_msg + if progress_msg then + local spent_time = current_time - progress_lineinfo.start_time + local time_seconds = spent_time / 1000 + + -- determine color based on time (use cached color constants) + local timecolor + if spent_time > 30000 then + timecolor = COLOR_SUPERSLOW + elseif spent_time > 1000 then + timecolor = COLOR_VERYSLOW + elseif spent_time > 500 then + timecolor = COLOR_SLOW + else + timecolor = "" + end + + progress_lineinfo.spent_time = spent_time + -- use string.format instead of vformat for better performance + local time_str = string.format("%s%0.02fs${clear} ", timecolor, time_seconds) + local subprogress_line = _strip_progress_line(" > " .. time_str .. progress_msg, maxwidth) + progress_lineinfo.progress_line = subprogress_line + table.insert(order_lineinfos, progress_lineinfo) + else + progress_lineinfo.spent_time = 0 + progress_lineinfo.progress_line = nil + end + end + + table.sort(order_lineinfos, function (a, b) return a.spent_time > b.spent_time end) + return order_lineinfos +end + +-- display subprocess progress lines (internal helper) +function _display_subprocess_lines(order_lineinfos) + local linecount = 0 + for _, lineinfo in ipairs(order_lineinfos) do + -- we need not show it if the progress job is idle in runjobs now + local progress_running = lineinfo.running + if progress_running and progress_running:data("runjobs.running") == false then + lineinfo.progress_line = nil + end + if lineinfo.progress_line then + tty.erase_line().cr() + cprint(lineinfo.progress_line) + linecount = linecount + 1 + end + end + _g.linecount = linecount +end + +-- redraw the multirow progress area (internal helper) +function _redraw_multirow_progress(maxwidth) + local last_total_progress = _g.last_total_progress + if not last_total_progress then + return + end + + -- redraw the total progress line + tty.erase_line().cr() + cprint(last_total_progress) + + -- build and display the subprocess lines + local current_time = os.mclock() + local order_lineinfos = _build_ordered_subprocess_lineinfos(maxwidth, current_time) + _display_subprocess_lines(order_lineinfos) + io.flush() +end + +-- show progress with multi-row refresh +-- @see https://github.com/xmake-io/xmake/issues/6805 +function _show_progress_with_multirow_refresh(progress, format, ...) + local running = scheduler.co_running() + if not running then + _show_progress_with_scroll(progress, format, ...) + return + end + + -- get window size and time once + local maxwidth = os.getwinsize().width + local current_time = os.mclock() + + -- get progress line + local is_first = false + local is_finished = math.floor(progress) == 100 + local progress_msg = vformat(format, ...) + local progress_line = _strip_progress_line(string.format(_get_progress_prefix(), progress) .. progress_msg, maxwidth) + + local progress_lineinfos = _g.progress_lineinfos + if progress_lineinfos == nil then + progress_lineinfos = {} + _g.progress_lineinfos = progress_lineinfos + is_first = true + end + if is_first then + tty.cursor_hide() + os.atexit(function (ok, errors) + tty.cursor_show() + end) + end + + -- show the total progress line + local linecount = _g.linecount or 0 + if not is_first and linecount > 0 then + tty.cursor_move_up(linecount + 1) + end + tty.erase_line().cr() + cprint(progress_line) + + -- save the total progress line and progress value for potential redraw in show_output + _g.last_total_progress = progress_line + _g.last_total_progress_value = progress + + -- update the current progress info + local current_lineinfo = progress_lineinfos[running] + if current_lineinfo == nil then + current_lineinfo = {start_time = current_time, spent_time = 0, running = running} + progress_lineinfos[running] = current_lineinfo + end + if is_finished then + current_lineinfo.progress_msg = nil + else + current_lineinfo.progress_msg = progress_msg + end + + -- build and display the subprocess lines + if not is_finished then + local order_lineinfos = _build_ordered_subprocess_lineinfos(maxwidth, current_time) + current_lineinfo.start_time = current_time + _display_subprocess_lines(order_lineinfos) + _g.refresh_mode = "multirow" + else + -- when finished, clear all subprocess lines without leaving empty lines + local old_linecount = _g.linecount or 0 + if old_linecount > 0 then + tty.erase_down() + end + _g.refresh_mode = nil + _g.progress_lineinfos = nil + _g.last_total_progress = nil + _g.last_total_progress_value = nil + _g.linecount = 0 + tty.cursor_show() + end + io.flush() +end + +-- show progress with single-row refresh (ninja style) +function _show_progress_with_singlerow_refresh(progress, format, ...) + local maxwidth = os.getwinsize().width + local is_finished = math.floor(progress) == 100 + local progress_msg = vformat(format, ...) + local progress_line = _strip_progress_line(string.format(_get_progress_prefix(), progress) .. progress_msg, maxwidth) + tty.erase_line().cr() + cprintf(progress_line) + if is_finished then + print("") + _g.refresh_mode = nil + else + _g.refresh_mode = "singlerow" + end + io.flush() end -- show the message with progress function show(progress, format, ...) progress = type(progress) == "table" and progress:percent() or math.floor(progress) - local progress_prefix = "${color.build.progress}" .. theme.get("text.build.progress_format") .. ":${clear} " if option.get("verbose") then - cprint(progress_prefix .. "${dim}" .. format, progress, ...) + _show_progress_with_verbose(progress, format, ...) + elseif _is_scroll() then + _show_progress_with_scroll(progress, format, ...) + elseif _is_multirow_refresh() then + _show_progress_with_multirow_refresh(progress, format, ...) + elseif _is_singlerow_refresh() then + _show_progress_with_singlerow_refresh(progress, format, ...) else - local is_scroll = _g.is_scroll - if is_scroll == nil then - is_scroll = theme.get("text.build.progress_style") == "scroll" - _g.is_scroll = is_scroll - end - if is_scroll then - cprint(progress_prefix .. format, progress, ...) - else - tty.erase_line_to_start().cr() - local msg = vformat(progress_prefix .. format, progress, ...) - local msg_plain = colors.translate(msg, {plain = true}) - local maxwidth = os.getwinsize().width - if #msg_plain <= maxwidth then - cprintf(msg) - else - -- windows width is too small? strip the partial message in middle - local partlen = math.floor(maxwidth / 2) - 3 - local sep = msg_plain:sub(partlen + 1, #msg_plain - partlen - 1) - local split = msg:split(sep, {plain = true, strict = true}) - cprintf(table.concat(split, "...")) - end - if math.floor(progress) == 100 then - print("") - _g.showing_without_scroll = false - else - _g.showing_without_scroll = true + _show_progress_with_scroll(progress, format, ...) + end +end + +-- print additional output logs with colors outside the progress log area, such as warning logs. +-- it's used when the progress style is multirow/singlerow refresh. +function show_output(format, ...) + local refresh_mode = _g.refresh_mode + if refresh_mode == "singlerow" then + print("") + cprint(format, ...) + elseif refresh_mode == "multirow" then + -- get window size once and the number of fixed progress lines at the bottom + -- +1 for the total progress line + local maxwidth = os.getwinsize().width + local linecount = (_g.linecount or 0) + 1 + + -- move to the top of progress area and clear to bottom + tty.cursor_move_up(linecount) + tty.erase_down() + tty.cr() + + -- show the current task's progress line before the log output + local progress_lineinfos = _g.progress_lineinfos + if progress_lineinfos then + local running = scheduler.co_running() + if running then + local current_lineinfo = progress_lineinfos[running] + if current_lineinfo and current_lineinfo.progress_msg then + local progress_value = _g.last_total_progress_value or 0 + local progress_line = _strip_progress_line(string.format(_get_progress_prefix(), math.floor(progress_value)) .. current_lineinfo.progress_msg, maxwidth) + tty.erase_line_to_end() + cprint(progress_line) + end end - io.flush() end + + -- print the log output, which will scroll naturally + cprint(format, ...) + + -- redraw the progress area immediately + _redraw_multirow_progress(maxwidth) + else + cprint(format, ...) end end -- get the message text with progress function text(progress, format, ...) progress = type(progress) == "table" and progress:percent() or math.floor(progress) - local progress_prefix = "${color.build.progress}" .. theme.get("text.build.progress_format") .. ":${clear} " + local progress_prefix = _get_progress_prefix() if option.get("verbose") then return string.format(progress_prefix .. "${dim}" .. format, progress, ...) else @@ -128,8 +392,6 @@ end -- @params opt - options -- - chars - an array of chars for progress indicator function new(stream, opt) - - -- set default values stream = stream or io.stdout opt = opt or {} if opt.chars == nil or #opt.chars == 0 then diff --git a/xmake/plugins/project/cmake/cmakelists.lua b/xmake/plugins/project/cmake/cmakelists.lua index a4d8361e1..167d4bb5f 100644 --- a/xmake/plugins/project/cmake/cmakelists.lua +++ b/xmake/plugins/project/cmake/cmakelists.lua @@ -1222,8 +1222,9 @@ function _get_command_string(cmd, outputdir) return string.format("cd %s", _get_relative_unix_path_to_cmake(cmd.dir, outputdir)) elseif kind == "mkdir" then return string.format("${CMAKE_COMMAND} -E make_directory %s", _get_relative_unix_path_to_cmake(cmd.dir, outputdir)) - elseif kind == "show" then - local text = colors.ignore(cmd.showtext) + elseif kind == "show" or kind == "show_progress" then + local text = string.format(cmd.format, table.unpack(cmd.argv)) + text = colors.ignore(text) -- we need to translate paths in text -- https://github.com/xmake-io/xmake/issues/6553 if is_host("windows") then diff --git a/xmake/plugins/project/make/makefile.lua b/xmake/plugins/project/make/makefile.lua index 7e818164c..3128a1c96 100644 --- a/xmake/plugins/project/make/makefile.lua +++ b/xmake/plugins/project/make/makefile.lua @@ -267,8 +267,9 @@ function _get_command_string(cmd, outputdir) return _get_cmd_cd(_get_relative_unix_path(cmd.dir, outputdir)) elseif kind == "mkdir" then return _get_cmd_mkdir(_get_relative_unix_path(cmd.dir, outputdir)) - elseif kind == "show" then - return _get_cmd_echo(cmd.showtext) + elseif kind == "show" or kind == "show_progress" then + local text = string.format(cmd.format, table.unpack(cmd.argv)) + return _get_cmd_echo(text) end end diff --git a/xmake/plugins/project/vstudio/impl/vs201x.lua b/xmake/plugins/project/vstudio/impl/vs201x.lua index 823787ab1..bc31acbbe 100644 --- a/xmake/plugins/project/vstudio/impl/vs201x.lua +++ b/xmake/plugins/project/vstudio/impl/vs201x.lua @@ -118,8 +118,9 @@ function _get_command_string(cmd, vcxprojdir) elseif kind == "mkdir" then local dir = _translate_path(cmd.dir, vcxprojdir) return string.format("if not exist \"%s\" mkdir \"%s\"", dir, dir) - elseif kind == "show" then - return string.format("echo %s", colors.ignore(cmd.showtext)) + elseif kind == "show" or kind == "show_progress" then + local text = string.format(cmd.format, table.unpack(cmd.argv)) + return string.format("echo %s", colors.ignore(text)) end end diff --git a/xmake/themes/soong/xmake.lua b/xmake/themes/soong/xmake.lua new file mode 100644 index 000000000..090e8f017 --- /dev/null +++ b/xmake/themes/soong/xmake.lua @@ -0,0 +1,106 @@ +--!A cross-platform build utility based on Lua +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- +-- Copyright (C) 2015-present, Xmake Open Source Community. +-- +-- @author ruki +-- @file xmake.lua +-- + +-- define theme +theme("soong") + + -- the success status + set_text("success", "$ok") + set_color("success", "green bright") + + -- the failure status + set_text("failure", "$failed") + set_color("failure", "red bright") + + -- the nothing status + set_text("nothing", "$no") + set_color("nothing", "red bright") + + -- the error info + set_text("error", "$error") + set_color("error", "red bright") + + -- the warning info + set_text("warning", "$warning") + set_color("warning", "yellow bright") + + -- the building progress + set_text("build.progress_format", "[%3d%%]") + set_text("build.progress_style", "multirow_refresh") + set_color("build.progress", "green bright") + -- only for multirow_refresh + set_color("build.progress_superslow", "red") + set_color("build.progress_veryslow", "magenta") + set_color("build.progress_slow", "yellow") + + -- the building object file + set_color("build.object", "") + + -- the building target file + if is_subhost("windows") and (os.term() == "powershell" or os.term() == "pwsh") then + set_color("build.target", "cyan bright") + else + set_color("build.target", "magenta bright") + end + + -- the spinner chars + if (is_subhost("windows") and winos.version():lt("win10")) or is_subhost("msys", "cygwin") then + set_text("spinner.chars", '\\', '-', '/', '|') + else + set_text("spinner.chars", '⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏') + end + + -- color dump + set_text("dump.default_format", "%s") + set_text("dump.udata_format", "%s") + set_text("dump.table_format", "%s") + set_text("dump.anchor", "&%s") + set_text("dump.reference", "*%s") + set_color("dump.anchor", "yellow") + set_color("dump.reference", "yellow") + set_color("dump.default", "red") + set_color("dump.udata", "yellow") + set_color("dump.table", "bright") + if is_subhost("windows") and (os.term() == "powershell" or os.term() == "pwsh") then + set_color("dump.string", "red bright") + set_color("dump.string_quote", "red") + else + set_color("dump.string", "magenta bright") + set_color("dump.string_quote", "magenta") + end + set_color("dump.keyword", "blue") + set_color("dump.number", "green bright") + set_color("dump.function", "cyan") + + -- menu + if is_subhost("windows") and (os.term() == "powershell" or os.term() == "pwsh") then + set_color("menu.main.task.name", "cyan bright") + set_color("menu.option.name", "green bright") + else + set_color("menu.main.task.name", "magenta") + set_color("menu.option.name", "green") + end + set_color("menu.usage", "cyan") + + -- interactive mode + set_text("interactive.prompt", "xmake>") + set_text("interactive.prompt2", "xmake>>") + set_color("interactive.prompt", "green") + set_color("interactive.prompt2", "green") |
