summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorruki <[email protected]>2025-11-21 13:53:59 +0800
committerGitHub <[email protected]>2025-11-21 13:53:59 +0800
commit76a0bd604dd7dd39e6f9e49969bb1a14bc1ac3b2 (patch)
treeabb7894092479e9d6f934073b15f73b77cb01250
parent0719de2b664bdfc82ce30534ed196bfc81fc072a (diff)
parente2b694841565457fc93a2295be46bada03e4d739 (diff)
Merge pull request #7043 from xmake-io/timer
improve progress refresh timer
-rw-r--r--tests/modules/scheduler/spinner.lua2
-rw-r--r--xmake/actions/test/main.lua10
-rw-r--r--xmake/actions/update/main.lua4
-rw-r--r--xmake/modules/async/runjobs.lua370
-rw-r--r--xmake/modules/private/action/build/target.lua22
-rw-r--r--xmake/modules/private/action/require/impl/download_packages.lua8
-rw-r--r--xmake/modules/private/action/require/impl/install_packages.lua8
-rw-r--r--xmake/modules/private/check/checkers/clang/tidy.lua11
-rw-r--r--xmake/modules/utils/progress.lua101
-rw-r--r--xmake/modules/utils/waiting_indicator.lua83
-rw-r--r--xmake/plugins/format/main.lua11
-rw-r--r--xmake/plugins/repo/main.lua2
-rw-r--r--xmake/rules/go/env/xmake.lua2
13 files changed, 416 insertions, 218 deletions
diff --git a/tests/modules/scheduler/spinner.lua b/tests/modules/scheduler/spinner.lua
index 8d20bf7b7..ee442163d 100644
--- a/tests/modules/scheduler/spinner.lua
+++ b/tests/modules/scheduler/spinner.lua
@@ -4,7 +4,7 @@ function main()
printf("testing .. ")
runjobs("test", function ()
os.sleep(10000)
- end, {progress = true})
+ end, {waiting_indicator = true})
print("ok")
end
diff --git a/xmake/actions/test/main.lua b/xmake/actions/test/main.lua
index 8a917678b..0e7192d83 100644
--- a/xmake/actions/test/main.lua
+++ b/xmake/actions/test/main.lua
@@ -310,6 +310,10 @@ function _run_tests(tests)
return
end
+ -- temporarily switch to scroll mode to avoid progress refresh interference with test output
+ -- @see https://github.com/xmake-io/xmake/issues/7045
+ progress.set_style("scroll")
+
-- do test
local spent = os.mclock()
print("running tests ...")
@@ -351,7 +355,11 @@ function _run_tests(tests)
end
end, {total = #ordertests,
comax = jobs,
- isolate = true})
+ isolate = true,
+ progress_refresh = true})
+
+ -- restore the original progress style
+ progress.restore_style()
-- generate report
spent = os.mclock() - spent
diff --git a/xmake/actions/update/main.lua b/xmake/actions/update/main.lua
index 8607e7ffb..8cfe237ee 100644
--- a/xmake/actions/update/main.lua
+++ b/xmake/actions/update/main.lua
@@ -238,7 +238,7 @@ function _install(sourcedir)
if option.get("verbose") then
install_task()
else
- runjobs("update/install", install_task, {progress = true})
+ runjobs("update/install", install_task, {waiting_indicator = true})
end
end
@@ -512,7 +512,7 @@ function main()
if option.get("verbose") then
download_task()
else
- runjobs("update/download", download_task, {progress = true})
+ runjobs("update/download", download_task, {waiting_indicator = true})
end
-- leave environment
diff --git a/xmake/modules/async/runjobs.lua b/xmake/modules/async/runjobs.lua
index e08acc908..6fc316f28 100644
--- a/xmake/modules/async/runjobs.lua
+++ b/xmake/modules/async/runjobs.lua
@@ -21,6 +21,7 @@
-- imports
import("core.base.scheduler")
import("utils.progress")
+import("utils.waiting_indicator")
-- print back characters
function _print_backchars(backnum)
@@ -32,21 +33,34 @@ function _print_backchars(backnum)
end
end
--- init progress
-function _init_progress(state, opt)
+-- init waiting indicator
+function _init_waiting_indicator(state, opt)
opt = opt or {}
-
- -- init progress helper
+
+ -- init waiting indicator helper
-- we need to hide wait characters if is not a tty
- state.show_progress = io.isatty() and (opt.progress or opt.showtips)
+ local waiting_indicator_opt = opt.waiting_indicator
+
+ -- compatibility: support deprecated opt.progress parameter
+ if opt.progress ~= nil and waiting_indicator_opt == nil then
+ waiting_indicator_opt = opt.progress
+ wprint("opt.progress is deprecated in runjobs, use opt.waiting_indicator instead")
+ end
+
+ state.show_waiting_indicator = io.isatty() and (waiting_indicator_opt or type(waiting_indicator_opt) == "table")
state.backnum = 0
- if state.show_progress then
- local progress_opt = nil
- if type(state.show_progress) == "table" then
- progress_opt = state.show_progress
+ if state.show_waiting_indicator then
+ local indicator_opt = nil
+ if type(waiting_indicator_opt) == "table" then
+ indicator_opt = waiting_indicator_opt
end
- state.progress_helper = progress.new(nil, progress_opt)
+ state.waiting_indicator_helper = waiting_indicator.new(nil, indicator_opt)
end
+end
+
+-- init progress
+function _init_progress(state, opt)
+ opt = opt or {}
-- init progress wrapper
state.progress_finished_count = 0
@@ -72,13 +86,188 @@ function _init_progress(state, opt)
end
})
state.progress_wrapper = progress_wrapper
+
+end
+
+-- start timer (on_timer callback)
+function _start_timer(state, name, opt)
+ if opt.on_timer then
+ state.on_timer = opt.on_timer
+ state.group_timer = state.group_name .. "/timer"
+ -- create semaphore for timer loop to wait with timeout for quick exit
+ state.timer_semaphore = scheduler.co_semaphore(state.group_name .. "/timer", 0)
+ scheduler.co_group_begin(state.group_timer, function (co_group)
+ scheduler.co_start_withopt({name = name .. "/timer", isolate = opt.isolate}, _timer_loop, state)
+ end)
+ end
+end
+
+-- start waiting indicator timer
+function _start_waiting_indicator_timer(state, name, opt)
+ if state.show_waiting_indicator then
+ state.group_waiting_indicator_timer = state.group_name .. "/waiting_indicator"
+ -- create semaphore for waiting indicator loop to wait with timeout for quick exit
+ state.waiting_indicator_semaphore = scheduler.co_semaphore(state.group_name .. "/waiting_indicator", 0)
+ scheduler.co_group_begin(state.group_waiting_indicator_timer, function (co_group)
+ scheduler.co_start_withopt({name = name .. "/waiting_indicator", isolate = opt.isolate}, _waiting_indicator_loop, state)
+ end)
+ end
+end
+
+-- start progress refresh timer for multirow progress
+function _start_progress_refresh_timer(state, name, opt)
+ if opt.progress_refresh and progress.is_multirow() then
+ state.group_progress_refresh_timer = state.group_name .. "/progress_refresh"
+ state.all_tasks_started = false
+ -- create semaphore for refresh loop
+ state.progress_refresh_semaphore = scheduler.co_semaphore(state.group_name .. "/progress_refresh", 0)
+ -- start progress refresh loop
+ scheduler.co_group_begin(state.group_progress_refresh_timer, function (co_group)
+ scheduler.co_start_withopt({name = name .. "/progress_refresh", isolate = opt.isolate}, _progress_refresh_loop, state)
+ end)
+ end
+end
+
+-- start all timers
+function _start_timers(state, name, opt)
+ _start_timer(state, name, opt)
+ _start_waiting_indicator_timer(state, name, opt)
+ _start_progress_refresh_timer(state, name, opt)
+end
+
+-- stop all timers and notify them to exit
+function _stop_timers(state)
+ -- signal all timer loops to exit quickly
+ if state.timer_semaphore then
+ state.timer_semaphore:post(1)
+ end
+ if state.waiting_indicator_semaphore then
+ state.waiting_indicator_semaphore:post(1)
+ end
+ if state.progress_refresh_semaphore then
+ state.progress_refresh_semaphore:post(1)
+ end
+end
+
+-- wait all timer jobs exited
+function _wait_timers(state)
+ if state.group_timer then
+ scheduler.co_group_wait(state.group_timer)
+ end
+ if state.group_waiting_indicator_timer then
+ scheduler.co_group_wait(state.group_waiting_indicator_timer)
+ end
+ if state.group_progress_refresh_timer then
+ scheduler.co_group_wait(state.group_progress_refresh_timer)
+ end
+end
+
+-- exit waiting indicator
+function _exit_waiting_indicator(state)
+ if state.show_waiting_indicator then
+ _print_backchars(state.backnum)
+ state.waiting_indicator_helper:stop()
+ end
+end
+
+-- exit progress
+function _exit_progress(state)
+ progress.show_abort()
+end
+
+-- isolate environments
+function _isolate_environments(state, opt)
+ local co_running = scheduler.co_running()
+ if co_running and opt.isolate then
+ local is_isolated = co_running:is_isolated()
+ co_running:isolate(true)
+ state.isolated_running = co_running
+ state.is_isolated = is_isolated
+ end
+end
+
+-- restore isolated environments
+function _restore_isolated_environments(state, opt)
+ if state.isolated_running and opt.isolate and state.is_isolated ~= nil then
+ state.isolated_running:isolate(state.is_isolated)
+ end
+end
+
+-- run jobs and wait for completion
+function _run_jobs(state, name, opt)
+ local distcc = opt.distcc
+ state.abort = false
+ state.abort_errors = nil
+ state.finished_count = 0
+ state.curdir = opt.curdir
+ state.waiting_count = 0
+ state.distcc_waiting_count = 0
+ scheduler.co_group_begin(state.group_name, function (co_group)
+ state.semaphore = scheduler.co_semaphore(state.group_name, 0)
+ if distcc then
+ state.distcc_semaphore = scheduler.co_semaphore(state.group_name .. "/distcc", 0)
+ end
+ -- @note we can set `remote_only = true` to run all jobs in remote only
+ local local_comax = 0
+ if not opt.remote_only then
+ local_comax = math.min(state.total, state.comax)
+ for id = 1, local_comax do
+ scheduler.co_start_withopt({name = name .. '/' .. tostring(id), isolate = opt.isolate}, _consume_jobs_loop, state, false)
+ end
+ end
+ if distcc then
+ local left_comax = state.total - local_comax
+ local remote_comax = math.min(distcc:freejobs(), left_comax)
+ for id = 1, remote_comax do
+ scheduler.co_start_withopt({name = name .. '/distcc/' .. tostring(id), isolate = opt.isolate}, _consume_jobs_loop, state, true)
+ end
+ end
+ end)
+
+ -- wait all jobs exited
+ scheduler.co_group_wait(state.group_name)
+ state.stop = true
+
+ -- stop all timers and notify them to exit
+ _stop_timers(state)
+
+ -- wait all timer jobs exited
+ _wait_timers(state)
+end
+
+-- cleanup and handle errors after jobs completion
+function _cleanup_jobs(state, opt)
+ -- restore isolated environments
+ _restore_isolated_environments(state, opt)
+
+ -- exit progress
+ _exit_progress(state)
+
+ -- exit waiting indicator
+ _exit_waiting_indicator(state)
+
+ -- do exit callback
+ if opt.on_exit then
+ opt.on_exit(state.abort_errors)
+ end
+
+ -- re-throw abort errors
+ --
+ -- @note we cannot throw it in coroutine,
+ -- because his causes a direct exit from the entire runloop and
+ -- a quick escape from nested try-catch blocks and coroutines groups.
+ -- so we can not catch runjobs errors, e.g. build fails
+ if state.abort then
+ raise(state.abort_errors)
+ end
end
-- the timer loop
function _timer_loop(state)
local timeout = state.timeout
while not state.stop do
- os.sleep(timeout)
+ -- wait for timeout, allows quick exit when state.stop is set via post
+ state.timer_semaphore:wait(timeout)
if not state.stop then
local indices
if state.running_jobs_indices then
@@ -89,12 +278,34 @@ function _timer_loop(state)
end
end
--- the progress loop
-function _progress_loop(state)
+-- the refresh loop for multirow progress (independent timer with its own timeout)
+function _progress_refresh_loop(state)
+ -- wait until all tasks have been started using semaphore
+ if state.progress_refresh_semaphore and not state.stop then
+ state.progress_refresh_semaphore:wait(-1)
+ else
+ return
+ end
+
+ -- start refreshing progress using semaphore wait with timeout for quick exit
+ while not state.stop do
+ -- wait for refresh timeout, allows quick exit when state.stop is set via post
+ state.progress_refresh_semaphore:wait(state.timeout)
+
+ -- refresh progress if not stopped
+ if not state.stop then
+ progress.refresh()
+ end
+ end
+end
+
+-- the waiting indicator loop
+function _waiting_indicator_loop(state)
local timeout = state.timeout
- local progress_helper = state.progress_helper
+ local waiting_indicator_helper = state.waiting_indicator_helper
while not state.stop do
- os.sleep(timeout)
+ -- wait for timeout, allows quick exit when state.stop is set via post
+ state.waiting_indicator_semaphore:wait(timeout)
if not state.stop then
-- show waitchars
@@ -122,14 +333,14 @@ function _progress_loop(state)
end
-- print back characters
- progress_helper:clear()
+ waiting_indicator_helper:clear()
_print_backchars(state.backnum)
if tips then
cprintf("${dim}%s${clear} ", tips)
state.backnum = #tips + 1
end
- progress_helper:write()
+ waiting_indicator_helper:write()
end
end
end
@@ -207,6 +418,17 @@ function _consume_jobs_loop(state, run_in_remote)
-- to avoid running the same task repeatedly,
-- we need to update the completion count in advance.
state.finished_count = state.finished_count + 1
+
+ -- check if all tasks have been started (all consumed, but may still be running)
+ if state.finished_count >= total and not state.all_tasks_started then
+ state.all_tasks_started = true
+ -- notify refresh loop that all tasks have been started
+ if state.progress_refresh_semaphore then
+ state.progress_refresh_semaphore:post(1)
+ end
+ end
+
+ -- run job
job_func(job_index, total, {progress = state.progress_wrapper})
-- update progress
@@ -221,12 +443,9 @@ function _consume_jobs_loop(state, run_in_remote)
-- stop timer and disable show waitchars first
state.stop = true
- -- stop progress
- progress.show_abort()
- if state.show_progress then
- _print_backchars(state.backnum)
- state.progress_helper:stop()
- end
+ -- stop progress and waiting indicator
+ _exit_progress(state)
+ _exit_waiting_indicator(state)
-- we need re-throw this errors outside scheduler
state.abort = true
@@ -270,8 +489,9 @@ end
--
-- e.g.
-- runjobs("test", function (index) print("hello") end, {total = 100, comax = 6, timeout = 1000, on_timer = function (running_jobs_indices) end})
--- runjobs("test", function () os.sleep(10000) end, { progress = true })
--- runjobs("test", function () os.sleep(10000) end, { progress = { chars = {'/','\'} } }) -- see module utils.progress
+-- runjobs("test", function () os.sleep(10000) end, { waiting_indicator = true })
+-- runjobs("test", function () os.sleep(10000) end, { waiting_indicator = { chars = {'/','\'} } }) -- see module utils.waiting_indicator
+-- runjobs("test", function () os.sleep(10000) end, { waiting_indicator = true, progress_refresh = true }) -- enable progress refresh timer for multirow progress
--
-- local jobs = jobpool.new()
-- local root = jobs:addjob("job/root", function (index, total, opt)
@@ -297,6 +517,8 @@ function main(name, jobs, opt)
state.timeout = opt.timeout or 500
state.group_name = name
state.jobs_cb = type(jobs) == "function" and jobs or nil
+ state.stop = false
+ state.running_jobs_indices = {}
assert(state.timeout < 60000, "runjobs: invalid timeout!")
-- build jobs queue
@@ -306,97 +528,21 @@ function main(name, jobs, opt)
assert(jobs, "runjobs: no jobs!")
state.jobs = jobs
- -- show waiting tips?
+ -- init waiting indicator
+ _init_waiting_indicator(state, opt)
+
+ -- init progress
_init_progress(state, opt)
-- isolate environments
- local is_isolated = false
- local co_running = scheduler.co_running()
- if co_running and opt.isolate then
- is_isolated = co_running:is_isolated()
- co_running:isolate(true)
- end
+ _isolate_environments(state, opt)
- -- run timer
- state.stop = false
- state.running_jobs_indices = {}
- local group_timer
- if opt.on_timer then
- state.on_timer = opt.on_timer
- group_timer = state.group_name .. "/timer"
- scheduler.co_group_begin(group_timer, function (co_group)
- scheduler.co_start_withopt({name = name .. "/timer", isolate = opt.isolate}, _timer_loop, state)
- end)
- elseif state.show_progress then
- group_timer = state.group_name .. "/timer"
- scheduler.co_group_begin(group_timer, function (co_group)
- scheduler.co_start_withopt({name = name .. "/tips", isolate = opt.isolate}, _progress_loop, state)
- end)
- end
+ -- start all timers
+ _start_timers(state, name, opt)
- -- run jobs
- local distcc = opt.distcc
- state.abort = false
- state.abort_errors = nil
- state.finished_count = 0
- state.curdir = opt.curdir
- state.waiting_count = 0
- state.distcc_waiting_count = 0
- scheduler.co_group_begin(state.group_name, function (co_group)
- state.semaphore = scheduler.co_semaphore(state.group_name, 0)
- if distcc then
- state.distcc_semaphore = scheduler.co_semaphore(state.group_name .. "/distcc", 0)
- end
- -- @note we can set `remote_only = true` to run all jobs in remote only
- local local_comax = 0
- if not opt.remote_only then
- local_comax = math.min(state.total, state.comax)
- for id = 1, local_comax do
- scheduler.co_start_withopt({name = name .. '/' .. tostring(id), isolate = opt.isolate}, _consume_jobs_loop, state, false)
- end
- end
- if distcc then
- local left_comax = state.total - local_comax
- local remote_comax = math.min(distcc:freejobs(), left_comax)
- for id = 1, remote_comax do
- scheduler.co_start_withopt({name = name .. '/distcc/' .. tostring(id), isolate = opt.isolate}, _consume_jobs_loop, state, true)
- end
- end
- end)
-
- -- wait all jobs exited
- scheduler.co_group_wait(state.group_name)
+ -- run jobs and wait for completion
+ _run_jobs(state, name, opt)
- -- wait timer job exited
- if group_timer then
- state.stop = true
- scheduler.co_group_wait(group_timer)
- end
-
- -- restore isolated environments
- if co_running and opt.isolate then
- co_running:isolate(is_isolated)
- end
-
- -- stop progress
- progress.show_abort()
- if state.show_progress then
- _print_backchars(state.backnum)
- state.progress_helper:stop()
- end
-
- -- do exit callback
- if opt.on_exit then
- opt.on_exit(state.abort_errors)
- end
-
- -- re-throw abort errors
- --
- -- @note we cannot throw it in coroutine,
- -- because his causes a direct exit from the entire runloop and
- -- a quick escape from nested try-catch blocks and coroutines groups.
- -- so we can not catch runjobs errors, e.g. build fails
- if state.abort then
- raise(state.abort_errors)
- end
+ -- cleanup and handle errors
+ _cleanup_jobs(state, opt)
end
diff --git a/xmake/modules/private/action/build/target.lua b/xmake/modules/private/action/build/target.lua
index c3d21f399..280610e71 100644
--- a/xmake/modules/private/action/build/target.lua
+++ b/xmake/modules/private/action/build/target.lua
@@ -797,16 +797,9 @@ function run_targetjobs(targets_root, opt)
curdir = curdir,
distcc = opt.distcc,
remote_only = opt.remote_only,
- progress_factor = opt.progress_factor
+ progress_factor = opt.progress_factor,
+ progress_refresh = true
}
- -- Only set timer for multirow progress mode
- if progress_utils.is_multirow() then
- runjobs_opt.timeout = 1000
- runjobs_opt.on_timer = function (running_indices)
- -- Periodically refresh multirow progress to update elapsed time
- progress_utils.refresh()
- end
- end
async_runjobs(job_kind, jobgraph, runjobs_opt)
os.cd(curdir)
return true
@@ -825,16 +818,9 @@ function run_filejobs(targets_root, opt)
curdir = curdir,
distcc = opt.distcc,
remote_only = opt.remote_only,
- progress_factor = opt.progress_factor
+ progress_factor = opt.progress_factor,
+ progress_refresh = true
}
- -- Only set timer for multirow progress mode
- if progress_utils.is_multirow() then
- runjobs_opt.timeout = 1000
- runjobs_opt.on_timer = function (running_indices)
- -- Periodically refresh multirow progress to update elapsed time
- progress_utils.refresh()
- end
- end
async_runjobs(job_kind, jobgraph, runjobs_opt)
os.cd(curdir)
return true
diff --git a/xmake/modules/private/action/require/impl/download_packages.lua b/xmake/modules/private/action/require/impl/download_packages.lua
index 4dfe1db9b..b25038672 100644
--- a/xmake/modules/private/action/require/impl/download_packages.lua
+++ b/xmake/modules/private/action/require/impl/download_packages.lua
@@ -25,7 +25,7 @@ import("core.base.scheduler")
import("core.project.project")
import("core.base.tty")
import("async.runjobs")
-import("utils.progress")
+import("utils.waiting_indicator", {alias = "waiting_indicator"})
import("net.fasturl")
import("private.action.require.impl.package")
import("private.action.require.impl.register_packages")
@@ -127,7 +127,7 @@ function _download_packages(packages_download)
local term_mode_stdout = tty.term_mode("stdout")
-- do download
- local progress_helper = show_wait and progress.new() or nil
+ local waiting_indicator_helper = show_wait and waiting_indicator.new() or nil
local packages_downloading = {}
local packages_pending = table.copy(packages_download)
local working_count = 0
@@ -215,14 +215,14 @@ function _download_packages(packages_download)
end
-- trace
- progress_helper:clear()
+ waiting_indicator_helper:clear()
tty.erase_line_to_start().cr()
cprintf("${yellow} => ")
if #downloading > 0 then
cprintf("downloading ${color.dump.string}%s", table.concat(downloading, ", "))
end
cprintf(" .. %s", tips and ("${dim}" .. tips .. "${clear} ") or "")
- progress_helper:write()
+ waiting_indicator_helper:write()
end, exit = function(errors)
if errors then
tty.erase_line_to_start().cr()
diff --git a/xmake/modules/private/action/require/impl/install_packages.lua b/xmake/modules/private/action/require/impl/install_packages.lua
index dea96b3f8..39d7d0bd4 100644
--- a/xmake/modules/private/action/require/impl/install_packages.lua
+++ b/xmake/modules/private/action/require/impl/install_packages.lua
@@ -25,7 +25,7 @@ import("core.base.scheduler")
import("core.project.project")
import("core.base.tty")
import("async.runjobs")
-import("utils.progress")
+import("utils.waiting_indicator", {alias = "waiting_indicator"})
import("net.fasturl")
import("private.action.require.impl.package")
import("private.action.require.impl.lock_packages")
@@ -414,7 +414,7 @@ function _do_install_packages(packages_install, packages_download, installdeps)
local term_mode_stdout = tty.term_mode("stdout")
-- do install
- local progress_helper = show_wait and progress.new() or nil
+ local waiting_indicator_helper = show_wait and waiting_indicator.new() or nil
local packages_installing = {}
local packages_downloading = {}
local packages_pending = table.copy(packages_install)
@@ -606,7 +606,7 @@ function _do_install_packages(packages_install, packages_download, installdeps)
end
-- trace
- progress_helper:clear()
+ waiting_indicator_helper:clear()
tty.erase_line_to_start().cr()
cprintf("${yellow} => ")
if #downloading > 0 then
@@ -616,7 +616,7 @@ function _do_install_packages(packages_install, packages_download, installdeps)
cprintf("%sinstalling ${color.dump.string}%s", #downloading > 0 and ", " or "", table.concat(installing, ", "))
end
cprintf(" .. %s", tips and ("${dim}" .. tips .. "${clear} ") or "")
- progress_helper:write()
+ waiting_indicator_helper:write()
end, exit = function(errors)
if errors then
tty.erase_line_to_start().cr()
diff --git a/xmake/modules/private/check/checkers/clang/tidy.lua b/xmake/modules/private/check/checkers/clang/tidy.lua
index 03568eb6e..82e9246fb 100644
--- a/xmake/modules/private/check/checkers/clang/tidy.lua
+++ b/xmake/modules/private/check/checkers/clang/tidy.lua
@@ -158,16 +158,9 @@ function _check_sourcefiles(clang_tidy, sourcefiles, opt)
local runjobs_opt = {
total = #sourcefiles,
comax = opt.jobs or os.default_njob(),
- showtips = false
+ showtips = false,
+ progress_refresh = true
}
- -- Only set timer for multirow progress mode
- if progress.is_multirow() then
- runjobs_opt.timeout = 1000
- runjobs_opt.on_timer = function (running_indices)
- -- Periodically refresh multirow progress to update elapsed time
- progress.refresh()
- end
- end
runjobs("checker.tidy", function (index, total, job_opt)
local sourcefile = sourcefiles[index]
local tidy_argv = table.join(argv, {sourcefile})
diff --git a/xmake/modules/utils/progress.lua b/xmake/modules/utils/progress.lua
index 60066ddc5..b6201d5cc 100644
--- a/xmake/modules/utils/progress.lua
+++ b/xmake/modules/utils/progress.lua
@@ -20,63 +20,23 @@
-- imports
import("core.base.option")
-import("core.base.object")
import("core.base.colors")
import("core.base.tty")
import("core.base.scheduler")
import("core.theme.theme")
import("core.project.project")
--- 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
- self:clear()
- self._RUNNING = 0
- self._INDEX = 0
- end
-end
-
-function progress:_clear()
- if self._RUNNING == 1 then
- tty.erase_line_to_end()
- self._RUNNING = 2
- return true
- end
-end
-
--- clear previous frame of the progress indicator
-function progress:clear()
- if self:_clear() then
- self._STREAM:flush()
- end
-end
-
--- write next frame of the progress indicator
-function progress:write()
- local chars = self._OPT.chars[self._INDEX % #self._OPT.chars + 1]
- tty.cursor_and_attrs_save()
- self._STREAM:write(chars)
- self._STREAM:flush()
- tty.cursor_and_attrs_restore()
- self._INDEX = self._INDEX + 1
- self._RUNNING = 1
-end
-
--- check if the progress indicator is running
-function progress:running()
- return self._RUNNING and true or false
-end
-
-- is scroll output?
function _is_scroll()
+ -- if style is forced, use it
+ if _g.forced_style then
+ return _g.forced_style == "scroll"
+ end
local is_scroll = _g.is_scroll
if is_scroll == nil then
local style = project.policy("build.progress_style") or theme.get("text.build.progress_style") or "scroll"
@@ -90,6 +50,10 @@ end
-- is multi-row refresh output?
function _is_multirow_refresh()
+ -- if style is forced, use it
+ if _g.forced_style then
+ return _g.forced_style == "multirow"
+ end
local is_multirow_refresh = _g.is_multirow_refresh
if is_multirow_refresh == nil then
local style = project.policy("build.progress_style") or theme.get("text.build.progress_style")
@@ -103,6 +67,10 @@ end
-- is single-row refresh output?
function _is_singlerow_refresh()
+ -- if style is forced, use it
+ if _g.forced_style then
+ return _g.forced_style == "singlerow"
+ end
local is_singlerow_refresh = _g.is_singlerow_refresh
if is_singlerow_refresh == nil then
local style = project.policy("build.progress_style") or theme.get("text.build.progress_style")
@@ -114,6 +82,39 @@ function _is_singlerow_refresh()
return is_singlerow_refresh
end
+-- set progress style (temporarily override the current style)
+-- @param style "scroll", "singlerow", or "multirow"
+function set_style(style)
+ -- save the original style if not already saved
+ if not _g.saved_style then
+ -- get current effective style
+ if _is_multirow_refresh() then
+ _g.saved_style = "multirow"
+ elseif _is_singlerow_refresh() then
+ _g.saved_style = "singlerow"
+ else
+ _g.saved_style = "scroll"
+ end
+ end
+
+ -- set forced style
+ _g.forced_style = style
+ -- clear cached flags to force recalculation
+ _g.is_scroll = nil
+ _g.is_multirow_refresh = nil
+ _g.is_singlerow_refresh = nil
+end
+
+-- restore progress style (restore the original style from project policy)
+function restore_style()
+ _g.forced_style = nil
+ _g.saved_style = nil
+ -- clear cached flags to force recalculation
+ _g.is_scroll = nil
+ _g.is_multirow_refresh = nil
+ _g.is_singlerow_refresh = nil
+end
+
-- get progress prefix
function _get_progress_prefix()
if not _g.progress_prefix then
@@ -475,15 +476,3 @@ function text(progress, format, ...)
end
end
--- build a progress indicator
--- @params stream - stream to write to, will use io.stdout if not provided
--- @params opt - options
--- - chars - an array of chars for progress indicator
-function new(stream, opt)
- stream = stream or io.stdout
- opt = opt or {}
- if opt.chars == nil or #opt.chars == 0 then
- opt.chars = theme.get("text.spinner.chars")
- end
- return progress {_OPT = opt, _STREAM = stream, _RUNNING = 0, _INDEX = 0}
-end
diff --git a/xmake/modules/utils/waiting_indicator.lua b/xmake/modules/utils/waiting_indicator.lua
new file mode 100644
index 000000000..afa5cfb6d
--- /dev/null
+++ b/xmake/modules/utils/waiting_indicator.lua
@@ -0,0 +1,83 @@
+--!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 waiting_indicator.lua
+--
+
+-- imports
+import("core.base.object")
+import("core.base.tty")
+import("core.theme.theme")
+
+-- define module
+local waiting_indicator = waiting_indicator or object { _init = { "_RUNNING", "_INDEX", "_STREAM", "_OPT" } }
+
+-- stop the waiting indicator, clear written frames
+function waiting_indicator:stop()
+ if self._RUNNING ~= 0 then
+ self:clear()
+ self._RUNNING = 0
+ self._INDEX = 0
+ end
+end
+
+function waiting_indicator:_clear()
+ if self._RUNNING == 1 then
+ tty.erase_line_to_end()
+ self._RUNNING = 2
+ return true
+ end
+end
+
+-- clear previous frame of the waiting indicator
+function waiting_indicator:clear()
+ if self:_clear() then
+ self._STREAM:flush()
+ end
+end
+
+-- write next frame of the waiting indicator
+function waiting_indicator:write()
+ local chars = self._OPT.chars[self._INDEX % #self._OPT.chars + 1]
+ tty.cursor_and_attrs_save()
+ self._STREAM:write(chars)
+ self._STREAM:flush()
+ tty.cursor_and_attrs_restore()
+ self._INDEX = self._INDEX + 1
+ self._RUNNING = 1
+end
+
+-- check if the waiting indicator is running
+function waiting_indicator:running()
+ return self._RUNNING and true or false
+end
+
+-- build a waiting indicator
+-- @params stream - stream to write to, will use io.stdout if not provided
+-- @params opt - options
+-- - chars - an array of chars for waiting indicator
+function new(stream, opt)
+ stream = stream or io.stdout
+ opt = opt or {}
+ if opt.chars == nil or #opt.chars == 0 then
+ opt.chars = theme.get("text.spinner.chars")
+ end
+ return waiting_indicator {_OPT = opt, _STREAM = stream, _RUNNING = 0, _INDEX = 0}
+end
+
+return {new = new}
+
diff --git a/xmake/plugins/format/main.lua b/xmake/plugins/format/main.lua
index 7a4c53394..f527b6c00 100644
--- a/xmake/plugins/format/main.lua
+++ b/xmake/plugins/format/main.lua
@@ -219,16 +219,9 @@ function main()
local runjobs_opt = {
total = #sourcefiles,
comax = jobs,
- showtips = false
+ showtips = false,
+ progress_refresh = true
}
- -- Only set timer for multirow progress mode
- if progress.is_multirow() then
- runjobs_opt.timeout = 1000
- runjobs_opt.on_timer = function (running_indices)
- -- Periodically refresh multirow progress to update elapsed time
- progress.refresh()
- end
- end
runjobs("clang-format", function (index, total, opt)
local sourcefile = sourcefiles[index]
local format_argv = table.join(argv, {sourcefile})
diff --git a/xmake/plugins/repo/main.lua b/xmake/plugins/repo/main.lua
index 40dad5c20..f11e350c5 100644
--- a/xmake/plugins/repo/main.lua
+++ b/xmake/plugins/repo/main.lua
@@ -150,7 +150,7 @@ function _update()
if option.get("verbose") then
task()
else
- runjobs("update repo", task, {progress = true, isolate = true})
+ runjobs("update repo", task, {waiting_indicator = true, isolate = true})
end
-- leave environment
diff --git a/xmake/rules/go/env/xmake.lua b/xmake/rules/go/env/xmake.lua
index 764ca65b5..5ac24297a 100644
--- a/xmake/rules/go/env/xmake.lua
+++ b/xmake/rules/go/env/xmake.lua
@@ -68,7 +68,7 @@ rule("go.env")
if option.get("verbose") then
build_task()
else
- runjobs("build/goenv", build_task, {progress = true})
+ runjobs("build/goenv", build_task, {waiting_indicator = true})
end
end
end