2026-07-23 01:24:42 +02:00
|
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
|
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
2026-04-29 03:27:11 +02:00
|
|
|
|
|
|
|
|
module;
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
refactor: drop Windows host support
crafter-build is moving to a single Flatpak distribution, and there is
no Windows host in that picture: no build.cmd, no MSVC or mingw host
build, no DLL plus launcher. Delete every host-Windows path instead of
leaving it to rot unverified: the cmd.exe command runner and quoting,
the LOCALAPPDATA cache, the MSVC and mingw-host LoadProject variants
with their LIBCXX_DIR and CRAFTER_MINGW_DIR plumbing, the winsock port
probe, the LoadLibrary libclang loader, the console-width and isatty
shims, and the preprocessor guards around the Linux bodies.
The x86_64-w64-mingw32 cross-compile target stays. It is target logic
(output names, -femulated-tls, the libstdc++ link extras, copying a
dependency DLL next to a mingw exe, wine as the runner) and did not
depend on any of the removed code. The msvc target goes with the host
that was the only thing able to use it.
Three target-path warts fixed on the way: ExpectedOutput named a mingw
static library <name>.lib while the archive step wrote lib<name>.a, so
those libraries relinked on every run; the import lib is no longer
copied next to a mingw exe, since the dependency's own bin dir is
already on the link line; and llvm-ar now writes every archive, not
only the LTO ones, so Debug and Release archives are made the same way.
CRAFTER_API and Crafter.Build-Api.h existed only for the DLL boundary.
Nothing downstream used them, so they are removed rather than kept as
an empty macro nobody can verify.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-02 21:56:38 +02:00
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <sys/ioctl.h>
|
2026-04-29 03:27:11 +02:00
|
|
|
|
|
|
|
|
export module Crafter.Build:Progress_impl;
|
|
|
|
|
import std;
|
|
|
|
|
import :Progress;
|
|
|
|
|
|
|
|
|
|
namespace {
|
2026-07-23 01:24:42 +02:00
|
|
|
std::mutex StateMutex;
|
|
|
|
|
std::atomic<std::int32_t> Total{0};
|
|
|
|
|
std::atomic<std::int32_t> Done{0};
|
|
|
|
|
Crafter::Progress::Verbosity ActiveVerbosity = Crafter::Progress::Verbosity::Default;
|
|
|
|
|
bool IsTty = false;
|
|
|
|
|
bool LineDirty = false; // status line has uncleared content
|
|
|
|
|
bool Finalized = false;
|
|
|
|
|
std::chrono::steady_clock::time_point StartTime = std::chrono::steady_clock::now();
|
2026-04-29 03:27:11 +02:00
|
|
|
|
2026-07-23 01:24:42 +02:00
|
|
|
std::int32_t TerminalWidth() {
|
2026-04-29 03:27:11 +02:00
|
|
|
winsize ws{};
|
|
|
|
|
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0) {
|
|
|
|
|
return ws.ws_col;
|
|
|
|
|
}
|
|
|
|
|
if (const char* col = std::getenv("COLUMNS")) {
|
2026-07-23 01:24:42 +02:00
|
|
|
try { std::int32_t c = std::stoi(col); if (c > 0) return c; } catch (...) {}
|
2026-04-29 03:27:11 +02:00
|
|
|
}
|
|
|
|
|
return 80;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-23 01:24:42 +02:00
|
|
|
// Caller holds StateMutex.
|
2026-04-29 03:27:11 +02:00
|
|
|
void RenderStatus(std::string_view label) {
|
2026-07-23 01:24:42 +02:00
|
|
|
if (ActiveVerbosity != Crafter::Progress::Verbosity::Default || !IsTty) return;
|
|
|
|
|
std::int32_t done = Done.load(std::memory_order_relaxed);
|
|
|
|
|
std::int32_t total = Total.load(std::memory_order_relaxed);
|
2026-04-29 03:27:11 +02:00
|
|
|
std::string prefix = std::format("[{}/{}] ", done, total);
|
2026-07-23 01:24:42 +02:00
|
|
|
std::int32_t width = TerminalWidth();
|
|
|
|
|
std::int32_t avail = width - static_cast<std::int32_t>(prefix.size()) - 1;
|
2026-04-29 03:27:11 +02:00
|
|
|
std::string trimmed{label};
|
2026-07-23 01:24:42 +02:00
|
|
|
if (avail > 0 && static_cast<std::int32_t>(trimmed.size()) > avail) {
|
2026-04-29 03:27:11 +02:00
|
|
|
trimmed.resize(static_cast<std::size_t>(avail));
|
|
|
|
|
}
|
|
|
|
|
// \r returns to col 0, \033[2K erases the whole line. No newline.
|
|
|
|
|
std::fputs("\r\033[2K", stdout);
|
|
|
|
|
std::fputs(prefix.c_str(), stdout);
|
|
|
|
|
std::fputs(trimmed.c_str(), stdout);
|
|
|
|
|
std::fflush(stdout);
|
2026-07-23 01:24:42 +02:00
|
|
|
LineDirty = true;
|
2026-04-29 03:27:11 +02:00
|
|
|
}
|
|
|
|
|
|
2026-07-23 01:24:42 +02:00
|
|
|
// Caller holds StateMutex.
|
2026-04-29 03:27:11 +02:00
|
|
|
void ClearLineLocked() {
|
2026-07-23 01:24:42 +02:00
|
|
|
if (LineDirty) {
|
2026-04-29 03:27:11 +02:00
|
|
|
std::fputs("\r\033[2K", stdout);
|
|
|
|
|
std::fflush(stdout);
|
2026-07-23 01:24:42 +02:00
|
|
|
LineDirty = false;
|
2026-04-29 03:27:11 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
namespace Crafter::Progress {
|
|
|
|
|
|
|
|
|
|
void SetVerbosity(Verbosity v) {
|
2026-07-23 01:24:42 +02:00
|
|
|
std::lock_guard lock(StateMutex);
|
|
|
|
|
ActiveVerbosity = v;
|
refactor: drop Windows host support
crafter-build is moving to a single Flatpak distribution, and there is
no Windows host in that picture: no build.cmd, no MSVC or mingw host
build, no DLL plus launcher. Delete every host-Windows path instead of
leaving it to rot unverified: the cmd.exe command runner and quoting,
the LOCALAPPDATA cache, the MSVC and mingw-host LoadProject variants
with their LIBCXX_DIR and CRAFTER_MINGW_DIR plumbing, the winsock port
probe, the LoadLibrary libclang loader, the console-width and isatty
shims, and the preprocessor guards around the Linux bodies.
The x86_64-w64-mingw32 cross-compile target stays. It is target logic
(output names, -femulated-tls, the libstdc++ link extras, copying a
dependency DLL next to a mingw exe, wine as the runner) and did not
depend on any of the removed code. The msvc target goes with the host
that was the only thing able to use it.
Three target-path warts fixed on the way: ExpectedOutput named a mingw
static library <name>.lib while the archive step wrote lib<name>.a, so
those libraries relinked on every run; the import lib is no longer
copied next to a mingw exe, since the dependency's own bin dir is
already on the link line; and llvm-ar now writes every archive, not
only the LTO ones, so Debug and Release archives are made the same way.
CRAFTER_API and Crafter.Build-Api.h existed only for the DLL boundary.
Nothing downstream used them, so they are removed rather than kept as
an empty macro nobody can verify.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-02 21:56:38 +02:00
|
|
|
IsTty = isatty(STDOUT_FILENO) != 0;
|
2026-07-23 01:24:42 +02:00
|
|
|
StartTime = std::chrono::steady_clock::now();
|
|
|
|
|
Total.store(0);
|
|
|
|
|
Done.store(0);
|
|
|
|
|
Finalized = false;
|
2026-04-29 03:27:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Verbosity GetVerbosity() {
|
2026-07-23 01:24:42 +02:00
|
|
|
std::lock_guard lock(StateMutex);
|
|
|
|
|
return ActiveVerbosity;
|
2026-04-29 03:27:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Task::Task(std::string label) : label_(std::move(label)) {
|
2026-07-23 01:24:42 +02:00
|
|
|
Total.fetch_add(1, std::memory_order_relaxed);
|
|
|
|
|
std::lock_guard lock(StateMutex);
|
|
|
|
|
if (ActiveVerbosity == Verbosity::Default && IsTty) {
|
2026-04-29 03:27:11 +02:00
|
|
|
RenderStatus(label_);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Task::~Task() {
|
2026-07-23 01:24:42 +02:00
|
|
|
std::int32_t done = Done.fetch_add(1, std::memory_order_relaxed) + 1;
|
|
|
|
|
std::lock_guard lock(StateMutex);
|
|
|
|
|
if (ActiveVerbosity == Verbosity::Default) {
|
|
|
|
|
if (IsTty) {
|
2026-04-29 03:27:11 +02:00
|
|
|
RenderStatus(label_);
|
|
|
|
|
} else {
|
|
|
|
|
// Non-TTY: one append-only line per completed task (ninja-style).
|
2026-07-23 01:24:42 +02:00
|
|
|
std::int32_t total = Total.load(std::memory_order_relaxed);
|
2026-04-29 03:27:11 +02:00
|
|
|
std::println("[{}/{}] {}", done, total, label_);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EchoCommand(std::string_view command) {
|
2026-07-23 01:24:42 +02:00
|
|
|
std::lock_guard lock(StateMutex);
|
|
|
|
|
if (ActiveVerbosity == Verbosity::Verbose) {
|
2026-04-29 03:27:11 +02:00
|
|
|
std::println("$ {}", command);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
fix: decide a build step by its exit code, not by whether it printed
RunCommand merged stderr into stdout, dropped pclose's status and returned
the text; every compile, link and archive site then read "printed something"
as "failed". A warning is printing something, so a translation unit that
warned failed the build — but only on the run that actually recompiled it,
since warnings aren't re-emitted for an object that's already up to date.
The same unchanged source therefore passed or failed depending on the state
of the build tree: flaky-looking tests locally, and a cold CI checkout
surfacing every latent warning in a project at once as unrelated failures.
RunCommand is gone, replaced by RunBuildCommand: it goes through
RunCommandChecked, returns "" when the command exited 0 (so every caller's
`if (!result.empty())` error path is unchanged) and hands any warnings to
the new Progress::Diagnostic instead of to the error path. That also closes
the quiet half of the bug — a compiler killed by the OOM killer prints
nothing, so it used to read as success and leave the build carrying on with
a missing object; it now reports the signal that killed it.
Warnings are now shown rather than swallowed, which they weren't in either
direction before: invisible on an incremental build, fatal on a cold one.
Failing on them stays a project's choice, via -Werror in compileFlags.
2026-08-26 00:51:15 +00:00
|
|
|
void Diagnostic(std::string_view text) {
|
|
|
|
|
std::lock_guard lock(StateMutex);
|
|
|
|
|
if (ActiveVerbosity == Verbosity::Quiet) return;
|
|
|
|
|
ClearLineLocked();
|
|
|
|
|
// Written whole under StateMutex: parallel compiles hand us one warning
|
|
|
|
|
// block each, and interleaving them line-by-line would make every
|
|
|
|
|
// "note: ..." continuation follow the wrong diagnostic.
|
|
|
|
|
std::fwrite(text.data(), 1, text.size(), stderr);
|
|
|
|
|
if (!text.ends_with('\n')) std::fputc('\n', stderr);
|
|
|
|
|
std::fflush(stderr);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-29 03:27:11 +02:00
|
|
|
void Clear() {
|
2026-07-23 01:24:42 +02:00
|
|
|
std::lock_guard lock(StateMutex);
|
2026-04-29 03:27:11 +02:00
|
|
|
ClearLineLocked();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Finalize() {
|
2026-07-23 01:24:42 +02:00
|
|
|
std::lock_guard lock(StateMutex);
|
|
|
|
|
if (Finalized) return;
|
|
|
|
|
Finalized = true;
|
2026-04-29 03:27:11 +02:00
|
|
|
ClearLineLocked();
|
2026-07-23 01:24:42 +02:00
|
|
|
if (ActiveVerbosity == Verbosity::Quiet) return;
|
|
|
|
|
std::int32_t done = Done.load();
|
2026-04-29 03:27:11 +02:00
|
|
|
if (done == 0) return; // Nothing happened (cached build); stay silent.
|
2026-07-23 01:24:42 +02:00
|
|
|
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - StartTime);
|
|
|
|
|
std::println("Built {} step{} in {}ms", done, done == 1 ? "" : "s", elapsed.count());
|
2026-04-29 03:27:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace Crafter::Progress
|