From 2d5076fbba423d24300996773d1fb7ecfcd1b02b Mon Sep 17 00:00:00 2001 From: ruki Date: Thu, 20 Nov 2025 23:55:47 +0800 Subject: improve progress refresh timer --- xmake/core/base/scheduler.lua | 5 ++ xmake/modules/async/runjobs.lua | 56 ++++++++++++++++++++++ xmake/modules/private/action/build/target.lua | 16 ------- .../modules/private/check/checkers/clang/tidy.lua | 8 ---- xmake/plugins/format/main.lua | 8 ---- 5 files changed, 61 insertions(+), 32 deletions(-) diff --git a/xmake/core/base/scheduler.lua b/xmake/core/base/scheduler.lua index b11be7d4e..30385d504 100644 --- a/xmake/core/base/scheduler.lua +++ b/xmake/core/base/scheduler.lua @@ -1110,6 +1110,11 @@ function scheduler:enable(enabled) self._ENABLED = enabled end +-- cancel all timers +function scheduler:cancel_timers() + self:_timer():kill() +end + -- stop the scheduler loop function scheduler:stop() -- mark scheduler status as stopped and spank the poller:wait() diff --git a/xmake/modules/async/runjobs.lua b/xmake/modules/async/runjobs.lua index e08acc908..031e3a028 100644 --- a/xmake/modules/async/runjobs.lua +++ b/xmake/modules/async/runjobs.lua @@ -89,6 +89,22 @@ function _timer_loop(state) end end +-- the refresh loop for multirow progress (independent timer with its own timeout) +function _refresh_loop(state) + -- wait until all tasks have been started using semaphore + if state.refresh_semaphore then + state.refresh_semaphore:wait(-1) + end + -- start refreshing progress with independent timeout + while not state.stop do + os.sleep(state.refresh_timeout) + if not state.stop then + -- refresh multirow progress to update elapsed time + progress.refresh() + end + end +end + -- the progress loop function _progress_loop(state) local timeout = state.timeout @@ -207,6 +223,16 @@ 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.refresh_semaphore then + state.refresh_semaphore:post(1) + end + end + job_func(job_index, total, {progress = state.progress_wrapper}) -- update progress @@ -295,9 +321,14 @@ function main(name, jobs, opt) state.total = opt.total or (type(jobs) == "table" and jobs:size()) or 1 state.comax = opt.comax and tonumber(opt.comax) or math.min(state.total, 4) state.timeout = opt.timeout or 500 + -- this timer only starts after all tasks have been consumed (waiting for completion), + -- so a shorter timeout (e.g., 100ms) is acceptable for quick refresh and doesn't affect performance + -- @see https://github.com/xmake-io/xmake/issues/7042 + state.refresh_timeout = opt.refresh_timeout or 100 state.group_name = name state.jobs_cb = type(jobs) == "function" and jobs or nil assert(state.timeout < 60000, "runjobs: invalid timeout!") + assert(state.refresh_timeout < 60000, "runjobs: invalid refresh_timeout!") -- build jobs queue if type(jobs) == "table" and jobs.build then @@ -320,6 +351,7 @@ function main(name, jobs, opt) -- run timer state.stop = false state.running_jobs_indices = {} + state.all_tasks_started = false local group_timer if opt.on_timer then state.on_timer = opt.on_timer @@ -333,6 +365,13 @@ function main(name, jobs, opt) scheduler.co_start_withopt({name = name .. "/tips", isolate = opt.isolate}, _progress_loop, state) end) end + + -- start independent refresh timer for multirow progress + local group_refresh_timer = nil + local need_refresh_timer = progress.is_multirow() + if need_refresh_timer then + group_refresh_timer = state.group_name .. "/refresh" + end -- run jobs local distcc = opt.distcc @@ -347,6 +386,10 @@ function main(name, jobs, opt) if distcc then state.distcc_semaphore = scheduler.co_semaphore(state.group_name .. "/distcc", 0) end + -- create semaphore for refresh loop to wait for all tasks started + if need_refresh_timer then + state.refresh_semaphore = scheduler.co_semaphore(state.group_name .. "/refresh", 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 @@ -363,6 +406,13 @@ function main(name, jobs, opt) end end end) + + -- start refresh timer after semaphore is created + if need_refresh_timer then + scheduler.co_group_begin(group_refresh_timer, function (co_group) + scheduler.co_start_withopt({name = name .. "/refresh", isolate = opt.isolate}, _refresh_loop, state) + end) + end -- wait all jobs exited scheduler.co_group_wait(state.group_name) @@ -372,6 +422,12 @@ function main(name, jobs, opt) state.stop = true scheduler.co_group_wait(group_timer) end + + -- wait refresh timer job exited + if group_refresh_timer then + state.stop = true + scheduler.co_group_wait(group_refresh_timer) + end -- restore isolated environments if co_running and opt.isolate then diff --git a/xmake/modules/private/action/build/target.lua b/xmake/modules/private/action/build/target.lua index c3d21f399..d623744b4 100644 --- a/xmake/modules/private/action/build/target.lua +++ b/xmake/modules/private/action/build/target.lua @@ -799,14 +799,6 @@ function run_targetjobs(targets_root, opt) remote_only = opt.remote_only, progress_factor = opt.progress_factor } - -- 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 @@ -827,14 +819,6 @@ function run_filejobs(targets_root, opt) remote_only = opt.remote_only, progress_factor = opt.progress_factor } - -- 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/check/checkers/clang/tidy.lua b/xmake/modules/private/check/checkers/clang/tidy.lua index 03568eb6e..963bd1bdc 100644 --- a/xmake/modules/private/check/checkers/clang/tidy.lua +++ b/xmake/modules/private/check/checkers/clang/tidy.lua @@ -160,14 +160,6 @@ function _check_sourcefiles(clang_tidy, sourcefiles, opt) comax = opt.jobs or os.default_njob(), showtips = false } - -- 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/plugins/format/main.lua b/xmake/plugins/format/main.lua index 7a4c53394..768901f6b 100644 --- a/xmake/plugins/format/main.lua +++ b/xmake/plugins/format/main.lua @@ -221,14 +221,6 @@ function main() comax = jobs, showtips = false } - -- 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}) -- cgit v1.3.1 From 9447d6b4e5f1060f832cbae1807b95df40a0ddce Mon Sep 17 00:00:00 2001 From: ruki Date: Thu, 20 Nov 2025 23:58:16 +0800 Subject: improve progress refresh timer --- xmake/modules/async/runjobs.lua | 44 ++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/xmake/modules/async/runjobs.lua b/xmake/modules/async/runjobs.lua index 031e3a028..c59027fac 100644 --- a/xmake/modules/async/runjobs.lua +++ b/xmake/modules/async/runjobs.lua @@ -72,6 +72,9 @@ function _init_progress(state, opt) end }) state.progress_wrapper = progress_wrapper + + -- init progress refresh timeout (for multirow progress refresh timer) + state.progress_refresh_timeout = 500 end -- the timer loop @@ -90,16 +93,21 @@ function _timer_loop(state) end -- the refresh loop for multirow progress (independent timer with its own timeout) -function _refresh_loop(state) +function _progress_refresh_loop(state) -- wait until all tasks have been started using semaphore - if state.refresh_semaphore then - state.refresh_semaphore:wait(-1) + if state.progress_refresh_semaphore and not state.stop then + state.progress_refresh_semaphore:wait(-1) + else + return end - -- start refreshing progress with independent timeout + + -- start refreshing progress using semaphore wait with timeout for quick exit while not state.stop do - os.sleep(state.refresh_timeout) + -- wait for refresh timeout, allows quick exit when state.stop is set via post + state.progress_refresh_semaphore:wait(state.progress_refresh_timeout) + + -- refresh progress if not stopped if not state.stop then - -- refresh multirow progress to update elapsed time progress.refresh() end end @@ -228,8 +236,8 @@ function _consume_jobs_loop(state, run_in_remote) 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.refresh_semaphore then - state.refresh_semaphore:post(1) + if state.progress_refresh_semaphore then + state.progress_refresh_semaphore:post(1) end end @@ -321,14 +329,9 @@ function main(name, jobs, opt) state.total = opt.total or (type(jobs) == "table" and jobs:size()) or 1 state.comax = opt.comax and tonumber(opt.comax) or math.min(state.total, 4) state.timeout = opt.timeout or 500 - -- this timer only starts after all tasks have been consumed (waiting for completion), - -- so a shorter timeout (e.g., 100ms) is acceptable for quick refresh and doesn't affect performance - -- @see https://github.com/xmake-io/xmake/issues/7042 - state.refresh_timeout = opt.refresh_timeout or 100 state.group_name = name state.jobs_cb = type(jobs) == "function" and jobs or nil assert(state.timeout < 60000, "runjobs: invalid timeout!") - assert(state.refresh_timeout < 60000, "runjobs: invalid refresh_timeout!") -- build jobs queue if type(jobs) == "table" and jobs.build then @@ -386,9 +389,10 @@ function main(name, jobs, opt) if distcc then state.distcc_semaphore = scheduler.co_semaphore(state.group_name .. "/distcc", 0) end - -- create semaphore for refresh loop to wait for all tasks started + -- create semaphore for refresh loop if need_refresh_timer then - state.refresh_semaphore = scheduler.co_semaphore(state.group_name .. "/refresh", 0) + -- semaphore to wait for all tasks started and for refresh timer to signal refresh loop + state.progress_refresh_semaphore = scheduler.co_semaphore(state.group_name .. "/refresh", 0) end -- @note we can set `remote_only = true` to run all jobs in remote only local local_comax = 0 @@ -407,10 +411,10 @@ function main(name, jobs, opt) end end) - -- start refresh timer after semaphore is created + -- start refresh loop after semaphore is created if need_refresh_timer then scheduler.co_group_begin(group_refresh_timer, function (co_group) - scheduler.co_start_withopt({name = name .. "/refresh", isolate = opt.isolate}, _refresh_loop, state) + scheduler.co_start_withopt({name = name .. "/refresh", isolate = opt.isolate}, _progress_refresh_loop, state) end) end @@ -423,9 +427,13 @@ function main(name, jobs, opt) scheduler.co_group_wait(group_timer) end - -- wait refresh timer job exited + -- wait refresh timer job exited and signal it to exit quickly if group_refresh_timer then state.stop = true + -- post signal to refresh loop to wake it up for quick exit + if state.progress_refresh_semaphore then + state.progress_refresh_semaphore:post(1) + end scheduler.co_group_wait(group_refresh_timer) end -- cgit v1.3.1 From 4627107fa7badf70828a6f569e4a8b8ba3d4160d Mon Sep 17 00:00:00 2001 From: ruki Date: Fri, 21 Nov 2025 00:45:05 +0800 Subject: improve progress_refresh --- xmake/modules/async/runjobs.lua | 35 +++++++++++----------- xmake/modules/private/action/build/target.lua | 6 ++-- .../modules/private/check/checkers/clang/tidy.lua | 3 +- xmake/plugins/format/main.lua | 3 +- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/xmake/modules/async/runjobs.lua b/xmake/modules/async/runjobs.lua index c59027fac..642668921 100644 --- a/xmake/modules/async/runjobs.lua +++ b/xmake/modules/async/runjobs.lua @@ -72,7 +72,7 @@ function _init_progress(state, opt) end }) state.progress_wrapper = progress_wrapper - + -- init progress refresh timeout (for multirow progress refresh timer) state.progress_refresh_timeout = 500 end @@ -231,7 +231,7 @@ 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 @@ -240,7 +240,7 @@ function _consume_jobs_loop(state, run_in_remote) state.progress_refresh_semaphore:post(1) end end - + job_func(job_index, total, {progress = state.progress_wrapper}) -- update progress @@ -306,6 +306,7 @@ end -- 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, { progress = 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) @@ -368,12 +369,12 @@ function main(name, jobs, opt) scheduler.co_start_withopt({name = name .. "/tips", isolate = opt.isolate}, _progress_loop, state) end) end - + -- start independent refresh timer for multirow progress - local group_refresh_timer = nil - local need_refresh_timer = progress.is_multirow() - if need_refresh_timer then - group_refresh_timer = state.group_name .. "/refresh" + local group_progress_refresh_timer = nil + local need_progress_refresh_timer = opt.progress_refresh and progress.is_multirow() + if need_progress_refresh_timer then + group_progress_refresh_timer = state.group_name .. "/progress_refresh" end -- run jobs @@ -390,9 +391,9 @@ function main(name, jobs, opt) state.distcc_semaphore = scheduler.co_semaphore(state.group_name .. "/distcc", 0) end -- create semaphore for refresh loop - if need_refresh_timer then + if need_progress_refresh_timer then -- semaphore to wait for all tasks started and for refresh timer to signal refresh loop - state.progress_refresh_semaphore = scheduler.co_semaphore(state.group_name .. "/refresh", 0) + state.progress_refresh_semaphore = scheduler.co_semaphore(state.group_name .. "/progress_refresh", 0) end -- @note we can set `remote_only = true` to run all jobs in remote only local local_comax = 0 @@ -410,11 +411,11 @@ function main(name, jobs, opt) end end end) - + -- start refresh loop after semaphore is created - if need_refresh_timer then - scheduler.co_group_begin(group_refresh_timer, function (co_group) - scheduler.co_start_withopt({name = name .. "/refresh", isolate = opt.isolate}, _progress_refresh_loop, state) + if need_progress_refresh_timer then + scheduler.co_group_begin(group_progress_refresh_timer, function (co_group) + scheduler.co_start_withopt({name = name .. "/progress_refresh", isolate = opt.isolate}, _progress_refresh_loop, state) end) end @@ -426,15 +427,15 @@ function main(name, jobs, opt) state.stop = true scheduler.co_group_wait(group_timer) end - + -- wait refresh timer job exited and signal it to exit quickly - if group_refresh_timer then + if group_progress_refresh_timer then state.stop = true -- post signal to refresh loop to wake it up for quick exit if state.progress_refresh_semaphore then state.progress_refresh_semaphore:post(1) end - scheduler.co_group_wait(group_refresh_timer) + scheduler.co_group_wait(group_progress_refresh_timer) end -- restore isolated environments diff --git a/xmake/modules/private/action/build/target.lua b/xmake/modules/private/action/build/target.lua index d623744b4..280610e71 100644 --- a/xmake/modules/private/action/build/target.lua +++ b/xmake/modules/private/action/build/target.lua @@ -797,7 +797,8 @@ 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 } async_runjobs(job_kind, jobgraph, runjobs_opt) os.cd(curdir) @@ -817,7 +818,8 @@ 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 } async_runjobs(job_kind, jobgraph, runjobs_opt) os.cd(curdir) diff --git a/xmake/modules/private/check/checkers/clang/tidy.lua b/xmake/modules/private/check/checkers/clang/tidy.lua index 963bd1bdc..82e9246fb 100644 --- a/xmake/modules/private/check/checkers/clang/tidy.lua +++ b/xmake/modules/private/check/checkers/clang/tidy.lua @@ -158,7 +158,8 @@ 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 } runjobs("checker.tidy", function (index, total, job_opt) local sourcefile = sourcefiles[index] diff --git a/xmake/plugins/format/main.lua b/xmake/plugins/format/main.lua index 768901f6b..f527b6c00 100644 --- a/xmake/plugins/format/main.lua +++ b/xmake/plugins/format/main.lua @@ -219,7 +219,8 @@ function main() local runjobs_opt = { total = #sourcefiles, comax = jobs, - showtips = false + showtips = false, + progress_refresh = true } runjobs("clang-format", function (index, total, opt) local sourcefile = sourcefiles[index] -- cgit v1.3.1 From 66dec67272856110859d38c01043fd2564f9c7a7 Mon Sep 17 00:00:00 2001 From: ruki Date: Fri, 21 Nov 2025 00:49:11 +0800 Subject: add waiting indicator --- tests/modules/scheduler/spinner.lua | 2 +- xmake/actions/update/main.lua | 4 +- xmake/modules/async/runjobs.lua | 44 ++++++------ .../action/require/impl/download_packages.lua | 8 +-- .../action/require/impl/install_packages.lua | 8 +-- xmake/modules/utils/progress.lua | 56 --------------- xmake/modules/utils/waiting_indicator.lua | 83 ++++++++++++++++++++++ xmake/plugins/repo/main.lua | 2 +- xmake/rules/go/env/xmake.lua | 2 +- 9 files changed, 119 insertions(+), 90 deletions(-) create mode 100644 xmake/modules/utils/waiting_indicator.lua 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/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 642668921..109ccd3f3 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) @@ -36,16 +37,17 @@ end function _init_progress(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 + state.show_waiting_indicator = io.isatty() and (waiting_indicator_opt == true 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 -- init progress wrapper @@ -113,10 +115,10 @@ function _progress_refresh_loop(state) end end --- the progress loop -function _progress_loop(state) +-- 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) if not state.stop then @@ -146,14 +148,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 @@ -257,9 +259,9 @@ function _consume_jobs_loop(state, run_in_remote) -- stop progress progress.show_abort() - if state.show_progress then + if state.show_waiting_indicator then _print_backchars(state.backnum) - state.progress_helper:stop() + state.waiting_indicator_helper:stop() end -- we need re-throw this errors outside scheduler @@ -304,9 +306,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, { progress = true, progress_refresh = true }) -- enable progress refresh timer for multirow 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) @@ -363,10 +365,10 @@ function main(name, jobs, opt) 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 + elseif state.show_waiting_indicator 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) + scheduler.co_start_withopt({name = name .. "/tips", isolate = opt.isolate}, _waiting_indicator_loop, state) end) end @@ -445,9 +447,9 @@ function main(name, jobs, opt) -- stop progress progress.show_abort() - if state.show_progress then + if state.show_waiting_indicator then _print_backchars(state.backnum) - state.progress_helper:stop() + state.waiting_indicator_helper:stop() end -- do exit callback diff --git a/xmake/modules/private/action/require/impl/download_packages.lua b/xmake/modules/private/action/require/impl/download_packages.lua index 4dfe1db9b..9536b1330 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_instance = 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_instance: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_instance: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..d719d1544 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_instance = 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_instance: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_instance:write() end, exit = function(errors) if errors then tty.erase_line_to_start().cr() diff --git a/xmake/modules/utils/progress.lua b/xmake/modules/utils/progress.lua index 60066ddc5..9cc4c53ac 100644 --- a/xmake/modules/utils/progress.lua +++ b/xmake/modules/utils/progress.lua @@ -20,61 +20,17 @@ -- 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() local is_scroll = _g.is_scroll @@ -475,15 +431,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/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 -- cgit v1.3.1 From 00782576966755c6bcebd30252e51333a6603d25 Mon Sep 17 00:00:00 2001 From: ruki Date: Fri, 21 Nov 2025 00:53:26 +0800 Subject: improve runjobs --- xmake/modules/async/runjobs.lua | 169 +++++++++++++++++++++++++--------------- 1 file changed, 105 insertions(+), 64 deletions(-) diff --git a/xmake/modules/async/runjobs.lua b/xmake/modules/async/runjobs.lua index 109ccd3f3..751260da5 100644 --- a/xmake/modules/async/runjobs.lua +++ b/xmake/modules/async/runjobs.lua @@ -33,10 +33,10 @@ 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 waiting indicator helper -- we need to hide wait characters if is not a tty local waiting_indicator_opt = opt.waiting_indicator @@ -49,6 +49,11 @@ function _init_progress(state, opt) end 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 @@ -79,6 +84,82 @@ function _init_progress(state, opt) state.progress_refresh_timeout = 500 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" + 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" + 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 progress refresh loop to exit quickly + if state.group_progress_refresh_timer and 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 + -- the timer loop function _timer_loop(state) local timeout = state.timeout @@ -257,12 +338,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_waiting_indicator then - _print_backchars(state.backnum) - state.waiting_indicator_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 @@ -343,7 +421,10 @@ 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 @@ -354,30 +435,12 @@ function main(name, jobs, opt) co_running:isolate(true) end - -- run timer + -- init timer state state.stop = false state.running_jobs_indices = {} - state.all_tasks_started = false - 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_waiting_indicator 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}, _waiting_indicator_loop, state) - end) - end - -- start independent refresh timer for multirow progress - local group_progress_refresh_timer = nil - local need_progress_refresh_timer = opt.progress_refresh and progress.is_multirow() - if need_progress_refresh_timer then - group_progress_refresh_timer = state.group_name .. "/progress_refresh" - end + -- start all timers + _start_timers(state, name, opt) -- run jobs local distcc = opt.distcc @@ -392,11 +455,6 @@ function main(name, jobs, opt) if distcc then state.distcc_semaphore = scheduler.co_semaphore(state.group_name .. "/distcc", 0) end - -- create semaphore for refresh loop - if need_progress_refresh_timer then - -- semaphore to wait for all tasks started and for refresh timer to signal refresh loop - state.progress_refresh_semaphore = scheduler.co_semaphore(state.group_name .. "/progress_refresh", 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 @@ -414,43 +472,26 @@ function main(name, jobs, opt) end end) - -- start refresh loop after semaphore is created - if need_progress_refresh_timer then - scheduler.co_group_begin(group_progress_refresh_timer, function (co_group) - scheduler.co_start_withopt({name = name .. "/progress_refresh", isolate = opt.isolate}, _progress_refresh_loop, state) - end) - end - -- wait all jobs exited scheduler.co_group_wait(state.group_name) - -- wait timer job exited - if group_timer then - state.stop = true - scheduler.co_group_wait(group_timer) - end - - -- wait refresh timer job exited and signal it to exit quickly - if group_progress_refresh_timer then - state.stop = true - -- post signal to refresh loop to wake it up for quick exit - if state.progress_refresh_semaphore then - state.progress_refresh_semaphore:post(1) - end - scheduler.co_group_wait(group_progress_refresh_timer) - end + -- stop all timers and notify them to exit + state.stop = true + _stop_timers(state) + + -- wait all timer jobs exited + _wait_timers(state) -- restore isolated environments if co_running and opt.isolate then co_running:isolate(is_isolated) end - -- stop progress - progress.show_abort() - if state.show_waiting_indicator then - _print_backchars(state.backnum) - state.waiting_indicator_helper:stop() - end + -- exit progress + _exit_progress(state) + + -- exit waiting indicator + _exit_waiting_indicator(state) -- do exit callback if opt.on_exit then -- cgit v1.3.1 From fc65d582ee06518b507f62b3065aaf15de619a7c Mon Sep 17 00:00:00 2001 From: ruki Date: Fri, 21 Nov 2025 00:54:55 +0800 Subject: use semaphore instead of os.sleep --- xmake/modules/async/runjobs.lua | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/xmake/modules/async/runjobs.lua b/xmake/modules/async/runjobs.lua index 751260da5..1cc2e4b53 100644 --- a/xmake/modules/async/runjobs.lua +++ b/xmake/modules/async/runjobs.lua @@ -89,6 +89,8 @@ 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) @@ -99,6 +101,8 @@ end 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) @@ -128,8 +132,14 @@ end -- stop all timers and notify them to exit function _stop_timers(state) - -- signal progress refresh loop to exit quickly - if state.group_progress_refresh_timer and state.progress_refresh_semaphore then + -- 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 @@ -164,7 +174,8 @@ end 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 @@ -201,7 +212,8 @@ function _waiting_indicator_loop(state) local timeout = state.timeout 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 @@ -324,6 +336,7 @@ function _consume_jobs_loop(state, run_in_remote) end end + -- run job job_func(job_index, total, {progress = state.progress_wrapper}) -- update progress -- cgit v1.3.1 From c68c8d698fdb270b86378567e7fad823a2a7acc2 Mon Sep 17 00:00:00 2001 From: ruki Date: Fri, 21 Nov 2025 00:58:59 +0800 Subject: improve isolated --- xmake/modules/async/runjobs.lua | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/xmake/modules/async/runjobs.lua b/xmake/modules/async/runjobs.lua index 1cc2e4b53..04bae03c7 100644 --- a/xmake/modules/async/runjobs.lua +++ b/xmake/modules/async/runjobs.lua @@ -170,6 +170,24 @@ 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 + -- the timer loop function _timer_loop(state) local timeout = state.timeout @@ -425,6 +443,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 @@ -441,16 +461,7 @@ function main(name, jobs, opt) _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 - - -- init timer state - state.stop = false - state.running_jobs_indices = {} + _isolate_environments(state, opt) -- start all timers _start_timers(state, name, opt) @@ -487,18 +498,16 @@ function main(name, jobs, opt) -- wait all jobs exited scheduler.co_group_wait(state.group_name) + state.stop = true -- stop all timers and notify them to exit - state.stop = true _stop_timers(state) -- wait all timer jobs exited _wait_timers(state) -- restore isolated environments - if co_running and opt.isolate then - co_running:isolate(is_isolated) - end + _restore_isolated_environments(state, opt) -- exit progress _exit_progress(state) -- cgit v1.3.1 From 44098a2d8c47acdb78abe34c0dc8104ed823cf00 Mon Sep 17 00:00:00 2001 From: ruki Date: Fri, 21 Nov 2025 22:33:24 +0800 Subject: disable multirow progress for xmake test --- xmake/actions/test/main.lua | 7 +++++++ xmake/modules/utils/progress.lua | 45 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/xmake/actions/test/main.lua b/xmake/actions/test/main.lua index 8a917678b..c85895b4d 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 ...") @@ -353,6 +357,9 @@ function _run_tests(tests) comax = jobs, isolate = true}) + -- restore the original progress style + progress.restore_style() + -- generate report spent = os.mclock() - spent local passed_rate = math.floor(report.passed * 100 / report.total) diff --git a/xmake/modules/utils/progress.lua b/xmake/modules/utils/progress.lua index 9cc4c53ac..b6201d5cc 100644 --- a/xmake/modules/utils/progress.lua +++ b/xmake/modules/utils/progress.lua @@ -33,6 +33,10 @@ local COLOR_SLOW = "${color.build.progress_slow}" -- 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" @@ -46,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") @@ -59,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") @@ -70,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 -- cgit v1.3.1 From 26004160d96a301b0cc2fc739413542ed6d324d6 Mon Sep 17 00:00:00 2001 From: ruki Date: Fri, 21 Nov 2025 22:34:08 +0800 Subject: modify timeout for runjobs --- xmake/actions/test/main.lua | 3 ++- xmake/modules/async/runjobs.lua | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/xmake/actions/test/main.lua b/xmake/actions/test/main.lua index c85895b4d..0e7192d83 100644 --- a/xmake/actions/test/main.lua +++ b/xmake/actions/test/main.lua @@ -355,7 +355,8 @@ 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() diff --git a/xmake/modules/async/runjobs.lua b/xmake/modules/async/runjobs.lua index 04bae03c7..1d5927e1d 100644 --- a/xmake/modules/async/runjobs.lua +++ b/xmake/modules/async/runjobs.lua @@ -80,8 +80,6 @@ function _init_progress(state, opt) }) state.progress_wrapper = progress_wrapper - -- init progress refresh timeout (for multirow progress refresh timer) - state.progress_refresh_timeout = 500 end -- start timer (on_timer callback) @@ -216,7 +214,7 @@ function _progress_refresh_loop(state) -- 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.progress_refresh_timeout) + state.progress_refresh_semaphore:wait(state.timeout) -- refresh progress if not stopped if not state.stop then -- cgit v1.3.1 From b793f5dd2ab1a6fdfaa862a2c2ffc99f06c1c8b5 Mon Sep 17 00:00:00 2001 From: ruki Date: Fri, 21 Nov 2025 22:34:57 +0800 Subject: improve waiting_indicator_helper --- xmake/modules/async/runjobs.lua | 9 ++++++++- xmake/modules/private/action/require/impl/download_packages.lua | 6 +++--- xmake/modules/private/action/require/impl/install_packages.lua | 6 +++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/xmake/modules/async/runjobs.lua b/xmake/modules/async/runjobs.lua index 1d5927e1d..5a5c73db3 100644 --- a/xmake/modules/async/runjobs.lua +++ b/xmake/modules/async/runjobs.lua @@ -40,7 +40,14 @@ function _init_waiting_indicator(state, opt) -- init waiting indicator helper -- we need to hide wait characters if is not a tty local waiting_indicator_opt = opt.waiting_indicator - state.show_waiting_indicator = io.isatty() and (waiting_indicator_opt == true or type(waiting_indicator_opt) == "table") + + -- 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_waiting_indicator then local indicator_opt = nil diff --git a/xmake/modules/private/action/require/impl/download_packages.lua b/xmake/modules/private/action/require/impl/download_packages.lua index 9536b1330..b25038672 100644 --- a/xmake/modules/private/action/require/impl/download_packages.lua +++ b/xmake/modules/private/action/require/impl/download_packages.lua @@ -127,7 +127,7 @@ function _download_packages(packages_download) local term_mode_stdout = tty.term_mode("stdout") -- do download - local waiting_indicator_instance = show_wait and waiting_indicator.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 - waiting_indicator_instance: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 "") - waiting_indicator_instance: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 d719d1544..39d7d0bd4 100644 --- a/xmake/modules/private/action/require/impl/install_packages.lua +++ b/xmake/modules/private/action/require/impl/install_packages.lua @@ -414,7 +414,7 @@ function _do_install_packages(packages_install, packages_download, installdeps) local term_mode_stdout = tty.term_mode("stdout") -- do install - local waiting_indicator_instance = show_wait and waiting_indicator.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 - waiting_indicator_instance: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 "") - waiting_indicator_instance:write() + waiting_indicator_helper:write() end, exit = function(errors) if errors then tty.erase_line_to_start().cr() -- cgit v1.3.1 From 3358ae8da2fab205edf2cf0d0bf9bf6332a15479 Mon Sep 17 00:00:00 2001 From: ruki Date: Fri, 21 Nov 2025 22:35:40 +0800 Subject: improve runjobs --- xmake/modules/async/runjobs.lua | 135 ++++++++++++++++++++++------------------ 1 file changed, 73 insertions(+), 62 deletions(-) diff --git a/xmake/modules/async/runjobs.lua b/xmake/modules/async/runjobs.lua index 5a5c73db3..6fc316f28 100644 --- a/xmake/modules/async/runjobs.lua +++ b/xmake/modules/async/runjobs.lua @@ -193,6 +193,75 @@ function _restore_isolated_environments(state, opt) 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 @@ -471,67 +540,9 @@ function main(name, jobs, opt) -- 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) + -- run jobs and wait for completion + _run_jobs(state, name, opt) - -- 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) - - -- 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 + -- cleanup and handle errors + _cleanup_jobs(state, opt) end -- cgit v1.3.1 From e2b694841565457fc93a2295be46bada03e4d739 Mon Sep 17 00:00:00 2001 From: ruki Date: Fri, 21 Nov 2025 22:38:33 +0800 Subject: remove unused codes --- xmake/core/base/scheduler.lua | 5 ----- 1 file changed, 5 deletions(-) diff --git a/xmake/core/base/scheduler.lua b/xmake/core/base/scheduler.lua index 30385d504..b11be7d4e 100644 --- a/xmake/core/base/scheduler.lua +++ b/xmake/core/base/scheduler.lua @@ -1110,11 +1110,6 @@ function scheduler:enable(enabled) self._ENABLED = enabled end --- cancel all timers -function scheduler:cancel_timers() - self:_timer():kill() -end - -- stop the scheduler loop function scheduler:stop() -- mark scheduler status as stopped and spank the poller:wait() -- cgit v1.3.1