summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorruki <[email protected]>2025-10-30 17:48:35 +0800
committerGitHub <[email protected]>2025-10-30 17:48:35 +0800
commitfe201a2122485acc09e5c5bda75cdb14a7d4d50e (patch)
treefe8a6eabbec8412f135b00fe3b7c3e1ec12d21ad
parentca24c32ea55d64c99d8cb911e03a02272b9350c4 (diff)
parent1a6b82357bb1ba437984fc46c77aa812873a169c (diff)
Merge pull request #6974 from xmake-io/progress
Support for multi-row refresh progress output
-rw-r--r--tests/apis/custom_toolchain/xmake/modules/core/tools/cl6x.lua5
-rw-r--r--xmake/actions/build/deprecated/build.lua4
-rw-r--r--xmake/core/base/io.lua5
-rw-r--r--xmake/modules/async/runjobs.lua2
-rw-r--r--xmake/modules/core/tools/armasm.lua5
-rw-r--r--xmake/modules/core/tools/armasm_msvc.lua5
-rw-r--r--xmake/modules/core/tools/armcc.lua5
-rw-r--r--xmake/modules/core/tools/armlink.lua5
-rw-r--r--xmake/modules/core/tools/c51.lua5
-rw-r--r--xmake/modules/core/tools/cl.lua5
-rw-r--r--xmake/modules/core/tools/cl2000.lua5
-rw-r--r--xmake/modules/core/tools/cl6x.lua5
-rw-r--r--xmake/modules/core/tools/gcc.lua5
-rw-r--r--xmake/modules/core/tools/iccarm.lua5
-rw-r--r--xmake/modules/core/tools/nvcc.lua5
-rw-r--r--xmake/modules/core/tools/sdasstm8.lua7
-rw-r--r--xmake/modules/core/tools/sdcc.lua5
-rw-r--r--xmake/modules/private/action/build/target.lua8
-rw-r--r--xmake/modules/private/utils/batchcmds.lua103
-rw-r--r--xmake/modules/utils/progress.lua332
-rw-r--r--xmake/plugins/project/cmake/cmakelists.lua5
-rw-r--r--xmake/plugins/project/make/makefile.lua5
-rw-r--r--xmake/plugins/project/vstudio/impl/vs201x.lua5
-rw-r--r--xmake/themes/soong/xmake.lua106
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")