fix(relinker): align console progress columns

This commit is contained in:
boykopovar 2026-10-13 12:50:32 +03:00
parent a534bd67ca
commit 04899cb0f7
3 changed files with 27 additions and 5 deletions

View File

@ -6,6 +6,8 @@
#include <chrono>
#include <sstream>
#include <stdexcept>
#include <algorithm>
#include <iomanip>
namespace Cli {
@ -13,6 +15,12 @@ class ConsoleProgress final : public Domain::IProgress {
public:
explicit ConsoleProgress(std::ostream& stream) : stream(stream) {}
void IncludeFile(std::string_view path) {
const auto separator = path.find_last_of("/\\");
const auto name = path.substr(separator == std::string_view::npos ? 0 : separator + 1);
nameWidth = std::max(nameWidth, textWidth(name));
}
void Report(const Domain::ProgressEvent& event) override {
if (event.Completed > event.Total) throw std::logic_error("Invalid progress counters");
const char* label = nullptr;
@ -51,9 +59,9 @@ public:
if (!changed && !complete && now - lastUpdate < std::chrono::milliseconds(80)) return;
if (changed || messages) Finish();
std::ostringstream line;
line << label;
if (!name.empty()) line << " | " << name;
line << " | " << percent << '%';
line << std::left << std::setw(22) << label;
line << " | " << name << std::string(nameWidth > textWidth(name) ? nameWidth - textWidth(name) : 0, ' ');
line << " | " << std::right << std::setw(3) << percent << '%';
if (event.Stage == Domain::ProgressStage::Modules) line << " (" << event.Completed << '/' << event.Total << ')';
const auto text = line.str();
stream << '\r' << text;
@ -84,6 +92,11 @@ public:
}
private:
static std::size_t textWidth(std::string_view text) {
return static_cast<std::size_t>(std::count_if(text.begin(), text.end(), [](unsigned char character) { return (character & 0xc0) != 0x80; }));
}
std::size_t nameWidth = 0;
std::ostream& stream;
Domain::ProgressStage lastStage = Domain::ProgressStage::Write;
std::string lastInput;

View File

@ -33,7 +33,7 @@ struct ConvertedExecutable {
std::string Diagnostics;
};
ConvertedExecutable Convert(const Cli::Args& args, const std::string& inputPath, const std::string& absPath, const bool mainExecutable, Domain::IProgress& progress) {
ConvertedExecutable Convert(const Cli::Args& args, const std::string& inputPath, const std::string& absPath, const bool mainExecutable, Cli::ConsoleProgress& progress) {
Io::FileReader fileReader;
Relinker::ConversionInput input;
input.Executable = fileReader.Read(inputPath);
@ -59,6 +59,9 @@ ConvertedExecutable Convert(const Cli::Args& args, const std::string& inputPath,
options.WindowsGui = args.windowsGui;
options.WindowsDiagnostics = args.windowsDiagnostics && mainExecutable;
options.ExcludedModules = args.excludedSceModules;
progress.IncludeFile(Io::Utf8Path(input.InputPath));
progress.IncludeFile(Io::Utf8Path(input.OutputPath));
for (const auto& module : input.Modules) progress.IncludeFile(Io::Utf8Path(module.Path));
auto converted = Relinker::Conversion(progress).Convert(std::move(input), options);
return {inputPath, absPath, std::move(converted.Analysis), std::move(converted.Modules), std::move(converted.Executable), std::move(converted.WindowsDiagnostics)};
}
@ -121,6 +124,12 @@ int Run(const int argc, char* argv[]) {
extraExecutables.push_back({executable, relative, input, output});
}
progress.IncludeFile(args.inputPath);
progress.IncludeFile(absPath);
for (const auto& extra : extraExecutables) {
progress.IncludeFile(Io::Utf8Path(extra.Input));
progress.IncludeFile(Io::Utf8Path(extra.Output));
}
std::vector<ConvertedExecutable> executables;
executables.push_back(Convert(args, args.inputPath, absPath, true, progress));
for (const auto& [executable, relative, input, output] : extraExecutables) {

View File

@ -36,7 +36,7 @@ def main():
assert secondary.read_bytes().startswith(b"MZ" if windows else b"\x7fELF"), secondary
assert "Executable: picker/picker.bin\n" in result.stdout, result.stdout
assert f"Executable file: {secondary}\n" in result.stdout, result.stdout
assert f"app{suffix}\npicker.picker.bin{suffix}\nlibs/\n" in result.stdout, result.stdout
assert f" |-- app{suffix}\n |-- picker.picker.bin{suffix}\n |-- libs/\n" in result.stdout, result.stdout
assert (output.parent / "picker.picker.bin.registry.json").is_file()
assert sorted(path.name for path in output.parent.rglob("*.guest.prx")) == ["bundled.prx.guest.prx"]
if not windows: