From 6ea7427b78e7dc45e14f50683547a8ee68b63783 Mon Sep 17 00:00:00 2001 From: sortA Date: Mon, 21 Sep 2026 21:55:22 +0900 Subject: [PATCH 01/14] Organize AGENTS.md --- AGENTS.md | 48 +++++++++++++++++++----------------------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index f1196a2..1ce3a2c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,31 +1,21 @@ -This is a C++ header-only library called "libcpprime", intended for fast primality testing of 64-bit integers. - -The library itself and its test code should be written to work with compilers supporting C++11. -Benchmark and other temporary files may use the latest C++ features. -The library implementation should pay particular attention to compatibility with older compilers. - -Supported compilers include gcc, clang, msvc, clang-cl, and the gcc and clang versions within mingw. - -When you want to run tests or benchmarks, execute the tasks described in Taskfile.yml. - -When optimizing code, primarily use gcc or msvc for benchmarks. -However, to avoid significant speed differences between compilers, run them with clang and clang-cl once you have finished the initial implementation. - -Please inform users of any breaking changes. - -.txt files often contain large amounts of data. Do not read files with the .txt extension. - -If you wish to generate data mechanically, please create C++ code or Python scripts within the tmp folder. - -For primality testing in Python, you can use Scipy. For execution speed, please prioritize using PyPy. - -All code and README.md should be written in English. However, this response uses the language currently used in our chat. +- This is a C++ header-only library called "libcpprime", intended for fast primality testing of 64-bit integers. +- The library itself and its test code should be written to work with compilers supporting C++11. + Benchmark and other temporary files may use the latest C++ features. + The library implementation should pay particular attention to compatibility with older compilers. +- Supported compilers include gcc, clang, msvc, clang-cl, and the gcc and clang versions within mingw. +- When you want to run tests or benchmarks, execute the tasks described in Taskfile.yml. +- When optimizing code, primarily use gcc or msvc for benchmarks. + However, to avoid significant speed differences between compilers, run them with clang and clang-cl once you have finished the initial implementation. +- Inform users of any breaking changes. +- If you wish to generate data mechanically, create C++ code or Python scripts within the tmp folder. +- For primality testing in Python, you can use Scipy. For execution speed, prioritize using PyPy. +- All code and README.md should be written in English. However, this response uses the language currently used in our chat. The directory structure is as follows: -include/libcpprime/ : Main library code -benchmarks/ : Code for benchmarks -benchmarks/bench_* : Benchmark results -tests/ : Code and test cases for tests -docs/ : Data used for documentation -tmp/ : Files used for experiments, etc. -/README.md : README +./include/libcpprime: Main library code +./benchmarks: Code for benchmarks +./benchmarks/bench_*: Benchmark results +./tests: Code and test cases for tests +./docs: Data used for documentation +./tmp: Files used for experiments, etc. +./README.md: README From c8b7046af004e8d98cc8b32239e228869ab2d594 Mon Sep 17 00:00:00 2001 From: sortA Date: Mon, 21 Sep 2026 21:55:46 +0900 Subject: [PATCH 02/14] Create CLAUDE.md --- CLAUDE.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..e69de29 From 4588c92db47378fc6207f456211ec82a4b488cbb Mon Sep 17 00:00:00 2001 From: sortA Date: Mon, 21 Sep 2026 22:27:41 +0900 Subject: [PATCH 03/14] Add a reference to AGENTS.md in CLAUDE.md --- CLAUDE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CLAUDE.md b/CLAUDE.md index e69de29..43c994c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md From 0af71dc8ed22795db429d2c3c3cdff90d9433cc9 Mon Sep 17 00:00:00 2001 From: sortA Date: Tue, 22 Sep 2026 23:35:02 +0900 Subject: [PATCH 04/14] Remove unnecessary comments --- include/libcpprime/IsPrime.hpp | 8 -------- include/libcpprime/IsPrimeNoTable.hpp | 10 ++-------- include/libcpprime/internal/IsPrimeCommon.hpp | 3 --- include/libcpprime/internal/Utils.hpp | 9 +-------- 4 files changed, 3 insertions(+), 27 deletions(-) diff --git a/include/libcpprime/IsPrime.hpp b/include/libcpprime/IsPrime.hpp index 9e8c2dd..1cd313a 100644 --- a/include/libcpprime/IsPrime.hpp +++ b/include/libcpprime/IsPrime.hpp @@ -41,13 +41,11 @@ namespace internal { constexpr std::uint64_t FlagTable17[1024] = { #include "internal/IsPrimeTable17.txt" }; -// Bitset for odd numbers < 2^17 (2 is handled explicitly). CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime17(const std::uint64_t n) noexcept { return n == 2 || (n % 2 == 1 && (FlagTable17[n / 128] & (1ull << (n % 128 / 2)))); } constexpr std::uint16_t Bases64[16384] = { #include "internal/IsPrimeBases64.txt" }; -// Deterministic base selection via a multiplicative hash (fast table lookup). CPPR_INTERNAL_CONSTEXPR_INLINE std::uint16_t GetBase(std::uint64_t x) noexcept { return Bases64[(0xad625b89u * static_cast(x)) >> 18]; } CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime49(const std::uint64_t x) noexcept { const MontgomeryModint64Impl mint(x); @@ -79,8 +77,6 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime49(const std::uint64_t x) noexcept { b = mint.mul(b, d); bool res1 = mint.same(a, one) || mint.same(a, mone); bool res2 = mint.same(b, one) || mint.same(b, mone); - // For x % 4 == 1 we may need the full Miller-Rabin squaring chain; - // for x % 4 == 3 the initial check is sufficient with the selected bases. if (x % 4 == 1 && !(res1 && res2)) { for (std::int32_t i = 0; i != S - 1; ++i) { a = mint.mul(a, a); @@ -99,7 +95,6 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64(const std::uint64_t x) noexcept { const auto one = mint.one(); const auto mone = mint.mone(); const std::uint32_t base = GetBase(x); - // Third base is packed as a small lookup indexed by the high bits of `base`. const std::uint64_t base_mask = static_cast(15ull | (135ull << 8) | (13ull << 16) | (60ull << 24) | (15ull << 32) | (117ull << 40) | (65ull << 48) | (29ull << 56)); auto d = mint.raw(2); auto e = mint.raw(base); @@ -133,7 +128,6 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64(const std::uint64_t x) noexcept { bool res1 = mint.same(a, one) || mint.same(a, mone); bool res2 = mint.same(b, one) || mint.same(b, mone); bool res3 = mint.same(c, one) || mint.same(c, mone); - // Same idea as IsPrime49: only run the MR squaring chain when needed. if (x % 4 == 1 && !(res1 && res2 && res3)) { for (std::int32_t i = 0; i != S - 1; ++i) { a = mint.mul(a, a); @@ -156,14 +150,12 @@ CPPR_INTERNAL_CONSTEXPR bool IsPrime(std::uint64_t n) noexcept { if (internal::TrialDivision32(static_cast(n))) return false; return internal::IsPrime32(static_cast(n)); } else { - // Cheap small-prime screening before the heavier probable-prime tests. if (internal::TrialDivision64(n)) return false; if (n < (std::uint64_t(1) << 49)) { return internal::IsPrime49(n); } else if (n < (std::uint64_t(1) << 62)) { return internal::IsPrime64(n); } else { - // Strict variant keeps Montgomery values within [0, mod) for the largest inputs. return internal::IsPrime64(n); } } diff --git a/include/libcpprime/IsPrimeNoTable.hpp b/include/libcpprime/IsPrimeNoTable.hpp index 8f1a8a0..29213ec 100644 --- a/include/libcpprime/IsPrimeNoTable.hpp +++ b/include/libcpprime/IsPrimeNoTable.hpp @@ -23,12 +23,11 @@ namespace internal { constexpr std::uint32_t FlagTable10[32] = { #include "internal/IsPrimeTable10.txt" }; -// Bitset for small n < 1024. CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime10(const std::uint64_t n) noexcept { return (FlagTable10[n / 32] >> (n % 32)) & 1; } CPPR_INTERNAL_CONSTEXPR_INLINE bool GCDFilter(const std::uint32_t n) noexcept { auto GCD = [](std::uint32_t x, std::uint32_t y) CPPR_INTERNAL_INLINE_LAMBDA -> std::uint32_t { - // Binary GCD (Stein's algorithm). Assumes y != 0 when x != 0. + // Binary GCD (Stein's algorithm). if (x == 0) return 0; Assume(y != 0); const std::int32_t n = CountrZero(x); @@ -50,7 +49,6 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool GCDFilter(const std::uint32_t n) noexcept { return x << l; }; - // Very small range: fast GCD-based filters with precomputed constants. const std::uint32_t a = static_cast(Modu128(272518712866683587u % n, 10755835586592736005u, n)); if (n < 11881) return GCD(a, n) == 1; const std::uint32_t b = static_cast(Modu128(827936745744686818u % n, 10132550402535125089u, n)); @@ -121,7 +119,6 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64MillerRabin(const std::uint64_t x) const auto one = mint.one(); const auto mone = mint.mone(); auto test2 = [=](std::uint64_t base1, std::uint64_t base2) -> bool { - // Two-base Miller-Rabin using Montgomery arithmetic. auto a = one; auto b = one; auto c = mint.build(base1); @@ -154,7 +151,6 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64MillerRabin(const std::uint64_t x) return true; }; auto test3 = [=](std::uint64_t base1, std::uint64_t base2, std::uint64_t base3) -> bool { - // Three-base Miller-Rabin using Montgomery arithmetic. auto a = one; auto b = one; auto c = one; @@ -241,13 +237,12 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64BailliePSW(const std::uint64_t x) n if (!IsPrime64Base2(x, mint)) return false; std::uint64_t D = GetLucasBase(x); if (D <= 1) return D == 1; - // Strong Lucas probable prime test (implemented via Lucas sequences in Montgomery domain). + // Strong Lucas probable prime test. const std::uint64_t Q = mint.raw(x - (D - 1) / 4); const auto one = mint.one(); std::uint64_t u = one; std::uint64_t v = one; std::uint64_t Qn = Q; - // Iterate Lucas sequences according to bits of (x + 1). std::uint64_t k = (x + 1) << CountlZero(x + 1); D = mint.raw(D); std::uint64_t t = (x >> 1) + 1; @@ -269,7 +264,6 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64BailliePSW(const std::uint64_t x) n } } if (mint.is_zero(u) || mint.is_zero(v)) return true; - // Extra check over factors of (x + 1) as required by the strong Lucas condition. std::uint64_t f = (x + 1) & ~x; for (f >>= 1; f; f >>= 1) { v = mint.sub(mint.mul(v, v), mint.add(Qn, Qn)); diff --git a/include/libcpprime/internal/IsPrimeCommon.hpp b/include/libcpprime/internal/IsPrimeCommon.hpp index dcd0cbf..86d9bd2 100644 --- a/include/libcpprime/internal/IsPrimeCommon.hpp +++ b/include/libcpprime/internal/IsPrimeCommon.hpp @@ -42,7 +42,6 @@ class MontgomeryModint64Impl { std::uint64_t mod_ = 0, rs = 0, nr = 0, np = 0; CPPR_INTERNAL_CONSTEXPR_INLINE std::uint64_t reduce(const std::uint64_t n) const noexcept { - // Montgomery reduction of a 128-bit value with implicit low half `n`. std::uint64_t q = n * nr; if CPPR_INTERNAL_IF_CONSTEXPR (Strict) { std::uint64_t m = Mulu128High(q, mod_); @@ -168,7 +167,6 @@ class MontgomeryModint64Impl { }; CPPR_INTERNAL_CONSTEXPR_INLINE bool TrialDivision32(const std::uint32_t n) noexcept { - // Branchless screening against a fixed set of small primes. return (n & 1) == 0 || 1431655766u > (0u - 1431655765u) * n || 858993460u > (0u - 858993459u) * n || 613566757u > (0u - 1227133513u) * n || 390451573u > (0u - 1171354717u) * n || 330382100u > (0u - 991146299u) * n || 252645136u > (0u - 252645135u) * n || 226050911u > 678152731u * n || 186737709u > (0u - 373475417u) * n; } @@ -230,7 +228,6 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime32(const std::uint32_t x) noexcept { } CPPR_INTERNAL_CONSTEXPR_INLINE bool TrialDivision64(const std::uint64_t n) noexcept { - // Branchless screening against a fixed set of small primes. return (n & 1) == 0 || 6148914691236517205u >= 12297829382473034411u * n || 3689348814741910323u >= 14757395258967641293u * n || 2635249153387078802u >= 7905747460161236407u * n || 1676976733973595601u >= 3353953467947191203u * n || 1418980313362273201u >= 5675921253449092805u * n || 1085102592571150095u >= 17361641481138401521u * n; } diff --git a/include/libcpprime/internal/Utils.hpp b/include/libcpprime/internal/Utils.hpp index 281ecc0..c084c01 100644 --- a/include/libcpprime/internal/Utils.hpp +++ b/include/libcpprime/internal/Utils.hpp @@ -56,7 +56,7 @@ struct Int64Pair { }; CPPR_INTERNAL_CONSTEXPR_INLINE void Assume(const bool cond) noexcept { - // Hint for the optimizer; ignored during constant evaluation. + // Hint for the optimizer. if (IsConstantEvaluated()) return; #if CPPR_INTERNAL_HAS_BUILTIN(__builtin_assume) __builtin_assume(cond); @@ -70,7 +70,6 @@ CPPR_INTERNAL_CONSTEXPR_INLINE void Assume(const bool cond) noexcept { } CPPR_INTERNAL_CONSTEXPR_INLINE std::int32_t CountrZero(std::uint32_t n) noexcept { - // Precondition: n != 0 (matches std::countr_zero requirements). Assume(n != 0); #ifdef __cpp_lib_bitops return std::countr_zero(n); @@ -109,7 +108,6 @@ CPPR_INTERNAL_CONSTEXPR_INLINE std::int32_t CountrZero(std::uint32_t n) noexcept } CPPR_INTERNAL_CONSTEXPR_INLINE std::int32_t CountrZero(std::uint64_t n) noexcept { - // Precondition: n != 0 (matches std::countr_zero requirements). Assume(n != 0); #ifdef __cpp_lib_bitops return std::countr_zero(n); @@ -129,7 +127,6 @@ CPPR_INTERNAL_CONSTEXPR_INLINE std::int32_t CountrZero(std::uint64_t n) noexcept } CPPR_INTERNAL_CONSTEXPR_INLINE std::int32_t CountlZero(std::uint32_t n) noexcept { - // Precondition: n != 0 (matches std::countl_zero requirements). Assume(n != 0); #ifdef __cpp_lib_bitops return std::countl_zero(n); @@ -168,7 +165,6 @@ CPPR_INTERNAL_CONSTEXPR_INLINE std::int32_t CountlZero(std::uint32_t n) noexcept } CPPR_INTERNAL_CONSTEXPR_INLINE std::int32_t CountlZero(std::uint64_t n) noexcept { - // Precondition: n != 0 (matches std::countl_zero requirements). Assume(n != 0); #ifdef __cpp_lib_bitops return std::countl_zero(n); @@ -188,7 +184,6 @@ CPPR_INTERNAL_CONSTEXPR_INLINE std::int32_t CountlZero(std::uint64_t n) noexcept } CPPR_INTERNAL_CONSTEXPR_INLINE Int64Pair Mulu128(std::uint64_t muler, std::uint64_t mulnd) noexcept { - // Full 128-bit product split into {high, low}. #if defined(CPPR_INTERNAL_HAS_INT128_T) UInt128 tmp = static_cast(muler) * mulnd; return {static_cast(tmp >> 64), static_cast(tmp)}; @@ -217,7 +212,6 @@ CPPR_INTERNAL_CONSTEXPR_INLINE Int64Pair Mulu128(std::uint64_t muler, std::uint6 } CPPR_INTERNAL_CONSTEXPR_INLINE std::uint64_t Mulu128High(std::uint64_t muler, std::uint64_t mulnd) noexcept { - // High 64 bits of the 128-bit product. #if defined(CPPR_INTERNAL_HAS_INT128_T) return static_cast((static_cast(muler) * mulnd) >> 64); #else @@ -229,7 +223,6 @@ CPPR_INTERNAL_CONSTEXPR_INLINE std::uint64_t Mulu128High(std::uint64_t muler, st } CPPR_INTERNAL_CONSTEXPR std::uint64_t Modu128(std::uint64_t numhi, std::uint64_t numlo, std::uint64_t den) { - // Computes ((numhi << 64) | numlo) % den. // Preconditions: den != 0 and numhi < den (so the quotient fits in 64 bits). Assume(den != 0); Assume(numhi < den); From dd200a38eadd4810086c25d9ac0ac81e948d9b1d Mon Sep 17 00:00:00 2001 From: sortA Date: Wed, 23 Sep 2026 19:22:37 +0900 Subject: [PATCH 05/14] Refactor IsPrime64BailliePSW --- include/libcpprime/IsPrimeNoTable.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/libcpprime/IsPrimeNoTable.hpp b/include/libcpprime/IsPrimeNoTable.hpp index 29213ec..01fc0d6 100644 --- a/include/libcpprime/IsPrimeNoTable.hpp +++ b/include/libcpprime/IsPrimeNoTable.hpp @@ -235,16 +235,16 @@ template CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64BailliePSW(const std::uint64_t x) noexcept { const MontgomeryModint64Impl mint(x); if (!IsPrime64Base2(x, mint)) return false; - std::uint64_t D = GetLucasBase(x); - if (D <= 1) return D == 1; // Strong Lucas probable prime test. - const std::uint64_t Q = mint.raw(x - (D - 1) / 4); + const std::uint64_t Base = GetLucasBase(x); + if (Base <= 1) return Base == 1; + const std::uint64_t Q = mint.raw(x - (Base - 1) / 4); + const std::uint64_t D = mint.raw(Base); const auto one = mint.one(); std::uint64_t u = one; std::uint64_t v = one; std::uint64_t Qn = Q; std::uint64_t k = (x + 1) << CountlZero(x + 1); - D = mint.raw(D); std::uint64_t t = (x >> 1) + 1; k <<= 1; while (k) { From 8a2355d7a9935695012ad1aea2352224a9899559 Mon Sep 17 00:00:00 2001 From: sortA Date: Wed, 23 Sep 2026 20:59:05 +0900 Subject: [PATCH 06/14] Speeding up the Strong Lucas Test for certain numbers --- include/libcpprime/IsPrimeNoTable.hpp | 50 ++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/include/libcpprime/IsPrimeNoTable.hpp b/include/libcpprime/IsPrimeNoTable.hpp index 01fc0d6..1aa59cc 100644 --- a/include/libcpprime/IsPrimeNoTable.hpp +++ b/include/libcpprime/IsPrimeNoTable.hpp @@ -61,11 +61,7 @@ CPPR_INTERNAL_CONSTEXPR_INLINE std::uint64_t GetLucasBase(const std::uint64_t x) // - 0: definitely composite (quick checks found a factor or perfect square) // - 1: no suitable D found in the search range (treated as pass by caller) // - otherwise: selected D - std::uint64_t tmp = x % 5; - if (tmp == 2 || tmp == 3) { - return 5; - } - tmp = x % 13; + std::uint64_t tmp = x % 13; if (tmp == 2 || tmp == 5 || tmp == 6 || tmp == 7 || tmp == 8 || tmp == 11) { return 13; } @@ -231,11 +227,55 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64Base2(const std::uint64_t x, const return false; } +template +CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64StrongLucasBase5(const std::uint64_t x, const MontgomeryModint64Impl mint) noexcept { + const std::uint64_t D = mint.raw(5); + const auto one = mint.one(); + const auto mone = mint.mone(); + const auto two = mint.add(one, one); + const auto mtwo = mint.add(mone, mone); + std::uint64_t u = one; + std::uint64_t v = one; + std::uint64_t Qt = mtwo; + std::uint64_t k = (x + 1) << CountlZero(x + 1); + std::uint64_t t = (x >> 1) + 1; + k <<= 1; + while (k) { + u = mint.mul(u, v); + v = mint.sub(mint.mul(v, v), Qt); + std::uint64_t tmp = k; + k <<= 1; + Qt = two; + if (tmp >> 63) { + std::uint64_t uu = u; + u = mint.add(u, v); + v = mint.add(mint.mul(D, uu), v); + u = (u >> 1) + ((u & 1) ? t : 0); + v = (v >> 1) + ((v & 1) ? t : 0); + Qt = mtwo; + } + } + if (mint.is_zero(u) || mint.is_zero(v)) return true; + std::uint64_t f = ((x + 1) & ~x) >> 1; + if (!f) return false; + v = mint.sub(mint.mul(v, v), Qt); + f >>= 1; + if (mint.is_zero(v)) return true; + while (f) { + v = mint.sub(mint.mul(v, v), two); + f >>= 1; + if (mint.is_zero(v)) return true; + } + return false; +} + template CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64BailliePSW(const std::uint64_t x) noexcept { const MontgomeryModint64Impl mint(x); if (!IsPrime64Base2(x, mint)) return false; // Strong Lucas probable prime test. + const std::uint64_t mod5 = x % 5; + if (mod5 == 2 || mod5 == 3) return IsPrime64StrongLucasBase5(x, mint); const std::uint64_t Base = GetLucasBase(x); if (Base <= 1) return Base == 1; const std::uint64_t Q = mint.raw(x - (Base - 1) / 4); From f0230d420649e77ee2447d23053b5bad8e7e623e Mon Sep 17 00:00:00 2001 From: sortA Date: Wed, 23 Sep 2026 21:08:57 +0900 Subject: [PATCH 07/14] Update README.md for release 1.3.5 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e5a74be..97e3386 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,8 @@ Workflow: [bench.yml](https://github.com/sortA0329/libcpprime/actions/workflows/ ## Releases +- 2026/09/23 ver 1.3.5 + - Improve performance of `cppr::IsPrimeNoTable` - 2026/06/28 ver 1.3.4 - Improve performance - Prevent unnecessary code from being included when `FeatureTestMacros.hpp` is included From 62a449a72150e9d4f611812154f1787c09bb358f Mon Sep 17 00:00:00 2001 From: sortA Date: Wed, 23 Sep 2026 21:23:26 +0900 Subject: [PATCH 08/14] Add GitHub Actions workflow for automated release creation --- .github/workflows/release.yml | 61 ++++++++++++++++ scripts/extract_changelog.py | 132 ++++++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 scripts/extract_changelog.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a74daee --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,61 @@ +name: Release + +on: + push: + tags: + - "v*" + workflow_dispatch: + inputs: + tag: + description: Tag name to release + required: true + type: string + +permissions: + contents: write + +jobs: + release: + name: Create GitHub Release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ inputs.tag || github.ref }} + + - name: Resolve tag and version + id: meta + run: | + if [ -n "${{ inputs.tag }}" ]; then + TAG="${{ inputs.tag }}" + else + TAG="${GITHUB_REF_NAME}" + fi + VERSION="${TAG#v}" + VERSION="${VERSION#V}" + echo "TAG=${TAG}" >> "$GITHUB_ENV" + echo "VERSION=${VERSION}" >> "$GITHUB_ENV" + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + echo "Releasing ${TAG} (version ${VERSION})" + + - name: Bundle headers + run: | + python3 scripts/bundle_headers.py + ls -la bundled/ + + - name: Extract changelog from README + run: python3 scripts/extract_changelog.py --version "$VERSION" --readme README.md --output RELEASE_BODY.md + + - name: Show release body + run: cat RELEASE_BODY.md + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.meta.outputs.tag }} + name: ${{ steps.meta.outputs.tag }} + body_path: RELEASE_BODY.md + files: bundled/*.hpp + fail_on_unmatched_files: true + generate_release_notes: false diff --git a/scripts/extract_changelog.py b/scripts/extract_changelog.py new file mode 100644 index 0000000..edeb7bf --- /dev/null +++ b/scripts/extract_changelog.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +"""Extract the changelog for a given version from README.md. + +The README contains a "Releases" section like: + + ## Releases + + - 2026/09/23 ver 1.3.5 + - Improve performance of `cppr::IsPrimeNoTable` + - 2026/06/28 ver 1.3.4 + - Improve performance + ... + +This script extracts the bullet points of the requested version so that +they can be used as the body of a GitHub Release. +""" + +from __future__ import annotations + +import argparse +import re +import sys +from pathlib import Path + +VERSION_HEADER_RE = re.compile(r"^\s*-\s+\S+\s+ver\s+(\S+)\s*$") +HEADING_RE = re.compile(r"^#{1,6}\s") + + +def normalize_version(version: str) -> str: + version = version.strip() + if version[:1] in ("v", "V"): + version = version[1:] + return version + + +def find_available_versions(lines: list[str]) -> list[str]: + versions = [] + for line in lines: + match = VERSION_HEADER_RE.match(line) + if match: + versions.append(normalize_version(match.group(1))) + return versions + + +def extract_changelog(readme_text: str, version: str) -> str: + lines = readme_text.splitlines() + + start = None + for i, line in enumerate(lines): + match = VERSION_HEADER_RE.match(line) + if match and normalize_version(match.group(1)) == version: + start = i + 1 + break + + if start is None: + available = ", ".join(find_available_versions(lines)) or "(none)" + raise ValueError( + f"Version {version!r} not found in README.md. " + f"Available versions: {available}" + ) + + body: list[str] = [] + for line in lines[start:]: + if HEADING_RE.match(line): + break + if VERSION_HEADER_RE.match(line): + break + body.append(line.rstrip()) + + while body and not body[0].strip(): + body.pop(0) + while body and not body[-1].strip(): + body.pop() + + if not body or not any(line.strip() for line in body): + raise ValueError(f"No changelog entries found for version {version!r}.") + + # Dedent by the common leading indentation so that nested bullets + # become top-level bullets in the release body. + indent = min( + len(line) - len(line.lstrip(" ")) for line in body if line.strip() + ) + dedented = [line[indent:] if len(line) >= indent else line for line in body] + return "\n".join(dedented) + "\n" + + +def main() -> int: + parser = argparse.ArgumentParser( + description="Extract the changelog for a given version from README.md." + ) + parser.add_argument( + "--version", + required=True, + help="Version to extract (e.g. 1.3.4 or v1.3.4).", + ) + parser.add_argument( + "--readme", + default="README.md", + help="Path to README.md (default: README.md).", + ) + parser.add_argument( + "--output", + default=None, + help="Output file. Defaults to stdout.", + ) + args = parser.parse_args() + + version = normalize_version(args.version) + + readme_path = Path(args.readme) + if not readme_path.is_file(): + print(f"Error: README not found: {readme_path}", file=sys.stderr) + return 1 + + try: + body = extract_changelog( + readme_path.read_text(encoding="utf-8"), version + ) + except ValueError as exc: + print(f"Error: {exc}", file=sys.stderr) + return 1 + + if args.output: + Path(args.output).write_text(body, encoding="utf-8") + else: + sys.stdout.write(body) + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 67c7c47733856b0afd041a871df3de9524c16825 Mon Sep 17 00:00:00 2001 From: sortA Date: Fri, 25 Sep 2026 00:49:39 +0900 Subject: [PATCH 09/14] Migrate from go-task to mise --- .clangd | 8 +- .devcontainer/Dockerfile | 17 -- .devcontainer/devcontainer.json | 18 -- .github/workflows/bench.yml | 297 ++++++++++++++++---------------- .github/workflows/ci.yml | 28 +++ .github/workflows/docs.yml | 99 +++++------ .github/workflows/release.yml | 116 +++++++------ .github/workflows/tests.yml | 157 +++++++++-------- .gitignore | 1 - .oxfmtrc.json | 3 + .vscode/extensions.json | 9 - .vscode/settings.json | 61 ------- .zed/settings.json | 66 ++++--- AGENTS.md | 3 +- CONTRIBUTING.md | 69 ++++---- Taskfile.yml | 188 -------------------- benchmarks/plot_isprime.py | 63 ++++--- mise.toml | 220 +++++++++++++++++++++++ mkdocs.yml | 63 ++++--- scripts/bundle_headers.py | 12 +- scripts/extract_changelog.py | 8 +- 21 files changed, 713 insertions(+), 793 deletions(-) delete mode 100644 .devcontainer/Dockerfile delete mode 100644 .devcontainer/devcontainer.json create mode 100644 .github/workflows/ci.yml create mode 100644 .oxfmtrc.json delete mode 100644 .vscode/extensions.json delete mode 100644 .vscode/settings.json delete mode 100644 Taskfile.yml create mode 100644 mise.toml diff --git a/.clangd b/.clangd index df215a5..0b6006a 100644 --- a/.clangd +++ b/.clangd @@ -1,7 +1,7 @@ Diagnostics: - UnusedIncludes: Strict - MissingIncludes: Strict + UnusedIncludes: Strict + MissingIncludes: Strict InlayHints: - ParameterNames: false - DeducedTypes: false + ParameterNames: false + DeducedTypes: false diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 48ce124..0000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM mcr.microsoft.com/devcontainers/base:ubuntu-24.04 - -RUN curl -1sLf 'https://dl.cloudsmith.io/public/task/task/setup.deb.sh' | sudo -E bash - -RUN apt-get update && apt-get install -y --no-install-recommends \ - ca-certificates \ - clang \ - clangd \ - cmake \ - curl \ - git \ - ninja-build \ - task \ - pngquant \ - && rm -rf /var/lib/apt/lists/* - -RUN curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR=/usr/local/bin sh diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 540cf28..0000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "libcpprime", - "build": { - "dockerfile": "Dockerfile" - }, - "customizations": { - "vscode": { - "extensions": [ - "llvm-vs-code-extensions.vscode-clangd", - "ms-vscode.cmake-tools", - "redhat.vscode-yaml", - "ms-python.python", - "charliermarsh.ruff" - ] - } - }, - "postCreateCommand": "task configure:clang" -} diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 17dc150..85e6248 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -1,162 +1,153 @@ name: Benchmark on: - push: - branches: [main] - paths: - - "include/**" - - "benchmarks/**" - - "CMakeLists.txt" - - "Taskfile.yml" - - ".github/workflows/bench.yml" - workflow_dispatch: + push: + branches: [main] + paths: + - "include/**" + - "benchmarks/**" + - "CMakeLists.txt" + - "mise.toml" + - ".github/workflows/bench.yml" + workflow_dispatch: permissions: - contents: write + contents: read concurrency: - group: benchmark-pages - cancel-in-progress: false + group: benchmark-pages + cancel-in-progress: false jobs: - linux: - name: Linux (${{ matrix.compiler }}) - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - include: - - compiler: gcc - build_dir: build-gcc - - compiler: clang - build_dir: build-clang - steps: - - uses: actions/checkout@v7 - - - name: Install build dependencies - run: | - sudo apt-get update -y - sudo apt-get install -y ninja-build ccache clang - - - name: Install Task - uses: go-task/setup-task@v2 - with: - version: 3.x - - - name: Setup uv - uses: astral-sh/setup-uv@v8.2.0 - with: - enable-cache: true - cache-dependency-glob: | - pyproject.toml - uv.lock - - - name: Cache deps - uses: actions/cache@v6 - with: - path: ${{ matrix.build_dir }}/_deps - key: ${{ runner.os }}-${{ matrix.compiler }}-deps-${{ hashFiles('CMakeLists.txt', '**/CMakeLists.txt', '**/*.cmake') }} - restore-keys: | - ${{ runner.os }}-${{ matrix.compiler }}-deps- - - - name: Run benchmark via task - run: task bench-heavy:${{ matrix.compiler }} - - - name: Upload benchmark artifacts - uses: actions/upload-artifact@v7 - with: - name: benchmark-${{ runner.os }}-${{ matrix.compiler }} - if-no-files-found: warn - path: | - benchmarks/bench_summary.md - benchmarks/bench_IsPrime.webp - benchmarks/bench_IsPrimeNoTable.webp - benchmarks/bench_summary.webp - - windows: - name: Windows (${{ matrix.compiler }}) - runs-on: windows-latest - strategy: - fail-fast: false - matrix: - include: - - compiler: msvc - build_dir: build-msvc - - compiler: clang-cl - build_dir: build-clang-cl - steps: - - uses: actions/checkout@v7 - - - name: Install LLVM + Ninja (clang-cl) - if: matrix.compiler == 'clang-cl' - shell: pwsh - run: | - choco install llvm ninja -y --no-progress - - - name: Install Task - uses: go-task/setup-task@v2 - with: - version: 3.x - - - name: Setup uv - uses: astral-sh/setup-uv@v8.2.0 - with: - enable-cache: true - cache-dependency-glob: | - pyproject.toml - uv.lock - - - name: Cache deps - uses: actions/cache@v6 - with: - path: ${{ matrix.build_dir }}/_deps - key: ${{ runner.os }}-${{ matrix.compiler }}-deps-${{ hashFiles('CMakeLists.txt', '**/CMakeLists.txt', '**/*.cmake') }} - restore-keys: | - ${{ runner.os }}-${{ matrix.compiler }}-deps- - - - name: Run benchmark via task - run: task bench-heavy:${{ matrix.compiler }} - - - name: Upload benchmark artifacts - uses: actions/upload-artifact@v7 - with: - name: benchmark-${{ runner.os }}-${{ matrix.compiler }} - if-no-files-found: warn - path: | - benchmarks/bench_summary.md - benchmarks/bench_IsPrime.webp - benchmarks/bench_IsPrimeNoTable.webp - benchmarks/bench_summary.webp - - publish-pages: - name: Publish benchmark results to GitHub Pages - runs-on: ubuntu-latest - needs: [linux, windows] - concurrency: - group: gh-pages-deploy - cancel-in-progress: false - steps: - - uses: actions/checkout@v7 - - - name: Download benchmark artifacts - uses: actions/download-artifact@v8 - with: - path: artifacts - pattern: benchmark-* - - - name: Prepare Pages content - run: >- - python3 scripts/publish_benchmarks_to_pages.py - --artifacts-dir artifacts - --site-dir site - --repo "${{ github.repository }}" - --run-id "${{ github.run_id }}" - - - name: Deploy to gh-pages - uses: peaceiris/actions-gh-pages@v4 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_branch: gh-pages - publish_dir: site - keep_files: true - commit_message: "chore(bench): publish run ${{ github.run_id }}" + linux: + name: Linux (${{ matrix.compiler }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - compiler: gcc + build_dir: build-gcc + - compiler: clang + build_dir: build-clang + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + + - name: Install build dependencies + run: | + sudo apt-get update -y + sudo apt-get install -y clang + + - name: Setup mise tools + uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4 + + - name: Cache deps + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 + with: + path: ${{ matrix.build_dir }}/_deps + key: ${{ runner.os }}-${{ matrix.compiler }}-deps-${{ hashFiles('CMakeLists.txt', '**/CMakeLists.txt', '**/*.cmake') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.compiler }}-deps- + + - name: Run benchmark via mise + run: mise run bench-heavy:${{ matrix.compiler }} + + - name: Upload benchmark artifacts + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: benchmark-${{ runner.os }}-${{ matrix.compiler }} + if-no-files-found: warn + path: | + benchmarks/bench_summary.md + benchmarks/bench_IsPrime.webp + benchmarks/bench_IsPrimeNoTable.webp + benchmarks/bench_summary.webp + + windows: + name: Windows (${{ matrix.compiler }}) + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + include: + - compiler: msvc + build_dir: build-msvc + - compiler: clang-cl + build_dir: build-clang-cl + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + + - name: Install LLVM (clang-cl) + if: matrix.compiler == 'clang-cl' + shell: pwsh + run: | + choco install llvm -y --no-progress + + - name: Setup mise tools + uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4 + + - name: Cache deps + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 + with: + path: ${{ matrix.build_dir }}/_deps + key: ${{ runner.os }}-${{ matrix.compiler }}-deps-${{ hashFiles('CMakeLists.txt', '**/CMakeLists.txt', '**/*.cmake') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.compiler }}-deps- + + - name: Run benchmark via mise + run: mise run bench-heavy:${{ matrix.compiler }} + + - name: Upload benchmark artifacts + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: benchmark-${{ runner.os }}-${{ matrix.compiler }} + if-no-files-found: warn + path: | + benchmarks/bench_summary.md + benchmarks/bench_IsPrime.webp + benchmarks/bench_IsPrimeNoTable.webp + benchmarks/bench_summary.webp + + publish-pages: + name: Publish benchmark results to GitHub Pages + runs-on: ubuntu-latest + needs: [linux, windows] + permissions: + contents: write + concurrency: + group: gh-pages-deploy + cancel-in-progress: false + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + + - name: Setup mise tools + uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4 + + - name: Download benchmark artifacts + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + path: artifacts + pattern: benchmark-* + + - name: Prepare Pages content + run: >- + mise x -- uv run scripts/publish_benchmarks_to_pages.py + --artifacts-dir artifacts + --site-dir site + --repo "${{ github.repository }}" + --run-id "${{ github.run_id }}" + + - name: Deploy to gh-pages + uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_branch: gh-pages + publish_dir: site + keep_files: true + commit_message: "chore(bench): publish run ${{ github.run_id }}" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3b09317 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,28 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + name: Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - name: Setup mise tools + uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4 + - name: Run check + run: mise run check diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index a73b9f6..4ce58f7 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,68 +1,49 @@ name: Docs on: - push: - branches: [main] - paths: - - "docs/**" - - "README.md" - - "mkdocs.yml" - - ".github/workflows/docs.yml" - workflow_dispatch: + push: + branches: [main] + paths: + - "docs/**" + - "README.md" + - "mkdocs.yml" + - "mise.toml" + - "pyproject.toml" + - "uv.lock" + - ".github/workflows/docs.yml" + workflow_dispatch: permissions: - contents: write + contents: write concurrency: - group: docs-pages - cancel-in-progress: true + group: docs-pages + cancel-in-progress: true jobs: - build-and-deploy: - name: Build and deploy MkDocs - runs-on: ubuntu-latest - concurrency: - group: gh-pages-deploy - cancel-in-progress: false - steps: - - uses: actions/checkout@v7 - with: - fetch-depth: 0 - - - name: Install system dependencies - run: | - sudo apt-get update -y - sudo apt-get install -y pngquant - - - name: Install Task - uses: go-task/setup-task@v2 - with: - version: 3.x - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: "3.14" - - - name: Setup uv - uses: astral-sh/setup-uv@v8.2.0 - with: - enable-cache: true - cache-dependency-glob: | - pyproject.toml - uv.lock - - - name: Install Python dependencies - run: uv sync --frozen - - - name: Build site - run: task docs - - - name: Deploy to gh-pages - uses: peaceiris/actions-gh-pages@v4 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_branch: gh-pages - publish_dir: site - keep_files: true - commit_message: "chore(docs): publish run ${{ github.run_id }}" + build-and-deploy: + name: Build and deploy MkDocs + runs-on: ubuntu-latest + concurrency: + group: gh-pages-deploy + cancel-in-progress: false + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Setup mise tools + uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4 + + - name: Build site + run: mise run docs + + - name: Deploy to gh-pages + uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_branch: gh-pages + publish_dir: site + keep_files: true + commit_message: "chore(docs): publish run ${{ github.run_id }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a74daee..b8925b4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,61 +1,69 @@ name: Release on: - push: - tags: - - "v*" - workflow_dispatch: - inputs: - tag: - description: Tag name to release - required: true - type: string + push: + tags: + - "v*" + workflow_dispatch: + inputs: + tag: + description: Tag name to release + required: true + type: string permissions: - contents: write + contents: write jobs: - release: - name: Create GitHub Release - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - ref: ${{ inputs.tag || github.ref }} - - - name: Resolve tag and version - id: meta - run: | - if [ -n "${{ inputs.tag }}" ]; then - TAG="${{ inputs.tag }}" - else - TAG="${GITHUB_REF_NAME}" - fi - VERSION="${TAG#v}" - VERSION="${VERSION#V}" - echo "TAG=${TAG}" >> "$GITHUB_ENV" - echo "VERSION=${VERSION}" >> "$GITHUB_ENV" - echo "tag=${TAG}" >> "$GITHUB_OUTPUT" - echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - echo "Releasing ${TAG} (version ${VERSION})" - - - name: Bundle headers - run: | - python3 scripts/bundle_headers.py - ls -la bundled/ - - - name: Extract changelog from README - run: python3 scripts/extract_changelog.py --version "$VERSION" --readme README.md --output RELEASE_BODY.md - - - name: Show release body - run: cat RELEASE_BODY.md - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ steps.meta.outputs.tag }} - name: ${{ steps.meta.outputs.tag }} - body_path: RELEASE_BODY.md - files: bundled/*.hpp - fail_on_unmatched_files: true - generate_release_notes: false + release: + name: Create GitHub Release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + ref: ${{ inputs.tag || github.ref }} + persist-credentials: false + + - name: Setup mise tools + uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4 + with: + cache: false + + - name: Resolve tag and version + id: meta + env: + INPUT_TAG: ${{ inputs.tag }} + run: | + if [ -n "${INPUT_TAG}" ]; then + TAG="${INPUT_TAG}" + else + TAG="${GITHUB_REF_NAME}" + fi + VERSION="${TAG#v}" + VERSION="${VERSION#V}" + echo "TAG=${TAG}" >> "$GITHUB_ENV" + echo "VERSION=${VERSION}" >> "$GITHUB_ENV" + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + echo "Releasing ${TAG} (version ${VERSION})" + + - name: Bundle headers + run: | + mise run bundle + ls -la bundled/ + + - name: Extract changelog from README + run: mise x -- uv run scripts/extract_changelog.py --version "$VERSION" --readme README.md --output RELEASE_BODY.md + + - name: Show release body + run: cat RELEASE_BODY.md + + - name: Create GitHub Release + uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 # zizmor: ignore[superfluous-actions] keep dedicated action for asset upload behavior + with: + tag_name: ${{ steps.meta.outputs.tag }} + name: ${{ steps.meta.outputs.tag }} + body_path: RELEASE_BODY.md + files: bundled/*.hpp + fail_on_unmatched_files: true + generate_release_notes: false diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8a3d0e2..5555f90 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,94 +1,91 @@ name: Tests on: - push: - branches: [main] - paths: - - "include/**" - - "tests/**" - - "CMakeLists.txt" - - "Taskfile.yml" - - ".github/workflows/tests.yml" - pull_request: - branches: [main] - workflow_dispatch: + push: + branches: [main] + paths: + - "include/**" + - "tests/**" + - "CMakeLists.txt" + - "mise.toml" + - ".github/workflows/tests.yml" + pull_request: + branches: [main] + workflow_dispatch: permissions: - contents: read + contents: read concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: - linux: - name: Linux (${{ matrix.compiler }}) - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - include: - - compiler: gcc - build_dir: build-gcc - cc: gcc - cxx: g++ - - compiler: clang - build_dir: build-clang - cc: clang - cxx: clang++ - steps: - - uses: actions/checkout@v7 - - run: | - sudo apt-get update -y - sudo apt-get install -y ninja-build ccache - - name: Install Task - uses: go-task/setup-task@v2 - with: - version: 3.x - - name: Cache build dir (${{ matrix.compiler }}) - uses: actions/cache@v6 - with: - path: ${{ matrix.build_dir }} - key: ${{ runner.os }}-${{ matrix.compiler }}-build-${{ hashFiles('CMakeLists.txt', '**/CMakeLists.txt', '**/*.cmake') }} - restore-keys: | - ${{ runner.os }}-${{ matrix.compiler }}-build- - - name: Run tests (${{ matrix.compiler }}) - env: - CC: ${{ matrix.cc }} - CXX: ${{ matrix.cxx }} - run: task test:${{ matrix.compiler }} + linux: + name: Linux (${{ matrix.compiler }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - compiler: gcc + build_dir: build-gcc + cc: gcc + cxx: g++ + - compiler: clang + build_dir: build-clang + cc: clang + cxx: clang++ + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - name: Setup mise tools + uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4 + - name: Cache build dir (${{ matrix.compiler }}) + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 + with: + path: ${{ matrix.build_dir }} + key: ${{ runner.os }}-${{ matrix.compiler }}-build-${{ hashFiles('CMakeLists.txt', '**/CMakeLists.txt', '**/*.cmake') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.compiler }}-build- + - name: Run tests (${{ matrix.compiler }}) + env: + CC: ${{ matrix.cc }} + CXX: ${{ matrix.cxx }} + run: mise run test:${{ matrix.compiler }} - windows: - name: Windows (${{ matrix.compiler }}) - runs-on: windows-latest - strategy: - fail-fast: false - matrix: - include: - - compiler: msvc - build_dir: build-msvc - - compiler: clang-cl - build_dir: build-clang-cl - steps: - - uses: actions/checkout@v7 + windows: + name: Windows (${{ matrix.compiler }}) + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + include: + - compiler: msvc + build_dir: build-msvc + - compiler: clang-cl + build_dir: build-clang-cl + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false - - name: Install LLVM + Ninja (clang-cl) - if: matrix.compiler == 'clang-cl' - shell: pwsh - run: | - choco install llvm ninja -y --no-progress + - name: Install LLVM (clang-cl) + if: matrix.compiler == 'clang-cl' + shell: pwsh + run: | + choco install llvm -y --no-progress - - uses: actions/cache@v6 - with: - path: ${{ matrix.build_dir }} - key: ${{ runner.os }}-${{ matrix.compiler }}-build-${{ hashFiles('CMakeLists.txt', '**/CMakeLists.txt', '**/*.cmake') }} - restore-keys: | - ${{ runner.os }}-${{ matrix.compiler }}-build- + - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 + with: + path: ${{ matrix.build_dir }} + key: ${{ runner.os }}-${{ matrix.compiler }}-build-${{ hashFiles('CMakeLists.txt', '**/CMakeLists.txt', '**/*.cmake') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.compiler }}-build- - - name: Install Task - uses: go-task/setup-task@v2 - with: - version: 3.x + - name: Setup mise tools + uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4 - - name: Run tests (${{ matrix.compiler }}) - run: task test:${{ matrix.compiler }} + - name: Run tests (${{ matrix.compiler }}) + run: mise run test:${{ matrix.compiler }} diff --git a/.gitignore b/.gitignore index 98f7abd..eb6426c 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,6 @@ Makefile *.cmake # IDE files -.vscode/settings.local.json .idea/ *.swp *.swo diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 0000000..0a02bce --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,3 @@ +{ + "tabWidth": 4 +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index ac8cd92..0000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "recommendations": [ - "llvm-vs-code-extensions.vscode-clangd", - "ms-vscode.cmake-tools", - "redhat.vscode-yaml", - "ms-python.python", - "charliermarsh.ruff" - ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index d3900a1..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "editor.formatOnSave": true, - "editor.formatOnType": true, - "editor.formatOnPaste": false, - "editor.insertSpaces": true, - "editor.tabSize": 4, - "editor.semanticHighlighting.enabled": true, - "files.eol": "\n", - "files.insertFinalNewline": true, - "files.trimTrailingWhitespace": true, - "files.associations": { - ".clang-format": "yaml", - ".clangd": "yaml", - }, - "files.exclude": { - "build-*": true, - ".cache": true, - ".venv": true - }, - "search.exclude": { - "build-*": true, - ".cache": true, - ".venv": true - }, - "terminal.integrated.defaultProfile.windows": "PowerShell", - "terminal.integrated.env.windows": { - "PSExecutionPolicyPreference": "RemoteSigned" - }, - "terminal.integrated.defaultProfile.linux": "bash", - "clangd.enable": true, - "ruff.enable": true, - "python.terminal.activateEnvironment": true, - "python.analysis.ignore": [ - "*" - ], - "cmake.configureOnOpen": false, - "cmake.configureOnEdit": false, - "cmake.buildBeforeRun": false, - "cmake.autoSelectActiveFolder": false, - "github.copilot.enable": { - "*": true, - "plaintext": false, - "markdown": true, - }, - "[json]": { - "editor.defaultFormatter": "vscode.json-language-features" - }, - "[jsonc]": { - "editor.defaultFormatter": "vscode.json-language-features" - }, - "[yaml]": { - "editor.defaultFormatter": "redhat.vscode-yaml", - "editor.tabSize": 2 - }, - "[cpp]": { - "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd", - }, - "[python]": { - "editor.defaultFormatter": "charliermarsh.ruff" - }, -} diff --git a/.zed/settings.json b/.zed/settings.json index 6178b55..04b6c0c 100644 --- a/.zed/settings.json +++ b/.zed/settings.json @@ -6,49 +6,45 @@ "ensure_final_newline_on_save": true, "remove_trailing_whitespace_on_save": true, "file_types": { - "YAML": [ - ".clang-format", - ".clangd" - ], - }, - "file_scan_exclusions": [ - "**/.git", - "**/.DS_Store", - "build-*", - ".cache", - ".venv" - ], - "lsp": { - "clangd": { - "binary": { - "path": "clangd", - }, - }, + "YAML": [".clang-format", ".clangd"] }, + "file_scan_exclusions": ["**/.git", "**/.DS_Store", "build-*", ".cache", ".venv"], "languages": { - "YAML": { - "tab_size": 2, - }, "C++": { - "language_servers": [ - "clangd" - ], + "language_servers": ["clangd"], "formatter": { "language_server": { - "name": "clangd", - }, - }, + "name": "clangd" + } + } }, "Python": { - "language_servers": [ - "basedpyright", - "ruff" - ], + "language_servers": ["ty", "ruff"], "formatter": { "language_server": { - "name": "ruff", - }, - }, - }, + "name": "ruff" + } + } + } }, + "lsp": { + "clangd": { + "binary": { + "path": "mise", + "arguments": ["x", "--", "clangd"] + } + }, + "ruff": { + "binary": { + "path": "mise", + "arguments": ["x", "--", "ruff", "server"] + } + }, + "ty": { + "binary": { + "path": "mise", + "arguments": ["x", "--", "ty", "server"] + } + } + } } diff --git a/AGENTS.md b/AGENTS.md index 1ce3a2c..52dd408 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,7 +3,8 @@ Benchmark and other temporary files may use the latest C++ features. The library implementation should pay particular attention to compatibility with older compilers. - Supported compilers include gcc, clang, msvc, clang-cl, and the gcc and clang versions within mingw. -- When you want to run tests or benchmarks, execute the tasks described in Taskfile.yml. +- When you want to run tests or benchmarks, execute the tasks defined in mise.toml via `mise run ` (list them with `mise tasks ls`). +- When using toolchains installed via mise, use `mise x -- `. - When optimizing code, primarily use gcc or msvc for benchmarks. However, to avoid significant speed differences between compilers, run them with clang and clang-cl once you have finished the initial implementation. - Inform users of any breaking changes. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ae46259..192bb33 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,21 +11,10 @@ Thanks for your interest in improving `libcpprime`. ## Required tools -### Build and test - -- CMake >= 3.20 -- A C++ compiler toolchain (GCC/Clang/MSVC/clang-cl) -- Ninja -- Task (`go-task`) +- A system C++ compiler toolchain +- mise - Git - -### Python tooling (benchmarks/docs) - -- Python >= 3.14 -- `uv` -- pngquant - -Python dependencies are managed by `uv`. +- Zed (recommended editor) ### Why Windows development is recommended @@ -35,11 +24,6 @@ However, since it is difficult to generate a proper Compilation database with MS Linux/macOS development is still fine for day-to-day work, especially for GCC/Clang checks. -### Dev Container support - -This repository includes a Dev Container at `.devcontainer/`. -Use it if you want a reproducible Linux environment without installing tools manually. - ## Quick start ### 1) Clone @@ -51,45 +35,56 @@ cd libcpprime ### 2) Install dependencies -- Install CMake, compiler(s), and Task. -- For docs/benchmark plots, install Python 3.14+ and `uv`. +- Install a system C++ compiler toolchain, mise, and Git. -### 3) Run tests +### 3) Open in Zed ```bash -task test:gcc -task test:clang -task test:msvc -task test:clang-cl +zed . +``` + +### 4) Run tests + +```bash +mise run test:gcc +mise run test:clang +mise run test:msvc +mise run test:clang-cl ``` Typical runtime for tests is around 10 seconds per run. ## Command reference -All project commands are defined in `Taskfile.yml`. +All project commands are defined in `mise.toml`. +List them with `mise tasks ls`. ### Configure -- `task configure:gcc|clang|clang-cl|msvc`: Configure a build directory -- `task configure`: Run all configure tasks above +- `mise run configure:gcc|clang|clang-cl|msvc`: Configure a build directory +- `mise run configure`: Run all configure tasks above ### Tests -- `task test:gcc|clang|clang-cl|msvc`: Run tests -- `task test`: Run all test tasks above +- `mise run test:gcc|clang|clang-cl|msvc`: Run tests +- `mise run test`: Run all test tasks above ### Benchmarks -- `task bench:gcc|clang|clang-cl|msvc`: Run standard benchmark and generate plots -- `task bench-heavy:gcc|clang|clang-cl|msvc`: Run heavier benchmark variant (`--heavy`) +- `mise run bench:gcc|clang|clang-cl|msvc`: Run standard benchmark and generate plots +- `mise run bench-heavy:gcc|clang|clang-cl|msvc`: Run heavier benchmark variant (`--heavy`) ### Docs -- `task docs`: Copy `README.md` to `docs/index.md`, then build MkDocs site -- `task serve`: Serve MkDocs locally +- `mise run docs`: Copy `README.md` to `docs/index.md`, then build MkDocs site +- `mise run serve`: Serve MkDocs locally ### Clean -- `task clean:gcc|clang|clang-cl|msvc`: Remove one build directory -- `task clean`: Remove all build directories +- `mise run clean:gcc|clang|clang-cl|msvc`: Remove one build directory +- `mise run clean`: Remove all build directories + +### Lint and format + +- `mise run check`: Check for lint and formatting issues +- `mise run fix`: Auto-fix lint and formatting issues diff --git a/Taskfile.yml b/Taskfile.yml deleted file mode 100644 index 45be615..0000000 --- a/Taskfile.yml +++ /dev/null @@ -1,188 +0,0 @@ -version: "3" - -tasks: - # ============================ - # Configure - # ============================ - configure:gcc: - desc: Configure GCC build - cmds: - - cmake -S . -B build-gcc -G "Ninja" - -DLIBCPPRIME_COMPILER=gcc - -DCMAKE_BUILD_TYPE=Release - -DCMAKE_EXPORT_COMPILE_COMMANDS=ON - - cp build-gcc/compile_commands.json . - - configure:clang: - desc: Configure Clang build - cmds: - - cmake -S . -B build-clang -G "Ninja" - -DLIBCPPRIME_COMPILER=clang - -DCMAKE_BUILD_TYPE=Release - -DCMAKE_EXPORT_COMPILE_COMMANDS=ON - - cp build-clang/compile_commands.json . - - configure:clang-cl: - desc: Configure clang-cl build - cmds: - - cmake -S . -B build-clang-cl -G "Ninja" - -DLIBCPPRIME_COMPILER=clang-cl - -DCMAKE_BUILD_TYPE=Release - - configure:msvc: - desc: Configure MSVC build - cmds: - - cmake -S . -B build-msvc - -DLIBCPPRIME_COMPILER=msvc - - # ============================ - # Clean - # ============================ - clean:gcc: - cmds: [rm -rf build-gcc] - - clean:clang: - cmds: [rm -rf build-clang] - - clean:clang-cl: - cmds: [rm -rf build-clang-cl] - - clean:msvc: - cmds: [rm -rf build-msvc] - - # ============================ - # Tests - # ============================ - test:gcc: - deps: [configure:gcc] - cmds: - - cmake --build build-gcc --parallel --target test_isprime - - ctest --test-dir build-gcc --output-on-failure --stop-on-failure --parallel - - test:clang: - deps: [configure:clang] - cmds: - - cmake --build build-clang --parallel --target test_isprime - - ctest --test-dir build-clang --output-on-failure --stop-on-failure --parallel - - test:clang-cl: - deps: [configure:clang-cl] - cmds: - - cmake --build build-clang-cl --parallel --target test_isprime - - ctest --test-dir build-clang-cl --output-on-failure --stop-on-failure --parallel - - test:msvc: - deps: [configure:msvc] - cmds: - - cmake --build build-msvc --parallel --config Release --target test_isprime - - ctest --test-dir build-msvc -C Release --output-on-failure --stop-on-failure --parallel - - # ============================ - # Benchmarks - # ============================ - bench:gcc: - deps: [configure:gcc] - cmds: - - cmake --build build-gcc --parallel --target benchmark_isprime - - ./build-gcc/benchmark_isprime - - uv run benchmarks/plot_isprime.py - - bench:clang: - deps: [configure:clang] - cmds: - - cmake --build build-clang --parallel --target benchmark_isprime - - ./build-clang/benchmark_isprime - - uv run benchmarks/plot_isprime.py - - bench:clang-cl: - deps: [configure:clang-cl] - cmds: - - cmake --build build-clang-cl --parallel --target benchmark_isprime - - ./build-clang-cl/benchmark_isprime - - uv run benchmarks/plot_isprime.py - - bench:msvc: - deps: [configure:msvc] - cmds: - - cmake --build build-msvc --parallel --config Release --target benchmark_isprime - - ./build-msvc/Release/benchmark_isprime.exe - - uv run benchmarks/plot_isprime.py - - bench-heavy:gcc: - deps: [configure:gcc] - cmds: - - cmake --build build-gcc --parallel --target benchmark_isprime - - ./build-gcc/benchmark_isprime --heavy - - uv run benchmarks/plot_isprime.py - - bench-heavy:clang: - deps: [configure:clang] - cmds: - - cmake --build build-clang --parallel --target benchmark_isprime - - ./build-clang/benchmark_isprime --heavy - - uv run benchmarks/plot_isprime.py - - bench-heavy:clang-cl: - deps: [configure:clang-cl] - cmds: - - cmake --build build-clang-cl --parallel --target benchmark_isprime - - ./build-clang-cl/benchmark_isprime --heavy - - uv run benchmarks/plot_isprime.py - - bench-heavy:msvc: - deps: [configure:msvc] - cmds: - - cmake --build build-msvc --parallel --config Release --target benchmark_isprime - - ./build-msvc/Release/benchmark_isprime.exe --heavy - - uv run benchmarks/plot_isprime.py - - # ============================ - # Documentation - # ============================ - docs: - desc: Generate documentation - cmds: - - cp README.md docs/index.md - - uv run mkdocs build - - serve: - desc: Serve documentation locally - cmds: - - cp README.md docs/index.md - - uv run mkdocs serve - - # ============================ - # Bundle - # ============================ - bundle: - desc: Bundle public headers by expanding all local includes - cmd: uv run python scripts/bundle_headers.py - - # ============================ - # Aggregates - # ============================ - configure: - deps: - - configure:gcc - - configure:clang - - configure:clang-cl - - configure:msvc - cmd: cp build-clang/compile_commands.json . - - test: - cmds: - - task: test:gcc - - task: test:clang - - task: test:clang-cl - - task: test:msvc - - clean: - cmds: - - task: clean:gcc - - task: clean:clang - - task: clean:clang-cl - - task: clean:msvc - - default: - cmds: - - task --list diff --git a/benchmarks/plot_isprime.py b/benchmarks/plot_isprime.py index 28906f5..6a17854 100644 --- a/benchmarks/plot_isprime.py +++ b/benchmarks/plot_isprime.py @@ -1,14 +1,13 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- import os import sys -from typing import Tuple +from typing import Any import matplotlib matplotlib.use("Agg") -import matplotlib.pyplot as plt import matplotlib.lines as mlines +import matplotlib.pyplot as plt import numpy as np file_type = "webp" @@ -17,7 +16,7 @@ COLOR_2 = "#F4CE14" -def read_csv_fast(path: str) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: +def read_csv_fast(path: str) -> tuple[np.ndarray, np.ndarray, np.ndarray]: # Columns: n, is_prime, time_ns # Use numpy.loadtxt with converters to avoid Python-level loops data = np.loadtxt(path, delimiter=",", skiprows=1, dtype=np.float64) @@ -119,39 +118,39 @@ def save_summary_plots(summary: np.ndarray, out_prefix: str) -> None: ax.grid(True, linestyle=":", linewidth=0.5, alpha=0.3, which="minor") ax.set_ylabel("avg time [ns]") - prime_style_a = dict( - color=COLOR_2, - marker="o", - markersize=3, - linewidth=1.2, - label="IsPrime", - ) - prime_style_b = dict( - color=COLOR_1, - marker="o", - markersize=3, - linewidth=1.2, - label="IsPrimeNoTable", - ) + prime_style_a: dict[str, Any] = { + "color": COLOR_2, + "marker": "o", + "markersize": 3, + "linewidth": 1.2, + "label": "IsPrime", + } + prime_style_b: dict[str, Any] = { + "color": COLOR_1, + "marker": "o", + "markersize": 3, + "linewidth": 1.2, + "label": "IsPrimeNoTable", + } ax1.plot(x, prime_isprime, **prime_style_a) ax1.plot(x, prime_notable, **prime_style_b) ax1.set_title("Average time by bit-length (Prime)") ax1.legend(loc="best") - comp_style_a = dict( - color=COLOR_2, - marker="o", - markersize=3, - linewidth=1.2, - label="IsPrime", - ) - comp_style_b = dict( - color=COLOR_1, - marker="o", - markersize=3, - linewidth=1.2, - label="IsPrimeNoTable", - ) + comp_style_a: dict[str, Any] = { + "color": COLOR_2, + "marker": "o", + "markersize": 3, + "linewidth": 1.2, + "label": "IsPrime", + } + comp_style_b: dict[str, Any] = { + "color": COLOR_1, + "marker": "o", + "markersize": 3, + "linewidth": 1.2, + "label": "IsPrimeNoTable", + } ax2.plot(x, comp_isprime, **comp_style_a) ax2.plot(x, comp_notable, **comp_style_b) ax2.set_title("Average time by bit-length (Composite)") diff --git a/mise.toml b/mise.toml new file mode 100644 index 0000000..8ac7386 --- /dev/null +++ b/mise.toml @@ -0,0 +1,220 @@ +[tools] +uv = "0.12.17" +cmake = "4.4.3" +ninja = "1.13.2" +ty = "0.0.84" +ruff = "0.16.8" +zizmor = "1.30.1" +"github:oxc-project/oxc" = "oxfmt_v0.70.0" +"github:clangd/clangd" = "23.1.0" +"aqua:nushell/nushell" = "0.115.1" + +[task_config] +shell = "nu -c" + +# ============================ +# Configure +# ============================ + +[tasks."configure:gcc"] +description = "Configure GCC build" +run = [ + 'cmake -S . -B build-gcc -G "Ninja" -DLIBCPPRIME_COMPILER=gcc -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXPORT_COMPILE_COMMANDS=ON', + "cp build-gcc/compile_commands.json compile_commands.json", +] + +[tasks."configure:clang"] +description = "Configure Clang build" +run = [ + 'cmake -S . -B build-clang -G "Ninja" -DLIBCPPRIME_COMPILER=clang -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXPORT_COMPILE_COMMANDS=ON', + "cp build-clang/compile_commands.json compile_commands.json", +] + +[tasks."configure:clang-cl"] +description = "Configure clang-cl build (Windows only)" +run = 'cmake -S . -B build-clang-cl -G "Ninja" -DLIBCPPRIME_COMPILER=clang-cl -DCMAKE_BUILD_TYPE=Release' + +[tasks."configure:msvc"] +description = "Configure MSVC build (Windows only)" +run = 'cmake -S . -B build-msvc -DLIBCPPRIME_COMPILER=msvc' + +[tasks.configure] +description = "Configure all builds (Windows only: includes msvc/clang-cl)" +depends = ["configure:gcc", "configure:clang", "configure:clang-cl", "configure:msvc"] +run = "cp build-clang/compile_commands.json compile_commands.json" + +# ============================ +# Clean +# ============================ + +[tasks."clean:gcc"] +description = "Remove GCC build directory" +run = "rm -rf build-gcc" + +[tasks."clean:clang"] +description = "Remove Clang build directory" +run = "rm -rf build-clang" + +[tasks."clean:clang-cl"] +description = "Remove clang-cl build directory" +run = "rm -rf build-clang-cl" + +[tasks."clean:msvc"] +description = "Remove MSVC build directory" +run = "rm -rf build-msvc" + +[tasks.clean] +description = "Remove all build directories" +depends = ["clean:gcc", "clean:clang", "clean:clang-cl", "clean:msvc"] + +# ============================ +# Tests +# ============================ + +[tasks."test:gcc"] +description = "Build and run tests (GCC)" +depends = ["configure:gcc"] +run = [ + "cmake --build build-gcc --parallel --target test_isprime", + "ctest --test-dir build-gcc --output-on-failure --stop-on-failure --parallel", +] + +[tasks."test:clang"] +description = "Build and run tests (Clang)" +depends = ["configure:clang"] +run = [ + "cmake --build build-clang --parallel --target test_isprime", + "ctest --test-dir build-clang --output-on-failure --stop-on-failure --parallel", +] + +[tasks."test:clang-cl"] +description = "Build and run tests (clang-cl, Windows only)" +depends = ["configure:clang-cl"] +run = [ + "cmake --build build-clang-cl --parallel --target test_isprime", + "ctest --test-dir build-clang-cl --output-on-failure --stop-on-failure --parallel", +] + +[tasks."test:msvc"] +description = "Build and run tests (MSVC, Windows only)" +depends = ["configure:msvc"] +run = [ + "cmake --build build-msvc --parallel --config Release --target test_isprime", + "ctest --test-dir build-msvc -C Release --output-on-failure --stop-on-failure --parallel", +] + +[tasks.test] +description = "Build and run tests with all compilers (Windows only)" +depends = ["test:gcc", "test:clang", "test:clang-cl", "test:msvc"] + +# ============================ +# Benchmarks +# ============================ + +[tasks."bench:gcc"] +description = "Run standard benchmark and generate plots (GCC)" +depends = ["configure:gcc"] +run = [ + "cmake --build build-gcc --parallel --target benchmark_isprime", + "./build-gcc/benchmark_isprime", + "uv run benchmarks/plot_isprime.py", +] +[tasks."bench:clang"] +description = "Run standard benchmark and generate plots (Clang)" +depends = ["configure:clang"] +run = [ + "cmake --build build-clang --parallel --target benchmark_isprime", + "./build-clang/benchmark_isprime", + "uv run benchmarks/plot_isprime.py", +] +[tasks."bench:clang-cl"] +description = "Run standard benchmark and generate plots (clang-cl, Windows only)" +depends = ["configure:clang-cl"] +run = [ + "cmake --build build-clang-cl --parallel --target benchmark_isprime", + "./build-clang-cl/benchmark_isprime.exe", + "uv run benchmarks/plot_isprime.py", +] +[tasks."bench:msvc"] +description = "Run standard benchmark and generate plots (MSVC, Windows only)" +depends = ["configure:msvc"] +run = [ + "cmake --build build-msvc --parallel --config Release --target benchmark_isprime", + "./build-msvc/Release/benchmark_isprime.exe", + "uv run benchmarks/plot_isprime.py", +] +[tasks."bench-heavy:gcc"] +description = "Run heavier benchmark variant (--heavy) and generate plots (GCC)" +depends = ["configure:gcc"] +run = [ + "cmake --build build-gcc --parallel --target benchmark_isprime", + "./build-gcc/benchmark_isprime --heavy", + "uv run benchmarks/plot_isprime.py", +] +[tasks."bench-heavy:clang"] +description = "Run heavier benchmark variant (--heavy) and generate plots (Clang)" +depends = ["configure:clang"] +run = [ + "cmake --build build-clang --parallel --target benchmark_isprime", + "./build-clang/benchmark_isprime --heavy", + "uv run benchmarks/plot_isprime.py", +] +[tasks."bench-heavy:clang-cl"] +description = "Run heavier benchmark variant (--heavy) and generate plots (clang-cl, Windows only)" +depends = ["configure:clang-cl"] +run = [ + "cmake --build build-clang-cl --parallel --target benchmark_isprime", + "./build-clang-cl/benchmark_isprime.exe --heavy", + "uv run benchmarks/plot_isprime.py", +] +[tasks."bench-heavy:msvc"] +description = "Run heavier benchmark variant (--heavy) and generate plots (MSVC, Windows only)" +depends = ["configure:msvc"] +run = [ + "cmake --build build-msvc --parallel --config Release --target benchmark_isprime", + "./build-msvc/Release/benchmark_isprime.exe --heavy", + "uv run benchmarks/plot_isprime.py", +] +# ============================ +# Documentation +# ============================ + +[tasks.docs] +description = "Generate documentation" +run = ["cp README.md docs/index.md", "uv run mkdocs build"] + +[tasks.serve] +description = "Serve documentation locally" +run = ["cp README.md docs/index.md", "uv run mkdocs serve"] + +# ============================ +# Bundle +# ============================ + +[tasks.bundle] +description = "Bundle public headers by expanding all local includes" +run = "uv run scripts/bundle_headers.py" + +# ============================ +# Lint and format +# ============================ + +[tasks.check] +description = "Check for lint and formatting issues" +run = [ + "ty check", + "ruff check", + "ruff format --check", + "oxfmt --check .", + "zizmor --quiet --offline .github/workflows", +] + +[tasks.fix] +description = "Auto-fix lint and formatting issues" +run = [ + "ty check --fix", + "ruff check --fix", + "ruff format", + "oxfmt .", + "zizmor --quiet --offline --fix .github/workflows", +] diff --git a/mkdocs.yml b/mkdocs.yml index 9008288..a6245cb 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,41 +1,40 @@ site_name: libcpprime Documentation site_url: https://sortA0329.github.io/libcpprime/ theme: - name: material - logo: assets/favicon.png - favicon: assets/favicon.png - features: - - navigation.sections - - navigation.expand - - navigation.indexes - - navigation.top - - toc.integrate - - content.code.copy - palette: - scheme: slate - primary: indigo - accent: indigo + name: material + logo: assets/favicon.png + favicon: assets/favicon.png + features: + - navigation.sections + - navigation.expand + - navigation.indexes + - navigation.top + - toc.integrate + - content.code.copy + palette: + scheme: slate + primary: indigo + accent: indigo repo_url: https://github.com/sortA0329/libcpprime repo_name: sortA0329/libcpprime copyright: "Copyright © 2026 sortA" nav: - - libcpprime: index.md - - "GitHub Repository": https://github.com/sortA0329/libcpprime + - libcpprime: index.md + - "GitHub Repository": https://github.com/sortA0329/libcpprime markdown_extensions: - - tables - - pymdownx.highlight: - use_pygments: true - pygments_lang_class: true - - pymdownx.superfences - - pymdownx.tasklist - - pymdownx.magiclink + - tables + - pymdownx.highlight: + use_pygments: true + pygments_lang_class: true + - pymdownx.superfences + - pymdownx.tasklist + - pymdownx.magiclink plugins: - - search - - optimize - - minify: - minify_html: true - minify_js: true - minify_css: true - htmlmin_opts: - remove_comments: true - remove_empty_space: true + - search + - minify: + minify_html: true + minify_js: true + minify_css: true + htmlmin_opts: + remove_comments: true + remove_empty_space: true diff --git a/scripts/bundle_headers.py b/scripts/bundle_headers.py index 62aa1b8..e3868b0 100644 --- a/scripts/bundle_headers.py +++ b/scripts/bundle_headers.py @@ -12,15 +12,15 @@ LOCAL_INCLUDE_RE = re.compile(r'#\s*include\s+"([^"]+)"') CPPR_LICENSE = re.compile( - r'/\*\*\n(?: \*[^\n]*\n)*?' - r' \* libcpprime https://github\.com/sortA0329/libcpprime\n' - r'(?: \*[^\n]*\n)*? \*\*/\n?' + r"/\*\*\n(?: \*[^\n]*\n)*?" + r" \* libcpprime https://github\.com/sortA0329/libcpprime\n" + r"(?: \*[^\n]*\n)*? \*\*/\n?" ) LIBDIVIDE_LICENSE = re.compile( - r'// This file contains code derived from libdivide\n' - r'(?://[^\n]*\n)*?' - r'// library is licensed under the MIT License\.\n?' + r"// This file contains code derived from libdivide\n" + r"(?://[^\n]*\n)*?" + r"// library is licensed under the MIT License\.\n?" ) IWYU_PRAGMA_RE = re.compile(r"\s*// IWYU pragma: export") diff --git a/scripts/extract_changelog.py b/scripts/extract_changelog.py index edeb7bf..19f7474 100644 --- a/scripts/extract_changelog.py +++ b/scripts/extract_changelog.py @@ -77,9 +77,7 @@ def extract_changelog(readme_text: str, version: str) -> str: # Dedent by the common leading indentation so that nested bullets # become top-level bullets in the release body. - indent = min( - len(line) - len(line.lstrip(" ")) for line in body if line.strip() - ) + indent = min(len(line) - len(line.lstrip(" ")) for line in body if line.strip()) dedented = [line[indent:] if len(line) >= indent else line for line in body] return "\n".join(dedented) + "\n" @@ -113,9 +111,7 @@ def main() -> int: return 1 try: - body = extract_changelog( - readme_path.read_text(encoding="utf-8"), version - ) + body = extract_changelog(readme_path.read_text(encoding="utf-8"), version) except ValueError as exc: print(f"Error: {exc}", file=sys.stderr) return 1 From 23b5eae6700ca6fefe3d22f520be2d5b6cb03c61 Mon Sep 17 00:00:00 2001 From: sortA Date: Fri, 25 Sep 2026 21:22:27 +0900 Subject: [PATCH 10/14] Enhance CI --- .github/workflows/ci.yml | 7 ++ .gitignore | 20 +---- .oxfmtrc.json | 3 - .zed/settings.json | 119 +++++++++++++++++++++++-- CMakeLists.txt | 16 ++-- README.md | 185 ++++++++++++++++++++++++--------------- dprint.json | 15 ++++ examples/basic.cpp | 6 +- mise.toml | 11 ++- 9 files changed, 272 insertions(+), 110 deletions(-) delete mode 100644 .oxfmtrc.json create mode 100644 dprint.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b09317..55927da 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,5 +24,12 @@ jobs: persist-credentials: false - name: Setup mise tools uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4 + - name: Cache dprint plugins + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 + with: + path: ~/.cache/dprint + key: ${{ runner.os }}-dprint-${{ hashFiles('dprint.json', 'mise.toml') }} + restore-keys: | + ${{ runner.os }}-dprint- - name: Run check run: mise run check diff --git a/.gitignore b/.gitignore index eb6426c..8c9debb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,6 @@ # Build directories -build/ build-*/ -cmake-build-*/ -CMakeFiles/ .cache/ -CMakeCache.txt -cmake_install.cmake -Makefile -*.cmake # IDE files .idea/ @@ -16,10 +9,6 @@ Makefile *~ .DS_Store -# Test outputs -Testing/ -*.testlog - # Executables *.exe *.pch @@ -41,16 +30,15 @@ __pycache__/ *.py[cod] *$py.class .Python -venv/ ENV/ # CMake user preference file CMakeUserPresets.json -/tmp -/bundled -compile_commands.json - # Documentation /docs/index.md /site + +/tmp +/bundled +compile_commands.json diff --git a/.oxfmtrc.json b/.oxfmtrc.json deleted file mode 100644 index 0a02bce..0000000 --- a/.oxfmtrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "tabWidth": 4 -} diff --git a/.zed/settings.json b/.zed/settings.json index 04b6c0c..348a68e 100644 --- a/.zed/settings.json +++ b/.zed/settings.json @@ -6,12 +6,25 @@ "ensure_final_newline_on_save": true, "remove_trailing_whitespace_on_save": true, "file_types": { - "YAML": [".clang-format", ".clangd"] + "YAML": [ + ".clang-format", + ".clangd" + ] }, - "file_scan_exclusions": ["**/.git", "**/.DS_Store", "build-*", ".cache", ".venv"], + "file_scan_exclusions": [ + "**/.git", + "**/.DS_Store", + "build-*", + "site", + ".cache", + ".ruff_cache", + ".venv", + ], "languages": { "C++": { - "language_servers": ["clangd"], + "language_servers": [ + "clangd" + ], "formatter": { "language_server": { "name": "clangd" @@ -19,31 +32,123 @@ } }, "Python": { - "language_servers": ["ty", "ruff"], + "language_servers": [ + "ty", + "ruff" + ], "formatter": { "language_server": { "name": "ruff" } } + }, + "JSON": { + "formatter": { + "external": { + "command": "mise", + "arguments": [ + "x", + "--", + "dprint", + "fmt", + "--stdin", + "{buffer_path}" + ] + } + } + }, + "JSONC": { + "formatter": { + "external": { + "command": "mise", + "arguments": [ + "x", + "--", + "dprint", + "fmt", + "--stdin", + "{buffer_path}" + ] + } + } + }, + "YAML": { + "formatter": { + "external": { + "command": "mise", + "arguments": [ + "x", + "--", + "dprint", + "fmt", + "--stdin", + "{buffer_path}" + ] + } + } + }, + "TOML": { + "formatter": { + "external": { + "command": "mise", + "arguments": [ + "x", + "--", + "dprint", + "fmt", + "--stdin", + "{buffer_path}" + ] + } + } + }, + "Markdown": { + "formatter": { + "external": { + "command": "mise", + "arguments": [ + "x", + "--", + "dprint", + "fmt", + "--stdin", + "{buffer_path}" + ] + } + } } }, "lsp": { "clangd": { "binary": { "path": "mise", - "arguments": ["x", "--", "clangd"] + "arguments": [ + "x", + "--", + "clangd" + ] } }, "ruff": { "binary": { "path": "mise", - "arguments": ["x", "--", "ruff", "server"] + "arguments": [ + "x", + "--", + "ruff", + "server" + ] } }, "ty": { "binary": { "path": "mise", - "arguments": ["x", "--", "ty", "server"] + "arguments": [ + "x", + "--", + "ty", + "server" + ] } } } diff --git a/CMakeLists.txt b/CMakeLists.txt index 8cd319b..4762e8a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -127,9 +127,9 @@ if(LIBCPPRIME_BUILD_EXAMPLES) target_compile_features(example_basic PRIVATE cxx_std_23) if(LIBCPPRIME_MSVC_LIKE) - target_compile_options(example_basic PRIVATE /W4 /permissive- /Zc:__cplusplus) + target_compile_options(example_basic PRIVATE /W4 /WX /permissive- /Zc:__cplusplus) else() - target_compile_options(example_basic PRIVATE -Wall -Wextra -Wpedantic -Wconversion) + target_compile_options(example_basic PRIVATE -Wall -Wextra -Wpedantic -Wconversion -Werror) endif() endif() @@ -155,22 +155,22 @@ if(LIBCPPRIME_BUILD_TESTS) function(libcpprime_apply_test_options target) if(LIBCPPRIME_CLANG_CL) target_compile_options(${target} PRIVATE - /W4 /permissive- /Zc:__cplusplus + /W4 /WX /permissive- /Zc:__cplusplus /clang:-fconstexpr-steps=10000000 ) elseif(MSVC) target_compile_options(${target} PRIVATE - /W4 /permissive- /Zc:__cplusplus + /W4 /WX /permissive- /Zc:__cplusplus /constexpr:steps10000000 ) elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") target_compile_options(${target} PRIVATE - -Wall -Wextra -Wpedantic -Wconversion + -Wall -Wextra -Wpedantic -Wconversion -Werror -fconstexpr-steps=10000000 ) else() target_compile_options(${target} PRIVATE - -Wall -Wextra -Wpedantic -Wconversion + -Wall -Wextra -Wpedantic -Wconversion -Werror ) endif() libcpprime_apply_optimization(${target}) @@ -210,9 +210,9 @@ if(LIBCPPRIME_BUILD_BENCHMARKS) target_link_libraries(benchmark_isprime PRIVATE libcpprime) if(LIBCPPRIME_MSVC_LIKE) - target_compile_options(benchmark_isprime PRIVATE /W4 /permissive- /Zc:__cplusplus) + target_compile_options(benchmark_isprime PRIVATE /W4 /WX /permissive- /Zc:__cplusplus) else() - target_compile_options(benchmark_isprime PRIVATE -Wall -Wextra -Wpedantic -Wconversion) + target_compile_options(benchmark_isprime PRIVATE -Wall -Wextra -Wpedantic -Wconversion -Werror) endif() libcpprime_apply_optimization(benchmark_isprime) diff --git a/README.md b/README.md index 97e3386..ee5bea7 100644 --- a/README.md +++ b/README.md @@ -83,8 +83,8 @@ int main() { ## Requirements -- C++11 -- GCC, Clang, GCC (MinGW), Clang (MinGW), MSVC, clang-cl +- C++11 +- GCC, Clang, GCC (MinGW), Clang (MinGW), MSVC, clang-cl ## Compilation @@ -105,9 +105,21 @@ Workflow: [bench.yml](https://github.com/sortA0329/libcpprime/actions/workflows/ [View Summary](https://sortA0329.github.io/libcpprime/benchmarks/latest/benchmark-Linux-gcc/bench_summary.md)

- Linux gcc summary - Linux gcc IsPrime - Linux gcc IsPrimeNoTable + Linux gcc summary + Linux gcc IsPrime + Linux gcc IsPrimeNoTable

### Linux (clang) @@ -115,9 +127,21 @@ Workflow: [bench.yml](https://github.com/sortA0329/libcpprime/actions/workflows/ [View summary](https://sortA0329.github.io/libcpprime/benchmarks/latest/benchmark-Linux-clang/bench_summary.md)

- Linux clang summary - Linux clang IsPrime - Linux clang IsPrimeNoTable + Linux clang summary + Linux clang IsPrime + Linux clang IsPrimeNoTable

### Windows (msvc) @@ -125,9 +149,21 @@ Workflow: [bench.yml](https://github.com/sortA0329/libcpprime/actions/workflows/ [View Summary](https://sortA0329.github.io/libcpprime/benchmarks/latest/benchmark-Windows-msvc/bench_summary.md)

- Windows msvc summary - Windows msvc IsPrime - Windows msvc IsPrimeNoTable + Windows msvc summary + Windows msvc IsPrime + Windows msvc IsPrimeNoTable

### Windows (clang-cl) @@ -135,68 +171,79 @@ Workflow: [bench.yml](https://github.com/sortA0329/libcpprime/actions/workflows/ [View Summary](https://sortA0329.github.io/libcpprime/benchmarks/latest/benchmark-Windows-clang-cl/bench_summary.md)

- Windows clang-cl summary - Windows clang-cl IsPrime - Windows clang-cl IsPrimeNoTable + Windows clang-cl summary + Windows clang-cl IsPrime + Windows clang-cl IsPrimeNoTable

- ## Releases -- 2026/09/23 ver 1.3.5 - - Improve performance of `cppr::IsPrimeNoTable` -- 2026/06/28 ver 1.3.4 - - Improve performance - - Prevent unnecessary code from being included when `FeatureTestMacros.hpp` is included - - Fix the name and link in the copyright notice - - Add the copyright notice for Bradley Berg's algorithm to the LICENSE file -- 2026/02/25 ver 1.3.3 - - Update copyright year to 2026 - - Add -O3 -march=native to the compilation flags during benchmarking and testing -- 2026/01/04 ver 1.3.2 - - Improve performance -- 2025/12/24 ver 1.3.1 - - Improve performance and reduce binary size for `cppr::IsPrime` -- 2025/12/21 ver 1.3.0 - - Add `CPPR_HAS_CONSTEXPR_IS_PRIME` - - Support clang-cl - - Accelerating Compile-Time Computation - - Improved compatibility -- 2025/03/10 ver 1.2.11 - - Change the name on the license - - Change Multiprication Algorithm - - Replace `__uint128_t` with `unsigned __int128` -- 2025/01/05 ver 1.2.10 - - Change the condition of `constexpr` -- 2025/01/03 ver 1.2.9 - - Fix a bug -- 2025/01/02 ver 1.2.8 - - Improve performance - - Suppress warnings -- 2024/12/31 ver 1.2.7 - - Improve performance -- 2024/12/30 ver 1.2.6 - - Improve performance -- 2024/12/29 ver 1.2.5 - - Add copyrights notice -- 2024/12/28 ver 1.2.4 - - Improve performance -- 2024/12/26 ver 1.2.3 - - Improve performance -- 2024/12/25 ver 1.2.2 - - Improve performance -- 2024/12/23 ver 1.2.1 - - Improve performance -- 2024/12/19 ver 1.2.0 - - Split `cppr::IsPrime` into `cppr::IsPrime` and `cppr::IsPrimeNoTable` -- 2024/12/19 ver 1.1.2 - - Fix typo -- 2024/12/18 ver 1.1.1 - - Add include guards -- 2024/12/18 ver 1.1.0 - - Add `cppr::IsPrime` with a table -- 2024/12/18 ver 1.0.0 - - Add `cppr::IsPrime` +- 2026/09/23 ver 1.3.5 + - Improve performance of `cppr::IsPrimeNoTable` +- 2026/06/28 ver 1.3.4 + - Improve performance + - Prevent unnecessary code from being included when `FeatureTestMacros.hpp` is included + - Fix the name and link in the copyright notice + - Add the copyright notice for Bradley Berg's algorithm to the LICENSE file +- 2026/02/25 ver 1.3.3 + - Update copyright year to 2026 + - Add -O3 -march=native to the compilation flags during benchmarking and testing +- 2026/01/04 ver 1.3.2 + - Improve performance +- 2025/12/24 ver 1.3.1 + - Improve performance and reduce binary size for `cppr::IsPrime` +- 2025/12/21 ver 1.3.0 + - Add `CPPR_HAS_CONSTEXPR_IS_PRIME` + - Support clang-cl + - Accelerating Compile-Time Computation + - Improved compatibility +- 2025/03/10 ver 1.2.11 + - Change the name on the license + - Change Multiprication Algorithm + - Replace `__uint128_t` with `unsigned __int128` +- 2025/01/05 ver 1.2.10 + - Change the condition of `constexpr` +- 2025/01/03 ver 1.2.9 + - Fix a bug +- 2025/01/02 ver 1.2.8 + - Improve performance + - Suppress warnings +- 2024/12/31 ver 1.2.7 + - Improve performance +- 2024/12/30 ver 1.2.6 + - Improve performance +- 2024/12/29 ver 1.2.5 + - Add copyrights notice +- 2024/12/28 ver 1.2.4 + - Improve performance +- 2024/12/26 ver 1.2.3 + - Improve performance +- 2024/12/25 ver 1.2.2 + - Improve performance +- 2024/12/23 ver 1.2.1 + - Improve performance +- 2024/12/19 ver 1.2.0 + - Split `cppr::IsPrime` into `cppr::IsPrime` and `cppr::IsPrimeNoTable` +- 2024/12/19 ver 1.1.2 + - Fix typo +- 2024/12/18 ver 1.1.1 + - Add include guards +- 2024/12/18 ver 1.1.0 + - Add `cppr::IsPrime` with a table +- 2024/12/18 ver 1.0.0 + - Add `cppr::IsPrime` ## References diff --git a/dprint.json b/dprint.json new file mode 100644 index 0000000..f59c0a0 --- /dev/null +++ b/dprint.json @@ -0,0 +1,15 @@ +{ + "indentWidth": 4, + "yaml": { + "associations": [ + ".clang-format", + ".clangd" + ] + }, + "plugins": [ + "npm:@dprint/json@0.24.0", + "npm:@dprint/markdown@0.24.0", + "npm:@dprint/toml@0.8.0", + "npm:dprint-plugin-yaml@0.6.0" + ] +} diff --git a/examples/basic.cpp b/examples/basic.cpp index b48a614..7043611 100644 --- a/examples/basic.cpp +++ b/examples/basic.cpp @@ -1,7 +1,7 @@ +#include +#include #include #include -#include -#include int main() { // Example usage of libcpprime @@ -10,7 +10,7 @@ int main() { std::cout << std::endl; // Test some known primes and composites - std::uint64_t test_cases[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 4, 6, 8, 9, 10, 12, 14, 15, 16, 18, 998244353, 1000000007, 999988224444335533ULL }; + std::uint64_t test_cases[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 4, 6, 8, 9, 10, 12, 14, 15, 16, 18, 998244353, 1000000007, 999988224444335533ULL}; std::cout << "Testing cppr::IsPrime():" << std::endl; for (const auto& n : test_cases) { diff --git a/mise.toml b/mise.toml index 8ac7386..945305a 100644 --- a/mise.toml +++ b/mise.toml @@ -2,10 +2,11 @@ uv = "0.12.17" cmake = "4.4.3" ninja = "1.13.2" +clang-format = "23.1.2" ty = "0.0.84" ruff = "0.16.8" zizmor = "1.30.1" -"github:oxc-project/oxc" = "oxfmt_v0.70.0" +dprint = "0.57.4" "github:clangd/clangd" = "23.1.0" "aqua:nushell/nushell" = "0.115.1" @@ -36,7 +37,7 @@ run = 'cmake -S . -B build-clang-cl -G "Ninja" -DLIBCPPRIME_COMPILER=clang-cl -D [tasks."configure:msvc"] description = "Configure MSVC build (Windows only)" -run = 'cmake -S . -B build-msvc -DLIBCPPRIME_COMPILER=msvc' +run = "cmake -S . -B build-msvc -DLIBCPPRIME_COMPILER=msvc" [tasks.configure] description = "Configure all builds (Windows only: includes msvc/clang-cl)" @@ -205,7 +206,8 @@ run = [ "ty check", "ruff check", "ruff format --check", - "oxfmt --check .", + "dprint check", + "clang-format --dry-run --Werror ...((glob 'include/**/*.{cpp,hpp,h}') ++ (glob 'tests/**/*.{cpp,hpp,h}') ++ (glob 'benchmarks/**/*.{cpp,hpp,h}') ++ (glob 'examples/**/*.{cpp,hpp,h}'))", "zizmor --quiet --offline .github/workflows", ] @@ -215,6 +217,7 @@ run = [ "ty check --fix", "ruff check --fix", "ruff format", - "oxfmt .", + "dprint fmt", + "clang-format -i ...((glob 'include/**/*.{cpp,hpp,h}') ++ (glob 'tests/**/*.{cpp,hpp,h}') ++ (glob 'benchmarks/**/*.{cpp,hpp,h}') ++ (glob 'examples/**/*.{cpp,hpp,h}'))", "zizmor --quiet --offline --fix .github/workflows", ] From e26f824a8f00925b072519e5baa4f3d39c60458e Mon Sep 17 00:00:00 2001 From: sortA Date: Fri, 25 Sep 2026 21:28:56 +0900 Subject: [PATCH 11/14] Introduce lefthook --- lefthook.yml | 5 +++++ mise.toml | 4 ++++ 2 files changed, 9 insertions(+) create mode 100644 lefthook.yml diff --git a/lefthook.yml b/lefthook.yml new file mode 100644 index 0000000..bd391ca --- /dev/null +++ b/lefthook.yml @@ -0,0 +1,5 @@ +pre-commit: + commands: + fix: + run: mise run fix + stage_fixed: true diff --git a/mise.toml b/mise.toml index 945305a..ec66da3 100644 --- a/mise.toml +++ b/mise.toml @@ -7,12 +7,16 @@ ty = "0.0.84" ruff = "0.16.8" zizmor = "1.30.1" dprint = "0.57.4" +lefthook = "2.1.14" "github:clangd/clangd" = "23.1.0" "aqua:nushell/nushell" = "0.115.1" [task_config] shell = "nu -c" +[hooks] +postinstall = "lefthook install" + # ============================ # Configure # ============================ From 0d1475e96bdf836cc376997ef0fe2fea28d88172 Mon Sep 17 00:00:00 2001 From: sortA Date: Fri, 25 Sep 2026 21:30:11 +0900 Subject: [PATCH 12/14] Fix release date --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ee5bea7..9d7f258 100644 --- a/README.md +++ b/README.md @@ -190,7 +190,7 @@ Workflow: [bench.yml](https://github.com/sortA0329/libcpprime/actions/workflows/ ## Releases -- 2026/09/23 ver 1.3.5 +- 2026/09/25 ver 1.3.5 - Improve performance of `cppr::IsPrimeNoTable` - 2026/06/28 ver 1.3.4 - Improve performance From 293704e3e40c6f8be85e73d287b0492300e00f5f Mon Sep 17 00:00:00 2001 From: sortA Date: Fri, 25 Sep 2026 21:41:27 +0900 Subject: [PATCH 13/14] Fix CI failure --- mise.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mise.toml b/mise.toml index ec66da3..702aa24 100644 --- a/mise.toml +++ b/mise.toml @@ -207,6 +207,7 @@ run = "uv run scripts/bundle_headers.py" [tasks.check] description = "Check for lint and formatting issues" run = [ + "uv sync --frozen", "ty check", "ruff check", "ruff format --check", @@ -218,6 +219,7 @@ run = [ [tasks.fix] description = "Auto-fix lint and formatting issues" run = [ + "uv sync --frozen", "ty check --fix", "ruff check --fix", "ruff format", From b00c4e5a7f950c9e027232ce58da4c3d1a43eb59 Mon Sep 17 00:00:00 2001 From: sortA Date: Fri, 25 Sep 2026 21:46:38 +0900 Subject: [PATCH 14/14] Remove shebang --- benchmarks/plot_isprime.py | 1 - scripts/bundle_headers.py | 1 - scripts/extract_changelog.py | 1 - scripts/publish_benchmarks_to_pages.py | 2 -- 4 files changed, 5 deletions(-) diff --git a/benchmarks/plot_isprime.py b/benchmarks/plot_isprime.py index 6a17854..1880aae 100644 --- a/benchmarks/plot_isprime.py +++ b/benchmarks/plot_isprime.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 import os import sys from typing import Any diff --git a/scripts/bundle_headers.py b/scripts/bundle_headers.py index e3868b0..711f717 100644 --- a/scripts/bundle_headers.py +++ b/scripts/bundle_headers.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """ Bundle script: for each public header in include/libcpprime/*.hpp, expand all local #include "..." directives (both .hpp and .txt) diff --git a/scripts/extract_changelog.py b/scripts/extract_changelog.py index 19f7474..7c2c324 100644 --- a/scripts/extract_changelog.py +++ b/scripts/extract_changelog.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """Extract the changelog for a given version from README.md. The README contains a "Releases" section like: diff --git a/scripts/publish_benchmarks_to_pages.py b/scripts/publish_benchmarks_to_pages.py index c784037..d1b8ea8 100644 --- a/scripts/publish_benchmarks_to_pages.py +++ b/scripts/publish_benchmarks_to_pages.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - from __future__ import annotations import argparse