Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
path: root/tools
diff options
context:
space:
mode:
authorLeitwolf112026-11-01 22:58:30 +0300
committerLeitwolf112026-11-01 22:58:30 +0300
commite66db1cf17714be2ebfeab37670ef3d8965fc7cc (patch)
tree88a8b55a14e9bc6152608662d062c1624c2863cf /tools
parente32c9d107274f42b9cca700475404c80e96533c5 (diff)
downloadcatch3-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.cpp58
-rw-r--r--tools/RepositoryReport.hpp7
-rw-r--r--tools/ThirdPartyMain.cpp29
-rw-r--r--tools/Tools.tests.cpp21
-rw-r--r--tools/VersionMain.cpp15
-rw-r--r--tools/Versions.cpp36
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;