diff options
| author | Leitwolf11 | 2026-11-01 22:58:30 +0300 |
|---|---|---|
| committer | Leitwolf11 | 2026-11-01 22:58:30 +0300 |
| commit | e66db1cf17714be2ebfeab37670ef3d8965fc7cc (patch) | |
| tree | 88a8b55a14e9bc6152608662d062c1624c2863cf /tools | |
| parent | e32c9d107274f42b9cca700475404c80e96533c5 (diff) | |
| download | catch3-e66db1cf17714be2ebfeab37670ef3d8965fc7cc.tar.gz | |
Answer the review of the tools
repo_info starts Git in the checkout with fixed commands, so a shell never
reads a path, reports an unknown state as null rather than clean, and
counts only files it can start. third_party reads every archive before
it changes a file and refuses two modes. version checks that the sources
make a distribution, computes every place before writing any, and
release_notes finds a three-number heading for a version with D = 0.
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/RepositoryReport.cpp | 58 | ||||
| -rw-r--r-- | tools/RepositoryReport.hpp | 7 | ||||
| -rw-r--r-- | tools/ThirdPartyMain.cpp | 29 | ||||
| -rw-r--r-- | tools/Tools.tests.cpp | 21 | ||||
| -rw-r--r-- | tools/VersionMain.cpp | 15 | ||||
| -rw-r--r-- | tools/Versions.cpp | 36 |
6 files changed, 133 insertions, 33 deletions
diff --git a/tools/RepositoryReport.cpp b/tools/RepositoryReport.cpp index 4b2b1502..f985335e 100644 --- a/tools/RepositoryReport.cpp +++ b/tools/RepositoryReport.cpp @@ -10,6 +10,10 @@ #include <string_view> #include <system_error> +#if !defined(_WIN32) +# include <unistd.h> +#endif + #include "tools/Amalgamation.hpp" #include "tools/Repository.hpp" #include "tools/RepositoryReport.hpp" @@ -70,6 +74,22 @@ namespace Progmasoft::Catch3::Tools #endif } + /// Whether `file` can be started, which only POSIX says of a file. + [[nodiscard]] bool + IsProgram(const fs::path &file) + { + std::error_code error; + if (!fs::is_regular_file(file, error)) + { + return false; + } +#if defined(_WIN32) + return true; +#else + return access(file.c_str(), X_OK) == 0; +#endif + } + [[nodiscard]] ToolInfo Find(std::string_view name, std::string_view searchPath) { @@ -92,8 +112,7 @@ namespace Progmasoft::Catch3::Tools const auto file = directory / (std::string{ name } + std::string{ extension }); - std::error_code error; - if (fs::is_regular_file(file, error)) + if (IsProgram(file)) { tool.Path = file.string(); tool.Available = true; @@ -104,13 +123,13 @@ namespace Progmasoft::Catch3::Tools return tool; } - /// What `git <arguments>` writes in `root`, or nothing when it fails. + /// What `git <arguments>` writes in the current directory, or nothing + /// when it fails. A shell reads the command, so it never holds a path. [[nodiscard]] std::optional<std::string> - Git(const fs::path &root, std::string_view arguments) + Git(std::string_view arguments) { - const auto command = "git -C \"" + root.string() + "\" " - + std::string{ arguments } - + std::string{ kNoErrors }; + const auto command + = "git " + std::string{ arguments } + std::string{ kNoErrors }; #if defined(_WIN32) auto *pipe = _popen(command.c_str(), "r"); #else @@ -197,11 +216,12 @@ namespace Progmasoft::Catch3::Tools report.Tools.push_back(Find(name, searchPath)); } - const auto branch = Git(root, "branch --show-current"); - const auto revision = Git(root, "rev-parse HEAD"); - const auto status - = Git(root, "status --porcelain --untracked-files=no"); - if (branch && revision && status) + std::error_code error; + fs::current_path(root, error); + const auto branch = Git("branch --show-current"); + const auto revision = Git("rev-parse HEAD"); + const auto status = Git("status --porcelain --untracked-files=no"); + if (!error && branch && revision && status) { report.Branch = *branch; report.Revision = *revision; @@ -224,7 +244,10 @@ namespace Progmasoft::Catch3::Tools text += "Branch: " + report.Branch + "\n"; text += "Revision: " + report.Revision + "\n"; text += std::string{ "Tracked changes: " } - + (report.Dirty ? "true" : "false") + "\n"; + + (!report.Dirty ? "unknown" + : *report.Dirty ? "true" + : "false") + + "\n"; if (!report.GitError.empty()) { text += report.GitError + "\n"; @@ -254,7 +277,14 @@ namespace Progmasoft::Catch3::Tools AddText(document, root, "engineVersion", report.EngineVersion); AddText(document, root, "branch", report.Branch); AddText(document, root, "revision", report.Revision); - yyjson_mut_obj_add_bool(document, root, "dirty", report.Dirty); + if (report.Dirty) + { + yyjson_mut_obj_add_bool(document, root, "dirty", *report.Dirty); + } + else + { + yyjson_mut_obj_add_null(document, root, "dirty"); + } AddText(document, root, "gitError", report.GitError); auto *tools = yyjson_mut_obj_add_arr(document, root, "tools"); for (const auto &tool : report.Tools) diff --git a/tools/RepositoryReport.hpp b/tools/RepositoryReport.hpp index 83ef85b7..57c5d5e5 100644 --- a/tools/RepositoryReport.hpp +++ b/tools/RepositoryReport.hpp @@ -3,6 +3,7 @@ #pragma once #include <filesystem> +#include <optional> #include <string> #include <vector> @@ -27,12 +28,14 @@ namespace Progmasoft::Catch3::Tools std::string EngineVersion; std::string Branch; std::string Revision; - bool Dirty = false; + // Empty when Git could not say. + std::optional<bool> Dirty; std::string GitError; std::vector<ToolInfo> Tools; }; - /// Reads the checkout at `root`, without changing anything. + /// Reads the checkout at `root`, without changing anything in it. It + /// leaves the process in `root`. [[nodiscard]] RepositoryReport CollectRepositoryReport(const std::filesystem::path &root); diff --git a/tools/ThirdPartyMain.cpp b/tools/ThirdPartyMain.cpp index 6223e85f..796c6182 100644 --- a/tools/ThirdPartyMain.cpp +++ b/tools/ThirdPartyMain.cpp @@ -11,6 +11,7 @@ #include <memory> #include <string> #include <string_view> +#include <utility> #include <vector> #include "rules_cc/cc/runfiles/runfiles.h" @@ -24,11 +25,13 @@ main(int argc, char **argv) std::vector<std::string_view> files; std::string_view mode; + bool usable = true; for (int index = 1; index < argc; ++index) { const std::string_view argument{ argv[index] }; if (argument == "--check" || argument == "--update") { + usable = usable && mode.empty(); mode = argument; } else if (argument.find('=') != std::string_view::npos) @@ -37,11 +40,10 @@ main(int argc, char **argv) } else { - mode = {}; - break; + usable = false; } } - if (mode.empty()) + if (!usable || mode.empty()) { std::cerr << "usage: bazelisk run //tools:third_party -- " "--check|--update\n"; @@ -63,16 +65,25 @@ main(int argc, char **argv) return 2; } Problems problems; + // Every archive is read before any file changes. + std::vector<std::pair<std::string_view, std::string>> archived; for (const auto file : files) { const auto separator = file.find('='); - const auto vendored = file.substr(0, separator); - const auto archived = ReadOrReport( - runfiles->Rlocation(std::string{ file.substr(separator + 1) }), - problems); + archived.emplace_back(file.substr(0, separator), + ReadOrReport(runfiles->Rlocation(std::string{ + file.substr(separator + 1) }), + problems)); + } + if (!problems.empty()) + { + return Finish(problems); + } + for (const auto &[vendored, text] : archived) + { if (mode == "--check") { - if (ReadText(*root / vendored) != archived) + if (ReadText(*root / vendored) != text) { problems.push_back(std::string{ vendored } + " differs from its pinned archive: run " @@ -80,7 +91,7 @@ main(int argc, char **argv) "--update"); } } - else if (!WriteText(*root / vendored, archived)) + else if (!WriteText(*root / vendored, text)) { problems.push_back("cannot write " + std::string{ vendored }); } diff --git a/tools/Tools.tests.cpp b/tools/Tools.tests.cpp index 51dca951..90e9b129 100644 --- a/tools/Tools.tests.cpp +++ b/tools/Tools.tests.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2026 Progmasoft. // SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-2.0 +#include <Progmasoft/Catch3/Matchers/String.hpp> #include <Progmasoft/Catch3/TestMacros.hpp> #include <string> @@ -10,6 +11,7 @@ #include "tools/Versions.hpp" using namespace Progmasoft::Catch3::Tools; +using Catch::Matchers::ContainsSubstring; TEST_CASE("Versions have three or four numbers", "[tools]") { @@ -55,6 +57,25 @@ TEST_CASE("Release notes are found by their heading", "[tools]") CHECK_FALSE(ReleaseNotesOf(notes, "0.3")); } +TEST_CASE("A release with D = 0 may be headed by three numbers", "[tools]") +{ + const std::string notes = "# Notes\n\n## 0.4.0\n\nThree.\n"; + CHECK(ReleaseNotesOf(notes, "0.4.0") == "Three.\n"); + CHECK(ReleaseNotesOf(notes, "0.4.0.0") == "Three.\n"); + CHECK_FALSE(ReleaseNotesOf(notes, "0.4.0.1")); +} + +TEST_CASE("Git that could not say is not a clean checkout", "[tools]") +{ + RepositoryReport report; + report.GitError = "unreadable"; + CHECK_THAT(ReportToJson(report), ContainsSubstring("\"dirty\": null")); + CHECK_THAT(FormatReport(report), + ContainsSubstring("Tracked changes: unknown")); + report.Dirty = false; + CHECK_THAT(ReportToJson(report), ContainsSubstring("\"dirty\": false")); +} + TEST_CASE("Includes are read as they are written", "[tools]") { const auto angled = ParseInclude(" #include <a/b.hpp> // why\n"); diff --git a/tools/VersionMain.cpp b/tools/VersionMain.cpp index a7fb123a..6f1c13cb 100644 --- a/tools/VersionMain.cpp +++ b/tools/VersionMain.cpp @@ -54,12 +54,19 @@ main(int argc, char **argv) } const auto version = FormatVersion(argument == "--next" ? NextVersion(*parsed) : *parsed); - SetVersion(*root, version, problems); + // The sources have to make a distribution before anything is changed. + (void)GenerateAmalgamation(*root, problems); if (problems.empty()) { - WriteAmalgamation(*root, - GenerateAmalgamation(*root, problems), - problems); + SetVersion(*root, version, problems); + } + if (problems.empty()) + { + const auto amalgamation = GenerateAmalgamation(*root, problems); + if (problems.empty()) + { + WriteAmalgamation(*root, amalgamation, problems); + } } if (problems.empty()) { diff --git a/tools/Versions.cpp b/tools/Versions.cpp index 73a2d663..abc6d5b3 100644 --- a/tools/Versions.cpp +++ b/tools/Versions.cpp @@ -4,6 +4,8 @@ #include <algorithm> #include <cctype> #include <charconv> +#include <utility> +#include <vector> #include "tools/Versions.hpp" @@ -176,24 +178,50 @@ namespace Progmasoft::Catch3::Tools const std::string &version, Problems &problems) { + std::vector<std::pair<std::filesystem::path, std::string>> writes; for (const auto &site : VersionSites()) { const auto path = root / site.File; - const auto text + auto text = WriteVersionSite(ReadOrReport(path, problems), site, version); - if (!text || !WriteText(path, *text)) + if (text) + { + writes.emplace_back(path, *std::move(text)); + } + else { problems.push_back("cannot set the version in " + std::string{ site.File }); } } + // All of them or none, so that the places never disagree. + if (!problems.empty()) + { + return; + } + for (const auto &[path, text] : writes) + { + if (!WriteText(path, text)) + { + problems.push_back("cannot write " + path.generic_string()); + } + } } std::optional<std::string> ReleaseNotesOf(std::string_view notes, std::string_view version) { - const auto heading = "\n## " + std::string{ version } + "\n"; - const auto found = notes.find(heading); + auto heading = "\n## " + std::string{ version } + "\n"; + auto found = notes.find(heading); + // A release with D = 0 may be headed by its three numbers. + const auto parsed = ParseVersion(version); + if (found == std::string_view::npos && parsed && (*parsed)[3] == 0) + { + heading = "\n## " + std::to_string((*parsed)[0]) + "." + + std::to_string((*parsed)[1]) + "." + + std::to_string((*parsed)[2]) + "\n"; + found = notes.find(heading); + } if (found == std::string_view::npos) { return std::nullopt; |