From 2ebc96a4a67e88399a14310fc371198d55793088 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Tue, 7 Jul 2026 22:02:39 +0100 Subject: [PATCH 1/5] perf: Use std::from/to_chars for JSON double parsing/formating (#7735) --- src/libxrpl/json/json_reader.cpp | 42 ++++++++------------------ src/libxrpl/json/json_writer.cpp | 27 ++++++++--------- src/tests/libxrpl/json/Value.cpp | 52 ++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 44 deletions(-) diff --git a/src/libxrpl/json/json_reader.cpp b/src/libxrpl/json/json_reader.cpp index f37cca506b..f9134e6629 100644 --- a/src/libxrpl/json/json_reader.cpp +++ b/src/libxrpl/json/json_reader.cpp @@ -5,12 +5,13 @@ #include #include +#include #include #include -#include #include #include #include +#include namespace json { // Implementation of class Reader @@ -605,36 +606,17 @@ bool Reader::decodeDouble(Token& token) { double value = 0; - int const bufferSize = 32; - int count = 0; - int const length = int(token.end - token.start); - // Sanity check to avoid buffer overflow exploits. - if (length < 0) - { - return addError("Unable to parse token length", token); - } - // Avoid using a string constant for the format control string given to - // sscanf, as this can cause hard to debug crashes on OS X. See here for - // more info: - // - // http://developer.apple.com/library/mac/#DOCUMENTATION/DeveloperTools/gcc-4.0.1/gcc/Incompatibilities.html - char format[] = "%lf"; - if (length <= bufferSize) - { - Char buffer[bufferSize + 1]; - memcpy(buffer, token.start, length); - buffer[length] = 0; - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) - count = sscanf(buffer, format, &value); - } - else - { - std::string const buffer(token.start, token.end); - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) - count = sscanf(buffer.c_str(), format, &value); - } - if (count != 1) + auto const [ptr, ec] = std::from_chars(token.start, token.end, value); + + // Reject anything from_chars could not turn into a finite double: + // - ec != std::errc{}: no valid conversion, or an out-of-range magnitude + // (e.g. 1e400). + // - ptr != token.end: readNumber() is permissive about which characters + // it collects into a token (it will, for example, keep a '+' mid-token), + // but from_chars() will stop at the first character it cannot parse. + if (ec != std::errc{} || ptr != token.end) return addError("'" + std::string(token.start, token.end) + "' is not a number.", token); + currentValue() = value; return true; } diff --git a/src/libxrpl/json/json_writer.cpp b/src/libxrpl/json/json_writer.cpp index 853dd9a5ac..fdfa5d3cf0 100644 --- a/src/libxrpl/json/json_writer.cpp +++ b/src/libxrpl/json/json_writer.cpp @@ -4,13 +4,14 @@ #include #include -#include +#include #include #include #include #include #include #include +#include #include namespace json { @@ -76,20 +77,18 @@ valueToString(UInt value) std::string valueToString(double value) { - // Allocate a buffer that is more than large enough to store the 16 digits - // of precision requested below. + // Format with 16 significant digits. + // We need not request the alternative representation that always has a + // decimal point because JSON doesn't distinguish the concepts of reals and integers. + // A double never needs more than 32 characters in this form, + // so to_chars cannot actually run out of room here. char buffer[32]; - // Print into the buffer. We need not request the alternative representation - // that always has a decimal point because JSON doesn't distinguish the - // concepts of reals and integers. -#if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) // Use secure version with visual studio 2005 - // to avoid warning. - sprintf_s(buffer, sizeof(buffer), "%.16g", value); -#else - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) - snprintf(buffer, sizeof(buffer), "%.16g", value); -#endif - return buffer; + auto const [ptr, ec] = + std::to_chars(buffer, buffer + sizeof(buffer), value, std::chars_format::general, 16); + XRPL_ASSERT(ec == std::errc{}, "json::valueToString(double) : conversion fits buffer"); + if (ec != std::errc{}) + return {}; + return std::string(buffer, ptr); } std::string diff --git a/src/tests/libxrpl/json/Value.cpp b/src/tests/libxrpl/json/Value.cpp index a1cd8160d7..a58a5df9fd 100644 --- a/src/tests/libxrpl/json/Value.cpp +++ b/src/tests/libxrpl/json/Value.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -592,6 +593,57 @@ TEST(json_value, bad_json) EXPECT_TRUE(r.parse(s, j)); } +namespace { + +std::optional +parseValue(std::string const& doc) +{ + json::Value j; + json::Reader r; + if (!r.parse("{\"v\":" + doc + "}", j)) + return std::nullopt; + return j["v"]; +} + +} // namespace + +TEST(json_value, parse_double_valid) +{ + // 1e300 is large but still representable, so it parses (unlike the out-of-range cases below). + for (auto const& [text, expected] : + {std::pair{"2.5", 2.5}, + std::pair{"-3.25e2", -325.0}, + std::pair{"0.0", 0.0}, + std::pair{"1E3", 1000.0}, + std::pair{"-0.5e-1", -0.05}, + std::pair{"1e300", 1e300}}) + { + auto const v = parseValue(text); + ASSERT_TRUE(v.has_value()) << text; + // NOLINTBEGIN(bugprone-unchecked-optional-access) + EXPECT_TRUE(v->isDouble()) << text; + EXPECT_EQ(v->asDouble(), expected) << text; + // NOLINTEND(bugprone-unchecked-optional-access) + } +} + +TEST(json_value, parse_double_out_of_range) +{ + // Magnitudes with no finite double representation are rejected. + for (char const* oor : {"1e400", "-1e400", "0.001e500", "1e-400", "-1e-400", "123e-500"}) + EXPECT_FALSE(parseValue(oor).has_value()) << oor; +} + +TEST(json_value, parse_double_malformed) +{ + // readNumber() collects any run of digits and '.eE+-' into a single Double + // token, so these malformed tokens reach decodeDouble. Each has a valid + // leading prefix that from_chars would accept on its own; requiring the + // entire token be consumed rejects them instead of silently truncating. + for (char const* bad : {"1+2", "1-2", "1.2.3", "1e5e6", "1..2", "++5", "1e", "1e+", ".", "-"}) + EXPECT_FALSE(parseValue(bad).has_value()) << bad; +} + TEST(json_value, edge_cases) { std::uint32_t const maxUInt = std::numeric_limits::max(); From e372c4583642ad42b7c4b3abac396a68c9918177 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Tue, 7 Jul 2026 22:03:56 +0100 Subject: [PATCH 2/5] chore: Enable most performance checks (#7727) --- .clang-tidy | 6 ------ include/xrpl/basics/IntrusivePointer.ipp | 3 +++ include/xrpl/shamap/detail/TaggedPointer.ipp | 6 ++++++ src/libxrpl/protocol/STParsedJSON.cpp | 21 +++++++++++++++----- src/libxrpl/rdb/SociDB.cpp | 6 ++++++ src/test/core/Config_test.cpp | 1 + 6 files changed, 32 insertions(+), 11 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index c13ca78ac2..88dd6f4e57 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -50,13 +50,7 @@ Checks: "-*, performance-*, -performance-avoid-endl, -performance-enum-size, - -performance-inefficient-algorithm, - -performance-inefficient-string-concatenation, - -performance-no-int-to-ptr, - -performance-noexcept-destructor, -performance-noexcept-move-constructor, - -performance-noexcept-swap, - -performance-type-promotion-in-math-fn, -performance-unnecessary-copy-initialization, -performance-unnecessary-value-param, diff --git a/include/xrpl/basics/IntrusivePointer.ipp b/include/xrpl/basics/IntrusivePointer.ipp index 8344a3e613..67d43b05d6 100644 --- a/include/xrpl/basics/IntrusivePointer.ipp +++ b/include/xrpl/basics/IntrusivePointer.ipp @@ -641,6 +641,9 @@ template T* SharedWeakUnion::unsafeGetRawPtr() const { + // tp_ packs a raw pointer together with a strength bit; recovering the + // pointer inherently requires an integer-to-pointer cast. + // NOLINTNEXTLINE(performance-no-int-to-ptr) return reinterpret_cast(tp_ & kPtrMask); } diff --git a/include/xrpl/shamap/detail/TaggedPointer.ipp b/include/xrpl/shamap/detail/TaggedPointer.ipp index 6606c49a6b..9275f3d15a 100644 --- a/include/xrpl/shamap/detail/TaggedPointer.ipp +++ b/include/xrpl/shamap/detail/TaggedPointer.ipp @@ -507,6 +507,9 @@ TaggedPointer::operator=(TaggedPointer&& other) [[nodiscard]] inline std::pair TaggedPointer::decode() const { + // tp_ packs a raw pointer together with the tag bits; recovering the + // pointer inherently requires an integer-to-pointer cast. + // NOLINTNEXTLINE(performance-no-int-to-ptr) return {tp_ & kTagMask, reinterpret_cast(tp_ & kPtrMask)}; } @@ -535,6 +538,9 @@ TaggedPointer::getHashesAndChildren() const [[nodiscard]] inline SHAMapHash* TaggedPointer::getHashes() const { + // tp_ packs a raw pointer together with the tag bits; recovering the + // pointer inherently requires an integer-to-pointer cast. + // NOLINTNEXTLINE(performance-no-int-to-ptr) return reinterpret_cast(tp_ & kPtrMask); }; diff --git a/src/libxrpl/protocol/STParsedJSON.cpp b/src/libxrpl/protocol/STParsedJSON.cpp index d3342ef728..33ca5424d1 100644 --- a/src/libxrpl/protocol/STParsedJSON.cpp +++ b/src/libxrpl/protocol/STParsedJSON.cpp @@ -69,6 +69,17 @@ toUnsigned(U2 value) return static_cast(value); } +static std::string +joinName(std::string const& jsonName, std::string const& fieldName) +{ + std::string result; + result.reserve(jsonName.size() + 1 + fieldName.size()); + result += jsonName; + result += '.'; + result += fieldName; + return result; +} + // LCOV_EXCL_START static inline std::string makeName(std::string const& object, std::string const& field) @@ -76,7 +87,7 @@ makeName(std::string const& object, std::string const& field) if (field.empty()) return object; - return object + "." + field; + return joinName(object, field); } static inline json::Value @@ -1036,8 +1047,8 @@ parseObject( try { - auto ret = - parseObject(jsonName + "." + fieldName, value, field, depth + 1, error); + auto ret = parseObject( + joinName(jsonName, fieldName), value, field, depth + 1, error); if (!ret) return std::nullopt; data.emplaceBack(std::move(*ret)); @@ -1054,8 +1065,8 @@ parseObject( case STI_ARRAY: try { - auto array = - parseArray(jsonName + "." + fieldName, value, field, depth + 1, error); + auto array = parseArray( + joinName(jsonName, fieldName), value, field, depth + 1, error); if (!array.has_value()) return std::nullopt; data.emplaceBack(std::move(*array)); diff --git a/src/libxrpl/rdb/SociDB.cpp b/src/libxrpl/rdb/SociDB.cpp index 06e58d373f..08c826b210 100644 --- a/src/libxrpl/rdb/SociDB.cpp +++ b/src/libxrpl/rdb/SociDB.cpp @@ -213,6 +213,12 @@ public: if (auto [conn, keepAlive] = getConnection(); conn) { (void)keepAlive; + // The checkpointer is identified to the C callback by an integer id + // (resolved via checkpointerFromId) rather than a raw `this`, so it + // cannot dangle if the checkpointer is destroyed. Passing the id + // through sqlite's void* user-data requires an integer-to-pointer + // cast. + // NOLINTNEXTLINE(performance-no-int-to-ptr) sqlite_api::sqlite3_wal_hook(conn, &sqliteWALHook, reinterpret_cast(id_)); } } diff --git a/src/test/core/Config_test.cpp b/src/test/core/Config_test.cpp index 38c14b8ac5..17c48b1ccc 100644 --- a/src/test/core/Config_test.cpp +++ b/src/test/core/Config_test.cpp @@ -1475,6 +1475,7 @@ r.ripple.com:51235 std::string toLoad(R"xrpldConfig( [amendment_majority_time] )xrpldConfig"); + // NOLINTNEXTLINE(performance-inefficient-string-concatenation) toLoad += std::to_string(val) + space + unit; space = space.empty() ? " " : ""; From 58af1e6f188549074e5cd82c38d06bcf7e8d334d Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 8 Jul 2026 13:40:24 +0100 Subject: [PATCH 3/5] release: Bump version to 3.3.0-b1 (#7755) --- src/libxrpl/protocol/BuildInfo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libxrpl/protocol/BuildInfo.cpp b/src/libxrpl/protocol/BuildInfo.cpp index 820936a22d..793ddb18d2 100644 --- a/src/libxrpl/protocol/BuildInfo.cpp +++ b/src/libxrpl/protocol/BuildInfo.cpp @@ -23,7 +23,7 @@ namespace { //------------------------------------------------------------------------------ // clang-format off // NOLINTNEXTLINE(readability-identifier-naming) -char const* const versionString = "3.3.0-b0" +char const* const versionString = "3.3.0-b1" // clang-format on ; From 71ee0f400b5d712ca121a639c859d18847098859 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 8 Jul 2026 15:51:39 +0100 Subject: [PATCH 4/5] chore: Use same compiler in Nix devshell as in CI (#7751) --- docs/build/environment.md | 15 ++-- docs/build/nix.md | 30 ++++---- nix/ci-env.nix | 18 +++-- nix/devshell.nix | 150 ++++++++++---------------------------- nix/packages.nix | 21 +++++- 5 files changed, 93 insertions(+), 141 deletions(-) diff --git a/docs/build/environment.md b/docs/build/environment.md index 2cca608567..e639ed2d5f 100644 --- a/docs/build/environment.md +++ b/docs/build/environment.md @@ -33,9 +33,10 @@ with a single command and without installing anything system-wide: nix --experimental-features 'nix-command flakes' develop ``` -On **Linux**, Nix also provides the compiler (GCC). On **macOS**, the shell uses -your **system-wide Apple Clang** as the compiler, so you still need to manage -its version (see below). +On **Linux**, Nix also provides the compiler (GCC); on **macOS**, it provides +Clang. If you instead opt to use your system-wide Apple Clang (via +`nix develop .#apple-clang`), you need to manage its version yourself (see +below). See [Using the Nix development shell](./nix.md) for installation and usage details, including how to select a different compiler. @@ -48,10 +49,10 @@ details, including how to select a different compiler. ### macOS: managing the Apple Clang version -Because the Nix shell uses the system-wide Apple Clang on macOS, the compiler -version is whatever your installed Xcode (or Command Line Tools) provides. The -following command should return a version greater than or equal to the -[minimum required](#tested-compiler-versions): +If you use your system-wide Apple Clang on macOS (via `nix develop .#apple-clang`), +the compiler version is whatever your installed Xcode (or Command Line Tools) +provides. The following command should return a version greater than or equal to +the [minimum required](#tested-compiler-versions): ```bash clang --version diff --git a/docs/build/nix.md b/docs/build/nix.md index 2ae483aefe..32592e037a 100644 --- a/docs/build/nix.md +++ b/docs/build/nix.md @@ -9,7 +9,7 @@ This guide explains how to use Nix to set up a reproducible development environm - **Reproducible environment**: Everyone gets the same versions of tools and compilers - **Matches CI**: The Linux CI runs in Docker images built from this exact Nix environment - **No system pollution**: Dependencies are isolated and don't affect your system packages -- **Multiple compiler versions**: Easily switch between different GCC and Clang versions +- **Consistent compilers**: The GCC and Clang shells use the same versions as CI - **Quick setup**: Get started with a single command - **Works on Linux and macOS**: Consistent experience across platforms @@ -31,8 +31,8 @@ This will: - Download and set up all required development tools (CMake, Ninja, Conan, etc.) - Configure the appropriate compiler for your platform: - - **Linux**: GCC 15.2 (provided by Nix) - - **macOS**: Apple Clang (your system compiler) + - **Linux**: GCC (provided by Nix) + - **macOS**: Clang (provided by Nix) The first time you run this command, it will take a few minutes to download and build the environment. Subsequent runs will be much faster. @@ -40,12 +40,12 @@ The first time you run this command, it will take a few minutes to download and - **Linux**: `nix develop` gives you a shell with all the tooling necessary to develop xrpld and with GCC 15.2 (also provided by Nix). There are no caveats. -- **macOS**: `nix develop` gives you a full environment too. The compiler is - your system-wide Apple Clang, while every other tool — including Conan — is - provided by Nix. Conan has no binary in the Nix cache for macOS, so it is - built from source the first time you enter the shell, which makes the initial - setup slower (this is handled automatically; see - [`nix/devshell.nix`](../../nix/devshell.nix)). +- **macOS**: `nix develop` gives you a full environment too, with Clang (and + every other tool, including Conan) provided by Nix. To use your system-wide + Apple Clang instead, enter `nix develop .#apple-clang`. Conan has no binary in + the Nix cache for macOS, so it is built from source the first time you enter + the shell, which makes the initial setup slower (this is handled + automatically; see [`nix/devshell.nix`](../../nix/devshell.nix)). > [!TIP] > To avoid typing `--experimental-features 'nix-command flakes'` every time, you can permanently enable flakes by creating `~/.config/nix/nix.conf`: @@ -62,7 +62,9 @@ The first time you run this command, it will take a few minutes to download and ### Choosing a different compiler -A compiler can be chosen by providing its name with the `.#` prefix, e.g. `nix develop .#gcc15`. +A compiler can be chosen by providing its name with the `.#` prefix, e.g. `nix develop .#clang`. +The `.#gcc` and `.#clang` shells provide the same GCC and Clang versions used in CI +(pinned in [`nix/packages.nix`](../../nix/packages.nix)). Use `nix flake show` to see all the available development shells. Use `nix develop .#no-compiler` to use the compiler from your system. @@ -70,11 +72,11 @@ Use `nix develop .#no-compiler` to use the compiler from your system. ### Example Usage ```bash -# Use GCC 14 -nix develop .#gcc14 +# Use GCC (same version as CI) +nix develop .#gcc -# Use Clang 19 -nix develop .#clang19 +# Use Clang (same version as CI) +nix develop .#clang # Use default for your platform nix develop diff --git a/nix/ci-env.nix b/nix/ci-env.nix index f823f71de0..9b754af97d 100644 --- a/nix/ci-env.nix +++ b/nix/ci-env.nix @@ -4,14 +4,16 @@ ... }: let - inherit (import ./packages.nix { inherit pkgs; }) commonPackages; - inherit (pkgs) lib; + inherit (import ./packages.nix { inherit pkgs; }) + commonPackages + gccPackage + llvmPackages + llvmVersion + ; - # Underlying compiler toolchains to wrap. Bump these in one place to - # roll the whole environment forward. - customGccPackage = pkgs.gcc15; - customLlvmPackages = pkgs.llvmPackages_22; - customClangMajor = lib.versions.major (lib.getVersion customLlvmPackages.clang-unwrapped); + # Underlying compiler toolchains to wrap (versions pinned in packages.nix). + customGccPackage = gccPackage; + customLlvmPackages = llvmPackages; # binutils wrapped to emit binaries that reference the custom glibc # (dynamic linker path, library search path, RPATH). @@ -90,7 +92,7 @@ let extraBuildCommands = '' rsrc="$out/resource-root" mkdir "$rsrc" - ln -s "${customLlvmPackages.clang-unwrapped.lib}/lib/clang/${customClangMajor}/include" "$rsrc/include" + ln -s "${customLlvmPackages.clang-unwrapped.lib}/lib/clang/${toString llvmVersion}/include" "$rsrc/include" ln -s "${customCompilerRt.out}/lib" "$rsrc/lib" ln -s "${customCompilerRt.out}/share" "$rsrc/share" || true echo "-resource-dir=$rsrc" >> $out/nix-support/cc-cflags diff --git a/nix/devshell.nix b/nix/devshell.nix index 1bd7ea4c0c..34f173ef08 100644 --- a/nix/devshell.nix +++ b/nix/devshell.nix @@ -1,127 +1,57 @@ { pkgs, ... }: let - inherit (import ./packages.nix { inherit pkgs; }) commonPackages; + inherit (import ./packages.nix { inherit pkgs; }) + commonPackages + gccVersion + llvmPackages + ; - # Supported compiler versions - gccVersion = pkgs.lib.range 13 15; - clangVersions = pkgs.lib.range 18 21; + # Plain nixpkgs stdenvs — no custom glibc, unlike ci-env.nix. + gccStdenv = pkgs."gcc${toString gccVersion}Stdenv"; + clangStdenv = llvmPackages.stdenv; - defaultCompiler = if pkgs.stdenv.isDarwin then "apple-clang" else "gcc"; - defaultGccVersion = pkgs.lib.last gccVersion; - defaultClangVersion = pkgs.lib.last clangVersions; - - strToCompilerEnv = - compiler: version: - ( - if compiler == "gcc" then - let - gccPkg = pkgs."gcc${toString version}Stdenv" or null; - in - if gccPkg != null && builtins.elem version gccVersion then - gccPkg - else - throw "Invalid GCC version: ${toString version}. Must be one of: ${toString gccVersion}" - else if compiler == "clang" then - let - clangPkg = pkgs."llvmPackages_${toString version}".stdenv or null; - in - if clangPkg != null && builtins.elem version clangVersions then - clangPkg - else - throw "Invalid Clang version: ${toString version}. Must be one of: ${toString clangVersions}" - else if compiler == "apple-clang" || compiler == "none" then - pkgs.stdenvNoCC - else - throw "Invalid compiler: ${compiler}. Must be one of: gcc, clang, apple-clang, none" - ); - - # Helper function to create a shell with a specific compiler + # compilerName is the command used to print the version, or null for none. makeShell = { - compiler ? defaultCompiler, - version ? ( - if compiler == "gcc" then - defaultGccVersion - else if compiler == "clang" then - defaultClangVersion - else - null - ), + stdenv, + compilerName, }: let - compilerStdEnv = strToCompilerEnv compiler version; - - compilerName = - if compiler == "apple-clang" then - "clang" - else if compiler == "none" then - null - else - compiler; - - gccOnMacWarning = - if pkgs.stdenv.isDarwin && compiler == "gcc" then - '' - echo "WARNING: Using GCC on macOS with Conan may not work." - echo " Consider using 'nix develop .#clang' or the default shell instead." - echo "" - '' - else - ""; - compilerVersion = - if compilerName != null then + if compilerName == null then + ''echo "No compiler specified - using system compiler"'' + else '' echo "Compiler: " ${compilerName} --version - '' - else - '' - echo "No compiler specified - using system compiler" ''; - - shellAttrs = { - packages = commonPackages; - - shellHook = '' - echo "Welcome to xrpld development shell"; - ${gccOnMacWarning}${compilerVersion} - ''; - }; in - pkgs.mkShell.override { stdenv = compilerStdEnv; } shellAttrs; - - # Generate shells for each compiler version - gccShells = builtins.listToAttrs ( - map (version: { - name = "gcc${toString version}"; - value = makeShell { - compiler = "gcc"; - version = version; - }; - }) gccVersion - ); - - clangShells = builtins.listToAttrs ( - map (version: { - name = "clang${toString version}"; - value = makeShell { - compiler = "clang"; - version = version; - }; - }) clangVersions - ); - + (pkgs.mkShell.override { inherit stdenv; }) { + packages = commonPackages; + shellHook = '' + echo "Welcome to xrpld development shell"; + ${compilerVersion} + ''; + }; in -gccShells -// clangShells -// { - # Default shells - default = makeShell { }; - gcc = makeShell { compiler = "gcc"; }; - clang = makeShell { compiler = "clang"; }; +rec { + # macOS: Nix Clang. Linux: Nix GCC. + default = if pkgs.stdenv.isDarwin then clang else gcc; - # No compiler - no-compiler = makeShell { compiler = "none"; }; - apple-clang = makeShell { compiler = "apple-clang"; }; + gcc = makeShell { + stdenv = gccStdenv; + compilerName = "gcc"; + }; + + clang = makeShell { + stdenv = clangStdenv; + compilerName = "clang"; + }; + + # Nix provides no compiler; use the one from your system (e.g. Apple Clang). + no-compiler = makeShell { + stdenv = pkgs.stdenvNoCC; + compilerName = null; + }; + apple-clang = no-compiler; } diff --git a/nix/packages.nix b/nix/packages.nix index bcadfe7456..bf10ee3712 100644 --- a/nix/packages.nix +++ b/nix/packages.nix @@ -1,15 +1,33 @@ { pkgs }: let + # Compiler versions used across the dev shell and the CI environment. + gccVersion = 15; + llvmVersion = 22; + + gccPackage = pkgs."gcc${toString gccVersion}"; + llvmPackages = pkgs."llvmPackages_${toString llvmVersion}"; + + # Bound explicitly so it tracks llvmPackages above, not the `with pkgs` default. + clangTools = llvmPackages.clang-tools; + # In LLVM 22, run-clang-tidy.py moved from share/clang/ to bin/, so nixpkgs # clang-tools no longer links it. Wrap it manually. runClangTidy = pkgs.writeShellScriptBin "run-clang-tidy" '' - exec ${pkgs.python3}/bin/python3 ${pkgs.llvmPackages_22.clang-unwrapped}/bin/run-clang-tidy "$@" + exec ${pkgs.python3}/bin/python3 ${llvmPackages.clang-unwrapped}/bin/run-clang-tidy "$@" ''; in { + inherit + gccVersion + llvmVersion + gccPackage + llvmPackages + ; + commonPackages = with pkgs; [ ccache clangbuildanalyzer + clangTools cmake conan curlMinimal # needed for codecov/codecov-action @@ -23,7 +41,6 @@ in gnumake gnupg # needed for signing commits & codecov/codecov-action graphviz - llvmPackages_22.clang-tools less # needed for git diff mold nettools # provides netstat, used to debug failures in CI From c7adb215ed6b77b2f93e643a7e21aab7bbb26122 Mon Sep 17 00:00:00 2001 From: Sergey Kuznetsov Date: Wed, 8 Jul 2026 18:28:48 +0100 Subject: [PATCH 5/5] chore: Add .envrc for automatic devshell switch by direnv (#7756) --- .envrc | 1 + docs/build/nix.md | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 .envrc diff --git a/.envrc b/.envrc new file mode 100644 index 0000000000..3550a30f2d --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/docs/build/nix.md b/docs/build/nix.md index 32592e037a..d6e53a254a 100644 --- a/docs/build/nix.md +++ b/docs/build/nix.md @@ -114,7 +114,15 @@ Once inside the Nix development shell, follow the standard [build instructions]( [direnv](https://direnv.net/) or [nix-direnv](https://github.com/nix-community/nix-direnv) can automatically activate the Nix development shell when you enter the repository directory. -This is also the most robust way to use the environment from **any shell** (bash, zsh, fish, …): direnv stays in your current shell and loads the environment _after_ your shell's startup files have run, so the Nix-provided tools take precedence over anything your shell configuration adds to `$PATH`. To use it, install direnv for your shell, then add an `.envrc` containing `use flake` at the repository root and run `direnv allow`. +This is also the most robust way to use the environment from **any shell** (bash, zsh, fish, …): direnv stays in your current shell and loads the environment _after_ your shell's startup files have run, so the Nix-provided tools take precedence over anything your shell configuration adds to `$PATH`. + +The repository already ships an `.envrc` at its root that activates the Nix flake development shell, so you don't need to create one. To use it: + +1. [Install direnv](https://direnv.net/docs/installation.html) and [hook it into your shell](https://direnv.net/docs/hook.html) (bash, zsh, fish, …). Installing [nix-direnv](https://github.com/nix-community/nix-direnv) as well is recommended: it caches the shell so that activation is near-instant after the first run. +2. Run `direnv allow` once in the repository root. direnv will then load (and reload) the Nix development shell automatically whenever you enter the directory. + +> [!NOTE] +> direnv only caches the `.direnv` directory (already listed in `.gitignore`); no other repository files are affected. ## Conan and Prebuilt Packages