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

summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorLeitwolf2026-11-02 00:51:11 +0300
committerGitHub2026-11-02 00:51:11 +0300
commit268698be92962247a21a86dda4de2d4267adcf8f (patch)
tree510f7ece1750b2da6d9b4666b7c58908c8d193bb
parent13324a043042110eba456424fa39c1334857ceaa (diff)
parent3ed0fd930371823b6d44ae64d00dbd79e421a552 (diff)
downloadcatch3-268698be92962247a21a86dda4de2d4267adcf8f.tar.gz
Merge pull request #36 from Progmasoft/feature/cpp-tools
Move the tools from Python to C++17 and Bazel
-rw-r--r--.github/renovate.json512
-rw-r--r--.github/workflows/validate-header-guards.yml40
-rw-r--r--MODULE.bazel27
-rw-r--r--docs/contributing.md30
-rw-r--r--docs/upstream-status.md2
-rw-r--r--extras/catch_amalgamated.cpp1
-rw-r--r--extras/catch_amalgamated.hpp1
-rw-r--r--extras/catch_amalgamated_third_party.hpp1
-rw-r--r--tests/CMakeLists.txt11
-rw-r--r--third_party/pugixml/BUILD.bazel2
-rw-r--r--third_party/pugixml/CMakeLists.txt2
-rw-r--r--third_party/pugixml/meson.build2
-rw-r--r--third_party/pugixml/premake5.lua2
-rw-r--r--third_party/pugixml/xmake.lua2
-rw-r--r--third_party/yyjson/BUILD.bazel7
-rw-r--r--third_party/yyjson/CMakeLists.txt2
-rw-r--r--third_party/yyjson/meson.build2
-rw-r--r--third_party/yyjson/premake5.lua2
-rw-r--r--third_party/yyjson/xmake.lua2
-rw-r--r--tools/AmalgamateMain.cpp43
-rw-r--r--tools/Amalgamation.cpp582
-rw-r--r--tools/Amalgamation.hpp54
-rw-r--r--tools/BUILD.bazel101
-rw-r--r--tools/CheckMain.cpp41
-rw-r--r--tools/Checks.cpp254
-rw-r--r--tools/Checks.hpp31
-rw-r--r--tools/ReleaseNotesMain.cpp49
-rw-r--r--tools/RepoInfoMain.cpp34
-rw-r--r--tools/Repository.cpp194
-rw-r--r--tools/Repository.hpp64
-rw-r--r--tools/RepositoryReport.cpp306
-rw-r--r--tools/RepositoryReport.hpp50
-rw-r--r--tools/ThirdPartyMain.cpp100
-rw-r--r--tools/Tools.tests.cpp137
-rw-r--r--tools/VersionMain.cpp76
-rw-r--r--tools/Versions.cpp233
-rw-r--r--tools/Versions.hpp67
-rw-r--r--tools/scripts/buildAndTest.cmd16
-rwxr-xr-xtools/scripts/buildAndTest.sh18
-rwxr-xr-xtools/scripts/checkConvenienceHeaders.py141
-rwxr-xr-xtools/scripts/checkDuplicateFilenames.py30
-rwxr-xr-xtools/scripts/checkLicense.py62
-rwxr-xr-xtools/scripts/fixWhitespace.py51
-rwxr-xr-xtools/scripts/generateAmalgamatedFiles.py298
-rw-r--r--tools/scripts/releaseCommon.py29
-rw-r--r--tools/scripts/scriptCommon.py11
-rwxr-xr-xtools/scripts/updateDocumentSnippets.py23
-rwxr-xr-xtools/scripts/updateDocumentToC.py447
-rwxr-xr-xtools/scripts/updateThirdParty.py103
-rw-r--r--tools/upstreamFiles.txt (renamed from tools/scripts/upstreamFiles.txt)0
50 files changed, 2503 insertions, 1292 deletions
diff --git a/.github/renovate.json5 b/.github/renovate.json5
index 4a634ea3..deaa4062 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -46,16 +46,16 @@
"custom.regex",
],
- // third_party/ holds copies of release archives, which
- // tools/scripts/updateThirdParty.py pins by version and sha256. Renovate
- // changes the version; the pull request then needs the new digest and
- // --update, which the third_party check of CI asks for.
+ // third_party/ holds copies of release archives, which MODULE.bazel pins by
+ // version and sha256. Renovate changes the version; the pull request then
+ // needs the new digest and //tools:third_party -- --update, which the
+ // third_party check of CI asks for.
customManagers: [
{
customType: "regex",
- managerFilePatterns: ["/^tools/scripts/updateThirdParty\\.py$/"],
+ managerFilePatterns: ["/^MODULE\\.bazel$/"],
matchStrings: [
- "# renovate: datasource=(?<datasource>\\S+) depName=(?<depName>\\S+)( extractVersion=(?<extractVersion>\\S+))?\\s+\"version\": \"(?<currentValue>[^\"]+)\"",
+ "# renovate: datasource=(?<datasource>\\S+) depName=(?<depName>\\S+)( extractVersion=(?<extractVersion>\\S+))?\\s+\\w+_VERSION = \"(?<currentValue>[^\"]+)\"",
],
},
],
diff --git a/.github/workflows/validate-header-guards.yml b/.github/workflows/validate-header-guards.yml
index a8ba91ba..bceb79ae 100644
--- a/.github/workflows/validate-header-guards.yml
+++ b/.github/workflows/validate-header-guards.yml
@@ -19,31 +19,23 @@ jobs:
with:
persist-credentials: false
- - name: Setup Dependencies
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
+ - uses: bazel-contrib/setup-bazel@c5acdfb288317d0b5c0bbd7a396a3dc868bb0f86 # 0.19.0
with:
- python-version: '3.12'
-
- # Every header is guarded with #pragma once, as the Progmasoft C++ Style
- # asks; the amalgamation script relies on it.
- - name: Check that every header starts its guard with pragma once
- run: |
- wrong_files=$(grep -rL --include='*.hpp' '^#pragma once' src/Progmasoft || true)
- if [[ $wrong_files ]]; then
- echo "Headers without #pragma once:"
- echo $wrong_files
- exit 1
- fi
-
- - name: Check that there are no duplicated filenames
- run: |
- ./tools/scripts/checkDuplicateFilenames.py
-
- - name: Check that all source files have the correct license header
- run: |
- ./tools/scripts/checkLicense.py
+ bazelisk-cache: true
+ repository-cache: true
+
+ - name: Test the tools
+ run: bazel test //tools:tools_tests
+ env:
+ CC: clang
+
+ - name: Check licenses, headers, file names, versions and the amalgamation
+ run: bazel run //tools:check
+ env:
+ CC: clang
# third_party/ must be the pinned release archives, byte for byte.
- name: Check that third_party matches its pinned archives
- run: |
- ./tools/scripts/updateThirdParty.py --check
+ run: bazel run //tools:third_party -- --check
+ env:
+ CC: clang
diff --git a/MODULE.bazel b/MODULE.bazel
index f8069a79..c4196428 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -5,6 +5,33 @@ bazel_dep(name = "rules_cc", version = "0.2.17")
bazel_dep(name = "rules_license", version = "1.0.0")
bazel_dep(name = "platforms", version = "1.0.0")
+# The archives third_party/ holds copies of. Renovate changes a version; the
+# pull request then needs the digest of the new archive and
+# `bazelisk run //tools:third_party -- --update`, which CI asks for.
+http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+# renovate: datasource=github-tags depName=ibireme/yyjson
+YYJSON_VERSION = "0.13.0"
+
+http_archive(
+ name = "yyjson_archive",
+ build_file_content = 'exports_files(["src/yyjson.c", "src/yyjson.h"])',
+ sha256 = "34e0f62a2bc11ab20d601e8ca1cc2b2079503aa45119a19133d89d19b94a0fae",
+ strip_prefix = "yyjson-" + YYJSON_VERSION,
+ urls = ["https://github.com/ibireme/yyjson/archive/refs/tags/" + YYJSON_VERSION + ".tar.gz"],
+)
+
+# renovate: datasource=github-tags depName=zeux/pugixml extractVersion=^v(?<version>.+)$
+PUGIXML_VERSION = "1.16"
+
+http_archive(
+ name = "pugixml_archive",
+ build_file_content = 'exports_files(["src/pugiconfig.hpp", "src/pugixml.cpp", "src/pugixml.hpp"])',
+ sha256 = "357bcab8877dc9943f355d3a72daba1b053238ba955f50fa81586afb65090219",
+ strip_prefix = "pugixml-" + PUGIXML_VERSION,
+ urls = ["https://github.com/zeux/pugixml/archive/refs/tags/v" + PUGIXML_VERSION + ".tar.gz"],
+)
+
# rules_swift reaches the graph through protobuf and rules_apple. Up to 3.4.1
# its Windows autoconfiguration, which runs whenever swiftc.exe is on PATH,
# writes a toolchain that indexes APPLE_PLATFORMS_CONSTRAINTS with an
diff --git a/docs/contributing.md b/docs/contributing.md
index ecccf68f..9f2cffc2 100644
--- a/docs/contributing.md
+++ b/docs/contributing.md
@@ -47,13 +47,28 @@ Meson, Xmake and Premake builds exist as well and are checked in CI.
Before you push, run the repository checks:
```
-python tools/scripts/checkLicense.py
-python tools/scripts/checkConvenienceHeaders.py
-python tools/scripts/checkDuplicateFilenames.py
+bazelisk run //tools:check
```
-A change to anything under `src/` also regenerates the single-file
-distribution with `python tools/scripts/generateAmalgamatedFiles.py`.
+It checks licenses, convenience headers, header guards, file names, that every
+place states the same version, and that the single-file distribution is up to
+date. A change to anything under `src/` regenerates that distribution:
+
+```
+bazelisk run //tools:amalgamate
+```
+
+The tools in `tools/` are C++17 and run through Bazel, with arguments after
+`--`:
+
+| Tool | Does |
+| --- | --- |
+| `//tools:amalgamate` | writes the single-file distribution; `-- --check` only compares it |
+| `//tools:check` | runs the repository checks |
+| `//tools:third_party` | `-- --check` compares `third_party/` with the archives `MODULE.bazel` pins, `-- --update` writes it |
+| `//tools:version` | prints the version; `-- --next` or `-- <version>` moves every place that states it, and the distribution |
+| `//tools:release_notes` | prints a section of the release notes, of `-- <version>` or the current one |
+| `//tools:repo_info` | reports the checkout and the tools on `PATH`; `-- --json` writes it as JSON |
## Two parts, two sets of rules
@@ -71,8 +86,7 @@ AdditionRef-Progmasoft-Exception-2.0`, follows the
C++17, reports recoverable errors with `Expected` rather than exceptions, and
is formatted with the `.clang-format` beside it.
-`tools/scripts/checkLicense.py` checks that every source file carries the
-header of its part.
+`//tools:check` checks that every source file carries the header of its part.
## Tests
@@ -88,7 +102,7 @@ comes with a test that fails without it.
User documentation lives in `docs/`. A new page is linked from
[the documentation index](Readme.md#top), and a page with sections gets a table
-of contents; `python tools/scripts/updateDocumentToC.py` keeps them current.
+of contents.
A user-visible change gets a line in [the release notes](release-notes.md#top).
diff --git a/docs/upstream-status.md b/docs/upstream-status.md
index 0899e31d..93483fb3 100644
--- a/docs/upstream-status.md
+++ b/docs/upstream-status.md
@@ -36,7 +36,7 @@ backported, in upstream order, and the compatibility engine is now 3.16.1:
Each applied without a conflict in the sources. The amalgamated distribution
under `extras/` did conflict, because it is generated; it was generated again
-from the sources of this repository with `generateAmalgamatedFiles.py`, which
+from the sources of this repository with `//tools:amalgamate`, which
also brought it up to date with fork changes it had not yet picked up. No
fork-owned file under `src/Progmasoft` needed a change.
diff --git a/extras/catch_amalgamated.cpp b/extras/catch_amalgamated.cpp
index df0d8f3c..5be0c78b 100644
--- a/extras/catch_amalgamated.cpp
+++ b/extras/catch_amalgamated.cpp
@@ -7,7 +7,6 @@
// SPDX-License-Identifier: BSL-1.0
// Catch3 v0.3.0.2, on the Catch2 v3.16.1 engine
-// Generated: 2026-11-01 23:05:23.688401
// ----------------------------------------------------------
// This file is an amalgamation of multiple different files.
// You probably shouldn't edit it directly.
diff --git a/extras/catch_amalgamated.hpp b/extras/catch_amalgamated.hpp
index d6bc26f6..fad2f28c 100644
--- a/extras/catch_amalgamated.hpp
+++ b/extras/catch_amalgamated.hpp
@@ -7,7 +7,6 @@
// SPDX-License-Identifier: BSL-1.0
// Catch3 v0.3.0.2, on the Catch2 v3.16.1 engine
-// Generated: 2026-11-01 23:05:23.296246
// ----------------------------------------------------------
// This file is an amalgamation of multiple different files.
// You probably shouldn't edit it directly.
diff --git a/extras/catch_amalgamated_third_party.hpp b/extras/catch_amalgamated_third_party.hpp
index 5cad151d..5a3aa21c 100644
--- a/extras/catch_amalgamated_third_party.hpp
+++ b/extras/catch_amalgamated_third_party.hpp
@@ -1,5 +1,4 @@
// Catch3 v0.3.0.2: yyjson and pugixml for catch_amalgamated.cpp
-// Generated: 2026-11-01 23:05:24.053462
// ----------------------------------------------------------
// This file is an amalgamation of src/Progmasoft/Runner/Yyjson.inl,
// src/Progmasoft/PugiXml.inl and the libraries they include from
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 40ed1358..b6b9b52c 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -33,7 +33,7 @@ if(CATCH3_BUILD_SURROGATES)
function(ExtractCatch3Headers OutArg)
# The engine's headers are the ones that come from Catch2; the surrogates
# check them at the language level of the build.
- file(STRINGS "${CATCH_DIR}/tools/scripts/upstreamFiles.txt" upstreamFiles REGEX "^src/")
+ file(STRINGS "${CATCH_DIR}/tools/upstreamFiles.txt" upstreamFiles REGEX "^src/")
get_target_property(targetSources Catch3 SOURCES)
foreach(Source ${targetSources})
string(REGEX MATCH "^.*\\.hpp$" isHeader ${Source})
@@ -366,15 +366,6 @@ set_tests_properties(TagAlias PROPERTIES
PASS_REGULAR_EXPRESSION "[1-9][0-9]* matching test\\(s\\)"
)
-add_test(NAME CheckConvenienceHeaders
- COMMAND
- Python3::Interpreter ${CATCH_DIR}/tools/scripts/checkConvenienceHeaders.py
-)
-set_tests_properties(CheckConvenienceHeaders
- PROPERTIES
- LABELS "uses-python"
-)
-
add_test(NAME "Benchmarking::SkipBenchmarkMacros"
COMMAND
$<TARGET_FILE:SelfTest> --no-history "Skip benchmark macros"
diff --git a/third_party/pugixml/BUILD.bazel b/third_party/pugixml/BUILD.bazel
index a73f10b7..120463bf 100644
--- a/third_party/pugixml/BUILD.bazel
+++ b/third_party/pugixml/BUILD.bazel
@@ -4,7 +4,7 @@
load("@rules_cc//cc:defs.bzl", "cc_library")
# pugixml (MIT), the XML writer of the reports.
-# The version is pinned in tools/scripts/updateThirdParty.py. Catch3
+# The version is pinned in MODULE.bazel. Catch3
# compiles the library into one of its own translation units with
# internal linkage, so this target only provides the files.
cc_library(
diff --git a/third_party/pugixml/CMakeLists.txt b/third_party/pugixml/CMakeLists.txt
index 85113fa3..d6e6004d 100644
--- a/third_party/pugixml/CMakeLists.txt
+++ b/third_party/pugixml/CMakeLists.txt
@@ -2,7 +2,7 @@
# SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-2.0
# pugixml (MIT), the XML writer of the reports.
-# The version is pinned in tools/scripts/updateThirdParty.py. Catch3
+# The version is pinned in MODULE.bazel. Catch3
# compiles the library into one of its own translation units with
# internal linkage, so this target only provides the include directory.
# Catch3 links it through $<BUILD_INTERFACE:...>; it is never installed.
diff --git a/third_party/pugixml/meson.build b/third_party/pugixml/meson.build
index 972ca1f1..b1897d87 100644
--- a/third_party/pugixml/meson.build
+++ b/third_party/pugixml/meson.build
@@ -2,7 +2,7 @@
# SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-2.0
# pugixml (MIT), the XML writer of the reports.
-# The version is pinned in tools/scripts/updateThirdParty.py. Catch3
+# The version is pinned in MODULE.bazel. Catch3
# compiles the library into one of its own translation units with
# internal linkage, so this only provides the include directory.
pugixml_dep = declare_dependency(
diff --git a/third_party/pugixml/premake5.lua b/third_party/pugixml/premake5.lua
index ee9ebd81..999fae79 100644
--- a/third_party/pugixml/premake5.lua
+++ b/third_party/pugixml/premake5.lua
@@ -2,7 +2,7 @@
-- SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-2.0
-- pugixml (MIT), the XML writer of the reports.
--- The version is pinned in tools/scripts/updateThirdParty.py. Catch3
+-- The version is pinned in MODULE.bazel. Catch3
-- compiles the library into one of its own translation units with
-- internal linkage, so the workspace only needs its directory.
catch3_third_party = catch3_third_party or {}
diff --git a/third_party/pugixml/xmake.lua b/third_party/pugixml/xmake.lua
index 198b54b7..29b985b1 100644
--- a/third_party/pugixml/xmake.lua
+++ b/third_party/pugixml/xmake.lua
@@ -2,7 +2,7 @@
-- SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-2.0
-- pugixml (MIT), the XML writer of the reports.
--- The version is pinned in tools/scripts/updateThirdParty.py. Catch3
+-- The version is pinned in MODULE.bazel. Catch3
-- compiles the library into one of its own translation units with
-- internal linkage, so this target only provides the files.
target("catch3_pugixml")
diff --git a/third_party/yyjson/BUILD.bazel b/third_party/yyjson/BUILD.bazel
index e44d069c..6b791e0d 100644
--- a/third_party/yyjson/BUILD.bazel
+++ b/third_party/yyjson/BUILD.bazel
@@ -4,7 +4,7 @@
load("@rules_cc//cc:defs.bzl", "cc_library")
# yyjson (MIT), the JSON reader and writer of the runner.
-# The version is pinned in tools/scripts/updateThirdParty.py. Catch3
+# The version is pinned in MODULE.bazel. Catch3
# compiles the library into one of its own translation units with
# internal linkage, so this target only provides the files.
cc_library(
@@ -12,5 +12,8 @@ cc_library(
hdrs = ["yyjson.h"],
includes = ["."],
textual_hdrs = ["yyjson.c"],
- visibility = ["//src/Progmasoft:__pkg__"],
+ visibility = [
+ "//src/Progmasoft:__pkg__",
+ "//tools:__pkg__",
+ ],
)
diff --git a/third_party/yyjson/CMakeLists.txt b/third_party/yyjson/CMakeLists.txt
index 77200553..30623718 100644
--- a/third_party/yyjson/CMakeLists.txt
+++ b/third_party/yyjson/CMakeLists.txt
@@ -2,7 +2,7 @@
# SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-2.0
# yyjson (MIT), the JSON reader and writer of the runner.
-# The version is pinned in tools/scripts/updateThirdParty.py. Catch3
+# The version is pinned in MODULE.bazel. Catch3
# compiles the library into one of its own translation units with
# internal linkage, so this target only provides the include directory.
# Catch3 links it through $<BUILD_INTERFACE:...>; it is never installed.
diff --git a/third_party/yyjson/meson.build b/third_party/yyjson/meson.build
index b3683188..6a6566e8 100644
--- a/third_party/yyjson/meson.build
+++ b/third_party/yyjson/meson.build
@@ -2,7 +2,7 @@
# SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-2.0
# yyjson (MIT), the JSON reader and writer of the runner.
-# The version is pinned in tools/scripts/updateThirdParty.py. Catch3
+# The version is pinned in MODULE.bazel. Catch3
# compiles the library into one of its own translation units with
# internal linkage, so this only provides the include directory.
yyjson_dep = declare_dependency(
diff --git a/third_party/yyjson/premake5.lua b/third_party/yyjson/premake5.lua
index 07f3b3c9..fc17e1bf 100644
--- a/third_party/yyjson/premake5.lua
+++ b/third_party/yyjson/premake5.lua
@@ -2,7 +2,7 @@
-- SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-2.0
-- yyjson (MIT), the JSON reader and writer of the runner.
--- The version is pinned in tools/scripts/updateThirdParty.py. Catch3
+-- The version is pinned in MODULE.bazel. Catch3
-- compiles the library into one of its own translation units with
-- internal linkage, so the workspace only needs its directory.
catch3_third_party = catch3_third_party or {}
diff --git a/third_party/yyjson/xmake.lua b/third_party/yyjson/xmake.lua
index 207ecf61..e4e9c58c 100644
--- a/third_party/yyjson/xmake.lua
+++ b/third_party/yyjson/xmake.lua
@@ -2,7 +2,7 @@
-- SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-2.0
-- yyjson (MIT), the JSON reader and writer of the runner.
--- The version is pinned in tools/scripts/updateThirdParty.py. Catch3
+-- The version is pinned in MODULE.bazel. Catch3
-- compiles the library into one of its own translation units with
-- internal linkage, so this target only provides the files.
target("catch3_yyjson")
diff --git a/tools/AmalgamateMain.cpp b/tools/AmalgamateMain.cpp
new file mode 100644
index 00000000..c5e2218c
--- /dev/null
+++ b/tools/AmalgamateMain.cpp
@@ -0,0 +1,43 @@
+// Copyright (c) 2026 Progmasoft.
+// SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-2.0
+
+// bazelisk run //tools:amalgamate [-- --check]
+
+#include <iostream>
+#include <string_view>
+
+#include "tools/Amalgamation.hpp"
+#include "tools/Repository.hpp"
+
+int
+main(int argc, char **argv)
+{
+ using namespace Progmasoft::Catch3::Tools;
+
+ const bool check = argc == 2 && std::string_view{ argv[1] } == "--check";
+ if (argc > 2 || (argc == 2 && !check))
+ {
+ std::cerr << "usage: bazelisk run //tools:amalgamate [-- --check]\n";
+ return 2;
+ }
+ const auto root = RepositoryRoot();
+ if (!root)
+ {
+ std::cerr << "run this tool with bazelisk run\n";
+ return 2;
+ }
+ Problems problems;
+ const auto amalgamation = GenerateAmalgamation(*root, problems);
+ if (problems.empty())
+ {
+ if (check)
+ {
+ CheckAmalgamation(*root, amalgamation, problems);
+ }
+ else
+ {
+ WriteAmalgamation(*root, amalgamation, problems);
+ }
+ }
+ return Finish(problems);
+}
diff --git a/tools/Amalgamation.cpp b/tools/Amalgamation.cpp
new file mode 100644
index 00000000..fc1d459a
--- /dev/null
+++ b/tools/Amalgamation.cpp
@@ -0,0 +1,582 @@
+// Copyright (c) 2026 Progmasoft.
+// SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-2.0
+
+#include <algorithm>
+#include <array>
+#include <cctype>
+#include <map>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include "tools/Amalgamation.hpp"
+
+namespace Progmasoft::Catch3::Tools
+{
+ namespace
+ {
+ namespace fs = std::filesystem;
+
+ constexpr std::string_view kHeaderHead = R"(
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+)";
+
+ constexpr std::string_view kHeaderTail
+ = R"(// ----------------------------------------------------------
+// This file is an amalgamation of multiple different files.
+// You probably shouldn't edit it directly.
+//
+// The parts that come from src/Progmasoft, the Catch3 runner, keep
+// their own copyright and license lines (MPL-2.0 WITH
+// AdditionRef-Progmasoft-Exception-2.0). The runner needs C++17.
+// ----------------------------------------------------------
+)";
+
+ constexpr std::string_view kThirdPartyTail
+ = R"(// ----------------------------------------------------------
+// This file is an amalgamation of src/Progmasoft/Runner/Yyjson.inl,
+// src/Progmasoft/PugiXml.inl and the libraries they include from
+// third_party/. You probably shouldn't edit it directly.
+//
+// yyjson and pugixml keep their MIT licenses, stated in their text below;
+// the rest is MPL-2.0 WITH AdditionRef-Progmasoft-Exception-2.0. Only
+// catch_amalgamated.cpp includes this file.
+// ----------------------------------------------------------
+)";
+
+ // The comments at the top of each engine file.
+ constexpr std::array<std::string_view, 5> kCopyrightLines{
+ "// Copyright Catch2 Authors\n",
+ "// Distributed under the Boost Software License, Version 1.0.\n",
+ "// (See accompanying file LICENSE.txt or copy at\n",
+ "// https://www.boost.org/LICENSE_1_0.txt)\n",
+ "// SPDX-License-Identifier: BSL-1.0\n",
+ };
+
+ constexpr std::string_view kUserConfig
+ = "Progmasoft/Catch3/UserConfig.hpp";
+
+ const std::map<std::string, std::string> &
+ ThirdPartyFiles()
+ {
+ static const auto *files = new std::map<std::string, std::string>{
+ { "yyjson.c", "yyjson/yyjson.c" },
+ { "yyjson.h", "yyjson/yyjson.h" },
+ { "pugix