loading bar
Some checks failed
CI / build-test-release (push) Failing after 4m41s

This commit is contained in:
Jorijn van der Graaf 2026-04-29 03:27:11 +02:00
commit 4d09eaac2a
10 changed files with 276 additions and 7 deletions

View file

@ -22,6 +22,7 @@ import std;
import :Clang;
import :Platform;
import :Test;
import :Progress;
namespace fs = std::filesystem;
using namespace Crafter;
@ -252,6 +253,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
for (const Shader& shader : config.shaders) {
if (shader.Check(outputDir)) continue;
threads.emplace_back([&shader, &outputDir, &buildError, &buildCancelled]() {
Progress::Task task(std::format("Compiling shader {}", shader.path.filename().string()));
if (buildCancelled.load(std::memory_order_relaxed)) return;
std::string result = shader.Compile(outputDir);
@ -265,6 +267,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
}
threads.emplace_back([&config, &outputDir, &buildCancelled, &buildError]() {
Progress::Task task(std::format("Copying files for {}", config.name));
if (buildCancelled.load(std::memory_order_relaxed)) return;
try {
for (const fs::path& additionalFile : config.files) {
@ -320,6 +323,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
externalThreads.reserve(config.externalDependencies.size());
for (std::size_t i = 0; i < config.externalDependencies.size(); ++i) {
externalThreads.emplace_back([&, i]() {
Progress::Task task(std::format("Building external dep {}", config.externalDependencies[i].name));
if (buildCancelled.load(std::memory_order_relaxed)) return;
externalResults[i] = BuildExternal(config.externalDependencies[i], config.target, buildCancelled);
if (!externalResults[i].error.empty()) {
@ -337,7 +341,11 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
fs::create_directories(stdPcmDir);
}
std::string stdPcmResult = BuildStdPcm(config, stdPcmDir/"std.pcm");
std::string stdPcmResult;
{
Progress::Task task(std::format("Building std PCM ({}-{})", config.target, config.march));
stdPcmResult = BuildStdPcm(config, stdPcmDir/"std.pcm");
}
if(!stdPcmResult.empty()) {
buildCancelled.store(true);
for(std::thread& thread : threads) thread.join();
@ -514,6 +522,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
const std::string srcPath = cFile.string() + ".c";
if (!fs::exists(objPath) || fs::last_write_time(srcPath) > fs::last_write_time(objPath)) {
threads.emplace_back([&cFile, &buildDir, &buildError, &buildCancelled, &config]() {
Progress::Task task(std::format("Compiling {}.c", cFile.filename().string()));
if (buildCancelled.load(std::memory_order_relaxed)) return;
std::string result = RunCommand(std::format("clang {}.c --target={} -march={} -mtune={} -O3 -c -o {}_source.o", cFile.string(), config.target, config.march, config.mtune, (buildDir / cFile.filename()).string()));
@ -533,6 +542,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
const std::string srcPath = cFile.string() + ".cu";
if (!fs::exists(objPath) || fs::last_write_time(srcPath) > fs::last_write_time(objPath)) {
threads.emplace_back([&cFile, &buildDir, &buildError, &buildCancelled]() {
Progress::Task task(std::format("Compiling {}.cu", cFile.filename().string()));
if (buildCancelled.load(std::memory_order_relaxed)) return;
std::string result = RunCommand(std::format("nvcc {}.cu -c -o {}_source.o -O3 -arch=sm_89", cFile.string(), (buildDir / cFile.filename()).string()));
@ -580,6 +590,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
if(interface->Check(pcmDir, externalFloor)) {
Module* mod = interface.get();
threads.emplace_back([mod, &command, &pcmDir, &buildDir, &buildCancelled, &buildError]() {
Progress::Task task(std::format("Compiling interface {}", mod->path.filename().string()));
try {
mod->Compile(command, pcmDir, buildDir, buildCancelled, buildError);
} catch (const std::exception& e) {
@ -602,6 +613,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
buildResult.repack = true;
Implementation* impl = &implementation;
threads.emplace_back([impl, &command, &buildDir, &buildCancelled, &buildError]() {
Progress::Task task(std::format("Compiling {}.cpp", impl->path.filename().string()));
try {
impl->Compile(command, buildDir, buildCancelled, buildError);
} catch (const std::exception& e) {
@ -694,6 +706,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
}
if(buildResult.repack) {
Progress::Task task(std::format("Linking {}", config.outputName));
if(config.type == ConfigurationType::Executable) {
#ifdef CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_linux_gnu
if(config.target == "x86_64-w64-mingw32") {
@ -848,6 +861,7 @@ int Crafter::Run(int argc, char** argv) {
bool runTests = false;
bool runAfterBuild = false;
RunTestsOptions testOpts;
Progress::Verbosity verbosity = Progress::Verbosity::Default;
for (int i = 1; i < argc; ++i) {
std::string_view arg = argv[i];
@ -855,6 +869,10 @@ int Crafter::Run(int argc, char** argv) {
runTests = true;
} else if (arg == "-r") {
runAfterBuild = true;
} else if (arg == "-v" || arg == "--verbose") {
verbosity = Progress::Verbosity::Verbose;
} else if (arg == "-q" || arg == "--quiet") {
verbosity = Progress::Verbosity::Quiet;
} else if (arg.starts_with("--project=")) {
projectFile = arg.substr(std::string_view("--project=").size());
} else if (runTests && arg.starts_with("--jobs=")) {
@ -872,6 +890,8 @@ int Crafter::Run(int argc, char** argv) {
}
}
Progress::SetVerbosity(verbosity);
// The test run is target-scoped: only tests whose cfg.target equals
// testOpts.targetFilter are included. Default = host triple, so a
// bare `crafter-build test` runs everything that targets host.
@ -899,10 +919,13 @@ int Crafter::Run(int argc, char** argv) {
BuildResult result = Build(config, depResults, depMutex);
if (!result.result.empty()) {
Progress::Clear();
std::println(std::cerr, "{}", result.result);
return 1;
}
Progress::Finalize();
if (runAfterBuild) {
if (config.type != ConfigurationType::Executable) {
std::println(std::cerr, "-r: cannot run a library");
@ -925,6 +948,7 @@ int Crafter::Run(int argc, char** argv) {
return 0;
} catch (const std::exception& e) {
Progress::Clear();
std::println(std::cerr, "{}", e.what());
return 1;
}

View file

@ -32,6 +32,7 @@ module Crafter.Build:Platform_impl;
import std;
import :Platform;
import :Clang;
import :Progress;
namespace fs = std::filesystem;
using namespace Crafter;
@ -79,6 +80,7 @@ fs::path Crafter::GetCrafterBuildHome() {
#if defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_windows_msvc) || defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_w64_mingw32)
std::string Crafter::RunCommand(const std::string_view cmd) {
Progress::EchoCommand(cmd);
std::array<char, 128> buffer;
std::string result;
@ -555,6 +557,7 @@ Configuration Crafter::LoadProject(const fs::path& projectFile, std::span<const
#ifdef CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_linux_gnu
std::string Crafter::RunCommand(const std::string_view cmd) {
Progress::EchoCommand(cmd);
std::array<char, 128> buffer;
std::string result;

View file

@ -0,0 +1,165 @@
/*
Crafter® Build
Copyright (C) 2026 Catcrafts®
Catcrafts.net
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License version 3.0 as published by the Free Software Foundation;
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
module;
#include <stdio.h>
#include <stdlib.h>
#if defined(_WIN32)
#include <io.h>
#include <windows.h>
#ifndef STDOUT_FILENO
#define STDOUT_FILENO _fileno(stdout)
#endif
#define CRAFTER_PROGRESS_ISATTY _isatty
#else
#include <unistd.h>
#include <sys/ioctl.h>
#define CRAFTER_PROGRESS_ISATTY isatty
#endif
export module Crafter.Build:Progress_impl;
import std;
import :Progress;
namespace {
std::mutex g_mutex;
std::atomic<int> g_total{0};
std::atomic<int> g_done{0};
Crafter::Progress::Verbosity g_verbosity = Crafter::Progress::Verbosity::Default;
bool g_isTty = false;
bool g_lineDirty = false; // status line has uncleared content
bool g_finalized = false;
std::chrono::steady_clock::time_point g_startTime = std::chrono::steady_clock::now();
int TerminalWidth() {
#if defined(_WIN32)
HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO info;
if (h != INVALID_HANDLE_VALUE && GetConsoleScreenBufferInfo(h, &info)) {
int w = info.srWindow.Right - info.srWindow.Left + 1;
if (w > 0) return w;
}
#else
winsize ws{};
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0) {
return ws.ws_col;
}
#endif
if (const char* col = std::getenv("COLUMNS")) {
try { int c = std::stoi(col); if (c > 0) return c; } catch (...) {}
}
return 80;
}
// Caller holds g_mutex.
void RenderStatus(std::string_view label) {
if (g_verbosity != Crafter::Progress::Verbosity::Default || !g_isTty) return;
int done = g_done.load(std::memory_order_relaxed);
int total = g_total.load(std::memory_order_relaxed);
std::string prefix = std::format("[{}/{}] ", done, total);
int width = TerminalWidth();
int avail = width - static_cast<int>(prefix.size()) - 1;
std::string trimmed{label};
if (avail > 0 && static_cast<int>(trimmed.size()) > avail) {
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);
g_lineDirty = true;
}
// Caller holds g_mutex.
void ClearLineLocked() {
if (g_lineDirty) {
std::fputs("\r\033[2K", stdout);
std::fflush(stdout);
g_lineDirty = false;
}
}
}
namespace Crafter::Progress {
void SetVerbosity(Verbosity v) {
std::lock_guard lock(g_mutex);
g_verbosity = v;
g_isTty = CRAFTER_PROGRESS_ISATTY(STDOUT_FILENO) != 0;
g_startTime = std::chrono::steady_clock::now();
g_total.store(0);
g_done.store(0);
g_finalized = false;
}
Verbosity GetVerbosity() {
std::lock_guard lock(g_mutex);
return g_verbosity;
}
Task::Task(std::string label) : label_(std::move(label)) {
g_total.fetch_add(1, std::memory_order_relaxed);
std::lock_guard lock(g_mutex);
if (g_verbosity == Verbosity::Default && g_isTty) {
RenderStatus(label_);
}
}
Task::~Task() {
int done = g_done.fetch_add(1, std::memory_order_relaxed) + 1;
std::lock_guard lock(g_mutex);
if (g_verbosity == Verbosity::Default) {
if (g_isTty) {
RenderStatus(label_);
} else {
// Non-TTY: one append-only line per completed task (ninja-style).
int total = g_total.load(std::memory_order_relaxed);
std::println("[{}/{}] {}", done, total, label_);
}
}
}
void EchoCommand(std::string_view command) {
std::lock_guard lock(g_mutex);
if (g_verbosity == Verbosity::Verbose) {
std::println("$ {}", command);
}
}
void Clear() {
std::lock_guard lock(g_mutex);
ClearLineLocked();
}
void Finalize() {
std::lock_guard lock(g_mutex);
if (g_finalized) return;
g_finalized = true;
ClearLineLocked();
if (g_verbosity == Verbosity::Quiet) return;
int done = g_done.load();
if (done == 0) return; // Nothing happened (cached build); stay silent.
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - g_startTime);
std::println("Built {} step{} in {}ms",
done, done == 1 ? "" : "s", elapsed.count());
}
} // namespace Crafter::Progress

View file

@ -22,6 +22,7 @@ import std;
import :Test;
import :Clang;
import :Platform;
import :Progress;
namespace fs = std::filesystem;
using namespace Crafter;
@ -161,6 +162,7 @@ namespace {
}
void PrintResult(const TestResult& r, std::string_view runnerName) {
Progress::Clear();
auto ms = r.duration.count();
std::string runnerSuffix = (runnerName.empty() || runnerName == "local")
? std::string()
@ -719,6 +721,7 @@ TestSummary Crafter::RunTests(Configuration& projectCfg, const RunTestsOptions&
}
summary.results = std::move(results);
Progress::Clear();
std::print("\n");
std::vector<std::string> parts;
if (summary.passed) parts.push_back(std::format("{} passed", summary.passed));