Compare commits

..

2 Commits

Author SHA1 Message Date
Mayukha Vadari
310bfc7b94 fix: Improve test debuggability 2026-06-24 20:44:47 -04:00
Mayukha Vadari
afc0b7ab8c add build errors 2026-06-24 20:14:13 -04:00
1605 changed files with 51085 additions and 121302 deletions

View File

@@ -1,92 +1,171 @@
---
Checks: "-*,
bugprone-*,
-bugprone-easily-swappable-parameters,
-bugprone-exception-escape,
-bugprone-implicit-widening-of-multiplication-result,
-bugprone-narrowing-conversions,
-bugprone-throwing-static-initialization,
cppcoreguidelines-*,
-cppcoreguidelines-avoid-c-arrays,
-cppcoreguidelines-avoid-const-or-ref-data-members,
-cppcoreguidelines-avoid-do-while,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-avoid-non-const-global-variables,
-cppcoreguidelines-c-copy-assignment-signature,
-cppcoreguidelines-interfaces-global-init,
-cppcoreguidelines-macro-usage,
-cppcoreguidelines-missing-std-forward,
-cppcoreguidelines-narrowing-conversions,
-cppcoreguidelines-noexcept-move-operations,
-cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-owning-memory,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-pro-bounds-avoid-unchecked-container-access,
-cppcoreguidelines-pro-bounds-constant-array-index,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
-cppcoreguidelines-pro-type-reinterpret-cast,
-cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-special-member-functions,
bugprone-argument-comment,
bugprone-assert-side-effect,
bugprone-bad-signal-to-kill-thread,
bugprone-bool-pointer-implicit-conversion,
bugprone-capturing-this-in-member-variable,
bugprone-casting-through-void,
bugprone-chained-comparison,
bugprone-compare-pointer-to-member-virtual-function,
bugprone-copy-constructor-init,
bugprone-crtp-constructor-accessibility,
bugprone-dangling-handle,
bugprone-dynamic-static-initializers,
bugprone-empty-catch,
bugprone-fold-init-type,
bugprone-forward-declaration-namespace,
bugprone-inaccurate-erase,
bugprone-inc-dec-in-conditions,
bugprone-incorrect-enable-if,
bugprone-incorrect-roundings,
bugprone-infinite-loop,
bugprone-integer-division,
bugprone-lambda-function-name,
bugprone-macro-parentheses,
bugprone-macro-repeated-side-effects,
bugprone-misleading-setter-of-reference,
bugprone-misplaced-operator-in-strlen-in-alloc,
bugprone-misplaced-pointer-arithmetic-in-alloc,
bugprone-misplaced-widening-cast,
bugprone-move-forwarding-reference,
bugprone-multi-level-implicit-pointer-conversion,
bugprone-multiple-new-in-one-expression,
bugprone-multiple-statement-macro,
bugprone-no-escape,
bugprone-non-zero-enum-to-bool-conversion,
bugprone-optional-value-conversion,
bugprone-parent-virtual-call,
bugprone-pointer-arithmetic-on-polymorphic-object,
bugprone-posix-return,
bugprone-redundant-branch-condition,
bugprone-reserved-identifier,
bugprone-return-const-ref-from-parameter,
bugprone-shared-ptr-array-mismatch,
bugprone-signal-handler,
bugprone-signed-char-misuse,
bugprone-sizeof-container,
bugprone-sizeof-expression,
bugprone-spuriously-wake-up-functions,
bugprone-standalone-empty,
bugprone-string-constructor,
bugprone-string-integer-assignment,
bugprone-string-literal-with-embedded-nul,
bugprone-stringview-nullptr,
bugprone-suspicious-enum-usage,
bugprone-suspicious-include,
bugprone-suspicious-memory-comparison,
bugprone-suspicious-memset-usage,
bugprone-suspicious-missing-comma,
bugprone-suspicious-realloc-usage,
bugprone-suspicious-semicolon,
bugprone-suspicious-string-compare,
bugprone-suspicious-stringview-data-usage,
bugprone-swapped-arguments,
bugprone-switch-missing-default-case,
bugprone-terminating-continue,
bugprone-throw-keyword-missing,
bugprone-too-small-loop-variable,
bugprone-unchecked-optional-access,
bugprone-undefined-memory-manipulation,
bugprone-undelegated-constructor,
bugprone-unhandled-exception-at-new,
bugprone-unhandled-self-assignment,
bugprone-unique-ptr-array-mismatch,
bugprone-unsafe-functions,
bugprone-unused-local-non-trivial-variable,
bugprone-unused-raii,
bugprone-unused-return-value,
bugprone-use-after-move,
bugprone-virtual-near-miss,
cppcoreguidelines-init-variables,
cppcoreguidelines-misleading-capture-default-by-value,
cppcoreguidelines-no-suspend-with-lock,
cppcoreguidelines-pro-type-member-init,
cppcoreguidelines-pro-type-static-cast-downcast,
cppcoreguidelines-rvalue-reference-param-not-moved,
cppcoreguidelines-use-default-member-init,
cppcoreguidelines-use-enum-class,
cppcoreguidelines-virtual-class-destructor,
hicpp-ignored-remove-result,
llvm-namespace-comment,
misc-*,
-misc-multiple-inheritance,
-misc-no-recursion,
-misc-non-private-member-variables-in-classes,
-misc-override-with-different-visibility,
-misc-unused-parameters,
-misc-use-anonymous-namespace,
-misc-use-internal-linkage,
modernize-*,
-modernize-avoid-c-arrays,
-modernize-avoid-c-style-cast,
-modernize-return-braced-init-list,
-modernize-use-integer-sign-comparison,
-modernize-use-trailing-return-type,
performance-*,
-performance-avoid-endl,
-performance-enum-size,
-performance-noexcept-move-constructor,
-performance-unnecessary-copy-initialization,
-performance-unnecessary-value-param,
readability-*,
-readability-avoid-const-params-in-decls,
-readability-container-data-pointer,
-readability-function-cognitive-complexity,
-readability-identifier-length,
-readability-inconsistent-declaration-parameter-name,
-readability-isolate-declaration,
-readability-magic-numbers,
-readability-named-parameter,
-readability-qualified-auto,
-readability-redundant-access-specifiers,
-readability-static-accessed-through-instance,
-readability-uppercase-literal-suffix
misc-const-correctness,
misc-definitions-in-headers,
misc-header-include-cycle,
misc-include-cleaner,
misc-misplaced-const,
misc-redundant-expression,
misc-static-assert,
misc-throw-by-value-catch-by-reference,
misc-unused-alias-decls,
misc-unused-using-decls,
modernize-concat-nested-namespaces,
modernize-deprecated-headers,
modernize-make-shared,
modernize-make-unique,
modernize-pass-by-value,
modernize-type-traits,
modernize-use-designated-initializers,
modernize-use-emplace,
modernize-use-equals-default,
modernize-use-equals-delete,
modernize-use-nodiscard,
modernize-use-override,
modernize-use-ranges,
modernize-use-scoped-lock,
modernize-use-starts-ends-with,
modernize-use-std-numbers,
modernize-use-using,
performance-faster-string-find,
performance-for-range-copy,
performance-implicit-conversion-in-loop,
performance-inefficient-vector-operation,
performance-move-const-arg,
performance-move-constructor-init,
performance-no-automatic-move,
performance-trivially-destructible,
readability-ambiguous-smartptr-reset-call,
readability-avoid-nested-conditional-operator,
readability-avoid-return-with-void-value,
readability-braces-around-statements,
readability-const-return-type,
readability-container-contains,
readability-container-size-empty,
readability-convert-member-functions-to-static,
readability-duplicate-include,
readability-else-after-return,
readability-enum-initial-value,
readability-identifier-naming,
readability-implicit-bool-conversion,
readability-make-member-function-const,
readability-math-missing-parentheses,
readability-misleading-indentation,
readability-non-const-parameter,
readability-redundant-casting,
readability-redundant-declaration,
readability-redundant-inline-specifier,
readability-redundant-member-init,
readability-redundant-string-init,
readability-reference-to-constructed-temporary,
readability-simplify-boolean-expr,
readability-static-definition-in-anonymous-namespace,
readability-suspicious-call-argument,
readability-use-std-min-max
"
# ---
# bugprone-narrowing-conversions, # This will break a lot of code but we should enable it in the future because it can eliminate a lot of bugs
# misc-override-with-different-visibility, # Will be addressed in a future PR, but for now it generates too many warnings
# bugprone-narrowing-conversions, # this will break a lot of code but we should enable it in the future because it can eliminate a lot of bugs
# readability-inconsistent-declaration-parameter-name, # In this codebase this check will break a lot of arg names
# readability-static-accessed-through-instance, # this check is probably unnecessary. It makes the code less readable
# ---
FormatStyle: file
CheckOptions:
bugprone-unsafe-functions.ReportMoreUnsafeFunctions: true
bugprone-unused-return-value.CheckedReturnTypes: ::std::error_code;::std::error_condition;::std::errc
misc-include-cleaner.IgnoreHeaders: ".*/(detail|impl)/.*;.*fwd\\.h(pp)?;time.h;stdlib.h;sqlite3.h;netinet/in\\.h;sys/resource\\.h;sys/sysinfo\\.h;linux/sysinfo\\.h;__chrono/.*;bits/.*;_abort\\.h;boost/.*;openssl/obj_mac\\.h"
misc-include-cleaner.IgnoreHeaders: ".*/(detail|impl)/.*;.*fwd\\.h(pp)?;time.h;stdlib.h;sqlite3.h;netinet/in\\.h;sys/resource\\.h;sys/sysinfo\\.h;linux/sysinfo\\.h;__chrono/.*;bits/.*;_abort\\.h;boost/uuid/uuid_hash.hpp;boost/beast/core/flat_buffer\\.hpp;boost/beast/http/field\\.hpp;boost/beast/http/dynamic_body\\.hpp;boost/beast/http/message\\.hpp;boost/beast/http/read\\.hpp;boost/beast/http/write\\.hpp;openssl/obj_mac\\.h"
readability-braces-around-statements.ShortStatementLines: 2
readability-identifier-naming.MacroDefinitionCase: UPPER_CASE
readability-identifier-naming.NamespaceCase: lower_case
readability-identifier-naming.InlineNamespaceCase: lower_case
readability-identifier-naming.ClassCase: CamelCase
readability-identifier-naming.StructCase: CamelCase
readability-identifier-naming.UnionCase: CamelCase

7
.envrc
View File

@@ -1,7 +0,0 @@
watch_file nix/*.nix
# The dev shell derivation includes all of conan/ (see nix/devshell.nix), so any
# change in there has to invalidate direnv's cached environment.
watch_dir conan
use flake

View File

@@ -11,9 +11,6 @@ endfunction()
function(create_symbolic_link target link)
endfunction()
function(xrpl_add_benchmark name)
endfunction()
macro(exclude_from_default target_)
endmacro()
@@ -51,12 +48,6 @@ endfunction()
function(add_module parent name)
endfunction()
function(verify_target_headers target headers_dir)
endfunction()
function(_verify_add_headers target dir)
endfunction()
function(setup_protocol_autogen)
endfunction()
@@ -105,6 +96,3 @@ function(verbose_find_path variable name)
${ARGN}
)
endfunction()
function(patch_nix_binary target)
endfunction()

View File

@@ -9,7 +9,7 @@ inputs:
remote_url:
description: "The URL of the Conan endpoint to use."
required: false
default: https://conan.xrplf.org/repository/conan/
default: https://conan.ripplex.io
runs:
using: composite

View File

@@ -15,7 +15,3 @@ updates:
commit-message:
prefix: "ci: [DEPENDABOT] "
target-branch: develop
groups:
github-actions:
patterns:
- "*"

View File

@@ -1,10 +1,10 @@
<!--
This PR template helps you write a good pull request description.
This PR template helps you to write a good pull request description.
Please feel free to include additional useful information even beyond what is requested below.
If your branch is on a personal fork and has a name that allows it to
run CI build/test jobs (e.g. "ci/foo"), remember to rename it BEFORE
opening the PR. This avoids redundant test runs. Renaming
opening the PR. This avoids unnecessary redundant test runs. Renaming
the branch after opening the PR will close the PR.
https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-branches-in-your-repository/renaming-a-branch
-->
@@ -15,7 +15,7 @@ https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-
Please include a summary of the changes.
This may be a direct input to the release notes.
If too broad, please consider splitting into multiple PRs.
If there is a relevant task or issue, please link it here.
If a relevant task or issue, please link it here.
-->
### Context of Change
@@ -65,5 +65,5 @@ This section may not be needed if your change includes thoroughly commented unit
<!--
## Future Tasks
For future tasks related to this PR.
For future tasks related to PR.
-->

View File

@@ -40,18 +40,18 @@ listed later.
| 04 | xrpl/protocol |
| 05 | xrpl/core xrpl/resource xrpl/server |
| 06 | xrpl/ledger xrpl/nodestore xrpl/net |
| 07 | xrpl/shamap xrpl/consensus |
| 07 | xrpl/shamap |
## xrpld Modules (Application Implementation)
| Level / Tier | Module(s) |
| ------------ | --------------------------- |
| 05 | xrpld/conditions |
| 06 | xrpld/core xrpld/peerfinder |
| 07 | xrpld/shamap xrpld/overlay |
| 08 | xrpld/app |
| 09 | xrpld/rpc |
| 10 | xrpld/perflog |
| Level / Tier | Module(s) |
| ------------ | -------------------------------- |
| 05 | xrpld/conditions xrpld/consensus |
| 06 | xrpld/core xrpld/peerfinder |
| 07 | xrpld/shamap xrpld/overlay |
| 08 | xrpld/app |
| 09 | xrpld/rpc |
| 10 | xrpld/perflog |
## Test Modules

View File

@@ -1,6 +1,15 @@
Loop: test.jtx test.toplevel
test.toplevel > test.jtx
Loop: test.jtx test.unit_test
test.unit_test ~= test.jtx
Loop: xrpld.app xrpld.overlay
xrpld.app > xrpld.overlay
Loop: xrpld.app xrpld.peerfinder
xrpld.peerfinder ~= xrpld.app
Loop: xrpld.app xrpld.rpc
xrpld.rpc > xrpld.app

View File

@@ -1,13 +1,8 @@
benchmarks.libxrpl > xrpl.basics
benchmarks.libxrpl > xrpl.config
benchmarks.libxrpl > xrpl.nodestore
libxrpl.basics > xrpl.basics
libxrpl.conditions > xrpl.basics
libxrpl.conditions > xrpl.conditions
libxrpl.config > xrpl.basics
libxrpl.config > xrpl.config
libxrpl.consensus > xrpl.basics
libxrpl.consensus > xrpl.consensus
libxrpl.core > xrpl.basics
libxrpl.core > xrpl.core
libxrpl.core > xrpl.json
@@ -27,9 +22,6 @@ libxrpl.nodestore > xrpl.config
libxrpl.nodestore > xrpl.json
libxrpl.nodestore > xrpl.nodestore
libxrpl.nodestore > xrpl.protocol
libxrpl.peerfinder > xrpl.basics
libxrpl.peerfinder > xrpl.peerfinder
libxrpl.peerfinder > xrpl.protocol
libxrpl.protocol > xrpl.basics
libxrpl.protocol > xrpl.json
libxrpl.protocol > xrpl.protocol
@@ -65,9 +57,9 @@ test.app > test.jtx
test.app > test.unit_test
test.app > xrpl.basics
test.app > xrpl.config
test.app > xrpl.consensus
test.app > xrpl.core
test.app > xrpld.app
test.app > xrpld.consensus
test.app > xrpld.core
test.app > xrpld.overlay
test.app > xrpld.rpc
@@ -80,6 +72,7 @@ test.app > xrpl.server
test.app > xrpl.shamap
test.app > xrpl.tx
test.basics > test.jtx
test.basics > test.unit_test
test.basics > xrpl.basics
test.basics > xrpl.core
test.basics > xrpld.rpc
@@ -88,9 +81,13 @@ test.basics > xrpl.protocol
test.beast > xrpl.basics
test.conditions > xrpl.basics
test.conditions > xrpl.conditions
test.consensus > test.csf
test.consensus > test.jtx
test.consensus > test.toplevel
test.consensus > test.unit_test
test.consensus > xrpl.basics
test.consensus > xrpld.app
test.consensus > xrpld.consensus
test.consensus > xrpl.ledger
test.consensus > xrpl.protocol
test.consensus > xrpl.shamap
@@ -105,9 +102,13 @@ test.core > xrpl.json
test.core > xrpl.protocol
test.core > xrpl.rdb
test.core > xrpl.server
test.csf > xrpl.basics
test.csf > xrpld.consensus
test.csf > xrpl.json
test.csf > xrpl.ledger
test.csf > xrpl.protocol
test.json > test.jtx
test.json > xrpl.json
test.jtx > test.unit_test
test.jtx > xrpl.basics
test.jtx > xrpl.config
test.jtx > xrpl.core
@@ -130,9 +131,12 @@ test.ledger > xrpl.json
test.ledger > xrpl.ledger
test.ledger > xrpl.protocol
test.nodestore > test.jtx
test.nodestore > test.unit_test
test.nodestore > xrpl.basics
test.nodestore > xrpl.config
test.nodestore > xrpld.core
test.nodestore > xrpl.nodestore
test.nodestore > xrpl.protocol
test.nodestore > xrpl.rdb
test.overlay > test.jtx
test.overlay > test.unit_test
@@ -141,19 +145,27 @@ test.overlay > xrpl.config
test.overlay > xrpld.app
test.overlay > xrpld.core
test.overlay > xrpld.overlay
test.overlay > xrpld.peerfinder
test.overlay > xrpl.json
test.overlay > xrpl.nodestore
test.overlay > xrpl.peerfinder
test.overlay > xrpl.protocol
test.overlay > xrpl.resource
test.overlay > xrpl.server
test.overlay > xrpl.shamap
test.peerfinder > test.beast
test.peerfinder > test.unit_test
test.peerfinder > xrpl.basics
test.peerfinder > xrpld.core
test.peerfinder > xrpld.peerfinder
test.peerfinder > xrpl.protocol
test.protocol > test.jtx
test.protocol > test.unit_test
test.protocol > xrpl.basics
test.protocol > xrpld.core
test.protocol > xrpl.json
test.protocol > xrpl.protocol
test.resource > test.unit_test
test.resource > xrpl.basics
test.resource > xrpl.resource
test.rpc > test.jtx
test.rpc > xrpl.basics
test.rpc > xrpl.config
@@ -177,46 +189,42 @@ test.server > xrpld.core
test.server > xrpl.json
test.server > xrpl.protocol
test.server > xrpl.server
test.shamap > test.unit_test
test.shamap > xrpl.basics
test.shamap > xrpl.config
test.shamap > xrpl.nodestore
test.shamap > xrpl.protocol
test.shamap > xrpl.shamap
test.toplevel > test.csf
test.toplevel > xrpl.json
test.unit_test > xrpl.basics
test.unit_test > xrpl.protocol
tests.libxrpl > xrpl.basics
tests.libxrpl > xrpl.config
tests.libxrpl > xrpl.consensus
tests.libxrpl > xrpl.core
tests.libxrpl > xrpl.json
tests.libxrpl > xrpl.ledger
tests.libxrpl > xrpl.net
tests.libxrpl > xrpl.nodestore
tests.libxrpl > xrpl.peerfinder
tests.libxrpl > xrpl.protocol
tests.libxrpl > xrpl.protocol_autogen
tests.libxrpl > xrpl.resource
tests.libxrpl > xrpl.server
tests.libxrpl > xrpl.shamap
tests.libxrpl > xrpl.tx
xrpl.conditions > xrpl.basics
xrpl.conditions > xrpl.protocol
xrpl.config > xrpl.basics
xrpl.consensus > xrpl.basics
xrpl.consensus > xrpl.json
xrpl.consensus > xrpl.ledger
xrpl.consensus > xrpl.protocol
xrpl.core > xrpl.basics
xrpl.core > xrpl.json
xrpl.core > xrpl.protocol
xrpl.json > xrpl.basics
xrpl.ledger > xrpl.basics
xrpl.ledger > xrpl.json
xrpl.ledger > xrpl.nodestore
xrpl.ledger > xrpl.protocol
xrpl.ledger > xrpl.shamap
xrpl.net > xrpl.basics
xrpl.nodestore > xrpl.basics
xrpl.nodestore > xrpl.config
xrpl.nodestore > xrpl.json
xrpl.nodestore > xrpl.protocol
xrpl.peerfinder > xrpl.basics
xrpl.peerfinder > xrpl.protocol
xrpl.protocol > xrpl.basics
xrpl.protocol > xrpl.json
xrpl.protocol_autogen > xrpl.json
@@ -233,6 +241,7 @@ xrpl.server > xrpl.json
xrpl.server > xrpl.protocol
xrpl.server > xrpl.rdb
xrpl.server > xrpl.resource
xrpl.server > xrpl.shamap
xrpl.shamap > xrpl.basics
xrpl.shamap > xrpl.nodestore
xrpl.shamap > xrpl.protocol
@@ -243,20 +252,23 @@ xrpl.tx > xrpl.protocol
xrpld.app > test.unit_test
xrpld.app > xrpl.basics
xrpld.app > xrpl.config
xrpld.app > xrpl.consensus
xrpld.app > xrpl.core
xrpld.app > xrpld.consensus
xrpld.app > xrpld.core
xrpld.app > xrpl.json
xrpld.app > xrpl.ledger
xrpld.app > xrpl.net
xrpld.app > xrpl.nodestore
xrpld.app > xrpl.peerfinder
xrpld.app > xrpl.protocol
xrpld.app > xrpl.rdb
xrpld.app > xrpl.resource
xrpld.app > xrpl.server
xrpld.app > xrpl.shamap
xrpld.app > xrpl.tx
xrpld.consensus > xrpl.basics
xrpld.consensus > xrpl.json
xrpld.consensus > xrpl.ledger
xrpld.consensus > xrpl.protocol
xrpld.core > xrpl.basics
xrpld.core > xrpl.config
xrpld.core > xrpl.core
@@ -265,30 +277,27 @@ xrpld.core > xrpl.protocol
xrpld.core > xrpl.rdb
xrpld.overlay > xrpl.basics
xrpld.overlay > xrpl.config
xrpld.overlay > xrpl.consensus
xrpld.overlay > xrpl.core
xrpld.overlay > xrpld.consensus
xrpld.overlay > xrpld.core
xrpld.overlay > xrpld.peerfinder
xrpld.overlay > xrpl.json
xrpld.overlay > xrpl.ledger
xrpld.overlay > xrpl.peerfinder
xrpld.overlay > xrpl.protocol
xrpld.overlay > xrpl.resource
xrpld.overlay > xrpl.server
xrpld.overlay > xrpl.shamap
xrpld.overlay > xrpl.tx
xrpld.peerfinder > xrpl.basics
xrpld.peerfinder > xrpld.app
xrpld.peerfinder > xrpl.config
xrpld.peerfinder > xrpld.core
xrpld.peerfinder > xrpl.peerfinder
xrpld.peerfinder > xrpl.protocol
xrpld.peerfinder > xrpl.rdb
xrpld.perflog > xrpl.basics
xrpld.perflog > xrpl.config
xrpld.perflog > xrpl.core
xrpld.perflog > xrpld.app
xrpld.perflog > xrpld.rpc
xrpld.perflog > xrpl.json
xrpld.perflog > xrpl.nodestore
xrpld.perflog > xrpl.protocol
xrpld.rpc > xrpl.basics
xrpld.rpc > xrpl.config
@@ -306,6 +315,5 @@ xrpld.rpc > xrpl.shamap
xrpld.rpc > xrpl.tx
xrpld.shamap > xrpl.basics
xrpld.shamap > xrpld.core
xrpld.shamap > xrpl.nodestore
xrpld.shamap > xrpl.protocol
xrpld.shamap > xrpl.shamap

View File

@@ -46,6 +46,9 @@ for DIRECTORY in "${DIRECTORIES[@]}"; do
done
done
# Special case for NuDBFactory that has ripple twice in the test suite name.
${SED_COMMAND} -i -E 's/(BEAST_DEFINE_TESTSUITE.+)ripple(.+)/\1xrpl\2/g' src/test/nodestore/NuDBFactory_test.cpp
DIRECTORY=$1
find "${DIRECTORY}" -type f -name "*.md" | while read -r FILE; do
echo "Processing file: ${FILE}"

View File

@@ -25,20 +25,24 @@ def get_cmake_args(build_type: str, extra_args: str) -> str:
return " ".join(args)
def runs_on_event(exclude_event_types: list[str], event: str | None) -> bool:
"""Whether a config should run for the current event.
'exclude_event_types' is a list of GitHub event names (e.g.
["pull_request"]) on which the config should NOT run; an empty list means
the config runs on every event. When no event is given (event is None), no
filtering is applied.
"""
if event is None:
return True
return event not in exclude_event_types
# ---------------------------------------------------------------------------
# Input types — shapes of the JSON config files
# ---------------------------------------------------------------------------
# Every config must declare 'minimal'. Minimal configs form the reduced matrix
# built for pull requests by default; the full matrix adds the rest. Packaging
# configs declare it too, but packaging is gated in the workflow, not by it.
#
# Configs may also opt into 'benchmark' to smoke-run the benchmarks. Note that
# the flag applies to every entry a config expands into, so only set it on
# configs that expand to a single combination.
@dataclasses.dataclass
class LinuxConfig:
"""One entry in linux.json's 'configs' or 'package_configs' arrays."""
@@ -46,12 +50,13 @@ class LinuxConfig:
compiler: list[str]
build_type: list[str]
arch: list[str]
minimal: bool
benchmark: bool = False # if true, smoke-run the benchmarks after testing
sanitizers: list[str] = dataclasses.field(default_factory=list)
suffix: str = ""
extra_cmake_args: str = ""
image: str = "" # only used by package_configs entries
# List of GitHub event names (e.g. "pull_request") on which this config
# should NOT run. Empty means it runs on every event.
exclude_event_types: list[str] = dataclasses.field(default_factory=list)
@dataclasses.dataclass
@@ -84,10 +89,11 @@ class PlatformConfig:
"""One entry in macos.json's or windows.json's 'configs' array."""
build_type: list[str]
minimal: bool
build_only: bool = False # if true, skip tests (e.g. macos/Windows Debug)
benchmark: bool = False # if true, smoke-run the benchmarks after testing
extra_cmake_args: str = ""
# List of GitHub event names (e.g. "pull_request") on which this config
# should NOT run. Empty means it runs on every event.
exclude_event_types: list[str] = dataclasses.field(default_factory=list)
def __post_init__(self) -> None:
if isinstance(self.build_type, str):
@@ -131,7 +137,6 @@ class MatrixEntry:
cmake_args: str
cmake_target: str
build_only: bool
benchmark: bool
build_type: str
architecture: Architecture
sanitizers: str
@@ -143,8 +148,7 @@ class MatrixEntry:
class PackagingEntry:
"""One entry in the generated packaging strategy matrix."""
xrpld_artifact_name: str
validator_keys_artifact_name: str
artifact_name: str
image: str
distro: str # e.g. "debian" or "rhel"; drives package-format-specific steps
@@ -164,18 +168,20 @@ _ARCHS: dict[str, Architecture] = {
}
def expand_linux_matrix(linux: LinuxFile, minimal: bool) -> list[MatrixEntry]:
def expand_linux_matrix(
linux: LinuxFile, event: str | None = None
) -> list[MatrixEntry]:
"""Expand a LinuxFile into a flat list of matrix entries.
Each config entry is expanded over the cross-product of its
compiler, build_type, sanitizers, and architecture lists. When 'minimal' is
true, only configs flagged as minimal are included.
compiler, build_type, sanitizers, and architecture lists. Configs that
exclude the current event are skipped.
"""
entries: list[MatrixEntry] = []
for distro, configs in linux.configs.items():
for cfg in configs:
if minimal and not cfg.minimal:
if not runs_on_event(cfg.exclude_event_types, event):
continue
# An empty sanitizers list means "one entry with no sanitizer".
effective_sanitizers = cfg.sanitizers or [""]
@@ -201,7 +207,6 @@ def expand_linux_matrix(linux: LinuxFile, minimal: bool) -> list[MatrixEntry]:
cmake_args=get_cmake_args(build_type, cfg.extra_cmake_args),
cmake_target="all",
build_only=False,
benchmark=cfg.benchmark,
build_type=build_type,
architecture=arch_info,
sanitizers=sanitizer,
@@ -219,19 +224,14 @@ def expand_linux_packaging(linux: LinuxFile) -> list[PackagingEntry]:
the nix-based build images, because deb/rpm tooling (debhelper, rpm-build)
is taken from the distro's archive rather than from nixpkgs. Each config
entry carries its own 'image'.
The artifact names must match what the build job uploads: one artifact per
binary, each named after the build config.
"""
entries = []
for distro, configs in linux.package_configs.items():
for cfg in configs:
for compiler, build_type in itertools.product(cfg.compiler, cfg.build_type):
config_name = f"{distro}-{compiler}-{build_type.lower()}-amd64"
entries.append(
PackagingEntry(
xrpld_artifact_name=f"xrpld-{config_name}",
validator_keys_artifact_name=f"validator-keys-{config_name}",
artifact_name=f"xrpld-{distro}-{compiler}-{build_type.lower()}-amd64",
image=cfg.image,
distro=distro,
)
@@ -240,17 +240,19 @@ def expand_linux_packaging(linux: LinuxFile) -> list[PackagingEntry]:
return entries
def expand_platform_matrix(pf: PlatformFile, minimal: bool) -> list[MatrixEntry]:
def expand_platform_matrix(
pf: PlatformFile, event: str | None = None
) -> list[MatrixEntry]:
"""Expand a PlatformFile (macOS or Windows) into matrix entries.
When 'minimal' is true, only configs flagged as minimal are included.
Configs that exclude the current event are skipped.
"""
platform_name, arch = pf.platform.split("/")
is_windows = platform_name == "windows"
entries: list[MatrixEntry] = []
for cfg in pf.configs:
if minimal and not cfg.minimal:
if not runs_on_event(cfg.exclude_event_types, event):
continue
for build_type in cfg.build_type:
entries.append(
@@ -259,7 +261,6 @@ def expand_platform_matrix(pf: PlatformFile, minimal: bool) -> list[MatrixEntry]
cmake_args=get_cmake_args(build_type, cfg.extra_cmake_args),
cmake_target="install" if is_windows else "all",
build_only=cfg.build_only,
benchmark=cfg.benchmark,
build_type=build_type,
architecture=Architecture(platform=pf.platform, runner=pf.runner),
sanitizers="",
@@ -291,12 +292,12 @@ if __name__ == "__main__":
action="store_true",
)
parser.add_argument(
"-m",
"--minimal",
help="Emit only the minimal matrix (the configs flagged 'minimal'), "
"used for pull requests by default. If omitted, the full matrix is "
"emitted.",
action="store_true",
"-e",
"--event",
help="The GitHub event name that triggered the workflow (e.g. 'push', "
"'pull_request'). Configs are filtered by their 'event_type'. If "
"omitted, no filtering is applied.",
default=None,
)
args = parser.parse_args()
@@ -307,15 +308,15 @@ if __name__ == "__main__":
else:
if args.config in ("linux", None):
matrix += expand_linux_matrix(
LinuxFile.load(THIS_DIR / "linux.json"), args.minimal
LinuxFile.load(THIS_DIR / "linux.json"), args.event
)
if args.config in ("macos", None):
matrix += expand_platform_matrix(
PlatformFile.load(THIS_DIR / "macos.json"), args.minimal
PlatformFile.load(THIS_DIR / "macos.json"), args.event
)
if args.config in ("windows", None):
matrix += expand_platform_matrix(
PlatformFile.load(THIS_DIR / "windows.json"), args.minimal
PlatformFile.load(THIS_DIR / "windows.json"), args.event
)
print(f"matrix={json.dumps({'include': [dataclasses.asdict(e) for e in matrix]})}")

View File

@@ -1,49 +1,31 @@
{
"image_tag": "sha-fecfc0c",
"image_tag": "sha-fe4c8ae",
"configs": {
"ubuntu": [
{
"compiler": ["gcc", "clang"],
"build_type": ["Debug", "Release"],
"arch": ["amd64", "arm64"]
},
{
"compiler": ["gcc", "clang"],
"build_type": ["Debug", "Release"],
"arch": ["amd64"],
"sanitizers": ["address", "undefinedbehavior"]
},
{
"compiler": ["gcc"],
"build_type": ["Debug"],
"arch": ["amd64"],
"minimal": true,
"suffix": "coverage",
"extra_cmake_args": "-DUNIT_TEST_REFERENCE_FEE=500 -Dcoverage=ON -Dcoverage_format=xml -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_C_FLAGS=-O0 -DCMAKE_CXX_FLAGS=-O0"
},
{
"compiler": ["clang"],
"build_type": ["Release"],
"arch": ["amd64"],
"minimal": true,
"benchmark": true
},
{
"compiler": ["gcc"],
"build_type": ["Release"],
"arch": ["amd64"],
"minimal": false
},
{
"compiler": ["gcc", "clang"],
"build_type": ["Debug", "Release"],
"arch": ["arm64"],
"minimal": false
},
{
"compiler": ["gcc", "clang"],
"build_type": ["Debug", "Release"],
"arch": ["amd64"],
"minimal": false,
"sanitizers": ["address", "undefinedbehavior"]
},
{
"compiler": ["clang"],
"build_type": ["Debug"],
"arch": ["amd64"],
"minimal": false,
"suffix": "voidstar",
"extra_cmake_args": "-Dvoidstar=ON"
},
@@ -51,7 +33,6 @@
"compiler": ["clang"],
"build_type": ["Release"],
"arch": ["amd64"],
"minimal": false,
"suffix": "reffee",
"extra_cmake_args": "-DUNIT_TEST_REFERENCE_FEE=1000"
},
@@ -59,9 +40,9 @@
"compiler": ["gcc"],
"build_type": ["Debug"],
"arch": ["amd64"],
"minimal": false,
"suffix": "unity",
"extra_cmake_args": "-Dunity=ON"
"extra_cmake_args": "-Dunity=ON",
"exclude_event_types": ["pull_request"]
}
],
@@ -69,9 +50,7 @@
{
"compiler": ["gcc"],
"build_type": ["Release"],
"arch": ["amd64"],
"minimal": false,
"extra_cmake_args": "-Dvalidator_keys=ON"
"arch": ["amd64"]
}
],
@@ -79,9 +58,7 @@
{
"compiler": ["gcc"],
"build_type": ["Release"],
"arch": ["amd64"],
"minimal": false,
"extra_cmake_args": "-Dvalidator_keys=ON"
"arch": ["amd64"]
}
]
},
@@ -91,7 +68,6 @@
"compiler": ["gcc"],
"build_type": ["Release"],
"arch": ["amd64"],
"minimal": false,
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-577d745"
}
],
@@ -101,7 +77,6 @@
"compiler": ["gcc"],
"build_type": ["Release"],
"arch": ["amd64"],
"minimal": false,
"image": "ghcr.io/xrplf/xrpld/packaging-rhel:sha-577d745"
}
]

View File

@@ -1,17 +1,16 @@
{
"platform": "macos/arm64",
"runner": ["self-hosted", "macOS", "ARM64", "macos-26-apple-clang-21"],
"runner": ["self-hosted", "macOS", "ARM64", "mac-runner-m1"],
"configs": [
{
"build_type": "Release",
"extra_cmake_args": "-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
"minimal": true
"extra_cmake_args": "-DCMAKE_POLICY_VERSION_MINIMUM=3.5"
},
{
"build_type": "Debug",
"extra_cmake_args": "-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
"build_only": true,
"minimal": false
"exclude_event_types": ["pull_request"]
}
]
}

View File

@@ -1,12 +1,12 @@
{
"platform": "windows/amd64",
"runner": ["self-hosted", "Windows", "dev-box-windows-2026"],
"runner": ["self-hosted", "Windows", "devbox"],
"configs": [
{ "build_type": "Release", "minimal": true },
{ "build_type": "Release" },
{
"build_type": "Debug",
"build_only": true,
"minimal": false
"exclude_event_types": ["pull_request"]
}
]
}

View File

@@ -1,4 +1,4 @@
name: Build `nix` Docker images
name: Build Nix Docker images
on:
push:
@@ -8,24 +8,20 @@ on:
- ".github/workflows/build-nix-images.yml"
- "flake.nix"
- "flake.lock"
- "rust-toolchain.toml"
- "nix/**"
- "!nix/docker/README.md"
- "!nix/devshell.nix"
- "bin/check-tools.sh"
- "bin/default-loader-path.sh"
- "bin/install-sanitizer-libs.sh"
pull_request:
paths:
- ".github/workflows/build-nix-images.yml"
- "flake.nix"
- "flake.lock"
- "rust-toolchain.toml"
- "nix/**"
- "!nix/docker/README.md"
- "!nix/devshell.nix"
- "bin/check-tools.sh"
- "bin/default-loader-path.sh"
- "bin/install-sanitizer-libs.sh"
workflow_dispatch:
@@ -40,7 +36,7 @@ defaults:
jobs:
build-merge:
name: Build and push `nix-${{ matrix.distro.name }}` image
name: Build and push nix-${{ matrix.distro.name }}
permissions:
contents: read
packages: write
@@ -58,7 +54,7 @@ jobs:
base_image: debian:bookworm
- name: rhel
base_image: registry.access.redhat.com/ubi9/ubi:latest
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@9e7e4e80af9e684c116b38369add8eea64451f32
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@ee03d31bcc4501d7599dc1b1ecd7a34af582ad1c
with:
image_name: xrpld/nix-${{ matrix.distro.name }}
dockerfile: nix/docker/Dockerfile

View File

@@ -1,4 +1,4 @@
name: Build `packaging` Docker images
name: Build packaging Docker images
on:
push:
@@ -26,7 +26,7 @@ defaults:
jobs:
build-merge:
name: Build and push `packaging-${{ matrix.distro.name }}` image
name: Build and push packaging-${{ matrix.distro.name }}
permissions:
contents: read
packages: write
@@ -38,7 +38,7 @@ jobs:
base_image: debian:bookworm
- name: rhel
base_image: registry.access.redhat.com/ubi9/ubi:latest
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@9e7e4e80af9e684c116b38369add8eea64451f32
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@ee03d31bcc4501d7599dc1b1ecd7a34af582ad1c
with:
image_name: xrpld/packaging-${{ matrix.distro.name }}
dockerfile: package/Dockerfile

View File

@@ -1,38 +0,0 @@
name: Build `pre-commit` Docker image
on:
push:
branches:
- develop
paths:
- ".github/workflows/build-pre-commit-image.yml"
- "bin/pre-commit/Dockerfile"
- "rust-toolchain.toml"
pull_request:
paths:
- ".github/workflows/build-pre-commit-image.yml"
- "bin/pre-commit/Dockerfile"
- "rust-toolchain.toml"
workflow_dispatch:
concurrency:
# Read `on-trigger.yml` for the rationale behind this concurrency group name.
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/develop' && github.sha || github.ref }}
cancel-in-progress: true
defaults:
run:
shell: bash
jobs:
build-merge:
name: Build and push `pre-commit` image
permissions:
contents: read
packages: write
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@9e7e4e80af9e684c116b38369add8eea64451f32
with:
image_name: xrpld/pre-commit
dockerfile: bin/pre-commit/Dockerfile
base_image: ubuntu:26.04
push: ${{ github.event_name == 'push' }}

View File

@@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Write PR body to file
env:

View File

@@ -20,4 +20,4 @@ on:
jobs:
check_title:
if: ${{ github.event.pull_request.draft != true }}
uses: XRPLF/actions/.github/workflows/check-pr-title.yml@d7c65e49225a38f6d8010eacf017bb5a98d7476c
uses: XRPLF/actions/.github/workflows/check-pr-title.yml@cba1f0891650baf1a9c88624dc2d72573be2eb81

View File

@@ -1,114 +0,0 @@
# Verifies the committed snapshots of `bin/check-tools.sh` output for each Nix
# environment (see nix/check-tools/). If the environment changes — a new image
# tag, an updated flake.lock, a different tool list — without the matching
# snapshot being regenerated and committed, this workflow fails so the drift is
# caught in review.
#
# To regenerate the snapshots, see nix/check-tools/README.md.
name: Check tools
on:
pull_request:
paths:
- ".github/workflows/check-tools.yml"
- ".github/scripts/strategy-matrix/linux.json"
- "bin/check-tools.sh"
- "nix/**"
- "flake.nix"
- "flake.lock"
- "rust-toolchain.toml"
push:
branches:
- "develop"
paths:
- ".github/workflows/check-tools.yml"
- ".github/scripts/strategy-matrix/linux.json"
- "bin/check-tools.sh"
- "nix/**"
- "flake.nix"
- "flake.lock"
- "rust-toolchain.toml"
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
defaults:
run:
shell: bash
jobs:
# The nix-nixos image tag is pinned alongside the build matrix in linux.json,
# so snapshots are checked against the exact image CI builds against.
linux-image-tag:
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.tag.outputs.tag }}
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Read nix image tag
id: tag
run: echo "tag=$(jq -r .image_tag .github/scripts/strategy-matrix/linux.json)" >>"${GITHUB_OUTPUT}"
# One job for all environments; they differ only in whether the tools come
# from the nix-nixos container (Linux) or `nix develop` (macOS).
check-tools:
needs: linux-image-tag
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-latest
snapshot: nix/check-tools/nix-ubuntu-amd64.txt
nix_develop: false
- runner: ubuntu-24.04-arm
snapshot: nix/check-tools/nix-ubuntu-arm64.txt
nix_develop: false
- runner: macos-26-apple-clang-21
snapshot: nix/check-tools/macos.txt
nix_develop: true
runs-on: ${{ matrix.runner }}
# Linux runs inside the pinned nix-nixos image; macOS runs natively and uses
# the flake's dev shell instead (see the run step below).
container: ${{ !matrix.nix_develop && format('ghcr.io/xrplf/xrpld/nix-ubuntu:{0}', needs.linux-image-tag.outputs.tag) || null }}
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@c00c22ada3bd6bcda48fcb0d62fbbab49fec8a0f
with:
enable_ccache: false
- name: Regenerate snapshot
env:
CHECK_TOOLS_SKIP_CLONE: "1"
# check-tools.sh skips some macOS tools when CI is set; the snapshots
# capture the full `nix develop` environment, so unset it here.
CI: ""
run: |
if [ "${{ matrix.nix_develop }}" = "true" ]; then
# `nix develop` prints the dev-shell greeting first; keep only the
# check-tools.sh output (from the "Detected OS:" line onward).
nix --extra-experimental-features "nix-command flakes" develop \
-c bash bin/check-tools.sh | sed -n '/^Detected OS:/,$p' >"${{ matrix.snapshot }}"
else
bash bin/check-tools.sh >"${{ matrix.snapshot }}"
fi
- name: Verify snapshot is up to date
run: |
if ! git diff --exit-code -- "${{ matrix.snapshot }}"; then
echo "::error::${{ matrix.snapshot }} is out of date. Regenerate it (see nix/check-tools/README.md) and commit the result."
exit 1
fi
- name: Upload regenerated snapshot
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: check-tools-${{ runner.os }}-${{ runner.arch }}
path: ${{ matrix.snapshot }}

View File

@@ -14,7 +14,6 @@ permissions:
jobs:
main:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'IgnoreConflicts') }}
runs-on: ubuntu-latest
steps:
- name: Check if PRs are dirty

View File

@@ -1,11 +1,7 @@
# This workflow runs workflows to check, build and test the project
# on every meaningful change on pull_request.
# However, it will not run if the PR is a draft
# unless it has the 'DraftRunCI' or 'Full CI build' label.
#
# By default a PR builds only a minimal matrix.
# The full matrix runs once the PR is labeled "Ready to merge" or "Full CI build".
# For commits to PRs that target a release branch,
# This workflow runs all workflows to check, build and test the project on
# various Linux flavors, as well as on MacOS and Windows, on every push to a
# user branch. However, it will not run if the pull request is a draft unless it
# has the 'DraftRunCI' label. For commits to PRs that target a release branch,
# it also uploads the libxrpl recipe to the Conan remote.
name: PR
@@ -19,16 +15,8 @@ on:
- reopened
- synchronize
- ready_for_review
# Trigger on label changes so toggling "Ready to merge" or "Full CI build"
# switches between the minimal and full matrix without needing a new push.
- labeled
- unlabeled
concurrency:
# A single per-ref group with cancel-in-progress means any newer run (a push
# or a label change) supersedes the in-progress one for that ref. Keeping
# exactly one authoritative run per ref ensures a fast do-nothing run can never
# mask a real build's checks.
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
@@ -37,22 +25,15 @@ defaults:
shell: bash
jobs:
# This job determines whether the rest of the workflow should run at all,
# based on the current set of labels: it runs when the PR is not a draft
# (which should also cover merge-group) or has the 'DraftRunCI' or
# 'Full CI build' label. Whether a build then happens, and whether it is the
# minimal or full matrix, is decided further below and in the strategy matrix.
# This job determines whether the rest of the workflow should run. It runs
# when the PR is not a draft (which should also cover merge-group) or
# has the 'DraftRunCI' label.
should-run:
if: >-
${{
!github.event.pull_request.draft
|| contains(github.event.pull_request.labels.*.name, 'DraftRunCI')
|| contains(github.event.pull_request.labels.*.name, 'Full CI build')
}}
if: ${{ !github.event.pull_request.draft || contains(github.event.pull_request.labels.*.name, 'DraftRunCI') }}
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Determine changed files
# This step checks whether any files have changed that should
# cause the next jobs to run. We do it this way rather than
@@ -82,7 +63,6 @@ jobs:
.github/scripts/strategy-matrix/**
.github/workflows/reusable-build-test-config.yml
.github/workflows/reusable-build-test.yml
.github/workflows/reusable-check-autogen.yml
.github/workflows/reusable-clang-tidy.yml
.github/workflows/reusable-package.yml
.github/workflows/reusable-strategy-matrix.yml
@@ -90,8 +70,6 @@ jobs:
.github/workflows/reusable-upload-recipe.yml
.clang-tidy
.codecov.yml
bin/check-tools.sh
bin/default-loader-path.sh
cfg/**
cmake/**
conan/**
@@ -112,26 +90,19 @@ jobs:
# least one of:
# * Any of the files checked in the `changes` step were modified
# * The PR is NOT a draft and is labeled "Ready to merge"
# * The PR is labeled "Full CI build" (draft or not)
# * The workflow is running from the merge queue
id: go
env:
FILES: ${{ steps.changes.outputs.any_changed }}
DRAFT: ${{ github.event.pull_request.draft }}
READY: ${{ contains(github.event.pull_request.labels.*.name, 'Ready to merge') }}
FULL: ${{ contains(github.event.pull_request.labels.*.name, 'Full CI build') }}
MERGE: ${{ github.event_name == 'merge_group' }}
run: |
echo "go=${{ (env.DRAFT != 'true' && env.READY == 'true') || env.FULL == 'true' || env.FILES == 'true' || env.MERGE == 'true' }}" >>"${GITHUB_OUTPUT}"
echo "go=${{ (env.DRAFT != 'true' && env.READY == 'true') || env.FILES == 'true' || env.MERGE == 'true' }}" >>"${GITHUB_OUTPUT}"
cat "${GITHUB_OUTPUT}"
outputs:
go: ${{ steps.go.outputs.go == 'true' }}
check-autogen:
needs: should-run
if: ${{ needs.should-run.outputs.go == 'true' }}
uses: ./.github/workflows/reusable-check-autogen.yml
check-levelization:
needs: should-run
if: ${{ needs.should-run.outputs.go == 'true' }}
@@ -150,6 +121,7 @@ jobs:
issues: write
contents: read
with:
check_only_changed: true
create_issue_on_failure: false
build-test:
@@ -170,10 +142,7 @@ jobs:
package:
needs: [should-run, build-test]
# Packaging consumes the debian/rhel release binaries, which are only built
# by the full matrix. Skip it for pull requests that ran only the minimal
# matrix (i.e. not yet labeled "Ready to merge" or "Full CI build").
if: ${{ needs.should-run.outputs.go == 'true' && (github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'Ready to merge') || contains(github.event.pull_request.labels.*.name, 'Full CI build')) }}
if: ${{ needs.should-run.outputs.go == 'true' }}
uses: ./.github/workflows/reusable-package.yml
upload-recipe:
@@ -184,8 +153,8 @@ jobs:
if: ${{ github.repository == 'XRPLF/rippled' && needs.should-run.outputs.go == 'true' && github.event_name == 'pull_request' && startsWith(github.event.pull_request.base.ref, 'release') }}
uses: ./.github/workflows/reusable-upload-recipe.yml
secrets:
remote_username: ${{ secrets.NEXUS_REMOTE_USERNAME }}
remote_password: ${{ secrets.NEXUS_REMOTE_PASSWORD }}
remote_username: ${{ secrets.CONAN_REMOTE_USERNAME }}
remote_password: ${{ secrets.CONAN_REMOTE_PASSWORD }}
notify-clio:
needs: upload-recipe
@@ -206,7 +175,6 @@ jobs:
passed:
if: failure() || cancelled()
needs:
- check-autogen
- check-levelization
- check-rename
- clang-tidy

View File

@@ -20,8 +20,8 @@ jobs:
if: ${{ github.repository == 'XRPLF/rippled' }}
uses: ./.github/workflows/reusable-upload-recipe.yml
secrets:
remote_username: ${{ secrets.NEXUS_REMOTE_USERNAME }}
remote_password: ${{ secrets.NEXUS_REMOTE_PASSWORD }}
remote_username: ${{ secrets.CONAN_REMOTE_USERNAME }}
remote_password: ${{ secrets.CONAN_REMOTE_PASSWORD }}
build-test:
if: ${{ github.repository == 'XRPLF/rippled' }}

View File

@@ -20,7 +20,6 @@ on:
- ".github/scripts/strategy-matrix/**"
- ".github/workflows/reusable-build-test-config.yml"
- ".github/workflows/reusable-build-test.yml"
- ".github/workflows/reusable-check-autogen.yml"
- ".github/workflows/reusable-clang-tidy.yml"
- ".github/workflows/reusable-package.yml"
- ".github/workflows/reusable-strategy-matrix.yml"
@@ -28,8 +27,6 @@ on:
- ".github/workflows/reusable-upload-recipe.yml"
- ".clang-tidy"
- ".codecov.yml"
- "bin/check-tools.sh"
- "bin/default-loader-path.sh"
- "cfg/**"
- "cmake/**"
- "conan/**"
@@ -68,15 +65,13 @@ defaults:
shell: bash
jobs:
check-autogen:
uses: ./.github/workflows/reusable-check-autogen.yml
clang-tidy:
uses: ./.github/workflows/reusable-clang-tidy.yml
permissions:
issues: write
contents: read
with:
check_only_changed: false
create_issue_on_failure: ${{ github.event_name == 'schedule' }}
build-test:
@@ -102,8 +97,8 @@ jobs:
if: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' && github.ref == 'refs/heads/develop' }}
uses: ./.github/workflows/reusable-upload-recipe.yml
secrets:
remote_username: ${{ secrets.NEXUS_REMOTE_USERNAME }}
remote_password: ${{ secrets.NEXUS_REMOTE_PASSWORD }}
remote_username: ${{ secrets.CONAN_REMOTE_USERNAME }}
remote_password: ${{ secrets.CONAN_REMOTE_PASSWORD }}
package:
needs: build-test

View File

@@ -14,7 +14,7 @@ on:
jobs:
# Call the workflow in the XRPLF/actions repo that runs the pre-commit hooks.
run-hooks:
uses: XRPLF/actions/.github/workflows/pre-commit.yml@3ba08d6ddf114092891d48491fc2e26c3ba15552
uses: XRPLF/actions/.github/workflows/pre-commit.yml@e06d4138c9ec8dceeb7c818645faa38087ea9e3d
with:
runs_on: ubuntu-latest
container: '{ "image": "ghcr.io/xrplf/xrpld/pre-commit:sha-f56b79f" }'
container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-41ec7c1" }'

View File

@@ -41,13 +41,13 @@ env:
jobs:
build:
runs-on: ubuntu-latest
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-fecfc0c
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-fe4c8ae
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@c00c22ada3bd6bcda48fcb0d62fbbab49fec8a0f
uses: XRPLF/actions/prepare-runner@c47daebb2f9db64ffbac71b47d68a661498d5ce8
with:
enable_ccache: false

View File

@@ -3,12 +3,6 @@ name: Build and test configuration
on:
workflow_call:
inputs:
benchmark:
description: "Whether to smoke-run the benchmarks after testing."
required: false
type: boolean
default: false
build_only:
description: 'Whether to only build or to build and test the code ("true", "false").'
required: true
@@ -88,7 +82,7 @@ jobs:
name: ${{ inputs.config_name }}
runs-on: ${{ fromJSON(inputs.runs_on) }}
container: ${{ inputs.image != '' && inputs.image || null }}
timeout-minutes: ${{ inputs.sanitizers != '' && 360 || 180 }}
timeout-minutes: ${{ inputs.sanitizers != '' && 360 || 90 }}
env:
# Use a namespace to keep the objects separate for each configuration.
CCACHE_NAMESPACE: ${{ inputs.config_name }}
@@ -106,10 +100,9 @@ jobs:
# header files are copied into separate directories by CMake, which will
# otherwise result in cache misses.
CCACHE_SLOPPINESS: include_file_ctime,include_file_mtime
# Determine if coverage, voidstar and validator-keys should be enabled.
# Determine if coverage and voidstar should be enabled.
COVERAGE_ENABLED: ${{ contains(inputs.cmake_args, '-Dcoverage=ON') }}
VOIDSTAR_ENABLED: ${{ contains(inputs.cmake_args, '-Dvoidstar=ON') }}
VALIDATOR_KEYS_ENABLED: ${{ contains(inputs.cmake_args, '-Dvalidator_keys=ON') }}
SANITIZERS_ENABLED: ${{ inputs.sanitizers != '' }}
steps:
- name: Cleanup workspace (macOS and Windows)
@@ -117,10 +110,10 @@ jobs:
uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@c00c22ada3bd6bcda48fcb0d62fbbab49fec8a0f
uses: XRPLF/actions/prepare-runner@c47daebb2f9db64ffbac71b47d68a661498d5ce8
with:
enable_ccache: ${{ inputs.ccache_enabled }}
@@ -131,7 +124,7 @@ jobs:
- name: Check tools
env:
CHECK_TOOLS_SKIP_CLONE: "1"
run: ./bin/check-tools.sh || true
run: ./bin/check-tools.sh
- name: Print build environment
uses: XRPLF/actions/print-build-env@59dec886e4afb05a1724443af08baccbc045b574
@@ -170,16 +163,16 @@ jobs:
CMAKE_ARGS: ${{ inputs.cmake_args }}
run: |
cmake \
-G '${{ runner.os == 'Windows' && 'Visual Studio 18 2026' || 'Ninja' }}' \
-G '${{ runner.os == 'Windows' && 'Visual Studio 17 2022' || 'Ninja' }}' \
-DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
${CMAKE_ARGS} \
..
# Export the sanitizer options before any instrumented binary runs. The
# build step below invokes instrumented dependency tools (protoc, grpc),
# so setting UBSAN_OPTIONS here lets the UBSan suppression list silence
# their diagnostics too, not just at test time.
# protocol code-gen and build steps below invoke instrumented dependency
# tools (protoc, grpc), so setting UBSAN_OPTIONS here lets the UBSan
# suppression list silence their diagnostics too, not just at test time.
# GITHUB_WORKSPACE (not the github.workspace context) is used so the path
# resolves correctly inside the container job.
- name: Set sanitizer options
@@ -197,6 +190,32 @@ jobs:
echo "UBSAN_OPTIONS=include=${SUPP}/runtime-ubsan-options.txt:suppressions=${SUPP}/ubsan.supp" >>${GITHUB_ENV}
echo "LSAN_OPTIONS=include=${SUPP}/runtime-lsan-options.txt:suppressions=${SUPP}/lsan.supp" >>${GITHUB_ENV}
- name: Check protocol autogen files are up-to-date
working-directory: ${{ env.BUILD_DIR }}
env:
MESSAGE: |
The generated protocol wrapper classes are out of date.
This typically happens when the macro files or generator scripts
have changed but the generated files were not regenerated.
To fix this:
1. Run: cmake --build . --target setup_code_gen
2. Run: cmake --build . --target code_gen
3. Commit and push the regenerated files
run: |
set -e
cmake --build . --target setup_code_gen
cmake --build . --target code_gen
DIFF=$(git -C .. status --porcelain -- include/xrpl/protocol_autogen src/tests/libxrpl/protocol_autogen)
if [ -n "${DIFF}" ]; then
echo "::error::Generated protocol files are out of date"
git -C .. diff -- include/xrpl/protocol_autogen src/tests/libxrpl/protocol_autogen
echo "${MESSAGE}"
exit 1
fi
- name: Build the binary
working-directory: ${{ env.BUILD_DIR }}
env:
@@ -204,13 +223,27 @@ jobs:
BUILD_TYPE: ${{ inputs.build_type }}
CMAKE_TARGET: ${{ inputs.cmake_target }}
run: |
set -o pipefail
cmake \
--build . \
--config "${BUILD_TYPE}" \
--parallel "${BUILD_NPROC}" \
--target "${CMAKE_TARGET}" \
2>&1 | tee "${GITHUB_WORKSPACE}/build.log"
2>&1 | tee build.log
# This step is needed to allow running in non-Nix environments
- name: Patch binary to use default loader and remove rpath (Linux)
if: ${{ runner.os == 'Linux' && env.SANITIZERS_ENABLED == 'false' }}
run: |
loader="$(/tmp/loader-path.sh)"
patchelf --set-interpreter "${loader}" --remove-rpath "${{ env.BUILD_DIR }}/xrpld"
# We're only running aarch64 Linux builds in Ubuntu-based images, so this is kept simple
- name: Install libatomic (Linux aarch64)
if: ${{ runner.os == 'Linux' && runner.arch == 'ARM64' }}
run: |
apt update --yes
apt install -y --no-install-recommends \
libatomic1
- name: Show ccache statistics
if: ${{ inputs.ccache_enabled }}
@@ -230,22 +263,6 @@ jobs:
retention-days: 3
if-no-files-found: error
- name: Run the validator-keys tests
if: ${{ env.VALIDATOR_KEYS_ENABLED == 'true' }}
working-directory: ${{ env.BUILD_DIR }}
run: ./validator-keys --unittest
- name: Upload the validator-keys binary
if: ${{ github.event.repository.visibility == 'public' && env.VALIDATOR_KEYS_ENABLED == 'true' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: validator-keys-${{ inputs.config_name }}
path: |
${{ env.BUILD_DIR }}/validator-keys
${{ env.BUILD_DIR }}/validator-keys-LICENSE
retention-days: 3
if-no-files-found: error
- name: Upload the test binary (Linux)
if: ${{ github.event.repository.visibility == 'public' && runner.os == 'Linux' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
@@ -263,7 +280,7 @@ jobs:
./xrpld --definitions | python3 -m json.tool >server_definitions.json
- name: Upload server definitions
if: ${{ github.event.repository.visibility == 'public' && inputs.config_name == 'ubuntu-gcc-debug-amd64-coverage' }}
if: ${{ github.event.repository.visibility == 'public' && inputs.config_name == 'debian-gcc-release-amd64' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: server-definitions
@@ -321,49 +338,31 @@ jobs:
PRELOAD=""
fi
LD_PRELOAD="$PRELOAD" ./xrpld --unittest --unittest-jobs "${BUILD_NPROC}" 2>&1 | tee "${GITHUB_WORKSPACE}/unittest.log"
LD_PRELOAD="$PRELOAD" ./xrpld --unittest --unittest-jobs "${BUILD_NPROC}" 2>&1 | tee unittest.log
# Smoke-run every benchmark module with a single repetition to confirm the
# benchmarks still build and execute. This is a correctness check, not a
# performance measurement, so there is nothing to gain from repeating it
# across configurations: it is opted into by a single config in the
# strategy matrix (see the 'benchmark' flag in the JSON files), which
# keeps it off instrumented builds (sanitizers/coverage/voidstar), where
# it would be slow and meaningless, off Debug builds, where it is much
# slower, and off Windows, where the `install` target does not build them.
- name: Run the benchmarks
if: ${{ inputs.benchmark }}
working-directory: ${{ env.BUILD_DIR }}
run: |
rc=0
while IFS= read -r bench; do
echo "::group::${bench}"
"./${bench}" --benchmark_repetitions=1 || rc=1
echo "::endgroup::"
done < <(find src/benchmarks -type f -perm -u+x -name 'xrpl.bench.*')
exit "${rc}"
- name: Show build/test failure summary
- name: Show test failure summary
if: ${{ failure() }}
env:
WORKING_DIR: ${{ runner.os == 'Windows' && format('{0}\{1}', env.BUILD_DIR, inputs.build_type) || env.BUILD_DIR }}
run: |
cd "${GITHUB_WORKSPACE}"
if [ ! -d "${WORKING_DIR}" ]; then
echo "Working directory '${WORKING_DIR}' does not exist."
exit 0
fi
cd "${WORKING_DIR}"
if [ -f unittest.log ]; then
if ! grep -E "failed" unittest.log | grep -vE "^I[0-9]|^[0-9]+> (ERR:|FTL:)"; then
echo "unittest.log present but no failure lines found."
fi
elif [ -f build.log ]; then
# GCC/Clang emit "error:" (covers "fatal error:"); MSVC emits
# "error C####:", "error LNK####:", and "fatal error LNK####:".
# -A6 prints the lines that follow each match (source line, caret,
# notes, and the "N errors generated" tally) to capture the whole
# diagnostic block.
if ! grep -E -A6 "error:|error C[0-9]{4}|error LNK[0-9]{4}|fatal error" build.log; then
echo "build.log present but no compile errors found."
fi
else
echo "unittest.log/build.log not found; something went wrong."
exit 1
echo "unittest.log not found; embedded tests may not have run."
if [ -f build.log ]; then
if ! grep -E "error:" build.log; then
echo "build.log present but no compile errors found."
fi
fi
fi
- name: Debug failure (Linux)
if: ${{ failure() && runner.os == 'Linux' && !inputs.build_only }}
@@ -387,7 +386,7 @@ jobs:
--target coverage
- name: Upload coverage report
if: ${{ github.repository_owner == 'XRPLF' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }}
if: ${{ github.repository == 'XRPLF/rippled' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }}
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
disable_search: true

View File

@@ -40,7 +40,6 @@ jobs:
fail-fast: ${{ github.event_name == 'merge_group' }}
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
with:
benchmark: ${{ matrix.benchmark }}
build_only: ${{ matrix.build_only }}
build_type: ${{ matrix.build_type }}
ccache_enabled: ${{ inputs.ccache_enabled }}

View File

@@ -1,76 +0,0 @@
# This workflow checks that the generated protocol wrapper classes are
# up-to-date with the macro files and generator scripts they are produced from,
# see more info in include/xrpl/protocol_autogen/README.md.
name: Check autogen
# This workflow can only be triggered by other workflows.
on: workflow_call
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-autogen
cancel-in-progress: true
defaults:
run:
shell: bash
env:
BUILD_DIR: build/codegen
jobs:
autogen:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.13"
# Code generation is pure Python, so the standalone project below offers
# the same targets as the main build without needing its dependencies or
# a compiler, which keeps this job down to a few seconds.
- name: Configure CMake
run: cmake -S cmake/codegen -B "${BUILD_DIR}"
- name: Install code generation dependencies
run: cmake --build "${BUILD_DIR}" --target setup_code_gen
- name: Generate code
run: cmake --build "${BUILD_DIR}" --target code_gen
- name: Check for differences
env:
MESSAGE: |
The generated protocol wrapper classes are out of date.
This typically happens when the macro files or generator scripts
have changed but the generated files were not regenerated.
Run the following from the repository root, then commit and push
the regenerated files. This needs neither the dependencies nor a
compiler. See include/xrpl/protocol_autogen/README.md for more info.
cmake -S cmake/codegen -B build/codegen
cmake --build build/codegen --target setup_code_gen
cmake --build build/codegen --target code_gen
In an already configured build directory, the 'setup_code_gen' and
'code_gen' targets do the same thing.
run: |
# Record untracked files in the index without staging their contents,
# so that classes generated for a newly added transaction or ledger
# entry type show up in the diff below rather than silently as an
# empty one.
git add --intent-to-add .
DIFF=$(git status --porcelain)
if [ -n "${DIFF}" ]; then
# Print the differences to give the contributor a hint about what to
# expect when running code generation on their own machine.
git diff
echo "${MESSAGE}"
exit 1
fi

View File

@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Check levelization
run: python .github/scripts/levelization/generate.py
- name: Check for differences

View File

@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Check definitions
run: .github/scripts/rename/definitions.sh .
- name: Check copyright notices

View File

@@ -3,6 +3,10 @@ name: Run clang-tidy on files
on:
workflow_call:
inputs:
check_only_changed:
description: "Check only changed files in PR. If false, checks all files in the repository."
type: boolean
default: false
create_issue_on_failure:
description: "Whether to create an issue if the check failed"
type: boolean
@@ -25,25 +29,26 @@ env:
jobs:
determine-files:
if: ${{ inputs.check_only_changed }}
permissions:
contents: read
uses: XRPLF/actions/.github/workflows/determine-tidy-files.yml@d041ac9f1fa9f07a4ba335eb4c1c82233fb3fef6
uses: XRPLF/actions/.github/workflows/determine-tidy-files.yml@c7045074aafe9fb92fa537aa4446f81fbfc17e8b
run-clang-tidy:
name: Run clang tidy
needs: [determine-files]
if: ${{ needs.determine-files.outputs.cpp_changed_files != '' || needs.determine-files.outputs.need_full_run == 'true' }}
if: ${{ always() && !cancelled() && (!inputs.check_only_changed || needs.determine-files.outputs.cpp_changed_files != '' || needs.determine-files.outputs.clang_tidy_config_changed == 'true') }}
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-fecfc0c"
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-fe4c8ae"
permissions:
contents: read
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@c00c22ada3bd6bcda48fcb0d62fbbab49fec8a0f
uses: XRPLF/actions/prepare-runner@c47daebb2f9db64ffbac71b47d68a661498d5ce8
with:
enable_ccache: false
@@ -79,7 +84,6 @@ jobs:
-Dtests=ON \
-Dwerr=ON \
-Dxrpld=ON \
-Dverify_headers=ON \
..
# clang-tidy needs headers generated from proto files
@@ -92,15 +96,15 @@ jobs:
id: run_clang_tidy
continue-on-error: true
env:
TARGETS: ${{ needs.determine-files.outputs.need_full_run != 'true' && needs.determine-files.outputs.cpp_changed_files || 'include src tests' }}
TARGETS: ${{ (needs.determine-files.outputs.clang_tidy_config_changed != 'true' && inputs.check_only_changed) && needs.determine-files.outputs.cpp_changed_files || 'src tests' }}
run: |
set -o pipefail
run-clang-tidy -j ${{ steps.nproc.outputs.nproc }} -p "${BUILD_DIR}" -quiet -fix -format -allow-no-checks ${TARGETS} 2>&1 | tee "${OUTPUT_FILE}"
run-clang-tidy -j ${{ steps.nproc.outputs.nproc }} -p "${BUILD_DIR}" -quiet -fix -allow-no-checks ${TARGETS} 2>&1 | tee "${OUTPUT_FILE}"
- name: Print filtered clang-tidy errors
- name: Print errors
if: ${{ steps.run_clang_tidy.outcome != 'success' }}
run: |
bin/filter-clang-tidy.py "${OUTPUT_FILE}"
sed '/error\||/!d' "${OUTPUT_FILE}"
- name: Upload clang-tidy output
if: ${{ github.event.repository.visibility == 'public' && steps.run_clang_tidy.outcome != 'success' }}
@@ -144,12 +148,12 @@ jobs:
\`\`\`
EOF
- name: Append filtered clang-tidy output to issue body
- name: Append clang-tidy output to issue body (filter for errors and warnings)
if: ${{ steps.run_clang_tidy.outcome != 'success' }}
run: |
if [ -f "${OUTPUT_FILE}" ]; then
# Filter to the unique errors with their source context.
bin/filter-clang-tidy.py "${OUTPUT_FILE}" >"${FILTERED_OUTPUT_FILE}" || true
# Extract lines containing 'error:', 'warning:', or 'note:'
grep -E '(error:|warning:|note:)' "${OUTPUT_FILE}" >"${FILTERED_OUTPUT_FILE}" || true
# If filtered output is empty, use original (might be a different error format)
if [ ! -s "${FILTERED_OUTPUT_FILE}" ]; then

View File

@@ -1,7 +1,7 @@
# Build Linux packages (DEB and RPM) from pre-built binary artifacts (xrpld and
# validator-keys). Discovers which configurations to package from linux.json
# (configs in "package_configs") and fans out one job per distro. Only
# linux/amd64 is supported; the runner is hardcoded in the job below.
# Build Linux packages (DEB and RPM) from pre-built binary artifacts.
# Discovers which configurations to package from linux.json (configs in
# "package_configs") and fans out one job per distro. Only linux/amd64 is
# supported; the runner is hardcoded in the job below.
name: Package
on:
@@ -27,10 +27,10 @@ jobs:
matrix: ${{ steps.generate.outputs.matrix }}
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.13"
@@ -39,13 +39,28 @@ jobs:
working-directory: .github/scripts/strategy-matrix
run: ./generate.py --packaging >>"${GITHUB_OUTPUT}"
generate-version:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
sparse-checkout: |
.github/actions/generate-version
src/libxrpl/protocol/BuildInfo.cpp
- name: Generate version
id: version
uses: ./.github/actions/generate-version
package:
needs: [generate-matrix]
needs: [generate-matrix, generate-version]
if: ${{ github.event.repository.visibility == 'public' }}
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
name: "${{ matrix.xrpld_artifact_name }}"
name: "${{ matrix.artifact_name }}"
permissions:
contents: read
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
@@ -54,32 +69,27 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Download pre-built xrpld binary
- name: Download pre-built binary
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ${{ matrix.xrpld_artifact_name }}
name: ${{ matrix.artifact_name }}
path: ${{ env.BUILD_DIR }}
- name: Download pre-built validator-keys binary
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ${{ matrix.validator_keys_artifact_name }}
path: ${{ env.BUILD_DIR }}
- name: Make binaries executable
run: chmod +x "${BUILD_DIR}/xrpld" "${BUILD_DIR}/validator-keys"
- name: Make binary executable
run: chmod +x "${BUILD_DIR}/xrpld"
- name: Build package
env:
PKG_VERSION: ${{ needs.generate-version.outputs.version }}
PKG_RELEASE: ${{ inputs.pkg_release }}
run: ./package/build_pkg.sh
- name: Upload package artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ matrix.xrpld_artifact_name }}-pkg
name: ${{ matrix.artifact_name }}-pkg-${{ needs.generate-version.outputs.version }}
path: |
${{ env.BUILD_DIR }}/debbuild/*.deb
${{ env.BUILD_DIR }}/debbuild/*.ddeb

View File

@@ -23,10 +23,10 @@ jobs:
matrix: ${{ steps.generate.outputs.matrix }}
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.13"
@@ -35,8 +35,5 @@ jobs:
id: generate
env:
GENERATE_CONFIG: ${{ inputs.os != '' && format('--config={0}', inputs.os) || '' }}
# Run only the minimal matrix for pull requests that are not yet
# labeled "Ready to merge" or "Full CI build". Any other event (merge
# queue, push, schedule, manual dispatch) runs the full matrix.
GENERATE_MINIMAL: ${{ (github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'Ready to merge') && !contains(github.event.pull_request.labels.*.name, 'Full CI build')) && '--minimal' || '' }}
run: ./generate.py ${GENERATE_CONFIG} ${GENERATE_MINIMAL} >>"${GITHUB_OUTPUT}"
GENERATE_EVENT: ${{ github.event_name }}
run: ./generate.py ${GENERATE_CONFIG} --event="${GENERATE_EVENT}" >>"${GITHUB_OUTPUT}"

View File

@@ -14,7 +14,7 @@ on:
description: "The URL of the Conan endpoint to use."
required: false
type: string
default: https://conan.xrplf.org/repository/conan/
default: https://conan.ripplex.io
secrets:
remote_username:
@@ -40,14 +40,10 @@ defaults:
jobs:
upload:
runs-on: ubuntu-latest
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-fecfc0c
env:
REMOTE_NAME: ${{ inputs.remote_name }}
CONAN_LOGIN_USERNAME_XRPLF: ${{ secrets.remote_username }}
CONAN_PASSWORD_XRPLF: ${{ secrets.remote_password }}
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-fe4c8ae
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Generate build version number
id: version
@@ -60,9 +56,15 @@ jobs:
remote_url: ${{ inputs.remote_url }}
- name: Log into Conan remote
run: conan remote login "${REMOTE_NAME}" "${CONAN_LOGIN_USERNAME_XRPLF}" --password "${CONAN_PASSWORD_XRPLF}"
env:
REMOTE_NAME: ${{ inputs.remote_name }}
REMOTE_USERNAME: ${{ secrets.remote_username }}
REMOTE_PASSWORD: ${{ secrets.remote_password }}
run: conan remote login "${REMOTE_NAME}" "${REMOTE_USERNAME}" --password "${REMOTE_PASSWORD}"
- name: Upload Conan recipe (version)
env:
REMOTE_NAME: ${{ inputs.remote_name }}
run: |
conan export . --version=${{ steps.version.outputs.version }}
conan upload --confirm --check --remote="${REMOTE_NAME}" xrpl/${{ steps.version.outputs.version }}
@@ -71,6 +73,8 @@ jobs:
# 'develop' branch, see on-trigger.yml.
- name: Upload Conan recipe (develop)
if: ${{ github.event_name == 'push' }}
env:
REMOTE_NAME: ${{ inputs.remote_name }}
run: |
conan export . --version=develop
conan upload --confirm --check --remote="${REMOTE_NAME}" xrpl/develop
@@ -79,6 +83,8 @@ jobs:
# one of the 'release' branches, see on-pr.yml.
- name: Upload Conan recipe (rc)
if: ${{ github.event_name == 'pull_request' }}
env:
REMOTE_NAME: ${{ inputs.remote_name }}
run: |
conan export . --version=rc
conan upload --confirm --check --remote="${REMOTE_NAME}" xrpl/rc
@@ -87,6 +93,8 @@ jobs:
# release, see on-tag.yml.
- name: Upload Conan recipe (release)
if: ${{ startsWith(github.ref, 'refs/tags/') }}
env:
REMOTE_NAME: ${{ inputs.remote_name }}
run: |
conan export . --version=release
conan upload --confirm --check --remote="${REMOTE_NAME}" xrpl/release

View File

@@ -34,7 +34,7 @@ on:
env:
CONAN_REMOTE_NAME: xrplf
CONAN_REMOTE_URL: https://conan.xrplf.org/repository/conan/
CONAN_REMOTE_URL: https://conan.ripplex.io
NPROC_SUBTRACT: 2
concurrency:
@@ -65,10 +65,10 @@ jobs:
uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@c00c22ada3bd6bcda48fcb0d62fbbab49fec8a0f
uses: XRPLF/actions/prepare-runner@c47daebb2f9db64ffbac71b47d68a661498d5ce8
with:
enable_ccache: false
@@ -108,12 +108,10 @@ jobs:
- name: Log into Conan remote
if: ${{ github.repository == 'XRPLF/rippled' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') }}
run: conan remote login "${CONAN_REMOTE_NAME}" "${{ secrets.NEXUS_REMOTE_USERNAME }}" --password "${{ secrets.NEXUS_REMOTE_PASSWORD }}"
run: conan remote login "${CONAN_REMOTE_NAME}" "${{ secrets.CONAN_REMOTE_USERNAME }}" --password "${{ secrets.CONAN_REMOTE_PASSWORD }}"
- name: Upload Conan packages
if: ${{ github.repository == 'XRPLF/rippled' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') }}
env:
FORCE_OPTION: ${{ github.event.inputs.force_upload == 'true' && '--force' || '' }}
CONAN_LOGIN_USERNAME_XRPLF: ${{ secrets.NEXUS_REMOTE_USERNAME }}
CONAN_PASSWORD_XRPLF: ${{ secrets.NEXUS_REMOTE_PASSWORD }}
run: conan upload "*" --remote="${CONAN_REMOTE_NAME}" --confirm ${FORCE_OPTION}

3
.gitignore vendored
View File

@@ -81,9 +81,6 @@ DerivedData
# Python
__pycache__
# Rust build artifacts.
target/
# Direnv's directory
/.direnv

View File

@@ -15,7 +15,6 @@ repos:
hooks:
- id: check-added-large-files
args: [--maxkb=400, --enforce-all]
- id: check-executables-have-shebangs
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-merge-conflict
@@ -28,47 +27,30 @@ repos:
entry: ./bin/pre-commit/clang_tidy_check.py
language: python
types_or: [c++, c]
# .ipp fragments are included by their owning header rather than compiled
# as standalone translation units, so they have no compile_commands.json
# entry to lint (verify_headers checks them transitively).
exclude: '^include/xrpl/protocol_autogen|\.ipp$'
# run-clang-tidy --fix may edit headers included by files it is not run on,
# so pre-commit must not split the files across parallel hook invocations.
# The script determines the staged files itself and lets run-clang-tidy
# handle parallelism internally.
pass_filenames: false
exclude: ^include/xrpl/protocol_autogen
pass_filenames: false # script determines the staged files itself
- id: fix-include-style
name: fix include style
entry: ./bin/pre-commit/fix_include_style.py
language: python
types_or: [c++, c]
exclude: ^include/xrpl/protocol_autogen/(transactions|ledger_entries)/
- id: fix-pragma-once
name: fix missing '#pragma once' declarations in header files
language: python
entry: ./bin/pre-commit/fix_pragma_once.py
files: \.(h|hpp)$
- id: check-doxygen-style
name: check Doxygen comment style
entry: ./bin/pre-commit/check_doxygen_style.py
language: python
types_or: [c++, c]
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: f4d7745e17a28aad7eed2f4874ca8d1568c11c4c # frozen: v22.1.8
rev: dd18dad857d6133e90bbe478f4f2f22ec0030269 # frozen: v22.1.5
hooks:
- id: clang-format
args: [--style=file]
types_or: [c++, c, proto]
"types_or": [c++, c, proto]
exclude: ^include/xrpl/protocol_autogen/(transactions|ledger_entries)/
- repo: https://github.com/BlankSpruce/gersemi-pre-commit
rev: e98930bdc210d3387007f9252d8c1694ea7e410f # frozen: 0.27.7
rev: faadd6a9d852369ca94f4d15b2404c967ba8cb01 # frozen: 0.27.6
hooks:
- id: gersemi
- repo: https://github.com/rbubley/mirrors-prettier
rev: 9337a74165b178ae2c766f60bee7252a0f06f3e8 # frozen: v3.9.5
rev: 515f543f5718ebfd6ce22e16708bb32c68ff96e1 # frozen: v3.8.3
hooks:
- id: prettier
args: [--end-of-line=auto]
@@ -98,21 +80,22 @@ repos:
files: \.md$
- repo: https://github.com/streetsidesoftware/cspell-cli
rev: ea11f9efc0bec520073405bc30552da887ba71bc # frozen: v10.0.1
rev: 4643f154907327ee0a2c7038f0296e0dd77d9776 # frozen: v10.0.0
hooks:
- id: cspell
name: check changed files spelling
- id: cspell # Spell check changed files
exclude: |
(?x)^(
\.cspell\.config\.yaml|
.config/cspell.config.yaml|
include/xrpl/protocol_autogen/(transactions|ledger_entries)/.*
)$
- id: cspell
- id: cspell # Spell check the commit message
name: check commit message spelling
args:
- --no-must-find-files
- --no-progress
- --no-summary
- --files
- .git/COMMIT_EDITMSG
stages: [commit-msg]
- repo: local

View File

@@ -28,9 +28,6 @@ This section contains changes targeting a future version.
### Additions
- `account_tx`: Added an optional `delegate` request object to filter delegated transactions. The object requires `delegate_filter`, which must be either `actor` for transactions owned by the requested account but signed by another account, or `authorizer` for transactions signed by the requested account on behalf of another account. The optional `counter_party` account narrows the results to a specific signer/delegate for `actor` or a specific owner/delegator for `authorizer`. Malformed `delegate`, `delegate_filter`, and `counter_party` values return standard invalid field errors, and invalid account IDs return `actMalformed`.
When paginating delegate-filtered queries, a marker from a delegate-filtered query includes a `delegate` flag and is only valid for follow-up requests that also supply `delegate` (mixing marker conventions returns `invalidParams`). Because filtering is applied after the ledger scan, a page may contain fewer results than `limit` (possibly zero) while still returning a marker, so callers must continue until no marker is present.
- `ledger_entry`, `account_objects`: The `Delegate` ledger entry now includes an optional `DestinationNode` field, which stores the index into the authorized account's owner directory. This field is present on entries created after bidirectional directory tracking was introduced and may appear in RPC responses for those entries. ([#6681](https://github.com/XRPLF/rippled/pull/6681))
- `server_definitions`: Added the following new sections to the response ([#6321](https://github.com/XRPLF/rippled/pull/6321)):
@@ -42,7 +39,6 @@ This section contains changes targeting a future version.
### Bugfixes
- `get_aggregate_price`: Duplicate entries in the `oracles` request array are now ignored. [#6586](https://github.com/XRPLF/rippled/pull/6586)
- Peer Crawler: The `port` field in `overlay.active[]` now consistently returns an integer instead of a string for outbound peers. [#6318](https://github.com/XRPLF/rippled/pull/6318)
- `ping`: The `ip` field is no longer returned as an empty string for proxied connections without a forwarded-for header. It is now omitted, consistent with the behavior for identified connections. [#6730](https://github.com/XRPLF/rippled/pull/6730)
- gRPC `GetLedgerDiff`: Fixed error message that incorrectly said "base ledger not validated" when the desired ledger was not validated. [#6730](https://github.com/XRPLF/rippled/pull/6730)
@@ -54,10 +50,6 @@ This section contains changes targeting a future version.
- `submit`: The `fail_hard` field now returns an error if the value is not a boolean. [#6529](https://github.com/XRPLF/rippled/pull/6529)
- `subscribe`: The `taker` field in the `books` array now returns `actMalformed` instead of `badIssuer` if the value is not a valid account. [#6529](https://github.com/XRPLF/rippled/pull/6529)
- Fixed a bug in `Forwarded` HTTP header parsing where the extracted IP address could be incorrect when no comma or semicolon delimiter follows the address. This could cause the server to misidentify a client's IP address when operating behind a reverse proxy. [#6529](https://github.com/XRPLF/rippled/pull/6529)
- `vault_info`: Errors now identify what the request got wrong instead of reporting every failure as the unregistered token `malformedRequest`, and the `error`, `error_code` and `error_message` fields now agree with each other. An invalid `vault_id` or `seq` returns `invalidParams`, an invalid `owner` returns `actMalformed`, and a request that mixes `vault_id` with `owner`/`seq` or supplies neither returns `invalidParams` with a message naming the accepted combinations. [#8015](https://github.com/XRPLF/rippled/pull/8015)
- `vault_info`: A well-formed all-zero `vault_id` now returns `entryNotFound` instead of being rejected as malformed, and `entryNotFound` responses now include `error_code` and `error_message`. Clients that request `ripplerpc` 3.0 or above therefore receive HTTP 400 with that error rather than HTTP 200. [#8015](https://github.com/XRPLF/rippled/pull/8015)
- `gateway_balances`: The `account` and `ident` fields now return an `invalidParams` error if the value is not a string, instead of an `internal` error. [#7655](https://github.com/XRPLF/rippled/pull/7655)
- `account_lines`: The `peer` field now returns an error if the value is not a string. [#7728](https://github.com/XRPLF/rippled/pull/7728)
## XRP Ledger server version 3.1.0

138
BUILD.md
View File

@@ -4,14 +4,34 @@
## Minimum Requirements
For the hardware needed to run a node, see
[System Requirements](https://xrpl.org/system-requirements.html).
See [System Requirements](https://xrpl.org/system-requirements.html).
For the software needed to build xrpld, see the
[environment setup guide](./docs/build/environment.md).
Building xrpld generally requires Git, Python, Conan, CMake, and a C++
compiler.
- [Python](https://www.python.org/downloads/)
- [Conan](https://conan.io/downloads.html)
- [CMake](https://cmake.org/download/)
You can verify that the required tools are installed and runnable with:
```bash
./bin/check-tools.sh
```
`xrpld` is written in the C++23 dialect. The [tested compiler versions][cpp23-support] are:
| Compiler | Version |
| ----------- | --------------- |
| GCC | 15.2 |
| Clang | 22 |
| Apple Clang | 17 |
| MSVC | 19.44[^windows] |
## Operating Systems
Please see the [environment setup guide](./docs/build/environment.md) for detailed instructions for all platforms.
### Linux
The Ubuntu Linux distribution has received the highest level of quality
@@ -22,13 +42,12 @@ Our Linux CI tooling is distro-independent and uses a Nix-based environment, so
### macOS
Many `xrpld` engineers use macOS for development.
The minimum supported version is macOS 15 (Sequoia).
CI testing is done in macOS 26 (Tahoe), but the build defaults `CMAKE_OSX_DEPLOYMENT_TARGET` to 15.
### Windows
Windows is used by some engineers for development only, and is not recommended
for production use.
Windows is used by some engineers for development only.
[^windows]: Windows is not recommended for production use.
## Steps
@@ -53,25 +72,37 @@ releases](https://github.com/XRPLF/rippled/releases).
### Set Up Conan
Once your [development environment](./docs/build/environment.md) is ready, set
Conan up for this repository:
After you have a [C++ development environment](./docs/build/environment.md) ready with Git, Python,
Conan, CMake, and a C++ compiler, you may need to set up your Conan profile.
These instructions assume a basic familiarity with Conan and CMake. If you are
unfamiliar with Conan, then please read [this crash course](./docs/build/conan.md) or the official
[Getting Started][conan-getting-started] walkthrough.
#### Profiles
We recommend that you install our Conan profiles:
```bash
./conan/init.sh
conan config install conan/profiles/ -tf $(conan config home)/profiles/
```
That installs our [`global.conf`](./conan/global.conf), our Conan
[profiles](./conan/profiles), and the `xrplf` remote that hosts some of our
dependencies. It honours `CONAN_HOME` and never deletes an existing Conan home,
so it is safe to re-run — it only overwrites the files it manages.
You can check your Conan profile by running:
> [!TIP]
> In the [Nix development shell](./docs/build/nix.md#conan-configuration) this is
> already done for you: the script runs on entry.
```bash
conan profile show
```
You can inspect the resulting profile with `conan profile show`. If it is not
suitable for your environment, create a custom profile and pass it to Conan — see
[Advanced Conan configuration](./docs/build/advanced_conan.md).
If the default profile is not suitable for your environment, you can create a custom profile and pass it to Conan.
More information on customizing Conan can be found in the [Advanced Conan configuration](./docs/build/advanced_conan.md).
#### Add xrplf remote
Run the following command to add the `xrplf` remote, which hosts some of our dependencies:
```bash
conan remote add --index 0 --force xrplf https://conan.ripplex.io
```
### Set Up Ccache
@@ -214,17 +245,7 @@ cmake --build . --target setup_code_gen # create venv and install dependencies
cmake --build . --target code_gen # regenerate code
```
The same targets are also available as a standalone project, which does not
need the dependencies to be configured first:
```
cmake -S cmake/codegen -B build/codegen
cmake --build build/codegen --target setup_code_gen
cmake --build build/codegen --target code_gen
```
The regenerated files should be committed alongside your changes. CI verifies
that they are up-to-date.
The regenerated files should be committed alongside your changes.
## Coverage report
@@ -236,14 +257,10 @@ which is only enabled when the `coverage` option is set, e.g. with
Prerequisites for the coverage report:
- [gcovr tool][gcovr] (can be installed e.g. with [pip][python-pip])
- `gcov` for GCC or `llvm-cov` for Clang, usually installed with the compiler
- `gcov` for GCC (installed with the compiler by default) or
- `llvm-cov` for Clang (installed with the compiler by default)
- `Debug` build type
> [!NOTE]
> Clang coverage is not available in the [Nix development shell](./docs/build/nix.md#building-xrpld-in-the-nix-shell):
> its `clang` shells do not ship `llvm-cov`. Use a `gcc` shell instead (`.#gcc`,
> or `.#gcc-plain` on Linux), which provides a `gcov` matching its compiler.
A coverage report is created when the following steps are completed, in order:
1. `xrpld` binary built with instrumentation data, enabled by the `coverage`
@@ -300,41 +317,21 @@ See [Sanitizers docs](./docs/build/sanitizers.md) for more details.
## Options
| Option | Default Value | Description |
| ---------------- | ------------- | ----------------------------------------------------------------------------- |
| `assert` | OFF | Force enabling assertions. |
| `coverage` | OFF | Prepare the coverage report. |
| `tests` | OFF | Build tests. |
| `unity` | OFF | Configure a unity build. |
| `verify_headers` | ON | Make the `verify-headers` target available to compile each header on its own. |
| `xrpld` | OFF | Build the xrpld application, and not just the libxrpl library. |
| `werr` | OFF | Treat compilation warnings as errors |
| `wextra` | OFF | Enable additional compilation warnings |
| Option | Default Value | Description |
| ---------- | ------------- | -------------------------------------------------------------- |
| `assert` | OFF | Force enabling assertions. |
| `coverage` | OFF | Prepare the coverage report. |
| `tests` | OFF | Build tests. |
| `unity` | OFF | Configure a unity build. |
| `xrpld` | OFF | Build the xrpld application, and not just the libxrpl library. |
| `werr` | OFF | Treat compilation warnings as errors |
| `wextra` | OFF | Enable additional compilation warnings |
[Unity builds][unity-build] may be faster for the first build (at the cost of much more
memory) since they concatenate sources into fewer translation units. Non-unity
builds may be faster for incremental builds, and can be helpful for detecting
`#include` omissions.
### Verifying headers
The regular build only compiles `.cpp` files, so a header is only ever checked
through whatever translation unit happens to include it. A header that forgets
an `#include` is not caught as long as every `.cpp` that uses it includes its
missing dependency first. The `verify_headers` option (ON by default) adds a
`verify-headers` target that compiles every header on its own, which fails if a
header is not self-contained:
```bash
cmake --build . --target verify-headers
```
The per-header objects are excluded from the `all` target, so a normal build
never compiles them; they are built only through `verify-headers`. The generated
translation units do appear in `compile_commands.json`, so clang-tidy (and
clangd and IDEs) can lint each header on its own. Pass `-Dverify_headers=OFF` to
omit them entirely.
## Troubleshooting
### Conan
@@ -360,14 +357,10 @@ After any updates or changes to dependencies, you may need to do the following:
4. [Regenerate lockfile](./docs/build/advanced_conan.md#conan-lockfile).
5. Re-run [conan install](#build-and-test).
If you are using the Nix development shell, whether prebuilt Conan binaries apply
depends on your platform — see
[Prebuilt packages](./docs/build/nix.md#prebuilt-packages).
#### ERROR: Package not resolved
If you're seeing an error like `ERROR: Package 'snappy/1.1.10' not resolved: Unable to find 'snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1756234314.246' in remotes.`,
please [set Conan up](#set-up-conan) so the `xrplf` remote is configured, or re-run `conan export` for [patched recipes](./docs/build/advanced_conan.md#patched-recipes).
please [add `xrplf` remote](#add-xrplf-remote) or re-run `conan export` for [patched recipes](./docs/build/advanced_conan.md#patched-recipes).
### `protobuf/port_def.inc` file not found
@@ -387,6 +380,7 @@ For example, if you want to build Debug:
1. For conan install, pass `--settings build_type=Debug`
2. For cmake, pass `-DCMAKE_BUILD_TYPE=Debug`
[cpp23-support]: https://en.cppreference.com/w/cpp/compiler_support/23
[conan-getting-started]: https://docs.conan.io/en/latest/getting_started.html
[unity-build]: https://en.wikipedia.org/wiki/Unity_build
[gcovr]: https://gcovr.com/en/stable/getting-started.html

View File

@@ -13,23 +13,6 @@ if(DEFINED CMAKE_MODULE_PATH)
endif()
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
# Must be set before project() because project() consumes it when configuring the compiler and SDK.
# A user-provided -DCMAKE_OSX_DEPLOYMENT_TARGET still takes precedence.
#
# CMAKE_SYSTEM_NAME can't be used before project(), so CMAKE_HOST_SYSTEM_NAME is used instead.
#
# When CMAKE_OSX_DEPLOYMENT_TARGET is bumped to >=26.0, FastFloat dependency won't be needed anymore
if(
CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin"
AND NOT DEFINED CMAKE_OSX_DEPLOYMENT_TARGET
)
set(CMAKE_OSX_DEPLOYMENT_TARGET
"15.0"
CACHE STRING
"Minimum macOS deployment version"
)
endif()
project(xrpl)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD 23)
@@ -74,8 +57,6 @@ if(target)
)
endif()
include(PatchNixBinary)
include(XrplSanity)
include(XrplVersion)
include(XrplSettings)
@@ -104,11 +85,9 @@ include(deps/Boost)
add_subdirectory(external/antithesis-sdk)
find_package(date REQUIRED)
find_package(ed25519 REQUIRED)
find_package(FastFloat REQUIRED)
find_package(gRPC REQUIRED)
find_package(LibArchive REQUIRED)
find_package(lz4 REQUIRED)
find_package(mpt-crypto REQUIRED)
find_package(nudb REQUIRED)
find_package(OpenSSL REQUIRED)
find_package(secp256k1 REQUIRED)
@@ -120,9 +99,7 @@ target_link_libraries(
xrpl_libs
INTERFACE
ed25519::ed25519
FastFloat::fast_float
lz4::lz4
mpt-crypto::mpt-crypto
OpenSSL::Crypto
OpenSSL::SSL
secp256k1::secp256k1
@@ -150,10 +127,6 @@ else()
endif()
target_link_libraries(xrpl_libs INTERFACE ${nudb})
if(benchmark)
find_package(benchmark REQUIRED)
endif()
if(coverage)
include(XrplCov)
endif()
@@ -161,16 +134,10 @@ endif()
include(XrplCore)
include(XrplProtocolAutogen)
include(XrplInstall)
include(XrplValidatorKeys)
# Must come after XrplValidatorKeys: the 'package' target depends on the
# validator-keys target existing.
include(XrplPackaging)
include(XrplValidatorKeys)
if(tests)
include(CTest)
add_subdirectory(src/tests/libxrpl)
endif()
if(benchmark)
add_subdirectory(src/benchmarks/libxrpl)
endif()

View File

@@ -1,4 +0,0 @@
# By default, anyone can review changes.
# The CI tooling team should review changes to the CI configuration.
/.github/ @XRPLF/ci-tooling

View File

@@ -83,11 +83,8 @@ If you create new source files, they must be organized as follows:
`src/libxrpl`.
- All other non-test files must go under `src/xrpld`.
- All test source files must go under `src/test`.
- All benchmark source files must go under `src/benchmarks`.
The source must be formatted according to the style guide below. The easiest
way to satisfy this is to install the [`pre-commit`](#pre-commit-hooks) hooks,
which format and lint your changes automatically on every commit.
The source must be formatted according to the style guide below.
Header includes must be [levelized](.github/scripts/levelization).
@@ -215,61 +212,13 @@ This is a non-exhaustive list of recommended style guidelines. These are
not always strictly enforced and serve as a way to keep the codebase
coherent rather than a set of _thou shalt not_ commandments.
## Pre-commit hooks
We use the [`pre-commit`](https://pre-commit.com/) framework to run the
formatting and linting tools that keep the codebase consistent. `pre-commit`
runs each tool configured in
[`.pre-commit-config.yaml`](./.pre-commit-config.yaml) in its own isolated
environment, so you don't need to install most of the individual tools
yourself. The version of each hook sourced from an external repository
(`clang-format`, `gersemi`, etc.) is pinned in that file, so running the hooks
locally uses exactly the same versions as CI. A few `local` hooks — most notably
`clang-tidy` — run tools from your own environment; see
[Installing clang-tidy](#installing-clang-tidy) for how to get those.
To get started, install `pre-commit` and enable the git hook scripts:
```bash
pip install pre-commit
pre-commit install
```
Once installed, the hooks run automatically on your staged files every time you
`git commit`. You can also run them on demand:
```bash
# Run all hooks against only the staged files
pre-commit run
# Run all hooks against every file in the repository
pre-commit run --all-files
# Run a single hook (e.g. clang-format) against all files
pre-commit run clang-format --all-files
```
The hooks configured in this repository include, among others:
- `clang-format` — C++/proto formatting (see [Formatting](#formatting))
- `clang-tidy` — C++ static analysis (see [Clang-tidy](#clang-tidy)); opt in with `TIDY=1`
- `fix-include-style`, `fix-pragma-once`, `check-doxygen-style` — C++ hygiene
- `gersemi` — CMake formatting
- `prettier`, `black`, `shfmt` — formatting for JavaScript/JSON/Markdown, Python, and shell
- `cspell` — spell checking
The same hooks run in CI on every pull request, so running them locally before
you push helps you avoid CI failures.
## Formatting
All code must conform to `clang-format`, according to the settings in
[`.clang-format`](./.clang-format), unless the result would be unreasonably
difficult to read or maintain. The `clang-format` version is pinned in
[`.pre-commit-config.yaml`](./.pre-commit-config.yaml), so the
[`pre-commit`](#pre-commit-hooks) hook always formats with the same version as
CI. To demarcate lines that should be left as-is, surround them with comments
like this:
All code must conform to `clang-format` version 22,
according to the settings in [`.clang-format`](./.clang-format),
unless the result would be unreasonably difficult to read or maintain.
To demarcate lines that should be left as-is, surround them with comments like
this:
```
// clang-format off
@@ -277,21 +226,9 @@ like this:
// clang-format on
```
The easiest way to format your changes is to let the `pre-commit` hook run
automatically on commit, or to run it manually:
```bash
pre-commit run clang-format --all-files
```
You can also format individual files in place by running `clang-format -i <file>...`
You can format individual files in place by running `clang-format -i <file>...`
from any directory within this project.
> [!NOTE]
> This uses whatever `clang-format` version is installed locally, which may
> differ from the pinned version used by `pre-commit` and CI, so the results
> can vary.
There is a Continuous Integration job that runs clang-format on pull requests. If the code doesn't comply, a patch file that corrects auto-fixable formatting issues is generated.
To download the patch file:
@@ -302,6 +239,13 @@ To download the patch file:
4. Download the zip file and extract it to your local git repository. Run `git apply [patch-file-name]`.
5. Commit and push.
You can install a pre-commit hook to automatically run `clang-format` before every commit:
```
pip3 install pre-commit
pre-commit install
```
## Clang-tidy
All code must pass `clang-tidy` checks according to the settings in [`.clang-tidy`](./.clang-tidy).
@@ -323,7 +267,7 @@ Before running clang-tidy, you must build the project to generate required files
#### Via pre-commit (recommended)
If you have already installed the [`pre-commit`](#pre-commit-hooks) hooks, you can run clang-tidy on your staged files using:
If you have already installed the pre-commit hooks (see above), you can run clang-tidy on your staged files using:
```
TIDY=1 pre-commit run clang-tidy
@@ -348,14 +292,12 @@ run-clang-tidy -p build -allow-no-checks src tests
```
This will check all source files in the `src`, `include` and `tests` directories using the compile commands from your `build` directory.
If you wish to automatically fix whatever clang-tidy finds _and_ is capable of fixing, add `-fix -format` to the above command:
If you wish to automatically fix whatever clang-tidy finds _and_ is capable of fixing, add `-fix` to the above command:
```
run-clang-tidy -p build -quiet -fix -format -allow-no-checks src tests
run-clang-tidy -p build -quiet -fix -allow-no-checks src tests
```
`-format` reformats the fixed code with [`.clang-format`](./.clang-format); without it the fixes are inserted in LLVM style and the `clang-format` hook rewrites them afterwards.
## Contracts and instrumentation
We are using [Antithesis](https://antithesis.com/) for continuous fuzzing,

View File

@@ -30,10 +30,8 @@ missing=()
checked=0
# check <name> [probe-command...]
# Runs the probe (default: "<name> --version"), capturing both stdout and
# stderr, and prints one aligned line: the status, the name, and the first
# non-blank line of the probe output (its version). Records <name> as missing
# if the command is not found or exits non-zero.
# Runs the probe (default: "<name> --version") quietly. Records <name> as
# missing if the command is not found or exits non-zero.
check() {
local name="$1"
shift
@@ -42,11 +40,10 @@ check() {
probe=("${name}" --version)
fi
echo "Checking ${name}..."
checked=$((checked + 1))
local output version
if output="$("${probe[@]}" 2>&1)"; then
version="$(printf '%s\n' "${output}" | grep -m1 '[^[:space:]]' || true)"
printf ' [ ok ] %-20s %s\n' "${name}" "${version}"
if "${probe[@]}" | head -n 1; then
printf ' [ ok ] %s\n' "${name}"
else
printf ' [MISS] %s\n' "${name}"
missing+=("${name}")
@@ -88,26 +85,21 @@ if [ "${os}" = "linux" ] || [ "${os}" = "macos" ]; then
check file
check less
check make
# net-tools netstat reports "net-tools X.Y"; macOS ships BSD netstat with no
# version flag, so fall back to a presence marker there.
check netstat sh -c 'command -v netstat >/dev/null && { netstat --version 2>&1 | grep -m1 -oE "net-tools [0-9.]+" || echo present; }'
check netstat which netstat
check ninja
check perl perl -e 'print "$^V\n"'
check perl
check pkg-config
check vim
check zip bash -c 'zip --version 2>&1 | grep -m1 -oE "Zip [0-9.]+"'
# These tools are present in our Linux CI images and in local development
# setups, but not in the macOS CI environment. So check them everywhere
# except when running in CI on macOS.
if [ "${os}" = "linux" ] || [ -z "${CI:-}" ]; then
check clang-format
check dot
check doxygen
check gcovr
check gh
check git-cliff
check git-lfs
check gpg
# pre-commit, or its alternative implementation prek
check pre-commit sh -c 'pre-commit --version || prek --version'
@@ -115,23 +107,6 @@ if [ "${os}" = "linux" ] || [ "${os}" = "macos" ]; then
fi
fi
# Rust toolchain. Part of the Nix commonPackages, so available on both Linux
# and macOS. The cargo plugins are invoked through cargo (`cargo <sub>`), which
# resolves the matching `cargo-<sub>` binary on PATH; `--version` is offline and
# does not need a Cargo project.
if [ "${os}" = "linux" ] || [ "${os}" = "macos" ]; then
echo
echo "Rust toolchain:"
check cargo
check cargo-audit cargo audit --version
check cargo-llvm-cov cargo llvm-cov --version
check cargo-nextest cargo nextest --version
check clippy clippy-driver --version
check rust-analyzer
check rustc
check rustfmt
fi
# GCC is the default compiler on Linux. macOS uses the system Apple Clang
# instead, so GCC/g++/gcov are not expected there.
if [ "${os}" = "linux" ]; then

View File

@@ -1,102 +0,0 @@
#!/usr/bin/env python3
"""
Reduce run-clang-tidy output to its unique errors.
It does two things:
1. Filters the raw output down to diagnostics and their source-context lines
(the indented " 103 | ..." / " | ^" lines clang-tidy prints),
matching the "path:line:col: error:" diagnostic shape.
2. Deduplicates. The same diagnostic in a header is reported once per
translation unit that includes it, so identical error blocks are collapsed
to their first occurrence.
An "error block" is an "error:" line together with the indented context lines
and any "note:" lines that follow it (up to the next "error:" line). Blocks are
compared as a whole, so an error stays attached to its own context, and
first-occurrence order is preserved.
The deduplicated output goes to stdout; a summary of unique error counts per
check is printed to stderr.
Usage:
bin/filter-clang-tidy.py [INPUT_FILE] # read from file, or
run-clang-tidy ... | bin/filter-clang-tidy.py # read from stdin
"""
import re
import sys
from collections import Counter
# A clang-tidy diagnostic line looks like "path:line:col: error: msg [check]".
# Matching on that shape (rather than a loose "error" substring) avoids treating
# progress lines whose paths contain "error" as diagnostics, e.g.
# [284/850][0.7s] /nix/.../clang-tidy ... src/.../error.cpp
DIAG_RE = re.compile(r":\d+:\d+: (?:error|warning|note):")
ERROR_RE = re.compile(r":\d+:\d+: error:")
CHECK_RE = re.compile(r" error: .*\[([^\],]+)")
def filter_and_dedup(lines: list[str]) -> list[str]:
"""Keep diagnostics with their context, then drop duplicate error blocks."""
blocks: list[str] = []
seen: set[str] = set()
current: list[str] = []
def flush() -> None:
if not current:
return
block = "".join(current)
if block not in seen:
seen.add(block)
blocks.append(block)
for line in lines:
# Keep only diagnostics and their indented source-context lines; drop
# progress/status output and blank lines.
if not (DIAG_RE.search(line) or line[:1] in (" ", "\t")):
continue
# An "error:" line starts a new block; its context and any following
# "note:" lines (and their context) belong to it.
if ERROR_RE.search(line):
flush()
current = []
current.append(line)
flush()
return blocks
def summarize(blocks: list[str]) -> Counter[str]:
"""Count unique errors per check name (e.g. "bugprone-branch-clone")."""
counts: Counter[str] = Counter()
for block in blocks:
# The error line is the first line of the block.
match = CHECK_RE.search(block.splitlines()[0])
if match:
counts[match.group(1)] += 1
return counts
def main() -> int:
if len(sys.argv) > 1 and sys.argv[1] != "-":
with open(sys.argv[1], encoding="utf-8") as f:
lines = f.readlines()
else:
lines = sys.stdin.readlines()
blocks = filter_and_dedup(lines)
# Blank line between blocks so distinct errors are easy to tell apart.
sys.stdout.write("\n".join(blocks))
print("\nUnique errors per check:", file=sys.stderr)
for check, count in summarize(blocks).most_common():
print(f"{count:>4} {check}", file=sys.stderr)
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,55 +0,0 @@
ARG BASE_IMAGE=ubuntu:26.04
FROM ${BASE_IMAGE}
SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"]
ENTRYPOINT ["/bin/bash"]
ARG DEBIAN_FRONTEND=noninteractive
RUN <<EOF
pkgs=()
pkgs+=(curl) # Required to install nix.
pkgs+=(doxygen) # Needed for Clio's check-doxygen-docs.sh.
pkgs+=(git) # Required for prepare-runner.
pkgs+=(libatomic1) # Required to run pre-commit provided `node`.
pkgs+=(python3) # Python 3 interpreter.
pkgs+=(python3-pip) # Package manager for Python applications.
pkgs+=(xz-utils) # Required to install nix
apt-get update
apt-get install -y --no-install-recommends "${pkgs[@]}"
apt-get clean
rm -rf /var/lib/apt/lists/*
EOF
ARG PRE_COMMIT_VERSION=4.6.0
RUN pip install --no-cache --break-system-packages \
pre-commit==${PRE_COMMIT_VERSION}
RUN sh <(curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install) --daemon --yes
# Add nix to PATH and set NIX environment variables,
# so nix is available in all shells including non-interactive shells (e.g., GitHub Actions).
ENV PATH="/nix/var/nix/profiles/default/bin:${PATH}"
ENV NIX_PROFILES="/nix/var/nix/profiles/default"
ENV NIX_SSL_CERT_FILE="/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt"
# Verify nix installation
RUN nix --version
ENV RUSTUP_HOME="/opt/rust/rustup"
ENV CARGO_HOME="/opt/rust/cargo"
ENV PATH="/opt/rust/cargo/bin:${PATH}"
WORKDIR /tmp
COPY rust-toolchain.toml /tmp/rust-toolchain.toml
RUN <<EOF
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --no-modify-path --profile minimal --default-toolchain none
rustup toolchain install
rustup show
cargo fmt --version
EOF
WORKDIR /

View File

@@ -1,440 +0,0 @@
#!/usr/bin/env python3
"""
Check C++ Doxygen comment style.
Enforces the house convention for documentation comments:
* Use ``/** ... */`` blocks, not ``///``, ``//!`` or ``/*! ... */``; a plain
``/* ... */`` that contains Doxygen commands is a doc comment missing its
second star. Trailing member-after comments use ``///<`` (not ``//!<``,
``/*!< ... */`` or ``/**< ... */`` -- the block forms get reflowed and
mis-attached by clang-format on packed enum values, the line form does not).
* ``/**`` sits alone on its line; the closing ``*/`` sits alone on its line.
* Every content line is prefixed with `` * `` (no bare-indented continuation).
* The first content line is flush (not over-indented).
* Doxygen commands use the ``@cmd`` form, not ``\\cmd``.
* Use ``@return`` / ``@throws`` rather than prose ``Returns:`` / ``Throws:``.
* A plain ``//`` comment carrying a block-level ``@command`` (``@param``,
``@return``, ``@see``, ...) is documentation and must be a ``/** ... */``
block (Doxygen ignores ``//``).
* Use canonical command spellings: ``@return`` (not ``@returns``),
``@throws`` (not ``@throw``), ``@see`` (not ``@sa``).
* Order block tags ``@tparam`` -> ``@param`` -> ``@return``. (Whether
``@param`` order matches the signature is not checked here -- too fragile to
parse; Doxygen's WARN_IF_DOC_ERROR covers name mismatches.)
* One-liners are expanded to three lines, EXCEPT bare markers ``@{`` / ``@}``
/ ``@cond [label]`` / ``@endcond`` / ``@file [name]`` which stay on one line.
Left intentionally alone (recognized, valid Doxygen that is not this style's
concern):
* ``///<`` trailing "member-after" comments (the house form).
* Divider lines made only of slashes (``//////////``).
* Plain ``/* ... */`` (non-Doxygen) comments.
Usage:
check_doxygen_style.py [FILE ...] # explicit files
check_doxygen_style.py # default: src/ and include/ trees
Exit status is non-zero if any violation is found.
"""
import argparse
import re
import sys
from collections.abc import Iterable, Iterator
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
class Category(Enum):
"""A kind of style violation: a printed ``label`` and its ``description``.
The description is the default message; a few categories whose wording
depends on the offending text (see ``Finding.detail``) override it.
"""
def __init__(self, label: str, description: str) -> None:
self.label = label
self.description = description
BACKSLASH_COMMAND = ("backslash-command", "use the @cmd form, not \\cmd")
WRONG_COMMAND = ("wrong-command", "use the canonical command spelling")
TRIPLE_SLASH = ("triple-slash", "use a /** ... */ block instead of ///")
QT_MEMBER = ("qt-member", "use ///< instead of //!<")
QT_LINE = ("qt-line", "use a /** ... */ block instead of //!")
BLOCK_MEMBER = ("block-member", "use ///< instead of /**<")
QT_BLOCK_MEMBER = ("qt-block-member", "use ///< instead of /*!<")
DOC_IN_LINE_COMMENT = (
"doc-in-line-comment",
"use a /** ... */ block for documentation, not //",
)
QT_COMMENT = ("qt-comment", "use /** instead of /*!")
SINGLE_LINE_BLOCK = (
"single-line-block",
"expand one-line /** ... */ to a multi-line block "
"(markers @{ @} @cond @endcond @file may stay)",
)
TEXT_ON_OPENER = ("text-on-opener", "move text off the /** opener line")
BARE_CONTINUATION = ("bare-continuation", 'prefix continuation lines with " * "')
OVER_INDENTED = ("over-indented", "first content line is over-indented")
OVER_INDENTED_TAG = (
"over-indented-tag",
'Doxygen tag over-indented; use a single space after "*"',
)
COMBINED_MARKER = (
"combined-marker",
"scope marker @{ / @} should be its own single-line /** @{ */ block",
)
PROSE_LABEL = ("prose-label", "use a Doxygen tag instead of a prose label")
CONTENT_ON_CLOSER = ("content-on-closer", "move content off the closing */ line")
PLAIN_BLOCK_DOC = (
"plain-block-doc",
"documentation comment must open with /** not /*",
)
TAG_ORDER = (
"tag-order",
"block tags out of order; expected @tparam, then @param, then @return",
)
@dataclass(frozen=True)
class Finding:
"""A single style violation at a 1-based line number.
``detail`` overrides the category's default description when the message
depends on the offending text (e.g. which command was misspelled).
"""
line: int
category: Category
detail: str | None = None
@property
def message(self) -> str:
return self.detail if self.detail is not None else self.category.description
DEFAULT_ROOTS = ("src", "include")
EXTS = {".h", ".hpp", ".cpp", ".ipp", ".cxx", ".cc"}
# Every Doxygen command we recognize when written with a backslash (\cmd).
_ALL_COMMANDS = (
"brief|param|tparam|return|returns|retval|note|warning|pre|post|see|sa|ref|"
"throw|throws|exception|deprecated|details|code|endcode|verbatim|endverbatim|"
"li|arg|c|internal|since|todo|attention|remark|remarks|ingroup|defgroup"
)
# Block-level tags whose over-indentation we flag inside a block body.
_BLOCK_TAGS = (
"param|tparam|returns?|retval|brief|throws?|note|warning|"
"pre|post|see|sa|details|deprecated"
)
# Tags that, appearing anywhere in a comment, mark it as documentation.
_ANY_DOC_TAGS = (
"param|tparam|returns?|retval|brief|throws?|note|warning|pre|post|see|sa"
)
# Tags that make a plain // comment a mis-styled doc comment.
_LINE_DOC_TAGS = "brief|param|tparam|returns?|retval|throws?|note|see|pre|post"
# \cmd that should be @cmd.
RE_BACKSLASH_CMD = re.compile(r"\\(" + _ALL_COMMANDS + r")\b")
# Bare markers that may legitimately stay on a single line.
RE_MARKER = re.compile(r"^@(\{|\}|cond(\s.*)?|endcond|file(\s.*)?)$")
# Prose section labels that should be Doxygen tags.
RE_PROSE_LABEL = re.compile(r"^\*\s(Returns|Throws|Exceptions):\s*$")
# An over-indented block tag: "*" followed by 2+ spaces then the tag.
RE_OVERINDENTED_TAG = re.compile(r"^\*\s{2,}@(" + _BLOCK_TAGS + r")\b")
# Any documentation tag (used to spot a doc comment hiding in a plain /* */).
RE_ANY_DOC_TAG = re.compile(r"@(" + _ANY_DOC_TAGS + r")\b")
# A documentation tag inside a // comment.
RE_LINE_DOC_TAG = re.compile(r"@(" + _LINE_DOC_TAGS + r")\b")
# Order-relevant tags, for the @tparam -> @param -> @return ordering check.
RE_ORDER_TAG = re.compile(r"^\*\s*@(param|tparam|returns?|retval)\b")
# First content line indented by 2+ spaces after the "*".
RE_FIRST_OVERINDENT = re.compile(r"^\s*\*\s{2,}\S")
# A scope marker @{ / @} sharing a comment with other text.
RE_COMBINED_MARKER = re.compile(r"^\*\s*@[{}]\s*$")
# Non-canonical command spellings -> the house spelling (bare command names).
# Used both to flag a wrong @form and to suggest the right @form for a \wrong.
CANONICAL_COMMAND = {"returns": "return", "throw": "throws", "sa": "see"}
WRONG_SPELLINGS = [
(re.compile(rf"@{wrong}\b"), f"@{right}")
for wrong, right in CANONICAL_COMMAND.items()
]
# Order block tags should appear in; a body out of this order is a violation.
EXPECTED_TAG_ORDER = ("tparam", "param", "return")
def is_doxy_open(stripped: str) -> bool:
"""True for a line-start Doxygen block opener we should normalize."""
if stripped.startswith("/*!"): # Qt-style Doxygen
return not stripped.startswith("/*!<") # member-after, leave inline
return (
stripped.startswith("/**")
and not stripped.startswith("/***")
and not stripped.startswith("/**/")
and not stripped.startswith("/**<")
)
def _flag_commands(raw_line: str, stripped: str, index: int) -> list[Finding]:
"""Flag \\cmd and misspelled @cmd on a comment line (opener, body, or closer)."""
if not stripped.startswith(("*", "//", "/*")):
return []
findings: list[Finding] = []
backslash = RE_BACKSLASH_CMD.search(raw_line)
if backslash:
command = backslash.group(1)
canonical = CANONICAL_COMMAND.get(command, command)
findings.append(
Finding(
index + 1,
Category.BACKSLASH_COMMAND,
f"use @{canonical} instead of \\{command}",
)
)
for pattern, replacement in WRONG_SPELLINGS:
wrong = pattern.search(raw_line)
if wrong:
findings.append(
Finding(
index + 1,
Category.WRONG_COMMAND,
f"use {replacement} instead of {wrong.group(0)}",
)
)
return findings
def _flag_line_comment(raw_line: str, stripped: str, index: int) -> Finding | None:
"""Return the finding for a single-line comment form (///, //!, /**<, ...), else None."""
if stripped.startswith("///") and not stripped.startswith(("////", "///<")):
return Finding(index + 1, Category.TRIPLE_SLASH)
if "//!<" in raw_line:
return Finding(index + 1, Category.QT_MEMBER)
if stripped.startswith("//!"):
return Finding(index + 1, Category.QT_LINE)
if "/**<" in raw_line:
return Finding(index + 1, Category.BLOCK_MEMBER)
if "/*!<" in raw_line:
return Finding(index + 1, Category.QT_BLOCK_MEMBER)
if stripped.startswith("//") and RE_LINE_DOC_TAG.search(stripped):
return Finding(index + 1, Category.DOC_IN_LINE_COMMENT)
return None
def _flag_single_line_block(stripped: str, line_no: int, is_qt: bool) -> list[Finding]:
"""Findings for a whole /** ... */ or /*! ... */ block on one line."""
inner = re.sub(r"^/\*[*!]", "", stripped)
inner = re.sub(r"\*/\s*$", "", inner).strip()
findings: list[Finding] = []
if is_qt:
findings.append(Finding(line_no, Category.QT_COMMENT))
if inner and not RE_MARKER.match(inner):
findings.append(Finding(line_no, Category.SINGLE_LINE_BLOCK))
return findings
def _canonical_order_tag(body: str) -> str | None:
"""The order-relevant tag (tparam/param/return) a body line opens with, if any."""
match = RE_ORDER_TAG.match(body)
if match is None:
return None
command = match.group(1)
return "return" if command in ("return", "returns", "retval") else command
def _flag_body_line(
body_line: str, line_no: int, is_first_content: bool
) -> list[Finding]:
"""Findings for one interior line of a multi-line block."""
body = body_line.strip()
findings: list[Finding] = []
if body and not body.startswith("*"):
findings.append(Finding(line_no, Category.BARE_CONTINUATION))
if body.startswith("*"):
if is_first_content and RE_FIRST_OVERINDENT.match(body_line):
findings.append(Finding(line_no, Category.OVER_INDENTED))
if RE_OVERINDENTED_TAG.match(body):
findings.append(Finding(line_no, Category.OVER_INDENTED_TAG))
if RE_COMBINED_MARKER.match(body):
findings.append(Finding(line_no, Category.COMBINED_MARKER))
label = RE_PROSE_LABEL.match(body)
if label:
suggested_tag = "@return" if label.group(1) == "Returns" else "@throws"
findings.append(
Finding(
line_no,
Category.PROSE_LABEL,
f'use {suggested_tag} instead of prose "{label.group(1)}:"',
)
)
return findings
def _flag_closer(closer_line: str, line_no: int) -> list[Finding]:
"""Findings for content sharing the closing */ line."""
before = closer_line[: closer_line.index("*/")].strip()
if before and before != "*":
return [Finding(line_no, Category.CONTENT_ON_CLOSER)]
return []
def _flag_tag_order(first_tag_line: dict[str, int]) -> list[Finding]:
"""One finding if the present block tags are not in EXPECTED_TAG_ORDER."""
tag_lines = [
first_tag_line[tag] for tag in EXPECTED_TAG_ORDER if tag in first_tag_line
]
if tag_lines != sorted(tag_lines):
return [Finding(min(tag_lines), Category.TAG_ORDER)]
return []
def _flag_doxy_block(lines: list[str], start: int) -> tuple[int, list[Finding]]:
"""Handle a /** or /*! block opening at ``start``; return (next index, findings)."""
raw_line = lines[start]
stripped = raw_line.lstrip()
open_pos = raw_line.index("/*")
is_qt = stripped.startswith("/*!")
# A whole block on one line: /** ... */.
if "*/" in raw_line[open_pos + 2 :]:
return start + 1, _flag_single_line_block(stripped, start + 1, is_qt)
# Multi-line block: opener, then scan the body to the closer.
findings: list[Finding] = []
if is_qt:
findings.append(Finding(start + 1, Category.QT_COMMENT))
if raw_line[open_pos + 3 :].strip():
findings.append(Finding(start + 1, Category.TEXT_ON_OPENER))
line_count = len(lines)
cursor = start + 1
is_first_content = True
first_tag_line: dict[str, int] = {} # canonical tag -> 1-based first line
while cursor < line_count and "*/" not in lines[cursor]:
body_line = lines[cursor]
body = body_line.strip()
findings.extend(_flag_commands(body_line, body, cursor))
tag = _canonical_order_tag(body)
if tag is not None:
first_tag_line.setdefault(tag, cursor + 1)
findings.extend(_flag_body_line(body_line, cursor + 1, is_first_content))
if body.startswith("*"):
is_first_content = False
cursor += 1
if cursor < line_count:
closer_line = lines[cursor]
findings.extend(_flag_commands(closer_line, closer_line.strip(), cursor))
findings.extend(_flag_closer(closer_line, cursor + 1))
findings.extend(_flag_tag_order(first_tag_line))
return cursor + 1, findings
def _flag_plain_block(lines: list[str], start: int) -> tuple[int, list[Finding]]:
"""Handle a line-start plain /* ... */ block; return (next index, findings).
Only flagged when it hides a documentation command (a missing second star).
"""
line_count = len(lines)
cursor = start
while cursor < line_count and "*/" not in lines[cursor]:
cursor += 1
findings: list[Finding] = []
# The opener (start) is command-checked by check_file; check the rest here.
for i in range(start + 1, min(cursor + 1, line_count)):
findings.extend(_flag_commands(lines[i], lines[i].strip(), i))
block_text = "\n".join(
lines[start : cursor + 1] if cursor < line_count else lines[start:]
)
if RE_ANY_DOC_TAG.search(block_text):
findings.append(Finding(start + 1, Category.PLAIN_BLOCK_DOC))
next_index = cursor + 1 if cursor < line_count else line_count
return next_index, findings
def check_source(text: str) -> list[Finding]:
"""Return all style violations found in the given source text."""
lines = text.split("\n")
findings: list[Finding] = []
line_count = len(lines)
index = 0
in_plain_block = False # inside a mid-line, non-Doxygen /* ... */
while index < line_count:
raw_line = lines[index]
stripped = raw_line.lstrip()
# Skip the interior of a plain block opened on an earlier line.
if in_plain_block:
in_plain_block = "*/" not in raw_line
index += 1
continue
findings.extend(_flag_commands(raw_line, stripped, index))
line_finding = _flag_line_comment(raw_line, stripped, index)
if line_finding is not None:
findings.append(line_finding)
index += 1
elif is_doxy_open(stripped):
index, block_findings = _flag_doxy_block(lines, index)
findings.extend(block_findings)
elif stripped.startswith("/*"):
index, block_findings = _flag_plain_block(lines, index)
findings.extend(block_findings)
else:
# A /* that opens mid-line without closing starts a plain block.
if "/*" in raw_line and not stripped.startswith("//"):
if "*/" not in raw_line[raw_line.index("/*") + 2 :]:
in_plain_block = True
index += 1
return findings
def check_file(path: Path) -> list[Finding]:
"""Return all style violations found in one file."""
return check_source(path.read_text(encoding="utf-8"))
def iter_files(paths: Iterable[str]) -> Iterator[Path]:
"""Yield every C++ source file among the given files and directories."""
for raw_path in paths:
path = Path(raw_path)
if path.is_dir():
for candidate in path.rglob("*"):
if candidate.is_file() and candidate.suffix in EXTS:
yield candidate
elif path.suffix in EXTS:
yield path
def main() -> int:
parser = argparse.ArgumentParser(description="Check Doxygen comment style.")
parser.add_argument(
"files", nargs="*", help="files or directories (default: src/ include/)"
)
parser.add_argument(
"-q", "--quiet", action="store_true", help="only print the summary count"
)
args = parser.parse_args()
roots = args.files or [root for root in DEFAULT_ROOTS if Path(root).is_dir()]
total = 0
for path in sorted(set(iter_files(roots)), key=str):
for finding in check_file(path):
total += 1
if not args.quiet:
print(
f"{path}:{finding.line}: {finding.category.label}: {finding.message}"
)
print(f"\n{total} doxygen-style violation(s)", file=sys.stderr)
return 1 if total else 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,46 +1,24 @@
#!/usr/bin/env python3
"""Pre-commit hook that runs clang-tidy on staged files using run-clang-tidy.
The script determines the staged files itself (see `pass_filenames: false` in
.pre-commit-config.yaml) so run-clang-tidy is run once and handles parallelism
internally: pre-commit would otherwise split the files across parallel hook
invocations that race when fixes edit a shared header.
Fixes are collected with `-export-fixes` and applied by clang-apply-replacements
in a separate step rather than with run-clang-tidy's `-fix`. The `add_module`
build isolates each module's headers behind a per-module symlink directory
(build/modules/<module>/...), so a header reachable from several translation
units is referenced through different paths that all resolve to the same source
file. clang-apply-replacements deduplicates identical replacements by their
literal path, so those paths must be canonicalised to the real source path
first; otherwise the same fix is applied once per path and corrupts the header.
"""
"""Pre-commit hook that runs clang-tidy on changed files using run-clang-tidy."""
from __future__ import annotations
import json
import os
import re
import shutil
import subprocess
import sys
import tempfile
from collections import defaultdict
from pathlib import Path
CLANG_TIDY_VERSION = 22
# Extensions run-clang-tidy can analyse: `.cpp` translation units and, thanks to
# the `verify_headers` build option, `.h`/`.hpp` headers (each has its own
# compile_commands.json entry). `.ipp` fragments have no entry and are skipped.
TIDY_EXTENSIONS = {".cpp", ".h", ".hpp"}
# A single-quoted `FilePath:` entry in an -export-fixes YAML file, allowing the
# `- ` marker that precedes it inside a `Replacements:` sequence. clang-tidy
# emits paths single-quoted and doubles any embedded quote per YAML rules.
FILEPATH_RE = re.compile(r"^(\s*(?:-\s+)?FilePath:\s*)'((?:[^']|'')*)'\s*$")
HEADER_EXTENSIONS = {".h", ".hpp", ".ipp"}
SOURCE_EXTENSIONS = {".cpp"}
INCLUDE_RE = re.compile(r"^\s*#\s*include\s*[<\"]([^>\"]+)[>\"]")
def find_tool(name: str) -> str | None:
for candidate in (f"{name}-{CLANG_TIDY_VERSION}", name):
def find_run_clang_tidy() -> str | None:
for candidate in ("run-clang-tidy-21", "run-clang-tidy"):
if path := shutil.which(candidate):
return path
return None
@@ -54,37 +32,136 @@ def find_build_dir(repo_root: Path) -> Path | None:
return None
def staged_files(repo_root: Path) -> list[Path]:
"""Return absolute paths of staged, lint-able C/C++ files.
`--diff-filter=d` excludes deletions so we never lint a removed file.
def build_include_graph(build_dir: Path, repo_root: Path) -> tuple[dict, set]:
"""
output = subprocess.check_output(
["git", "diff", "--staged", "--name-only", "--diff-filter=d", "--"]
+ [f"*{ext}" for ext in TIDY_EXTENSIONS],
Scan all files reachable from compile_commands.json and build an inverted include graph.
Returns:
inverted: header_path -> set of files that include it
source_files: set of all TU paths from compile_commands.json
"""
with open(build_dir / "compile_commands.json") as f:
db = json.load(f)
source_files = {Path(e["file"]).resolve() for e in db}
include_roots = [repo_root / "include", repo_root / "src"]
inverted: dict[Path, set[Path]] = defaultdict(set)
to_scan: set[Path] = set(source_files)
scanned: set[Path] = set()
while to_scan:
file = to_scan.pop()
if file in scanned or not file.exists():
continue
scanned.add(file)
content = file.read_text()
for line in content.splitlines():
m = INCLUDE_RE.match(line)
if not m:
continue
for root in include_roots:
candidate = (root / m.group(1)).resolve()
if candidate.exists():
inverted[candidate].add(file)
if candidate not in scanned:
to_scan.add(candidate)
break
return inverted, source_files
def find_tus_for_headers(
headers: list[Path],
inverted: dict[Path, set[Path]],
source_files: set[Path],
) -> set[Path]:
"""
For each header, pick one TU that transitively includes it.
Prefers a TU whose stem matches the header's stem, otherwise picks the first found.
"""
result: set[Path] = set()
for header in headers:
preferred: Path | None = None
visited: set[Path] = {header}
stack: list[Path] = [header]
while stack:
h = stack.pop()
for inc in inverted.get(h, ()):
if inc in source_files:
if inc.stem == header.stem:
preferred = inc
break
if preferred is None:
preferred = inc
if inc not in visited:
visited.add(inc)
stack.append(inc)
if preferred is not None and preferred.stem == header.stem:
break
if preferred is not None:
result.add(preferred)
return result
def resolve_files(
input_files: list[str], build_dir: Path, repo_root: Path
) -> list[str]:
"""
Split input into source files and headers. Source files are passed through;
headers are resolved to the TUs that transitively include them.
"""
sources: list[Path] = []
headers: list[Path] = []
for f in input_files:
p = Path(f).resolve()
if p.suffix in SOURCE_EXTENSIONS:
sources.append(p)
elif p.suffix in HEADER_EXTENSIONS:
headers.append(p)
if not headers:
return [str(p) for p in sources]
print(
f"Resolving {len(headers)} header(s) to compilation units...", file=sys.stderr
)
inverted, source_files = build_include_graph(build_dir, repo_root)
tus = find_tus_for_headers(headers, inverted, source_files)
if not tus:
print(
"Warning: no compilation units found that include the modified headers; "
"skipping clang-tidy for headers.",
file=sys.stderr,
)
return sorted({str(p) for p in (*sources, *tus)})
def staged_files(repo_root: Path) -> list[str]:
result = subprocess.run(
["git", "diff", "--staged", "--name-only", "--diff-filter=d"],
capture_output=True,
text=True,
cwd=repo_root,
)
return [repo_root / rel for rel in output.splitlines() if rel]
def canonicalize_fix_paths(fixes_dir: Path) -> None:
"""Rewrite every `FilePath` in the exported fixes to its real source path.
A header included through a module's isolation symlink is recorded under that
symlink's path; collapsing all paths to the same real file lets
clang-apply-replacements recognise the per-translation-unit duplicates and
apply each fix once.
"""
for yaml in fixes_dir.glob("*.yaml"):
lines = []
for line in yaml.read_text().splitlines():
if m := FILEPATH_RE.match(line):
path = m.group(2).replace("''", "'")
real = os.path.realpath(path).replace("'", "''")
line = f"{m.group(1)}'{real}'"
lines.append(line)
yaml.write_text("\n".join(lines) + "\n")
if result.returncode != 0:
print(
"clang-tidy check failed: 'git diff --staged' command failed.",
file=sys.stderr,
)
if result.stderr:
print(result.stderr, file=sys.stderr)
sys.exit(result.returncode or 1)
return [str(repo_root / p) for p in result.stdout.splitlines() if p]
def main():
@@ -98,25 +175,15 @@ def main():
text=True,
).strip()
)
files = staged_files(repo_root)
if not files:
return 0
run_clang_tidy = find_tool("run-clang-tidy")
clang_apply_replacements = find_tool("clang-apply-replacements")
missing = [
name
for name, path in (
("run-clang-tidy", run_clang_tidy),
("clang-apply-replacements", clang_apply_replacements),
)
if not path
]
if missing:
run_clang_tidy = find_run_clang_tidy()
if not run_clang_tidy:
print(
f"clang-tidy check failed: TIDY is enabled but {' and '.join(missing)} "
f"was not found in PATH (tried the '-{CLANG_TIDY_VERSION}' suffix too).",
"clang-tidy check failed: TIDY is enabled but neither "
"'run-clang-tidy-21' nor 'run-clang-tidy' was found in PATH.",
file=sys.stderr,
)
return 1
@@ -130,27 +197,15 @@ def main():
)
return 1
with tempfile.TemporaryDirectory() as fixes_dir:
result = subprocess.run(
[
run_clang_tidy,
"-quiet",
"-p",
build_dir,
"-export-fixes",
fixes_dir,
"-allow-no-checks",
]
+ files
)
canonicalize_fix_paths(Path(fixes_dir))
# `FormatStyle` in .clang-tidy does not reach this path,
# so ask for the repository style here.
applied = subprocess.run(
[clang_apply_replacements, "--format", "--style=file", fixes_dir]
)
tidy_files = resolve_files(files, build_dir, repo_root)
if not tidy_files:
return 0
return result.returncode or applied.returncode
result = subprocess.run(
[run_clang_tidy, "-quiet", "-p", str(build_dir), "-fix", "-allow-no-checks"]
+ tidy_files
)
return result.returncode
if __name__ == "__main__":

View File

@@ -1,34 +0,0 @@
#!/usr/bin/env python3
"""
Adds "#pragma once" to the top of header files that don't already have it.
Usage: ./bin/pre-commit/fix_pragma_once.py <file1> <file2> ...
"""
import sys
from pathlib import Path
PRAGMA_ONCE = "#pragma once\n\n"
def fix_pragma_once(path: Path) -> bool:
original = path.read_text(encoding="utf-8")
if PRAGMA_ONCE not in original:
path.write_text(PRAGMA_ONCE + original, encoding="utf-8")
return False
return True
def main() -> int:
files = [Path(f) for f in sys.argv[1:]]
success = True
for path in files:
success &= fix_pragma_once(path)
return 0 if success else 1
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,406 +0,0 @@
#!/usr/bin/env python3
"""
Tests for check_doxygen_style.py.
Run directly (no test framework needed):
./bin/pre-commit/test_check_doxygen_style.py
or under pytest:
pytest bin/pre-commit/test_check_doxygen_style.py
"""
import sys
import textwrap
from check_doxygen_style import Finding, check_source
def findings_for(text: str) -> list[Finding]:
"""Return the style violations for the given source text.
The text is dedented and its leading newline stripped, so fixtures can be
written as indented triple-quoted here-docs while keeping honest 1-based
line numbers.
"""
text = textwrap.dedent(text).lstrip("\n")
return check_source(text)
def labels_for(text: str) -> list[str]:
return [f.category.label for f in findings_for(text)]
def messages_for(text: str) -> list[str]:
return [f.message for f in findings_for(text)]
# --- well-formed input produces nothing -------------------------------------
def test_clean_block_ok() -> None:
code = """
/**
* Brief.
*
* @tparam T a type
* @param x the x
* @return the result
*/
"""
assert findings_for(code) == []
def test_blank_lines_inside_block_ok() -> None:
code = """
/**
* a
*
* b
*/
"""
assert findings_for(code) == []
def test_member_and_divider_allowed() -> None:
assert findings_for("int x; ///< ok member\n") == []
assert findings_for("//////////\n") == []
assert findings_for("//// text\n") == []
# --- line-comment forms ------------------------------------------------------
def test_triple_slash() -> None:
code = "/// doc\n"
assert labels_for(code) == ["triple-slash"]
def test_qt_line() -> None:
code = "//! doc\n"
assert labels_for(code) == ["qt-line"]
def test_qt_member() -> None:
code = "int x; //!< doc\n"
assert labels_for(code) == ["qt-member"]
def test_block_member() -> None:
code = "int x; /**< doc */\n"
assert labels_for(code) == ["block-member"]
def test_qt_block_member() -> None:
code = "int x; /*!< doc */\n"
assert labels_for(code) == ["qt-block-member"]
def test_doc_in_line_comment() -> None:
code = "// @param x\n"
assert labels_for(code) == ["doc-in-line-comment"]
# --- block forms -------------------------------------------------------------
def test_qt_comment() -> None:
code = """
/*!
* brief
*/
"""
assert labels_for(code) == ["qt-comment"]
def test_qt_comment_single_line() -> None:
# /*! ... */ on one line -> qt-comment (plus single-line-block)
code = "/*! brief */\n"
assert labels_for(code) == ["qt-comment", "single-line-block"]
def test_single_line_block() -> None:
code = "/** brief */\n"
assert labels_for(code) == ["single-line-block"]
def test_single_line_markers_allowed() -> None:
for marker in ("@{", "@}", "@cond LABEL", "@endcond", "@file foo.h"):
code = f"/** {marker} */\n"
assert findings_for(code) == [], marker
def test_text_on_opener() -> None:
code = """
/** text here
* more
*/
"""
assert labels_for(code) == ["text-on-opener"]
def test_bare_continuation() -> None:
code = """
/**
* a
bare line
*/
"""
assert labels_for(code) == ["bare-continuation"]
def test_over_indented_first_line() -> None:
code = """
/**
* over
*/
"""
assert labels_for(code) == ["over-indented"]
def test_over_indented_tag() -> None:
# a flush first line consumes "first content", isolating the tag check
code = """
/**
* brief
* @param x
*/
"""
assert labels_for(code) == ["over-indented-tag"]
def test_combined_marker() -> None:
code = """
/**
* @{
*/
"""
assert labels_for(code) == ["combined-marker"]
def test_prose_label() -> None:
for word in ("Returns", "Throws", "Exceptions"):
code = f"""
/**
* {word}:
*/
"""
assert labels_for(code) == ["prose-label"], word
def test_content_on_closer() -> None:
code = """
/**
* a
* b */
"""
assert labels_for(code) == ["content-on-closer"]
def test_plain_block_doc() -> None:
assert labels_for("/* @param x */\n") == ["plain-block-doc"]
assert findings_for("/* just an ordinary note */\n") == []
def test_tag_order() -> None:
out_of_order = """
/**
* @param x
* @tparam T
*/
"""
assert labels_for(out_of_order) == ["tag-order"]
correct = """
/**
* @tparam T
* @param x
* @return r
*/
"""
assert findings_for(correct) == []
single = """
/**
* @param x
*/
"""
assert findings_for(single) == [] # single tag: never out of order
# --- command spelling (must work on body/closer lines, not just the opener) --
def test_backslash_command_on_body_line() -> None:
code = r"""
/**
* \brief x
*/
"""
assert labels_for(code) == ["backslash-command"]
def test_backslash_command_suggests_canonical_spelling() -> None:
# a backslash + non-canonical spelling is fixed in one pass, not two:
# \sa -> @see (not @sa), \returns -> @return (not @returns)
sa = r"""
/**
* \sa other
*/
"""
assert messages_for(sa) == [r"use @see instead of \sa"]
returns = r"""
/**
* \returns x
*/
"""
assert messages_for(returns) == [r"use @return instead of \returns"]
def test_wrong_command_on_body_line() -> None:
code = """
/**
* @returns x
*/
"""
assert labels_for(code) == ["wrong-command"]
def test_body_line_commands_regression() -> None:
# regression: these live on body lines of a multi-line block
code = r"""
/**
* @returns bad
* @throw ex
* @sa other
* \param y
*/
"""
assert labels_for(code) == [
"wrong-command",
"wrong-command",
"wrong-command",
"backslash-command",
]
def test_command_on_closer_line() -> None:
code = """
/**
* a
* @sa b */
"""
assert labels_for(code) == ["wrong-command", "content-on-closer"]
def test_no_double_count_across_opener_body_closer() -> None:
code = """
/** @returns opener
* @throw body
* @sa closer */
"""
assert labels_for(code).count("wrong-command") == 3
def test_code_with_word_allowed() -> None:
# @code{.cpp} is valid Doxygen and must not be flagged
code = """
/**
* @code{.cpp}
* int x;
* @endcode
*/
"""
assert findings_for(code) == []
# --- rendered message text ---------------------------------------------------
def test_message_uses_category_description() -> None:
# a static category renders its default description
code = "/// doc\n"
assert messages_for(code) == ["use a /** ... */ block instead of ///"]
def test_message_detail_overrides() -> None:
# dynamic categories render the offending text via Finding.detail
backslash = r"""
/**
* \param y
*/
"""
assert messages_for(backslash) == [r"use @param instead of \param"]
wrong = """
/**
* @returns x
*/
"""
assert messages_for(wrong) == ["use @return instead of @returns"]
prose = """
/**
* Throws:
*/
"""
assert messages_for(prose) == ['use @throws instead of prose "Throws:"']
# --- robustness --------------------------------------------------------------
def test_empty_file_no_crash() -> None:
assert findings_for("") == []
def test_mid_line_plain_block_skipped() -> None:
# a /* opened mid-line (after code) and spanning lines is skipped, so its
# comment-like contents are not analyzed
code = """
int x = 0; /* note: @returns is not a real tag here
* @param also not real
*/
int y = 0;
"""
assert findings_for(code) == []
def test_unclosed_block_scanned_to_eof() -> None:
# an unterminated /** block is still scanned to EOF (no crash, body checked)
code = """
/**
* @returns x
"""
assert labels_for(code) == ["wrong-command"]
def test_banner_and_empty_comment_not_flagged() -> None:
code = """
/***
* banner
***/
"""
assert findings_for(code) == []
assert findings_for("/**/\n") == []
def main() -> int:
tests = sorted(
(name, fn)
for name, fn in globals().items()
if name.startswith("test_") and callable(fn)
)
failed = 0
for name, fn in tests:
try:
fn()
print(f"PASS {name}")
except AssertionError as exc:
failed += 1
print(f"FAIL {name}: {exc!r}")
print(f"\n{len(tests) - failed}/{len(tests)} passed")
return 1 if failed else 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -488,17 +488,6 @@
# Must be a number between 100 and 1000, defaults to 250
#
#
# [max_subscriptions_per_connection]
#
# Maximum number of account, real-time account, and account-history
# subscriptions a single client connection may hold at once. Bounds the
# per-connection state torn down when the connection disconnects. Book
# subscriptions are tracked separately and are not counted here.
#
# Defaults to 100000 if not set; large enough for legitimate power users
# such as block explorers.
#
#
# [overlay]
#
# Controls settings related to the peer to peer overlay.
@@ -549,45 +538,6 @@
# only be used for local testing and debugging. Do not disable
# on mainnet.
#
# max_untrusted_count = <number>
#
# The number of manifests the server keeps for validators it does not
# list, and the number it sends and processes in a single peer protocol
# message. Once the server holds this many, a manifest for a new
# unlisted validator is rejected, so peer gossip cannot grow the cache
# without end.
#
# This option can take any value between 50 and 1000, inclusive. If
# the option is not present the server uses its built-in value.
#
# The current default (which is subject to change) is 300.
#
# max_trusted_count = <number>
#
# The number of manifests for listed validators to allow for when
# sizing peer protocol messages. Manifests for listed validators are
# never dropped, whether sending or receiving, because doing so would
# delay a validator key change reaching this server. Set this above the
# number of validators the server lists.
#
# Together the two counts above set the largest manifest message the
# server accepts: bigger messages are discarded without reading them,
# and without penalising the sender. Raising either means the server
# accepts and sends bigger messages than a peer using the defaults, and
# those peers will discard what this server sends. Lowering either below
# what peers send makes this server discard their manifest messages,
# which it does without recording anything.
#
# This option can take any value between 50 and 1000, inclusive. If
# the option is not present the server uses its built-in value.
#
# The current default (which is subject to change) is 300.
#
# NOTE: These two options (max_untrusted_count and max_trusted_count)
# are transitional. They exist to bound manifest-message size and cache
# growth during the network upgrade. They may be removed in a future
# release once the fleet has upgraded, and should not be relied upon as
# stable configuration.
#
# [transaction_queue] EXPERIMENTAL
#

View File

@@ -29,27 +29,6 @@ if(CMAKE_GENERATOR STREQUAL "Xcode")
set(is_xcode TRUE)
endif()
# --------------------------------------------------------------------
# Nix toolchain detection
# --------------------------------------------------------------------
# True when the C++ compiler resolves into the Nix store. CMAKE_CXX_COMPILER may
# be referenced through a symlink outside the store (a Nix profile, a /usr/bin
# alternative, ...), so resolve the real path before matching.
set(is_nix_compiler FALSE)
get_filename_component(_cxx_real "${CMAKE_CXX_COMPILER}" REALPATH)
if(_cxx_real MATCHES "^/nix/store/")
set(is_nix_compiler TRUE)
endif()
unset(_cxx_real)
# True inside the Nix CI Docker image, identified by the /nix/ci-env tree it
# ships (see nix/docker/Dockerfile). The dev shell and bare systems don't have
# it, so it distinguishes the CI image from other Nix-compiler environments.
set(is_ci_image FALSE)
if(EXISTS "/nix/ci-env/bin")
set(is_ci_image TRUE)
endif()
# --------------------------------------------------------------------
# Operating system detection
# --------------------------------------------------------------------
@@ -77,16 +56,3 @@ elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64|ARM64")
else()
message(FATAL_ERROR "Unknown architecture: ${CMAKE_SYSTEM_PROCESSOR}")
endif()
# --------------------------------------------------------------------
# Sanitizers
# --------------------------------------------------------------------
# SANITIZERS is injected by the Conan toolchain when a sanitizer build is
# requested (see conan/profiles/sanitizers). The flags are applied to the
# 'common' target in XrplSanitizers; this flag lets other modules know a
# sanitizer build is active without depending on that module.
if(DEFINED SANITIZERS)
set(SANITIZERS_ENABLED TRUE)
else()
set(SANITIZERS_ENABLED FALSE)
endif()

View File

@@ -1,90 +0,0 @@
#[===================================================================[
Patch executables to run in non-Nix environments.
The Nix toolchain links binaries against an ELF interpreter (loader)
that lives in the Nix store, so the resulting binaries don't run elsewhere
(including once installed from the .deb package). `patch_nix_binary` resets
the interpreter to the system default loader and drops the rpath, once the
binary has been linked.
This runs by default for Nix-toolchain builds (determined by whether the compiler resolves under /nix/store/).
Those builds are where binaries get a Nix-store loader.
It is opted out of by setting the XRPLD_NO_PATCH_NIX_BINARY environment variable
the plain Nix dev shells set it, since their binaries link a newer glibc
and must not be retargeted to the system loader.
Non-Nix builds (a system compiler, already using the system loader) and sanitizer builds
(runtime libraries resolved through the rpath) are skipped too.
Everywhere else `patch_nix_binary` is a no-op.
The default loader is resolved by bin/default-loader-path.sh.
#]===================================================================]
include_guard(GLOBAL)
include(CompilationEnv)
# Resolves the system default ELF loader path for the current architecture.
set(_loader_path_script "${CMAKE_SOURCE_DIR}/bin/default-loader-path.sh")
if(
is_linux
AND NOT SANITIZERS_ENABLED
AND is_nix_compiler
AND NOT DEFINED ENV{XRPLD_NO_PATCH_NIX_BINARY}
)
execute_process(
COMMAND "${_loader_path_script}"
OUTPUT_VARIABLE DEFAULT_LOADER_PATH
OUTPUT_STRIP_TRAILING_WHITESPACE
COMMAND_ERROR_IS_FATAL ANY
)
find_program(PATCHELF_COMMAND patchelf REQUIRED)
set(PATCH_NIX_BINARIES TRUE)
message(
STATUS
"Binaries will be patched to use loader '${DEFAULT_LOADER_PATH}'"
)
else()
set(PATCH_NIX_BINARIES FALSE)
endif()
function(patch_nix_binary target)
if(NOT PATCH_NIX_BINARIES)
return()
endif()
set(patch_command
"${PATCHELF_COMMAND}"
--set-interpreter
"${DEFAULT_LOADER_PATH}"
--remove-rpath
"$<TARGET_FILE:${target}>"
)
set(comment "Patching ${target}: set default loader, remove rpath")
# POST_BUILD is the cheap way to do this: it runs only when the binary is
# relinked. It is also only available in the directory that defined the
# target, so for a target from elsewhere (e.g. a FetchContent subproject)
# fall back to a custom target that runs after the binary is linked. That
# one runs on every build, which is harmless because patchelf is idempotent.
get_target_property(target_source_dir ${target} SOURCE_DIR)
if("${target_source_dir}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}")
add_custom_command(
TARGET ${target}
POST_BUILD
COMMAND ${patch_command}
COMMENT "${comment}"
VERBATIM
)
else()
add_custom_target(
${target}-patch-nix
ALL
COMMAND ${patch_command}
COMMENT "${comment}"
VERBATIM
)
add_dependencies(${target}-patch-nix ${target})
endif()
endfunction()

View File

@@ -1,38 +0,0 @@
include_guard()
include(isolate_headers)
# Define a benchmark executable for the module `name`.
#
# This follows the same general pattern as other build helpers in this repo
# (e.g. `add_module`): create a target and isolate headers, but here the target
# is a benchmark executable and no `add_test(...)` is registered.
#
# `isolate_headers` exposes only `${CMAKE_CURRENT_SOURCE_DIR}/${name}` on the
# include path, rooted at `src`, so a benchmark's own headers are reached as
# `<benchmarks/.../${name}/...>` and nothing else in the tree leaks in.
function(xrpl_add_benchmark name)
set(target ${PROJECT_NAME}.bench.${name})
file(
GLOB_RECURSE sources
CONFIGURE_DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/${name}/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/${name}.cpp"
)
add_executable(${target} ${ARGN} ${sources})
# Benchmark sources register cases through Google Benchmark's static
# registrars (anonymous-namespace lambdas). Merging several such files into
# one unity translation unit collides those internal-linkage entities, so
# keep benchmarks out of the unity build - mirroring xrpl.libpb in
# XrplCore.cmake. Each file compiles fine on its own.
set_target_properties(${target} PROPERTIES UNITY_BUILD OFF)
isolate_headers(
${target}
"${CMAKE_SOURCE_DIR}/src"
"${CMAKE_CURRENT_SOURCE_DIR}/${name}"
PRIVATE
)
endfunction()

View File

@@ -154,15 +154,6 @@ else()
>
)
# On aarch64, libatomic is required for atomic operations. It is not needed on x86_64.
# Linking it statically on Linux
if(is_arm64 AND is_linux)
target_link_options(
common
INTERFACE -Wl,--push-state -Wl,-Bstatic -latomic -Wl,--pop-state
)
endif()
# Keep -stdlib=libstdc++ off the compile commands, but preserve it for linking.
#
# Conan turns `compiler.libcxx=libstdc++` into `-stdlib=libstdc++` and puts it in
@@ -171,8 +162,9 @@ else()
# Clang wrapper supplies those paths itself (via -nostdinc++), so at compile time the
# flag is unused -> Clang errors under our -Werror. At link time the flag IS consumed
# (it selects the C++ runtime), so we move it there instead of dropping it entirely.
get_filename_component(_cxx_real "${CMAKE_CXX_COMPILER}" REALPATH)
if(
is_nix_compiler
_cxx_real MATCHES "^/nix/store/"
AND is_linux
AND is_clang
AND CMAKE_CXX_FLAGS MATCHES "stdlib=libstdc"
@@ -266,50 +258,10 @@ elseif(use_lld)
)
if("${LD_VERSION}" MATCHES "LLD")
target_link_libraries(common INTERFACE -fuse-ld=lld)
# remembered for the linker flag probe below
set(fuse_ld_flag "-fuse-ld=lld")
endif()
unset(LD_VERSION)
endif()
# Linker warnings are errors where we control the toolchain and the dependencies: CI and the Nix dev shell.
# On non-Nix macOS we suppress the deployment target warning: an old Conan profile may not pin os.version.
# Only the new Apple linker understands the flag, so probe the actual linker (lld may be selected above).
if(is_macos OR is_linux)
if(is_ci OR is_nix_compiler)
if(is_macos)
set(fatal_warnings_flag "-Wl,-fatal_warnings")
else()
set(fatal_warnings_flag "-Wl,--fatal-warnings")
endif()
message(
STATUS
"Treating all linker warnings as errors (${fatal_warnings_flag})"
)
target_link_options(common INTERFACE "${fatal_warnings_flag}")
unset(fatal_warnings_flag)
elseif(is_macos)
set(silence_flag "-Wl,-deployment_target_mismatches,suppress")
set(probe_flags ${fuse_ld_flag} "${silence_flag}")
include(CheckLinkerFlag)
check_linker_flag(
CXX
"${probe_flags}"
have_deployment_target_mismatches
)
if(have_deployment_target_mismatches)
message(
STATUS
"Silencing macOS deployment target mismatch warnings (${silence_flag})"
)
target_link_options(common INTERFACE "${silence_flag}")
endif()
unset(probe_flags)
unset(silence_flag)
endif()
endif()
unset(fuse_ld_flag)
if(assert)
foreach(var_ CMAKE_C_FLAGS_RELEASE CMAKE_CXX_FLAGS_RELEASE)
string(REGEX REPLACE "[-/]DNDEBUG" "" ${var_} "${${var_}}")

View File

@@ -133,12 +133,6 @@ target_link_libraries(
add_module(xrpl resource)
target_link_libraries(xrpl.libxrpl.resource PUBLIC xrpl.libxrpl.protocol)
add_module(xrpl peerfinder)
target_link_libraries(
xrpl.libxrpl.peerfinder
PUBLIC xrpl.libxrpl.basics xrpl.libxrpl.protocol
)
# Level 08
add_module(xrpl net)
target_link_libraries(
@@ -207,16 +201,6 @@ target_link_libraries(
add_module(xrpl tx)
target_link_libraries(xrpl.libxrpl.tx PUBLIC xrpl.libxrpl.ledger)
add_module(xrpl consensus)
target_link_libraries(
xrpl.libxrpl.consensus
PUBLIC
xrpl.libxrpl.basics
xrpl.libxrpl.json
xrpl.libxrpl.protocol
xrpl.libxrpl.ledger
)
add_library(xrpl.libxrpl)
set_target_properties(xrpl.libxrpl PROPERTIES OUTPUT_NAME xrpl)
@@ -236,7 +220,6 @@ target_link_modules(
beast
conditions
config
consensus
core
crypto
git
@@ -244,7 +227,6 @@ target_link_modules(
ledger
net
nodestore
peerfinder
protocol
protocol_autogen
rdb
@@ -265,7 +247,6 @@ target_link_modules(
if(xrpld)
add_executable(xrpld)
patch_nix_binary(xrpld)
if(tests)
target_compile_definitions(xrpld PUBLIC ENABLE_TESTS)
target_compile_definitions(
@@ -311,13 +292,4 @@ if(xrpld)
PRIVATE ${CMAKE_SOURCE_DIR}/external/antithesis-sdk
)
endif()
# The xrpld headers are not built with add_module, so verify them against
# the executable's own compile environment.
if(verify_headers)
verify_target_headers(xrpld "${CMAKE_CURRENT_SOURCE_DIR}/src/xrpld")
if(tests)
verify_target_headers(xrpld "${CMAKE_CURRENT_SOURCE_DIR}/src/test")
endif()
endif()
endif()

View File

@@ -44,7 +44,6 @@ setup_target_for_coverage_gcovr(
EXCLUDE
"src/test"
"src/tests"
"src/benchmarks"
"include/xrpl/beast/test"
"include/xrpl/beast/unit_test"
"${CMAKE_BINARY_DIR}/pb-xrpl.libpb"

View File

@@ -25,22 +25,10 @@ if(NOT (RPMBUILD_EXECUTABLE OR DPKG_BUILDPACKAGE_EXECUTABLE))
return()
endif()
if(NOT TARGET xrpld)
message(STATUS "xrpld=ON is required; 'package' target not available")
return()
endif()
if(NOT TARGET validator-keys)
message(
STATUS
"validator_keys=ON is required; 'package' target not available"
)
return()
endif()
set(package_env
SRC_DIR=${CMAKE_SOURCE_DIR}
BUILD_DIR=${CMAKE_BINARY_DIR}
PKG_VERSION=${xrpld_version}
PKG_RELEASE=${pkg_release}
)
@@ -50,7 +38,7 @@ add_custom_target(
${CMAKE_COMMAND} -E env ${package_env}
${CMAKE_SOURCE_DIR}/package/build_pkg.sh
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
DEPENDS xrpld validator-keys
DEPENDS xrpld
COMMENT "Building Linux package (deb/rpm inferred from host tooling)"
VERBATIM
)

View File

@@ -2,22 +2,21 @@
Protocol Autogen - Code generation for protocol wrapper classes
#]===================================================================]
# The repository root, derived from the location of this file rather than from
# the including project, so that the targets below can also be offered on their
# own by cmake/codegen/CMakeLists.txt.
get_filename_component(XRPL_ROOT "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
set(CODEGEN_VENV_DIR
"${XRPL_ROOT}/.venv"
"${CMAKE_CURRENT_SOURCE_DIR}/.venv"
CACHE PATH
"Path to a Python virtual environment for code generation. A venv will be created here by setup_code_gen and used to run generation scripts."
)
# Directory paths
set(MACRO_DIR "${XRPL_ROOT}/include/xrpl/protocol/detail")
set(AUTOGEN_HEADER_DIR "${XRPL_ROOT}/include/xrpl/protocol_autogen")
set(AUTOGEN_TEST_DIR "${XRPL_ROOT}/src/tests/libxrpl/protocol_autogen")
set(SCRIPTS_DIR "${XRPL_ROOT}/cmake/scripts/codegen")
set(MACRO_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include/xrpl/protocol/detail")
set(AUTOGEN_HEADER_DIR
"${CMAKE_CURRENT_SOURCE_DIR}/include/xrpl/protocol_autogen"
)
set(AUTOGEN_TEST_DIR
"${CMAKE_CURRENT_SOURCE_DIR}/src/tests/libxrpl/protocol_autogen"
)
set(SCRIPTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cmake/scripts/codegen")
# Input macro files
set(TRANSACTIONS_MACRO "${MACRO_DIR}/transactions.macro")
@@ -115,14 +114,14 @@ if(CODEGEN_VENV_DIR)
setup_code_gen
COMMAND ${Python3_EXECUTABLE} -m venv "${CODEGEN_VENV_DIR}"
COMMAND ${CODEGEN_PYTHON} -m pip install -r "${REQUIREMENTS_FILE}"
WORKING_DIRECTORY "${XRPL_ROOT}"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
COMMENT "Creating venv and installing code generation dependencies..."
)
else()
add_custom_target(
setup_code_gen
COMMAND ${Python3_EXECUTABLE} -m pip install -r "${REQUIREMENTS_FILE}"
WORKING_DIRECTORY "${XRPL_ROOT}"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
COMMENT "Installing code generation dependencies..."
)
endif()
@@ -140,8 +139,8 @@ add_custom_target(
-DSFIELDS_MACRO=${SFIELDS_MACRO}
-DAUTOGEN_HEADER_DIR=${AUTOGEN_HEADER_DIR}
-DAUTOGEN_TEST_DIR=${AUTOGEN_TEST_DIR} -P
"${CMAKE_CURRENT_LIST_DIR}/XrplProtocolAutogenRun.cmake"
WORKING_DIRECTORY "${XRPL_ROOT}"
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/XrplProtocolAutogenRun.cmake"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
COMMENT "Running protocol code generation..."
SOURCES ${ALL_INPUT_FILES}
)

View File

@@ -14,9 +14,11 @@
include_guard(GLOBAL)
include(CompilationEnv)
if(NOT SANITIZERS_ENABLED)
if(NOT DEFINED SANITIZERS)
set(SANITIZERS_ENABLED FALSE)
return()
endif()
set(SANITIZERS_ENABLED TRUE)
message(STATUS "=== Configuring Sanitizers ===")
message(STATUS " SANITIZERS: ${SANITIZERS}")

View File

@@ -36,19 +36,6 @@ elseif(is_gcc)
endif()
endif()
# A Nix compiler is only meant to be used from a managed environment: the xrpld
# dev shell (which exports XRPL_DEVSHELL) or the CI image. Using one from a bare
# shell usually means a leaked toolchain (picked up via PATH or a Conan profile)
# and leads to confusing breakage, so fail early with guidance.
if(is_nix_compiler AND NOT is_ci_image AND NOT DEFINED ENV{XRPL_DEVSHELL})
message(
FATAL_ERROR
"A Nix compiler (${CMAKE_CXX_COMPILER}) is being used outside the xrpld "
"dev shell. Enter it with `nix develop` (see docs/build/nix.md) before "
"configuring the build."
)
endif()
# check for in-source build and fail
if("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}")
message(

View File

@@ -30,25 +30,6 @@ if(tests)
endif()
endif()
option(benchmark "Build benchmarks" ON)
# Enabled by default so every header is compiled on its own as the main file of
# its own compile_commands.json entry - this is what lets clang-tidy (and clangd
# and IDEs) analyse a header's own includes directly. The per-header objects are
# EXCLUDE_FROM_ALL (see cmake/verify_headers.cmake) and the aggregate target
# below is not part of `all`, so a normal `cmake --build` never compiles them.
option(
verify_headers
"Compile every header on its own to verify it is self-contained."
ON
)
if(verify_headers)
# Aggregate target that builds every per-module header-verification library
# created by add_module (see cmake/verify_headers.cmake). Build it with:
# cmake --build . --target verify-headers
add_custom_target(verify-headers)
endif()
option(unity "Creates a build using UNITY support in cmake." OFF)
if(unity)
if(NOT is_ci)

View File

@@ -5,39 +5,22 @@ option(
)
if(validator_keys)
# Own the install destination below rather than relying on another module
# having pulled this in first.
include(GNUInstallDirs)
# Pinned to an exact commit, not a branch: the tool ships inside our
# packages, so the same xrpld version must always package the same
# validator-keys. Bump this deliberately.
set(validator_keys_commit "4c0fb75eec9601c711645998c904507e87e910ae")
message(STATUS "Using ValidatorKeys commit: ${validator_keys_commit}")
git_branch(current_branch)
# default to tracking VK master branch unless we are on release
if(NOT (current_branch STREQUAL "release"))
set(current_branch "master")
endif()
message(STATUS "Tracking ValidatorKeys branch: ${current_branch}")
FetchContent_Declare(
validator_keys
GIT_REPOSITORY https://github.com/ripple/validator-keys-tool.git
GIT_TAG "${validator_keys_commit}"
GIT_TAG "${current_branch}"
)
FetchContent_MakeAvailable(validator_keys)
# The tool's own CMakeLists excludes the target from 'all' when it is built
# as a subproject. Undo that, so validator_keys=ON really does build it.
set_target_properties(
validator-keys
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
EXCLUDE_FROM_ALL OFF
EXCLUDE_FROM_DEFAULT_BUILD OFF
)
# We ship this binary, so like xrpld it must not keep the Nix store's ELF
# loader, or it cannot run on the target distro at all.
patch_nix_binary(validator-keys)
configure_file(
"${validator_keys_SOURCE_DIR}/LICENSE"
"${CMAKE_BINARY_DIR}/validator-keys-LICENSE"
COPYONLY
PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
)
install(TARGETS validator-keys RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
endif()

View File

@@ -1,5 +1,4 @@
include(isolate_headers)
include(verify_headers)
# Create an OBJECT library target named
#
@@ -38,20 +37,4 @@ function(add_module parent name)
"${CMAKE_CURRENT_SOURCE_DIR}/src/lib${parent}/${name}"
PRIVATE
)
# protocol_autogen contains generated headers that are deliberately exempt
# from clang-tidy (see ExcludeHeaderFilterRegex in .clang-tidy), so we do not
# verify them either.
if(
verify_headers
AND NOT "${parent}/${name}" STREQUAL "xrpl/protocol_autogen"
)
verify_target_headers(
${target}
"${CMAKE_CURRENT_SOURCE_DIR}/include/${parent}/${name}"
)
verify_target_headers(
${target}
"${CMAKE_CURRENT_SOURCE_DIR}/src/lib${parent}/${name}"
)
endif()
endfunction()

View File

@@ -1,21 +0,0 @@
#[===================================================================[
Protocol Autogen - Standalone project
Exposes the 'setup_code_gen' and 'code_gen' targets on their own, without
configuring the rest of xrpl. Code generation is pure Python, so this needs
neither the dependencies nor a compiler, which makes it usable in CI and by
contributors who only want to regenerate the protocol wrapper classes:
cmake -S cmake/codegen -B build/codegen
cmake --build build/codegen --target setup_code_gen
cmake --build build/codegen --target code_gen
The targets are identical to the ones offered by the top-level build, since
both come from cmake/XrplProtocolAutogen.cmake.
#]===================================================================]
cmake_minimum_required(VERSION 3.16)
project(xrpl_codegen LANGUAGES NONE)
include("${CMAKE_CURRENT_LIST_DIR}/../XrplProtocolAutogen.cmake")

View File

@@ -177,9 +177,7 @@ ${field['typeData']['setter_type']} ${field['paramName']}${',' if i < len(requir
object_ = *sle;
}
/**
* @brief Ledger entry-specific field setters
*/
/** @brief Ledger entry-specific field setters */
% for field in fields:
/**

View File

@@ -185,9 +185,7 @@ public:
object_ = *tx;
}
/**
* @brief Transaction-specific field setters
*/
/** @brief Transaction-specific field setters */
% for field in fields:
/**

View File

@@ -1,84 +0,0 @@
# Our normal build only ever compiles `.cpp` files, so a header is only ever
# checked through whatever translation unit happens to include it. A header that
# is missing an `#include` is never caught as long as every `.cpp` that uses it
# includes its missing dependency first. To check a header on its own we compile
# it directly as a translation unit.
#
# Compiling the header itself - rather than a `.cpp` wrapper that includes it -
# gives two checks at once:
# * the compiler fails if the header is not self-contained, i.e. it uses a
# declaration that is not available (directly or transitively); and
# * the header is the *main file* of its `compile_commands.json` entry, so
# clang-tidy's misc-include-cleaner analyses (and can --fix) the header's own
# includes - flagging a dependency that is only available transitively, which
# a plain compile cannot catch. A wrapper would be the main file instead, and
# include-cleaner never looks inside the headers a main file includes.
#
# The objects are never linked anywhere; we build them only for these checks.
# Verify that the headers under headers_dir compile on their own, using the
# compile environment of an existing target so each header is compiled exactly as
# that target compiles it. This works for both add_module libraries and the xrpld
# and test binaries: a library's isolated public and private include directories
# and a binary's `-I src` both live in its INCLUDE_DIRECTORIES, and the modules or
# libraries it links live in its LINK_LIBRARIES. We copy those usage requirements
# through generator expressions (rather than linking ${target}, which is
# impossible for an executable), evaluated at generation time so they capture
# requirements the caller adds after this runs. The verify library is created
# once; call this repeatedly to add more header directories.
#
# verify_target_headers(target headers_dir)
function(verify_target_headers target headers_dir)
set(verify ${target}.verify)
if(NOT TARGET ${verify})
add_library(${verify} OBJECT EXCLUDE_FROM_ALL)
# A unity build would concatenate the headers into a single translation
# unit, where a header missing an include could be satisfied by one that
# precedes it in the blob - exactly the bug we want to catch.
set_target_properties(${verify} PROPERTIES UNITY_BUILD OFF)
target_include_directories(
${verify}
PRIVATE $<TARGET_PROPERTY:${target},INCLUDE_DIRECTORIES>
)
target_compile_definitions(
${verify}
PRIVATE $<TARGET_PROPERTY:${target},COMPILE_DEFINITIONS>
)
target_compile_options(
${verify}
PRIVATE $<TARGET_PROPERTY:${target},COMPILE_OPTIONS>
)
target_link_libraries(
${verify}
PRIVATE $<TARGET_PROPERTY:${target},LINK_LIBRARIES>
)
add_dependencies(verify-headers ${verify})
endif()
_verify_add_headers(${verify} "${headers_dir}")
endfunction()
# Add every .h/.hpp under dir to target as a directly-compiled C++ translation
# unit. .ipp files are inline-implementation fragments included by their owning
# header (often after a class declaration), so they are not self-contained on
# their own and are verified transitively when that header is verified.
function(_verify_add_headers target dir)
file(GLOB_RECURSE headers CONFIGURE_DEPENDS "${dir}/*.h" "${dir}/*.hpp")
if(NOT headers)
return()
endif()
# `-xc++` forces the header to be compiled as a C++ translation unit; a lone
# `.h` is otherwise treated as a header to precompile. `#pragma once` is
# harmless (and warns) when the header is the main file, so silence it.
# Compiled on its own, a header legitimately defines constants and static or
# template functions that nothing in this single translation unit uses (they
# exist for the files that include it), so the resulting unused-entity
# warnings are expected and must not fail the build under -Werror.
set_source_files_properties(
${headers}
PROPERTIES
LANGUAGE CXX
COMPILE_OPTIONS
"-xc++;-Wno-pragma-once-outside-header;-Wno-unused-const-variable;-Wno-unused-function"
)
target_sources(${target} PRIVATE ${headers})
endfunction()

View File

@@ -1,46 +1,43 @@
{
"version": "0.5",
"requires": [
"zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1782392402.122708",
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1782392402.420688",
"sqlite3/3.53.0#324ada52333108388a9a6108bfa96734%1782392403.185447",
"soci/4.0.3#e726491a03468795453f7c83fc924a96%1782392402.679521",
"snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1782307151.633168",
"secp256k1/0.7.1#b1f450b7f78a36fff75bb6934a356f3a%1782338841.3729",
"rocksdb/10.5.1#4a197eca381a3e5ae8adf8cffa5aacd0%1782392413.075713",
"re2/20251105#8579cfd0bda4daf0683f9e3898f964b4%1782392402.431897",
"protobuf/6.33.5#ff253ead763bd8d9904a52979cd21e81%1782392410.233933",
"openssl/3.6.3#f806de8933e3bf6f01016c6a888cee2e%1783945160.863288",
"nudb/2.0.9#11149c73f8f2baff9a0198fe25971fc7%1782392402.297166",
"mpt-crypto/1.0.2#b313cef0c1a493eb970ad185b2e9bab7%1784285108.866483",
"lz4/1.10.0#982d9b673900f665a1da109e09c17cab%1782392402.164188",
"libiconv/1.17#9923bc6dc6f106646d6967e0039a5ada%1782392792.775744",
"libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1782392402.420732",
"libarchive/3.8.7#c446109bd1f1d8ba7936c94189bc50e6%1782392403.066892",
"zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1777558780.503",
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1765850149.987",
"sqlite3/3.53.0#324ada52333108388a9a6108bfa96734%1776096494.149",
"soci/4.0.3#fe32b9ad5eb47e79ab9e45a68f363945%1774450067.231",
"snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1765850147.878",
"secp256k1/0.7.1#481881709eb0bdd0185a12b912bbe8ad%1770910500.329",
"rocksdb/10.5.1#4a197eca381a3e5ae8adf8cffa5aacd0%1765850186.86",
"re2/20251105#8579cfd0bda4daf0683f9e3898f964b4%1774398111.888",
"protobuf/6.33.5#d96d52ba5baaaa532f47bda866ad87a5%1774467363.12",
"openssl/3.6.2#4789bbf131b77d0515d15e094c8f697f%1778071755.506",
"nudb/2.0.9#11149c73f8f2baff9a0198fe25971fc7%1775040983.408",
"lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504%1765850143.914",
"libiconv/1.17#1e65319e945f2d31941a9d28cc13c058%1765842973.492",
"libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1765842973.03",
"libarchive/3.8.7#c446109bd1f1d8ba7936c94189bc50e6%1776147552.838",
"jemalloc/5.3.1#1fc58d55316041f10fbc1e8a2eae632a%1776700028.228",
"gtest/1.17.0#5224b3b3ff3b4ce1133cbdd27d53ee7d%1782392402.791979",
"grpc/1.81.1#f729f6d75992d20f9c72828e9142d62f%1783945160.094135",
"fast_float/8.2.10#f6f28d6bb22112078e7dbda611caf681%1782494504.298",
"ed25519/2015.03#ae761bdc52730a843f0809bdf6c1b1f6%1782307148.15562",
"date/3.0.4#862e11e80030356b53c2c38599ceb32b%1782392402.538492",
"c-ares/1.34.6#545240bb1c40e2cacd4362d6b8967650%1782392402.681654",
"bzip2/1.0.8#c470882369c2d95c5c77e970c0c7e321%1782392402.296732",
"boost/1.91.0#ea540ca2133d831b560036aa24dece3c%1782392419.475605",
"benchmark/1.9.5#b885dc73ad67b40a55d45684d1c88ad1%1782736613.864841",
"abseil/20250127.0#9ef01c1451a8340f9022e46238c0fbb6%1783945159.651047"
"gtest/1.17.0#5224b3b3ff3b4ce1133cbdd27d53ee7d%1768312129.152",
"grpc/1.78.1#b1a9e74b145cc471bed4dc64dc6eb2c1%1774467387.342",
"ed25519/2015.03#ae761bdc52730a843f0809bdf6c1b1f6%1765850143.772",
"date/3.0.4#862e11e80030356b53c2c38599ceb32b%1765850143.772",
"c-ares/1.34.6#545240bb1c40e2cacd4362d6b8967650%1774439234.681",
"bzip2/1.0.8#c470882369c2d95c5c77e970c0c7e321%1765850143.837",
"boost/1.91.0#ea540ca2133d831b560036aa24dece3c%1778050991.9",
"abseil/20250127.0#bb0baf1f362bc4a725a24eddd419b8f7%1774365460.196"
],
"build_requires": [
"zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1782392402.122708",
"strawberryperl/5.32.1.1#8d114504d172cfea8ea1662d09b6333e%1782395692.540639",
"protobuf/6.33.5#ff253ead763bd8d9904a52979cd21e81%1782392410.233933",
"nasm/2.16.01#31e26f2ee3c4346ecd347911bd126904%1782395690.33162",
"zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1777558780.503",
"strawberryperl/5.32.1.1#8d114504d172cfea8ea1662d09b6333e%1774447376.964",
"protobuf/6.33.5#d96d52ba5baaaa532f47bda866ad87a5%1774467363.12",
"nasm/2.16.01#31e26f2ee3c4346ecd347911bd126904%1765850144.707",
"msys2/cci.latest#d22fe7b2808f5fd34d0a7923ace9c54f%1770657326.649",
"m4/1.4.19#1727f439cf74e83826ec96d0b4904eee%1784541921.659",
"cmake/4.3.3#840cf00ea09777e05c2050a50a82c722%1782392418.696091",
"b2/5.4.2#ffd6084a119587e70f11cd45d1a386e2%1782392402.624226",
"m4/1.4.19#4523e4347b55cd26ae918bd5770cab9a%1778062762.471",
"cmake/4.3.0#b939a42e98f593fb34d3a8c5cc860359%1774439249.183",
"b2/5.4.2#ffd6084a119587e70f11cd45d1a386e2%1774439233.447",
"automake/1.16.5#b91b7c384c3deaa9d535be02da14d04f%1755524470.56",
"autoconf/2.71#51077f068e61700d65bb05541ea1e4b0%1731054366.86",
"abseil/20250127.0#9ef01c1451a8340f9022e46238c0fbb6%1783945159.651047"
"abseil/20250127.0#bb0baf1f362bc4a725a24eddd419b8f7%1774365460.196"
],
"python_requires": [],
"overrides": {
@@ -60,7 +57,7 @@
"boost/1.91.0"
],
"lz4/[>=1.9.4 <2]": [
"lz4/1.10.0#982d9b673900f665a1da109e09c17cab"
"lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504"
]
},
"config_requires": []

View File

@@ -1,21 +0,0 @@
#!/usr/bin/env bash
# Install our Conan configuration, profiles and the xrplf remote into CONAN_HOME.
# Safe to re-run; never deletes the Conan home.
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
CONAN_DIR="$(conan config home)"
echo "Installing Conan configuration into ${CONAN_DIR}"
conan config install "${SCRIPT_DIR}/global.conf"
conan config install "${SCRIPT_DIR}/profiles" -tf "${CONAN_DIR}/profiles"
# This script manages these files, so make them read-only - Conan does not
# preserve the source mode. Only the files: the directories must stay writable
# for `conan config install` to replace them.
chmod a-w "${CONAN_DIR}/global.conf"
find "${CONAN_DIR}/profiles" -type f -exec chmod a-w {} +
echo "Adding the xrplf Conan remote"
# --index 0: our patched recipes must win over Conan Center.
conan remote add --index 0 --force xrplf https://conan.xrplf.org/repository/conan/

View File

@@ -14,7 +14,7 @@ export CONAN_HOME="$TEMP_DIR"
# Ensure that the xrplf remote is the first to be consulted, so any recipes we
# patched are used. We also add it there to not created huge diff when the
# official Conan Center Index is updated.
conan remote add --force --index 0 xrplf https://conan.xrplf.org/repository/conan/
conan remote add --force --index 0 xrplf https://conan.ripplex.io
# Delete any existing lockfile.
rm -f conan.lock

View File

@@ -1,60 +1,25 @@
{% set os = detect_api.detect_os() %}
{% set arch = detect_api.detect_arch() %}
{% set compiler, version, compiler_exe = detect_api.detect_default_compiler() %}
{% set compiler_version = version %}
{% if os == "Linux" %}
{% set compiler_version = detect_api.default_compiler_version(compiler, version) %}
{% if os == "Macos" %}
{# Minimum macOS the dependencies target. #}
{# Without this, Conan builds each dependency against the (possibly newer) host SDK, so the #}
{# dependency objects target a newer macOS than the binary and the linker warns. #}
{# Keep at or below CMAKE_OSX_DEPLOYMENT_TARGET in CMakeLists.txt. #}
{% set min_macos_version = "15.0" %}
{% endif %}
[settings]
os={{ os }}
arch={{ arch }}
build_type=Debug
compiler={{ compiler }}
compiler={{compiler}}
compiler.version={{ compiler_version }}
compiler.cppstd=23
{% if os == "Windows" %}
compiler.runtime=static
{% else %}
compiler.libcxx={{ detect_api.detect_libcxx(compiler, version, compiler_exe) }}
{% endif %}
{% if os == "Macos" %}
os.version={{ min_macos_version }}
compiler.libcxx={{detect_api.detect_libcxx(compiler, version, compiler_exe)}}
{% endif %}
[conf]
{# The Boost recipe builds with b2, which doesn't use Conan's toolchain files. #}
{# Instead it hand-rolls the compiler for user-config.jam, #}
{# and its fallback probes a version-suffixed binary (e.g. `g++-15`) before plain `g++`. #}
{# Inside the Nix shell the wrapper only provides `g++`/`gcc` (no `-15` suffix), #}
{# so on a host that also has a system `g++-15` the probe escapes Nix #}
{# and picks the system compiler, which is mismatched with the Nix libraries #}
{# and breaks the build (e.g. Boost.Stacktrace link checks fail). #}
{# Pinning the executables here short-circuits that probe so Boost (and the rest of the toolchain) #}
{# resolve the same compiler. #}
{# Not part of the package ID, so binaries stay shareable. #}
{% if os != "Windows" %}
{% set cc_exe = {"gcc": "gcc", "clang": "clang", "apple-clang": "clang"}.get(compiler) %}
{% set cxx_exe = {"gcc": "g++", "clang": "clang++", "apple-clang": "clang++"}.get(compiler) %}
tools.build:compiler_executables={'c':'{{ cc_exe }}','cpp':'{{ cxx_exe }}'}
{% endif %}
{# By default, Conan tries to reuse binaries built with different cppstd versions. #}
{# We want to avoid that to improve reproduceability, so we add the cppstd version to the package ID. #}
{# More info: https://docs.conan.io/2/reference/extensions/binary_compatibility.html #}
user.package:cppstd_version=23
tools.info.package_id:confs+=["user.package:cppstd_version"]
{% if os == "Macos" %}
[buildenv]
{# os.version adds -mmacosx-version-min to compiler command lines, #}
{# but Boost.Context's b2 assembly (.S) rule ignores it, #}
{# so those objects keep the host SDK version and still warn at link time. #}
{# clang's assembler honors this env var regardless, pinning them. #}
{# Scoped to boost/* since it is the only gap. #}
boost/*:MACOSX_DEPLOYMENT_TARGET={{ min_macos_version }}
{% if compiler == "gcc" and compiler_version < 13 %}
tools.build:cxxflags+=['-Wno-restrict']
{% endif %}

View File

@@ -87,15 +87,15 @@ include(default)
{% endif %}
[conf]
tools.build:defines+={{ defines }}
tools.build:cxxflags+={{ sanitizer_compiler_flags }}
tools.build:sharedlinkflags+={{ sanitizer_linker_flags }}
tools.build:exelinkflags+={{ sanitizer_linker_flags }}
tools.build:defines+={{defines}}
tools.build:cxxflags+={{sanitizer_compiler_flags}}
tools.build:sharedlinkflags+={{sanitizer_linker_flags}}
tools.build:exelinkflags+={{sanitizer_linker_flags}}
tools.info.package_id:confs+=["tools.build:cxxflags", "tools.build:exelinkflags", "tools.build:sharedlinkflags", "tools.build:defines"]
# &: means "apply only to the consumer/root package"
&:tools.cmake.cmaketoolchain:extra_variables={"SANITIZERS": "{{ sanitizers }}", "SANITIZERS_COMPILER_FLAGS": "{{ sanitizer_compiler_flags | join(' ') }}", "SANITIZERS_LINKER_FLAGS": "{{ sanitizer_linker_flags | join(' ') }}"}
&:tools.cmake.cmaketoolchain:extra_variables={"SANITIZERS": "{{sanitizers}}", "SANITIZERS_COMPILER_FLAGS": "{{sanitizer_compiler_flags | join(' ')}}", "SANITIZERS_LINKER_FLAGS": "{{sanitizer_linker_flags | join(' ')}}"}
[options]
{% if enable_asan %}

View File

@@ -15,7 +15,6 @@ class Xrpl(ConanFile):
settings = "os", "compiler", "build_type", "arch"
options = {
"assertions": [True, False],
"benchmark": [True, False],
"coverage": [True, False],
"fPIC": [True, False],
"jemalloc": [True, False],
@@ -29,11 +28,11 @@ class Xrpl(ConanFile):
requires = [
"ed25519/2015.03",
"fast_float/8.2.10",
"grpc/1.81.1",
"grpc/1.78.1",
"libarchive/3.8.7",
"nudb/2.0.9",
"openssl/3.6.3",
"openssl/3.6.2",
"secp256k1/0.7.1",
"soci/4.0.3",
"zlib/1.3.2",
]
@@ -48,7 +47,6 @@ class Xrpl(ConanFile):
default_options = {
"assertions": False,
"benchmark": True,
"coverage": False,
"fPIC": True,
"jemalloc": False,
@@ -132,19 +130,15 @@ class Xrpl(ConanFile):
self.options["boost"].without_cobalt = True
def requirements(self):
if self.options.benchmark:
self.requires("benchmark/1.9.5")
self.requires("boost/1.91.0", force=True, transitive_headers=True)
self.requires("date/3.0.4", transitive_headers=True)
self.requires("lz4/1.10.0", force=True)
self.requires("protobuf/6.33.5", force=True)
self.requires("sqlite3/3.53.0", force=True)
if self.options.jemalloc:
self.requires("jemalloc/5.3.1")
self.requires("lz4/1.10.0", force=True)
self.requires("mpt-crypto/1.0.2", transitive_headers=True)
self.requires("protobuf/6.33.5", force=True)
if self.options.rocksdb:
self.requires("rocksdb/10.5.1")
self.requires("secp256k1/0.7.1", transitive_headers=True)
self.requires("sqlite3/3.53.0", force=True)
self.requires("xxhash/0.8.3", transitive_headers=True)
exports_sources = (
@@ -167,7 +161,6 @@ class Xrpl(ConanFile):
def generate(self):
tc = CMakeToolchain(self)
tc.variables["tests"] = self.options.tests
tc.variables["benchmark"] = self.options.benchmark
tc.variables["assert"] = self.options.assertions
tc.variables["coverage"] = self.options.coverage
tc.variables["jemalloc"] = self.options.jemalloc
@@ -212,11 +205,9 @@ class Xrpl(ConanFile):
"boost::thread",
"date::date",
"ed25519::ed25519",
"fast_float::fast_float",
"grpc::grpc++",
"libarchive::libarchive",
"lz4::lz4",
"mpt-crypto::mpt-crypto",
"nudb::nudb",
"openssl::crypto",
"protobuf::libprotobuf",

View File

@@ -30,15 +30,15 @@ ignoreRegExpList:
- ABCDEFGHIJKLMNOPQRSTUVWXYZ
- ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
overrides:
- filename:
- "**/*_test.cpp" # legacy boost.test files
- "src/tests/**/*.cpp" # gtest test files
- filename: "**/*_test.cpp" # all test files
ignoreRegExpList:
- /"[^"]*"/g # double-quoted strings
- /'[^']*'/g # single-quoted strings
- /`[^`]*`/g # backtick strings
suggestWords:
- unsynched->unsynced
- xprl->xrpl
- xprld->xrpld # cspell: disable-line not sure what this problem is....
- unsynched->unsynced # cspell: disable-line not sure what this problem is....
- synched->synced
- synch->sync
words:
@@ -60,14 +60,12 @@ words:
- autobridging
- bimap
- bindir
- blindings
- bookdir
- Bougalis
- Britto
- Btrfs
- Buildx
- canonicality
- canonicalised
- changespq
- checkme
- choco
@@ -75,7 +73,6 @@ words:
- citardauq
- clawback
- clawbacks
- clippy
- cmaketoolchain
- coeffs
- coldwallet
@@ -98,7 +95,6 @@ words:
- daria
- dcmake
- dearmor
- decryptor
- dedented
- deleteme
- demultiplexer
@@ -110,7 +106,6 @@ words:
- distro
- doxyfile
- dxrpl
- elgamal
- enabled
- enablerepo
- endmacro
@@ -124,7 +119,6 @@ words:
- fmtdur
- fsanitize
- funclets
- Gamal
- gcov
- gcovr
- ghead
@@ -132,7 +126,6 @@ words:
- godexsoft
- gpgcheck
- gpgkey
- Hinnant
- hotwallet
- hwaddress
- hwrap
@@ -166,7 +159,6 @@ words:
- llection
- LOCALGOOD
- logwstream
- Lombrozo
- lseq
- lsmf
- ltype
@@ -176,6 +168,7 @@ words:
- MPTAMM
- MPTDEX
- Merkle
- Metafuncton
- misprediction
- missingok
- mptbalance
@@ -203,7 +196,6 @@ words:
- nftokens
- nftpage
- nikb
- Nikolaos
- nixfmt
- nixos
- nixpkgs
@@ -220,12 +212,10 @@ words:
- Nyffenegger
- onlatest
- ostr
- oxalica
- pargs
- partitioner
- paychan
- paychans
- Pedersen
- permdex
- perminute
- permissioned
@@ -249,10 +239,6 @@ words:
- Raphson
- rcflags
- replayer
- rerandomize
- rerandomization
- rerandomized
- rerandomizes
- rerere
- retriable
- RIPD
@@ -266,13 +252,9 @@ words:
- rocksdb
- Rohrs
- roundings
- rustc
- rustfmt
- rustup
- sahyadri
- Satoshi
- scons
- Schnorr
- secp
- sendq
- seqit
@@ -289,8 +271,6 @@ words:
- sles
- soci
- socidb
- sponsee
- sponsees
- SRPMS
- sslws
- statsd
@@ -305,18 +285,12 @@ words:
- stvar
- stvector
- stxchainattestations
- summands
- superpeer
- superpeers
- takergets
- takerpays
- tapanito
- ters
- tialymov
- tyalymov
- TMEndpointv2
- toolchain
- tparam
- trixie
- tx
- txid
@@ -327,7 +301,6 @@ words:
- txs
- ubsan
- UBSAN
- ufdio
- umant
- unacquired
- unambiguity
@@ -344,11 +317,9 @@ words:
- unserviced
- unshareable
- unshares
- unsponsored
- unsquelch
- unsquelched
- unsquelching
- unsuffixed
- unvalidated
- unveto
- unvetoed
@@ -358,7 +329,6 @@ words:
- venv
- vfalco
- vinnie
- vkeylet
- wasmi
- wextra
- wptr
@@ -369,7 +339,6 @@ words:
- xchain
- ximinez
- XMACRO
- xored
- xrpkuwait
- xrpl
- xrpld

View File

@@ -288,7 +288,7 @@ components with non-trivial changes are colored green.
validated.
![Sequence diagram](./negativeUNL_highLevel_sequence.png?raw=true "Negative UNL
Changes")
Changes")
## Roads Not Taken

Binary file not shown.

View File

@@ -34,7 +34,7 @@ higher index than the default Conan Center remote, so it is consulted first. You
can do this by running:
```bash
conan remote add --index 0 --force xrplf https://conan.xrplf.org/repository/conan/
conan remote add --index 0 --force xrplf https://conan.ripplex.io
```
Alternatively, you can pull our recipes from the repository and export them locally:

View File

@@ -6,52 +6,22 @@ This document explains how to set one up.
## Tested compiler versions
`xrpld` is built in the **C++23** dialect by default, so your toolchain has to
support it — see [compiler support for C++23][cpp23-support].
The versions currently tested in CI are:
`xrpld` is built in the **C++23** dialect by default.
Make sure your toolchain is recent enough — the compiler versions currently tested in CI are:
| Compiler | Version |
| ----------- | ------------------ |
| GCC | 15.2 |
| Clang | 22 |
| Apple Clang | 21 |
| MSVC | Visual Studio 2026 |
| Compiler | Version |
| ----------- | ------- |
| GCC | 15.2 |
| Clang | 22 |
| Apple Clang | 17 |
| MSVC | 19.44 |
LLVM tools (`clang-tidy` and `clang-format`) are also pinned to version 22.
### Older compilers
Older compilers may fail to build the latest `develop` code: the codebase now
relies on C++23 features and has been adjusted for `clang-tidy`.
If the latest code doesn't build for you, update your build toolchain first.
If updating isn't an option for you, we do accept pull requests that fix builds
on older compilers, as long as the change is small and doesn't make the code
harder to read. What we can't promise is that older compilers will keep working:
only the versions in the table above are tested in CI, and we won't hold back
the use of C++23 features or add invasive workarounds to keep an untested
compiler building. Treat support for anything outside the table as best-effort.
## Required tools
Besides a compiler, building `xrpld` requires:
| Tool | Minimum version |
| ------------------------------------------- | --------------- |
| [Git](https://git-scm.com/downloads) | any recent |
| [Python](https://www.python.org/downloads/) | 3.11 |
| [Conan](https://conan.io/downloads.html) | 2.17 |
| [CMake](https://cmake.org/download/) | 3.16 |
On Linux and macOS, the [Nix development shell](./nix.md) provides all of them
(see below). On Windows they have to be installed manually.
Once they are in place, verify that everything is installed and runnable with:
```bash
./bin/check-tools.sh
```
## Linux and macOS
The **recommended way** to get a development environment on Linux and macOS is
@@ -63,21 +33,25 @@ 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**, 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).
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).
See [Using the Nix development shell](./nix.md) for installation and usage
details, including how to select a different compiler and why we recommend Nix
over a hand-maintained environment.
details, including how to select a different compiler.
> [!NOTE]
> Using Nix is not mandatory. Any custom environment (Homebrew packages or
> anything else) will continue to work, but then it is up to you to keep it in
> sync with the environment used in CI. Nix unifies the development environment
> for everyone and synchronizes updates, which is why we recommend it.
### macOS: managing the Apple Clang version
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 [tested one](#tested-compiler-versions):
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):
```bash
clang --version
@@ -114,23 +88,23 @@ building xrpld. You may want to install and pin a specific version of Xcode:
Nix is not available on Windows, so the required tools have to be installed
manually:
- [Visual Studio 2026](https://visualstudio.microsoft.com/) with the
- [Visual Studio 2022](https://visualstudio.microsoft.com/) with the
**"Desktop development with C++"** workload — this provides MSVC and the
"x64 Native Tools Command Prompt". CI configures CMake with the
`Visual Studio 18 2026` generator.
"x64 Native Tools Command Prompt".
- [Git for Windows](https://git-scm.com/download/win)
- Python, Conan, and CMake, at the versions listed in
[Required tools](#required-tools).
- [Python 3.11](https://www.python.org/downloads/), or higher
- [Conan 2.17](https://conan.io/downloads.html), or higher
- [CMake 3.22](https://cmake.org/download/), or higher
> [!NOTE]
> Windows is used for development only and is not recommended for production.
## Clang-tidy
`clang-tidy` is required to run static analysis checks locally (see
[CONTRIBUTING.md](../../CONTRIBUTING.md)). It is not required to build the
project. The version this project uses is listed in
[Tested compiler versions](#tested-compiler-versions).
project. This project currently uses `clang-tidy` version 22.
On Linux and macOS, the [Nix development shell](./nix.md) provides that exact
version out of the box — run it via `run-clang-tidy`. No separate installation
is needed.
[cpp23-support]: https://en.cppreference.com/w/cpp/compiler_support/23
On Linux and macOS, the [Nix development shell](./nix.md) provides `clang-tidy`
22 out of the box — run it via `run-clang-tidy`. No separate installation is
needed.

96
docs/build/nix.md vendored
View File

@@ -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
- **Consistent compilers**: The GCC and Clang shells use the same versions as CI
- **Multiple compiler versions**: Easily switch between different GCC and Clang versions
- **Quick setup**: Get started with a single command
- **Works on Linux and macOS**: Consistent experience across platforms
@@ -31,23 +31,21 @@ This will:
- Download and set up all required development tools (CMake, Ninja, Conan, etc.)
- Configure the appropriate compiler for your platform:
- **Linux**: GCC (provided by Nix)
- **macOS**: Clang (provided by Nix)
- **Linux**: GCC 15.2 (provided by Nix)
- **macOS**: Apple Clang (your system compiler)
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.
### Platform notes
- **Linux**: `nix develop` gives you a shell with all the tooling necessary to develop xrpld
and with the same GCC/glibc toolchain that Nix builds for CI.
See [Choosing a different compiler](#choosing-a-different-compiler)
for the custom-vs-plain toolchain trade-off.
- **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)).
- **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)).
> [!TIP]
> To avoid typing `--experimental-features 'nix-command flakes'` every time, you can permanently enable flakes by creating `~/.config/nix/nix.conf`:
@@ -64,17 +62,7 @@ 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 .#clang`.
On Linux, `.#gcc` and `.#clang` provide the exact toolchain CI uses:
the compiler (pinned in [`nix/packages.nix`](../../nix/packages.nix))
rebuilt against the pinned custom glibc (see [`nix/compilers.nix`](../../nix/compilers.nix)).
Building that toolchain the first time is slow unless it is fetched from a Nix binary cache.
If you don't need the custom glibc, the Linux-only `.#gcc-plain` and `.#clang-plain`
give you the stock nixpkgs compilers of the same versions.
On macOS there is no custom glibc, so `.#gcc` and `.#clang` are already the plain nixpkgs toolchain,
and the `-plain` variants do not exist.
A compiler can be chosen by providing its name with the `.#` prefix, e.g. `nix develop .#gcc15`.
Use `nix flake show` to see all the available development shells.
Use `nix develop .#no-compiler` to use the compiler from your system.
@@ -82,18 +70,14 @@ Use `nix develop .#no-compiler` to use the compiler from your system.
### Example Usage
```bash
# Use GCC — same toolchain as CI (custom glibc on Linux)
nix develop .#gcc
# Use GCC 14
nix develop .#gcc14
# Use Clang — same toolchain as CI (custom glibc on Linux)
nix develop .#clang
# Use Clang 19
nix develop .#clang19
# Use default for your platform
nix develop
# Stock nixpkgs GCC/Clang, Linux only — skips the custom-glibc build, but does not match CI
nix develop .#gcc-plain
nix develop .#clang-plain
```
### Using a different shell
@@ -120,62 +104,28 @@ nix develop -c "$SHELL"
>
> If it doesn't, either adjust your shell configuration so it doesn't override `$PATH`, or use [direnv](#automatic-activation-with-direnv) (below), which loads the environment _after_ your shell config and so takes precedence regardless of the shell you use.
## Building xrpld in the Nix shell
## Building xrpld with Nix
Once inside the Nix development shell, follow the standard [build instructions](../../BUILD.md#steps). The Nix shell provides all necessary tools (CMake, Ninja, Conan, etc.).
Coverage builds (`-Dcoverage=ON`) work in the `gcc` shell (and `gcc-plain` on Linux):
each ships a `gcov` matching its compiler, since Nix's cc-wrapper does not expose one.
The `clang` shells do not include `llvm-cov`, so use a `gcc` shell for coverage.
## Conan configuration
The shell runs [`conan/init.sh`](../../conan/init.sh) on entry, so
[Set Up Conan](../../BUILD.md#set-up-conan) is already done for you. It installs
into the shell's own Conan home: `CONAN_HOME=~/.conan2-nix`.
### Prebuilt packages
On **Linux**, the binaries on the `xrplf` remote are built in this same Nix
environment — CI runs in Docker images that bundle the dev shell's toolchain (see
[`nix/docker`](../../nix/docker)) — so `.#gcc` and `.#clang` can reuse them. The
`-plain` shells do not match that toolchain's glibc, so binaries from the remote
are not a reliable match there.
On **macOS**, CI builds with Apple Clang, so the remote holds nothing for the Nix
`clang` toolchain and dependencies are compiled locally. We do not publish
Nix-built macOS binaries because a Conan package ID records the compiler version
but not the nixpkgs revision.
To compile everything from source, add `--build '*'` to the `conan install`
command.
## Automatic Activation with direnv
[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`.
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`.
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:
## Conan and Prebuilt Packages
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.
Please note that there is no guarantee that binaries from conan cache will work when using nix. If you encounter any errors, please use `--build '*'` to force conan to compile everything from source:
> [!NOTE]
> direnv only caches the `.direnv` directory (already listed in `.gitignore`); no other repository files are affected.
```bash
conan install .. --output-folder . --build '*' --settings build_type=Release
```
## Updating `flake.lock` file
To update `flake.lock` to the latest revision use `nix flake update` command.
## Tooling snapshots
The tool versions in each Nix environment are recorded in
[`nix/check-tools/`](../../nix/check-tools) and verified by CI. If you change the
environment (bump the CI image tag, update `flake.lock`, or edit the tool list in
`bin/check-tools.sh`), CI fails until you regenerate and commit the affected
snapshot — see [`nix/check-tools/README.md`](../../nix/check-tools/README.md).
## Troubleshooting
See [Troubleshooting Nix problems](./nix_troubleshooting.md) for common issues,

View File

@@ -3,78 +3,6 @@
Common issues encountered when using the [Nix development shell](./nix.md), and
how to resolve them.
## `command not found: nix` after a macOS update
If a shell suddenly can't find `nix` at all:
```
$ nix develop
zsh: command not found: nix
```
then Nix is almost certainly still installed — only the shell hook that puts it
on your `PATH` is gone. Confirm that first:
```bash
ls -l /nix/var/nix/profiles/default/bin/nix
```
If that exists, the installation is fine and this is purely a `PATH` problem.
### Why it happens
The installer does not touch your dotfiles. Instead it sources a setup script
from the Nix store by editing **system-wide** rc files:
| Shell | File the installer edits |
| ----- | ------------------------------------- |
| bash | `/etc/bashrc`, `/etc/bash.bashrc` |
| zsh | `/etc/zshrc` |
| fish | `$__fish_sysconf_dir/conf.d/nix.fish` |
macOS manages `/etc/zshrc`, so an OS update can replace it with the vendor copy
and silently drop the Nix block. `/etc/bashrc` and the fish file usually survive,
which is why the breakage often shows up in zsh only. You can verify this by
diffing against the backup the installer left behind:
```bash
diff /etc/zshrc /etc/zshrc.backup-before-nix
```
If they are identical, the Nix snippet was wiped. This is upstream issue
[NixOS/nix#3616](https://github.com/NixOS/nix/issues/3616).
### Fix
To unblock the current shell:
```bash
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
```
For a permanent fix, add the snippet to your **user** rc file rather than
restoring `/etc/zshrc` — user dotfiles are not clobbered by OS updates:
```bash
cat >>~/.zshrc <<'EOF'
# Nix
if [ -e '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh' ]; then
. '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh'
fi
# End Nix
EOF
```
The scripts guard against double-sourcing via `__ETC_PROFILE_NIX_SOURCED`, so
this is safe even if a system-wide hook is later restored.
> [!NOTE]
> `/etc/zshrc` and `~/.zshrc` are only read by **interactive** zsh. If the
> snippet is present but `zsh -c '…'`, a script, or an IDE terminal still can't
> find `nix`, that shell is non-interactive — put the snippet in `~/.zshenv`
> instead.
## Git worktrees
If `nix develop` fails with an error like:

24
docs/sample_chart.doc Normal file
View File

@@ -0,0 +1,24 @@
/*!
\page somestatechart Example state diagram
\startuml SomeState "my state diagram"
scale 600 width
[*] -> State1
State1 --> State2 : Succeeded
State1 --> [*] : Aborted
State2 --> State3 : Succeeded
State2 --> [*] : Aborted
state State3 {
state "Accumulate Enough Data\nLong State Name" as long1
long1 : Just a test
[*] --> long1
long1 --> long1 : New Data
long1 --> ProcessData : Enough Data
}
State3 --> State3 : Failed
State3 --> [*] : Succeeded / Save Result
State3 --> [*] : Aborted
\enduml
*/

23
flake.lock generated
View File

@@ -36,28 +36,7 @@
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"nixpkgs-custom-glibc": "nixpkgs-custom-glibc",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1784611586,
"narHash": "sha256-OfqgY+0hp/zseZB7uyH0U8kIDPS4scZZCyAurEplvG0=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "14f58845249f3552a89b07772626b8d3c632fa86",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
"nixpkgs-custom-glibc": "nixpkgs-custom-glibc"
}
}
},

View File

@@ -10,25 +10,12 @@
url = "github:NixOS/nixpkgs/9cd98386a38891d1074fc18036b842dc4416f562";
flake = false;
};
# Pinned Rust toolchains, delivered from the Nix store. Lets the Nix CI
# image and dev shell honour the single `rust-toolchain.toml` pin (shared
# with the rustup-based non-Nix runners) while staying hermetic — the
# toolchain lands in the image's Nix closure and is locked by flake.lock.
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
{
nixpkgs,
nixpkgs-custom-glibc,
rust-overlay,
...
}:
{ nixpkgs, nixpkgs-custom-glibc, ... }:
let
forEachSystem = import ./nix/utils.nix { inherit nixpkgs nixpkgs-custom-glibc rust-overlay; };
forEachSystem = import ./nix/utils.nix { inherit nixpkgs nixpkgs-custom-glibc; };
in
{
devShells = forEachSystem (import ./nix/devshell.nix);

View File

@@ -1,18 +1,17 @@
#pragma once
#include <filesystem>
#include <boost/filesystem.hpp>
namespace xrpl {
/**
* Extract a tar archive compressed with lz4
*
* @param src the path of the archive to be extracted
* @param dst the directory to extract to
*
* @throws runtime_error
*/
/** Extract a tar archive compressed with lz4
@param src the path of the archive to be extracted
@param dst the directory to extract to
@throws runtime_error
*/
void
extractTarLz4(std::filesystem::path const& src, std::filesystem::path const& dst);
extractTarLz4(boost::filesystem::path const& src, boost::filesystem::path const& dst);
} // namespace xrpl

View File

@@ -4,10 +4,9 @@
namespace xrpl {
/**
* Storage for linear binary data.
* Blocks of binary data appear often in various idioms and structures.
*/
/** Storage for linear binary data.
Blocks of binary data appear often in various idioms and structures.
*/
using Blob = std::vector<unsigned char>;
} // namespace xrpl

View File

@@ -3,18 +3,15 @@
#include <xrpl/basics/Slice.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <memory>
#include <utility>
namespace xrpl {
/**
* Like std::vector<char> but better.
* Meets the requirements of BufferFactory.
*/
/** Like std::vector<char> but better.
Meets the requirements of BufferFactory.
*/
class Buffer
{
private:
@@ -26,37 +23,30 @@ public:
Buffer() = default;
/**
* Create an uninitialized buffer with the given size.
*/
/** Create an uninitialized buffer with the given size. */
explicit Buffer(std::size_t size)
: p_((size != 0u) ? new std::uint8_t[size] : nullptr), size_(size)
{
}
/**
* Create a buffer as a copy of existing memory.
*
* @param data a pointer to the existing memory. If
* size is non-zero, it must not be null.
* @param size size of the existing memory block.
*/
/** Create a buffer as a copy of existing memory.
@param data a pointer to the existing memory. If
size is non-zero, it must not be null.
@param size size of the existing memory block.
*/
Buffer(void const* data, std::size_t size) : Buffer(size)
{
if (size != 0u)
std::memcpy(p_.get(), data, size);
}
/**
* Copy-construct
*/
/** Copy-construct */
Buffer(Buffer const& other) : Buffer(other.p_.get(), other.size_)
{
}
/**
* Copy assign
*/
/** Copy assign */
Buffer&
operator=(Buffer const& other)
{
@@ -68,19 +58,17 @@ public:
return *this;
}
/**
* Move-construct.
* The other buffer is reset.
*/
/** Move-construct.
The other buffer is reset.
*/
Buffer(Buffer&& other) noexcept : p_(std::move(other.p_)), size_(other.size_)
{
other.size_ = 0;
}
/**
* Move-assign.
* The other buffer is reset.
*/
/** Move-assign.
The other buffer is reset.
*/
Buffer&
operator=(Buffer&& other) noexcept
{
@@ -93,16 +81,12 @@ public:
return *this;
}
/**
* Construct from a slice
*/
/** Construct from a slice */
explicit Buffer(Slice s) : Buffer(s.data(), s.size())
{
}
/**
* Assign from slice
*/
/** Assign from slice */
Buffer&
operator=(Slice s)
{
@@ -116,9 +100,7 @@ public:
return *this;
}
/**
* Returns the number of bytes in the buffer.
*/
/** Returns the number of bytes in the buffer. */
[[nodiscard]] std::size_t
size() const noexcept
{
@@ -138,11 +120,10 @@ public:
return Slice{p_.get(), size_};
}
/**
* Return a pointer to beginning of the storage.
* @note The return type is guaranteed to be a pointer
* to a single byte, to facilitate pointer arithmetic.
*/
/** Return a pointer to beginning of the storage.
@note The return type is guaranteed to be a pointer
to a single byte, to facilitate pointer arithmetic.
*/
/** @{ */
[[nodiscard]] std::uint8_t const*
data() const noexcept
@@ -157,23 +138,9 @@ public:
}
/** @} */
/**
* Set every byte in the buffer to the given value.
*
* The size is unchanged, and this is a no-op on an empty buffer.
*
* @param value the byte to write to every position.
*/
void
fill(std::uint8_t value) noexcept
{
std::fill_n(p_.get(), size_, value);
}
/**
* Reset the buffer.
* All memory is deallocated. The resulting size is 0.
*/
/** Reset the buffer.
All memory is deallocated. The resulting size is 0.
*/
void
clear() noexcept
{
@@ -181,10 +148,9 @@ public:
size_ = 0;
}
/**
* Reallocate the storage.
* Existing data, if any, is discarded.
*/
/** Reallocate the storage.
Existing data, if any, is discarded.
*/
std::uint8_t*
alloc(std::size_t n)
{
@@ -240,4 +206,10 @@ operator==(Buffer const& lhs, Buffer const& rhs) noexcept
return std::memcmp(lhs.data(), rhs.data(), lhs.size()) == 0;
}
inline bool
operator!=(Buffer const& lhs, Buffer const& rhs) noexcept
{
return !(lhs == rhs);
}
} // namespace xrpl

View File

@@ -5,15 +5,13 @@
#include <lz4.h>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <stdexcept>
#include <vector>
namespace xrpl::compression_algorithms {
/**
* LZ4 block compression.
/** LZ4 block compression.
* @tparam BufferFactory Callable object or lambda.
* Takes the requested buffer size and returns allocated buffer pointer.
* @param in Data to compress
@@ -81,8 +79,7 @@ lz4Decompress(
return decompressedSize;
}
/**
* LZ4 block decompression.
/** LZ4 block decompression.
* @tparam InputStream ZeroCopyInputStream
* @param in Input source stream
* @param inSize Size of compressed data

View File

@@ -9,9 +9,7 @@
namespace xrpl {
/**
* Manages all counted object types.
*/
/** Manages all counted object types. */
class CountedObjects
{
public:
@@ -25,11 +23,10 @@ public:
getCounts(int minimumThreshold) const;
public:
/**
* Implementation for @ref CountedObject.
*
* @internal
*/
/** Implementation for @ref CountedObject.
@internal
*/
class Counter
{
public:
@@ -97,14 +94,13 @@ private:
//------------------------------------------------------------------------------
/**
* Tracks the number of instances of an object.
*
* Derived classes have their instances counted automatically. This is used
* for reporting purposes.
*
* @ingroup basics
*/
/** Tracks the number of instances of an object.
Derived classes have their instances counted automatically. This is used
for reporting purposes.
@ingroup basics
*/
template <class Object>
class CountedObject
{

View File

@@ -2,34 +2,31 @@
#include <chrono>
#include <cmath>
#include <cstddef>
namespace xrpl {
/**
* Sampling function using exponential decay to provide a continuous value.
* @tparam The number of seconds in the decay window.
*/
/** Sampling function using exponential decay to provide a continuous value.
@tparam The number of seconds in the decay window.
*/
template <int Window, typename Clock>
class DecayingSample
{
public:
using value_type = Clock::duration::rep;
using time_point = Clock::time_point;
using value_type = typename Clock::duration::rep;
using time_point = typename Clock::time_point;
DecayingSample() = delete;
/**
* @param now Start time of DecayingSample.
*/
@param now Start time of DecayingSample.
*/
explicit DecayingSample(time_point now) : value_(value_type()), when_(now)
{
}
/**
* Add a new sample.
* The value is first aged according to the specified time.
*/
/** Add a new sample.
The value is first aged according to the specified time.
*/
value_type
add(value_type value, time_point now)
{
@@ -38,10 +35,9 @@ public:
return value_ / Window;
}
/**
* Retrieve the current value in normalized units.
* The samples are first aged according to the specified time.
*/
/** Retrieve the current value in normalized units.
The samples are first aged according to the specified time.
*/
value_type
value(time_point now)
{
@@ -90,15 +86,14 @@ private:
//------------------------------------------------------------------------------
/**
* Sampling function using exponential decay to provide a continuous value.
* @tparam HalfLife The half life of a sample, in seconds.
*/
/** Sampling function using exponential decay to provide a continuous value.
@tparam HalfLife The half life of a sample, in seconds.
*/
template <int HalfLife, class Clock>
class DecayWindow
{
public:
using time_point = Clock::time_point;
using time_point = typename Clock::time_point;
explicit DecayWindow(time_point now) : when_(now)
{

View File

@@ -1,79 +1,22 @@
#pragma once
#include <cstddef>
#include <filesystem>
#include <boost/filesystem.hpp>
#include <boost/system/error_code.hpp>
#include <optional>
#include <string>
#include <system_error>
namespace xrpl {
std::string
getFileContents(
std::error_code& ec,
std::filesystem::path const& sourcePath,
boost::system::error_code& ec,
boost::filesystem::path const& sourcePath,
std::optional<std::size_t> maxSize = std::nullopt);
void
writeFileContents(
std::error_code& ec,
std::filesystem::path const& destPath,
boost::system::error_code& ec,
boost::filesystem::path const& destPath,
std::string const& contents);
/**
* Generate a unique, non-existing path under @p base whose filename starts with
* @p prefix and ends with a random hex suffix.
*
* Attempts up to @p maxAttempts paths. Throws `std::runtime_error` if a unique
* path cannot be found or if the filesystem returns an error while checking for
* existence.
*/
std::filesystem::path
uniqueRandomPath(
std::filesystem::path const& base,
std::string const& prefix = "",
std::size_t maxAttempts = 100);
/**
* RAII temporary directory.
*
* The directory and all its contents are deleted when
* the instance of `TempDir` is destroyed.
*/
class TempDir
{
std::filesystem::path path_;
public:
#if !GENERATING_DOCS
TempDir(TempDir const&) = delete;
TempDir&
operator=(TempDir const&) = delete;
#endif
/**
* Construct a temporary directory.
*/
TempDir();
/**
* Destroy a temporary directory.
*/
~TempDir();
/**
* Get the native path for the temporary directory.
*/
[[nodiscard]] std::string
path() const;
/**
* Get the native path for a file.
*
* The file does not need to exist.
*/
[[nodiscard]] std::string
file(std::string const& name) const;
};
} // namespace xrpl

View File

@@ -1,7 +1,6 @@
#pragma once
#include <concepts>
#include <cstddef>
#include <cstdint>
#include <type_traits>
#include <utility>
@@ -10,37 +9,33 @@ namespace xrpl {
//------------------------------------------------------------------------------
/**
* Tag to create an intrusive pointer from another intrusive pointer by using a
* static cast. This is useful to create an intrusive pointer to a derived
* class from an intrusive pointer to a base class.
*/
/** Tag to create an intrusive pointer from another intrusive pointer by using a
static cast. This is useful to create an intrusive pointer to a derived
class from an intrusive pointer to a base class.
*/
struct StaticCastTagSharedIntrusive
{
};
/**
* Tag to create an intrusive pointer from another intrusive pointer by using a
* dynamic cast. This is useful to create an intrusive pointer to a derived
* class from an intrusive pointer to a base class. If the cast fails an empty
* (null) intrusive pointer is created.
*/
/** Tag to create an intrusive pointer from another intrusive pointer by using a
dynamic cast. This is useful to create an intrusive pointer to a derived
class from an intrusive pointer to a base class. If the cast fails an empty
(null) intrusive pointer is created.
*/
struct DynamicCastTagSharedIntrusive
{
};
/**
* When creating or adopting a raw pointer, controls whether the strong count
* is incremented or not. Use this tag to increment the strong count.
*/
/** When creating or adopting a raw pointer, controls whether the strong count
is incremented or not. Use this tag to increment the strong count.
*/
struct SharedIntrusiveAdoptIncrementStrongTag
{
};
/**
* When creating or adopting a raw pointer, controls whether the strong count
* is incremented or not. Use this tag to leave the strong count unchanged.
*/
/** When creating or adopting a raw pointer, controls whether the strong count
is incremented or not. Use this tag to leave the strong count unchanged.
*/
struct SharedIntrusiveAdoptNoIncrementTag
{
};
@@ -54,21 +49,20 @@ concept CAdoptTag = std::is_same_v<T, SharedIntrusiveAdoptIncrementStrongTag> ||
//------------------------------------------------------------------------------
/**
* A shared intrusive pointer class that supports weak pointers.
*
* This is meant to be used for SHAMapInnerNodes, but may be useful for other
* cases. Since the reference counts are stored on the pointee, the pointee is
* not destroyed until both the strong _and_ weak pointer counts go to zero.
* When the strong pointer count goes to zero, the "partialDestructor" is
* called. This can be used to destroy as much of the object as possible while
* still retaining the reference counts. For example, for SHAMapInnerNodes the
* children may be reset in that function. Note that std::shared_pointer WILL
* run the destructor when the strong count reaches zero, but may not free the
* memory used by the object until the weak count reaches zero. In xrpld, we
* typically allocate shared pointers with the `make_shared` function. When
* that is used, the memory is not reclaimed until the weak count reaches zero.
*/
/** A shared intrusive pointer class that supports weak pointers.
This is meant to be used for SHAMapInnerNodes, but may be useful for other
cases. Since the reference counts are stored on the pointee, the pointee is
not destroyed until both the strong _and_ weak pointer counts go to zero.
When the strong pointer count goes to zero, the "partialDestructor" is
called. This can be used to destroy as much of the object as possible while
still retaining the reference counts. For example, for SHAMapInnerNodes the
children may be reset in that function. Note that std::shared_pointer WILL
run the destructor when the strong count reaches zero, but may not free the
memory used by the object until the weak count reaches zero. In xrpld, we
typically allocate shared pointers with the `make_shared` function. When
that is used, the memory is not reclaimed until the weak count reaches zero.
*/
template <class T>
class SharedIntrusive
{
@@ -96,6 +90,9 @@ public:
SharedIntrusive&
operator=(SharedIntrusive const& rhs);
bool
operator!=(std::nullptr_t) const;
bool
operator==(std::nullptr_t) const;
@@ -113,9 +110,8 @@ public:
operator=(
SharedIntrusive<TT>&& rhs); // NOLINT(cppcoreguidelines-rvalue-reference-param-not-moved)
/**
* Adopt the raw pointer. The strong reference may or may not be
* incremented, depending on the TAdoptTag
/** Adopt the raw pointer. The strong reference may or may not be
incremented, depending on the TAdoptTag
*/
template <CAdoptTag TAdoptTag = SharedIntrusiveAdoptIncrementStrongTag>
void
@@ -123,31 +119,27 @@ public:
~SharedIntrusive();
/**
* Create a new SharedIntrusive by statically casting the pointer
* controlled by the rhs param.
*/
/** Create a new SharedIntrusive by statically casting the pointer
controlled by the rhs param.
*/
template <class TT>
SharedIntrusive(StaticCastTagSharedIntrusive, SharedIntrusive<TT> const& rhs);
/**
* Create a new SharedIntrusive by statically casting the pointer
* controlled by the rhs param.
*/
/** Create a new SharedIntrusive by statically casting the pointer
controlled by the rhs param.
*/
template <class TT>
SharedIntrusive(StaticCastTagSharedIntrusive, SharedIntrusive<TT>&& rhs);
/**
* Create a new SharedIntrusive by dynamically casting the pointer
* controlled by the rhs param.
*/
/** Create a new SharedIntrusive by dynamically casting the pointer
controlled by the rhs param.
*/
template <class TT>
SharedIntrusive(DynamicCastTagSharedIntrusive, SharedIntrusive<TT> const& rhs);
/**
* Create a new SharedIntrusive by dynamically casting the pointer
* controlled by the rhs param.
*/
/** Create a new SharedIntrusive by dynamically casting the pointer
controlled by the rhs param.
*/
template <class TT>
SharedIntrusive(DynamicCastTagSharedIntrusive, SharedIntrusive<TT>&& rhs);
@@ -160,22 +152,17 @@ public:
explicit
operator bool() const noexcept;
/**
* Set the pointer to null, decrement the strong count, and run the
* appropriate release action.
*/
/** Set the pointer to null, decrement the strong count, and run the
appropriate release action.
*/
void
reset();
/**
* Get the raw pointer
*/
/** Get the raw pointer */
[[nodiscard]] T*
get() const;
/**
* Return the strong count
*/
/** Return the strong count */
[[nodiscard]] std::size_t
useCount() const;
@@ -193,51 +180,43 @@ public:
friend class WeakIntrusive;
private:
/**
* Return the raw pointer held by this object.
*/
/** Return the raw pointer held by this object. */
[[nodiscard]] T*
unsafeGetRawPtr() const;
/**
* Exchange the current raw pointer held by this object with the given
* pointer. Decrement the strong count of the raw pointer previously held
* by this object and run the appropriate release action.
/** Exchange the current raw pointer held by this object with the given
pointer. Decrement the strong count of the raw pointer previously held
by this object and run the appropriate release action.
*/
void
unsafeReleaseAndStore(T* next);
/**
* Set the raw pointer directly. This is wrapped in a function so the class
* can support both atomic and non-atomic pointers in a future patch.
/** Set the raw pointer directly. This is wrapped in a function so the class
can support both atomic and non-atomic pointers in a future patch.
*/
void
unsafeSetRawPtr(T* p);
/**
* Exchange the raw pointer directly.
* This sets the raw pointer to the given value and returns the previous
* value. This is wrapped in a function so the class can support both
* atomic and non-atomic pointers in a future patch.
/** Exchange the raw pointer directly.
This sets the raw pointer to the given value and returns the previous
value. This is wrapped in a function so the class can support both
atomic and non-atomic pointers in a future patch.
*/
T*
unsafeExchange(T* p);
/**
* pointer to the type with an intrusive count
*/
/** pointer to the type with an intrusive count */
T* ptr_{nullptr};
};
//------------------------------------------------------------------------------
/**
* A weak intrusive pointer class for the SharedIntrusive pointer class.
*
* Note that this weak pointer class asks differently from normal weak pointer
* classes. When the strong pointer count goes to zero, the "partialDestructor"
* is called. See the comment on SharedIntrusive for a fuller explanation.
*/
/** A weak intrusive pointer class for the SharedIntrusive pointer class.
Note that this weak pointer class asks differently from normal weak pointer
classes. When the strong pointer count goes to zero, the "partialDestructor"
is called. See the comment on SharedIntrusive for a fuller explanation.
*/
template <class T>
class WeakIntrusive
{
@@ -267,62 +246,54 @@ public:
WeakIntrusive&
operator=(SharedIntrusive<TT> const& rhs);
/**
* Adopt the raw pointer and increment the weak count.
*/
/** Adopt the raw pointer and increment the weak count. */
void
adopt(T* ptr);
~WeakIntrusive();
/**
* Get a strong pointer from the weak pointer, if possible. This will
* only return a seated pointer if the strong count on the raw pointer
* is non-zero before locking.
/** Get a strong pointer from the weak pointer, if possible. This will
only return a seated pointer if the strong count on the raw pointer
is non-zero before locking.
*/
SharedIntrusive<T>
lock() const;
/**
* Return true if the strong count is zero.
*/
/** Return true if the strong count is zero. */
[[nodiscard]] bool
expired() const;
/**
* Set the pointer to null and decrement the weak count.
*
* Note: This may run the destructor if the strong count is zero.
*/
/** Set the pointer to null and decrement the weak count.
Note: This may run the destructor if the strong count is zero.
*/
void
reset();
private:
T* ptr_ = nullptr;
/**
* Decrement the weak count. This does _not_ set the raw pointer to
* null.
*
* Note: This may run the destructor if the strong count is zero.
*/
/** Decrement the weak count. This does _not_ set the raw pointer to
null.
Note: This may run the destructor if the strong count is zero.
*/
void
unsafeReleaseNoStore();
};
//------------------------------------------------------------------------------
/**
* A combination of a strong and a weak intrusive pointer stored in the
* space of a single pointer.
*
* This class is similar to a `std::variant<SharedIntrusive,WeakIntrusive>`
* with some optimizations. In particular, it uses a low-order bit to
* determine if the raw pointer represents a strong pointer or a weak
* pointer. It can also be quickly switched between its strong pointer and
* weak pointer representations. This class is useful for storing intrusive
* pointers in tagged caches.
*/
/** A combination of a strong and a weak intrusive pointer stored in the
space of a single pointer.
This class is similar to a `std::variant<SharedIntrusive,WeakIntrusive>`
with some optimizations. In particular, it uses a low-order bit to
determine if the raw pointer represents a strong pointer or a weak
pointer. It can also be quickly switched between its strong pointer and
weak pointer representations. This class is useful for storing intrusive
pointers in tagged caches.
*/
template <class T>
class SharedWeakUnion
@@ -364,83 +335,69 @@ public:
~SharedWeakUnion();
/**
* Return a strong pointer if this is already a strong pointer (i.e.
* don't lock the weak pointer. Use the `lock` method if that's what's
* needed)
/** Return a strong pointer if this is already a strong pointer (i.e.
don't lock the weak pointer. Use the `lock` method if that's what's
needed)
*/
[[nodiscard]] SharedIntrusive<T>
getStrong() const;
/**
* Return true if this is a strong pointer and the strong pointer is
* seated.
/** Return true if this is a strong pointer and the strong pointer is
seated.
*/
explicit
operator bool() const noexcept;
/**
* Set the pointer to null, decrement the appropriate ref count, and
* run the appropriate release action.
/** Set the pointer to null, decrement the appropriate ref count, and
run the appropriate release action.
*/
void
reset();
/**
* If this is a strong pointer, return the raw pointer. Otherwise
* return null.
/** If this is a strong pointer, return the raw pointer. Otherwise
return null.
*/
[[nodiscard]] T*
get() const;
/**
* If this is a strong pointer, return the strong count. Otherwise
/** If this is a strong pointer, return the strong count. Otherwise
* return 0
*/
[[nodiscard]] std::size_t
useCount() const;
/**
* Return true if there is a non-zero strong count.
*/
/** Return true if there is a non-zero strong count. */
[[nodiscard]] bool
expired() const;
/**
* If this is a strong pointer, return the strong pointer. Otherwise
* attempt to lock the weak pointer.
/** If this is a strong pointer, return the strong pointer. Otherwise
attempt to lock the weak pointer.
*/
[[nodiscard]] SharedIntrusive<T>
lock() const;
/**
* Return true is this represents a strong pointer.
*/
/** Return true is this represents a strong pointer. */
[[nodiscard]] bool
isStrong() const;
/**
* Return true is this represents a weak pointer.
*/
/** Return true is this represents a weak pointer. */
[[nodiscard]] bool
isWeak() const;
/**
* If this is a weak pointer, attempt to convert it to a strong
* pointer.
*
* @return true if successfully converted to a strong pointer (or was
* already a strong pointer). Otherwise false.
*/
/** If this is a weak pointer, attempt to convert it to a strong
pointer.
@return true if successfully converted to a strong pointer (or was
already a strong pointer). Otherwise false.
*/
bool
convertToStrong();
/**
* If this is a strong pointer, attempt to convert it to a weak
* pointer.
*
* @return false if the pointer is null. Otherwise return true.
*/
/** If this is a strong pointer, attempt to convert it to a weak
pointer.
@return false if the pointer is null. Otherwise return true.
*/
bool
convertToWeak();
@@ -453,27 +410,23 @@ private:
static constexpr std::uintptr_t kPtrMask = ~kTagMask;
private:
/**
* Return the raw pointer held by this object.
/** Return the raw pointer held by this object.
*/
[[nodiscard]] T*
unsafeGetRawPtr() const;
enum class RefStrength { Strong, Weak };
/**
* Set the raw pointer and tag bit directly.
/** Set the raw pointer and tag bit directly.
*/
void
unsafeSetRawPtr(T* p, RefStrength rs);
/**
* Set the raw pointer and tag bit to all zeros (strong null pointer).
/** Set the raw pointer and tag bit to all zeros (strong null pointer).
*/
void unsafeSetRawPtr(std::nullptr_t);
/**
* Decrement the appropriate ref count, and run the appropriate release
* action. Note: this does _not_ set the raw pointer to null.
/** Decrement the appropriate ref count, and run the appropriate release
action. Note: this does _not_ set the raw pointer to null.
*/
void
unsafeReleaseNoStore();
@@ -481,13 +434,12 @@ private:
//------------------------------------------------------------------------------
/**
* Create a shared intrusive pointer.
*
* Note: unlike std::shared_ptr, where there is an advantage of allocating
* the pointer and control block together, there is no benefit for intrusive
* pointers.
*/
/** Create a shared intrusive pointer.
Note: unlike std::shared_ptr, where there is an advantage of allocating
the pointer and control block together, there is no benefit for intrusive
pointers.
*/
template <class TT, class... Args>
SharedIntrusive<TT>
makeSharedIntrusive(Args&&... args)

View File

@@ -111,6 +111,13 @@ SharedIntrusive<T>::operator=(SharedIntrusive<TT>&& rhs)
return *this;
}
template <class T>
bool
SharedIntrusive<T>::operator!=(std::nullptr_t) const
{
return this->get() != nullptr;
}
template <class T>
bool
SharedIntrusive<T>::operator==(std::nullptr_t) const
@@ -634,9 +641,6 @@ template <class T>
T*
SharedWeakUnion<T>::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<T*>(tp_ & kPtrMask);
}

View File

@@ -3,43 +3,39 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <atomic>
#include <cstddef>
#include <cstdint>
namespace xrpl {
/**
* Action to perform when releasing a strong pointer.
*
* noop: Do nothing. For example, a `noop` action will occur when a count is
* decremented to a non-zero value.
*
* partialDestroy: Run the `partialDestructor`. This action will happen when a
* strong count is decremented to zero and the weak count is non-zero.
*
* destroy: Run the destructor. This action will occur when either the strong
* count or weak count is decremented and the other count is also zero.
/** Action to perform when releasing a strong pointer.
noop: Do nothing. For example, a `noop` action will occur when a count is
decremented to a non-zero value.
partialDestroy: Run the `partialDestructor`. This action will happen when a
strong count is decremented to zero and the weak count is non-zero.
destroy: Run the destructor. This action will occur when either the strong
count or weak count is decremented and the other count is also zero.
*/
enum class ReleaseStrongRefAction { NoOp, PartialDestroy, Destroy };
/**
* Action to perform when releasing a weak pointer.
*
* noop: Do nothing. For example, a `noop` action will occur when a count is
* decremented to a non-zero value.
*
* destroy: Run the destructor. This action will occur when either the strong
* count or weak count is decremented and the other count is also zero.
/** Action to perform when releasing a weak pointer.
noop: Do nothing. For example, a `noop` action will occur when a count is
decremented to a non-zero value.
destroy: Run the destructor. This action will occur when either the strong
count or weak count is decremented and the other count is also zero.
*/
enum class ReleaseWeakRefAction { NoOp, Destroy };
/**
* Implement the strong count, weak count, and bit flags for an intrusive
* pointer.
*
* A class can satisfy the requirements of an xrpl::IntrusivePointer by
* inheriting from this class.
*/
/** Implement the strong count, weak count, and bit flags for an intrusive
pointer.
A class can satisfy the requirements of an xrpl::IntrusivePointer by
inheriting from this class.
*/
struct IntrusiveRefCounts
{
virtual ~IntrusiveRefCounts() noexcept;
@@ -108,123 +104,109 @@ private:
static constexpr size_t kFieldTypeBits = sizeof(FieldType) * 8;
static constexpr FieldType kOne = 1;
/**
* `refCounts` consists of four fields that are treated atomically:
*
* 1. Strong count. This is a count of the number of shared pointers that
* hold a reference to this object. When the strong counts goes to zero,
* if the weak count is zero, the destructor is run. If the weak count is
* non-zero when the strong count goes to zero then the partialDestructor
* is run.
*
* 2. Weak count. This is a count of the number of weak pointer that hold
* a reference to this object. When the weak count goes to zero and the
* strong count is also zero, then the destructor is run.
*
* 3. Partial destroy started bit. This bit is set if the
* `partialDestructor` function has been started (or is about to be
* started). This is used to prevent the destructor from running
* concurrently with the partial destructor. This can easily happen when
* the last strong pointer release its reference in one thread and starts
* the partialDestructor, while in another thread the last weak pointer
* goes out of scope and starts the destructor while the partialDestructor
* is still running. Both a start and finished bit is needed to handle a
* corner-case where the last strong pointer goes out of scope, then then
* last `weakPointer` goes out of scope, but this happens before the
* `partialDestructor` bit is set. It would be possible to use a single
* bit if it could also be set atomically when the strong count goes to
* zero and the weak count is non-zero, but that would add complexity (and
* likely slow down common cases as well).
*
* 4. Partial destroy finished bit. This bit is set when the
* `partialDestructor` has finished running. See (3) above for more
* information.
*/
/** `refCounts` consists of four fields that are treated atomically:
1. Strong count. This is a count of the number of shared pointers that
hold a reference to this object. When the strong counts goes to zero,
if the weak count is zero, the destructor is run. If the weak count is
non-zero when the strong count goes to zero then the partialDestructor
is run.
2. Weak count. This is a count of the number of weak pointer that hold
a reference to this object. When the weak count goes to zero and the
strong count is also zero, then the destructor is run.
3. Partial destroy started bit. This bit is set if the
`partialDestructor` function has been started (or is about to be
started). This is used to prevent the destructor from running
concurrently with the partial destructor. This can easily happen when
the last strong pointer release its reference in one thread and starts
the partialDestructor, while in another thread the last weak pointer
goes out of scope and starts the destructor while the partialDestructor
is still running. Both a start and finished bit is needed to handle a
corner-case where the last strong pointer goes out of scope, then then
last `weakPointer` goes out of scope, but this happens before the
`partialDestructor` bit is set. It would be possible to use a single
bit if it could also be set atomically when the strong count goes to
zero and the weak count is non-zero, but that would add complexity (and
likely slow down common cases as well).
4. Partial destroy finished bit. This bit is set when the
`partialDestructor` has finished running. See (3) above for more
information.
*/
mutable std::atomic<FieldType> refCounts_{kStrongDelta};
/**
* Amount to change the strong count when adding or releasing a reference
*
* Note: The strong count is stored in the low `StrongCountNumBits` bits
* of refCounts
*/
/** Amount to change the strong count when adding or releasing a reference
Note: The strong count is stored in the low `StrongCountNumBits` bits
of refCounts
*/
static constexpr FieldType kStrongDelta = 1;
/**
* Amount to change the weak count when adding or releasing a reference
*
* Note: The weak count is stored in the high `WeakCountNumBits` bits of
* refCounts
*/
/** Amount to change the weak count when adding or releasing a reference
Note: The weak count is stored in the high `WeakCountNumBits` bits of
refCounts
*/
static constexpr FieldType kWeakDelta = (kOne << kStrongCountNumBits);
/**
* Flag that is set when the partialDestroy function has started running
* (or is about to start running).
*
* See description of the `refCounts` field for a fuller description of
* this field.
*/
/** Flag that is set when the partialDestroy function has started running
(or is about to start running).
See description of the `refCounts` field for a fuller description of
this field.
*/
static constexpr FieldType kPartialDestroyStartedMask = (kOne << (kFieldTypeBits - 1));
/**
* Flag that is set when the partialDestroy function has finished running
*
* See description of the `refCounts` field for a fuller description of
* this field.
*/
/** Flag that is set when the partialDestroy function has finished running
See description of the `refCounts` field for a fuller description of
this field.
*/
static constexpr FieldType kPartialDestroyFinishedMask = (kOne << (kFieldTypeBits - 2));
/**
* Mask that will zero out all the `count` bits and leave the tag bits
* unchanged.
*/
/** Mask that will zero out all the `count` bits and leave the tag bits
unchanged.
*/
static constexpr FieldType kTagMask = kPartialDestroyStartedMask | kPartialDestroyFinishedMask;
/**
* Mask that will zero out the `tag` bits and leave the count bits
* unchanged.
*/
/** Mask that will zero out the `tag` bits and leave the count bits
unchanged.
*/
static constexpr FieldType kValueMask = ~kTagMask;
/**
* Mask that will zero out everything except the strong count.
/** Mask that will zero out everything except the strong count.
*/
static constexpr FieldType kStrongMask = ((kOne << kStrongCountNumBits) - 1) & kValueMask;
/**
* Mask that will zero out everything except the weak count.
/** Mask that will zero out everything except the weak count.
*/
static constexpr FieldType kWeakMask =
(((kOne << kWeakCountNumBits) - 1) << kStrongCountNumBits) & kValueMask;
/**
* Unpack the count and tag fields from the packed atomic integer form.
*/
/** Unpack the count and tag fields from the packed atomic integer form. */
struct RefCountPair
{
CountType strong;
CountType weak;
/**
* The `partialDestroyStartedBit` is set to on when the partial
* destroy function is started. It is not a boolean; it is a uint32
* with all bits zero with the possible exception of the
* `partialDestroyStartedMask` bit. This is done so it can be directly
* masked into the `combinedValue`.
/** The `partialDestroyStartedBit` is set to on when the partial
destroy function is started. It is not a boolean; it is a uint32
with all bits zero with the possible exception of the
`partialDestroyStartedMask` bit. This is done so it can be directly
masked into the `combinedValue`.
*/
FieldType partialDestroyStartedBit{0};
/**
* The `partialDestroyFinishedBit` is set to on when the partial
* destroy function has finished.
/** The `partialDestroyFinishedBit` is set to on when the partial
destroy function has finished.
*/
FieldType partialDestroyFinishedBit{0};
RefCountPair(FieldType v) noexcept;
RefCountPair(CountType s, CountType w) noexcept;
/**
* Convert back to the packed integer form.
*/
/** Convert back to the packed integer form. */
[[nodiscard]] FieldType
combinedValue() const noexcept;
@@ -232,10 +214,9 @@ private:
static_cast<CountType>((kOne << kStrongCountNumBits) - 1);
static constexpr CountType kMaxWeakValue =
static_cast<CountType>((kOne << kWeakCountNumBits) - 1);
/**
* Put an extra margin to detect when running up against limits.
* This is only used in debug code, and is useful if we reduce the
* number of bits in the strong and weak counts (to 16 and 14 bits).
/** Put an extra margin to detect when running up against limits.
This is only used in debug code, and is useful if we reduce the
number of bits in the strong and weak counts (to 16 and 14 bits).
*/
static constexpr CountType kCheckStrongMaxValue = kMaxStrongValue - 32;
static constexpr CountType kCheckWeakMaxValue = kMaxWeakValue - 32;

View File

@@ -70,15 +70,11 @@ public:
{
}
/**
* Stores instance of T specific to the calling coroutine or thread.
*/
/** Stores instance of T specific to the calling coroutine or thread. */
T&
operator*();
/**
* Stores instance of T specific to the calling coroutine or thread.
*/
/** Stores instance of T specific to the calling coroutine or thread. */
T*
operator->()
{

View File

@@ -1,24 +1,21 @@
#pragma once
#include <xrpl/basics/UnorderedContainers.h>
#include <xrpl/beast/utility/Journal.h>
#include <boost/beast/core/string.hpp>
#include <boost/filesystem.hpp>
#include <filesystem>
#include <fstream>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <utility>
#include <vector>
namespace xrpl {
/**
* Manages partitions for logging.
*/
/** Manages partitions for logging. */
class Logs
{
private:
@@ -42,81 +39,69 @@ private:
writeAlways(beast::Severity level, std::string const& text) override;
};
/**
* Manages a system file containing logged output.
* The system file remains open during program execution. Interfaces
* are provided for interoperating with standard log management
* tools like logrotate(8):
* http://linuxcommand.org/man_pages/logrotate8.html
* @note None of the listed interfaces are thread-safe.
*/
/** Manages a system file containing logged output.
The system file remains open during program execution. Interfaces
are provided for interoperating with standard log management
tools like logrotate(8):
http://linuxcommand.org/man_pages/logrotate8.html
@note None of the listed interfaces are thread-safe.
*/
class File
{
public:
/**
* Construct with no associated system file.
* A system file may be associated later with @ref open.
* @see open
*/
/** Construct with no associated system file.
A system file may be associated later with @ref open.
@see open
*/
File();
/**
* Destroy the object.
* If a system file is associated, it will be flushed and closed.
*/
/** Destroy the object.
If a system file is associated, it will be flushed and closed.
*/
~File() = default;
/**
* Determine if a system file is associated with the log.
* @return `true` if a system file is associated and opened for
* writing.
*/
/** Determine if a system file is associated with the log.
@return `true` if a system file is associated and opened for
writing.
*/
[[nodiscard]] bool
isOpen() const noexcept;
/**
* Associate a system file with the log.
* If the file does not exist an attempt is made to create it
* and open it for writing. If the file already exists an attempt is
* made to open it for appending.
* If a system file is already associated with the log, it is closed
* first.
* @return `true` if the file was opened.
*/
/** Associate a system file with the log.
If the file does not exist an attempt is made to create it
and open it for writing. If the file already exists an attempt is
made to open it for appending.
If a system file is already associated with the log, it is closed
first.
@return `true` if the file was opened.
*/
bool
open(std::filesystem::path const& path);
open(boost::filesystem::path const& path);
/**
* Close and re-open the system file associated with the log
* This assists in interoperating with external log management tools.
* @return `true` if the file was opened.
*/
/** Close and re-open the system file associated with the log
This assists in interoperating with external log management tools.
@return `true` if the file was opened.
*/
bool
closeAndReopen();
/**
* Close the system file if it is open.
*/
/** Close the system file if it is open. */
void
close();
/**
* write to the log file.
* Does nothing if there is no associated system file.
*/
/** write to the log file.
Does nothing if there is no associated system file.
*/
void
write(char const* text);
/**
* write to the log file and append an end of line marker.
* Does nothing if there is no associated system file.
*/
/** write to the log file and append an end of line marker.
Does nothing if there is no associated system file.
*/
void
writeln(char const* text);
/**
* Write to the log file using std::string.
*/
/** Write to the log file using std::string. */
/** @{ */
void
write(std::string const& str)
@@ -133,7 +118,7 @@ private:
private:
std::unique_ptr<std::ofstream> stream_;
std::filesystem::path path_;
boost::filesystem::path path_;
};
std::mutex mutable mutex_;
@@ -152,7 +137,7 @@ public:
virtual ~Logs() = default;
bool
open(std::filesystem::path const& pathToLogFile);
open(boost::filesystem::path const& pathToLogFile);
beast::Journal::Sink&
get(std::string const& name);
@@ -221,7 +206,8 @@ private:
#ifndef JLOG
#define JLOG(x) \
if (!(x)) \
; \
{ \
} \
else \
x
#endif
@@ -237,21 +223,19 @@ private:
//------------------------------------------------------------------------------
// Debug logging:
/**
* Set the sink for the debug journal.
*
* @param sink unique_ptr to new debug Sink.
* @return unique_ptr to the previous Sink. nullptr if there was no Sink.
*/
/** Set the sink for the debug journal.
@param sink unique_ptr to new debug Sink.
@return unique_ptr to the previous Sink. nullptr if there was no Sink.
*/
std::unique_ptr<beast::Journal::Sink>
setDebugLogSink(std::unique_ptr<beast::Journal::Sink> sink);
/**
* Returns a debug journal.
* The journal may drain to a null sink, so its output
* may never be seen. Never use it for critical
* information.
*/
/** Returns a debug journal.
The journal may drain to a null sink, so its output
may never be seen. Never use it for critical
information.
*/
beast::Journal
debugLog();

View File

@@ -6,8 +6,7 @@
namespace xrpl {
/**
* Calculate one number divided by another number in percentage.
/** Calculate one number divided by another number in percentage.
* The result is rounded up to the next integer, and capped in the range [0,100]
* E.g. calculatePercent(1, 100) = 1 because 1/100 = 0.010000
* calculatePercent(1, 99) = 2 because 1/99 = 0.010101
@@ -20,7 +19,7 @@ namespace xrpl {
* @return the percentage, in [0, 100]
*
* @note total cannot be zero.
*/
* */
constexpr std::size_t
calculatePercent(std::size_t count, std::size_t total)
{

View File

@@ -2,9 +2,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <limits>
@@ -13,8 +11,7 @@
#include <set>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <unordered_map>
namespace xrpl {
@@ -47,54 +44,46 @@ isPowerOfTen(T value)
namespace detail {
/**
* Builds a table of the powers of 10
/** Builds a table of the powers of 10
*
* This function is marked consteval, so it can only be run in
* a constexpr context. This assures that it is and can only be run at
* compile time. Doing it at runtime would be pretty wasteful and
* inefficient.
*/
constexpr std::size_t kUint64Digits = 20;
[[maybe_unused]] constexpr std::size_t kUint128Digits = 39;
template <typename T, std::size_t Digits>
consteval std::array<T, Digits>
constexpr std::size_t kInt64Digits = 20;
consteval std::array<std::uint64_t, kInt64Digits>
buildPowersOfTen()
{
std::array<T, Digits> result{};
std::array<std::uint64_t, kInt64Digits> result{};
T power = 1;
std::uint64_t power = 1;
std::size_t exponent = 0;
// end the loop early so it doesn't overflow;
for (; exponent < result.size() - 1; ++exponent, power *= 10)
{
result[exponent] = power;
if (power > std::numeric_limits<T>::max() / 10)
if (power > std::numeric_limits<std::uint64_t>::max() / 10)
throw std::logic_error("Power of 10 table is too big");
}
result[exponent] = power;
if (power < std::numeric_limits<T>::max() / 10)
throw std::logic_error("Power of 10 table is not big enough for the given type");
if (power < std::numeric_limits<std::uint64_t>::max() / 10)
throw std::logic_error("Power of 10 table is not big enough for the uint64_t type");
return result;
}
} // namespace detail
template <typename T = std::uint64_t, std::size_t Digits = detail::kUint64Digits>
constexpr std::array<T, Digits> kPowerOfTenImpl = detail::buildPowersOfTen<T, Digits>();
constexpr auto kPowerOfTen = kPowerOfTenImpl<std::uint64_t, detail::kUint64Digits>;
constexpr std::array<std::uint64_t, detail::kInt64Digits> kPowerOfTen = detail::buildPowersOfTen();
static_assert(kPowerOfTen[0] == 1);
static_assert(kPowerOfTen[1] == 10);
static_assert(kPowerOfTen[10] == 10'000'000'000);
static_assert(
isPowerOfTen(kPowerOfTen.back()) && *logTen(kPowerOfTen.back()) == detail::kUint64Digits - 1);
isPowerOfTen(kPowerOfTen.back()) && *logTen(kPowerOfTen.back()) == detail::kInt64Digits - 1);
/**
* MantissaRange defines a range for the mantissa of a normalized Number.
/** MantissaRange defines a range for the mantissa of a normalized Number.
*
* The mantissa is in the range [min, max], where
* * min is a power of 10, and
@@ -131,37 +120,17 @@ struct MantissaRange final
{
using rep = std::uint64_t;
// NOLINTBEGIN(readability-enum-initial-value)
// The values don't matter, except for Large
enum class MantissaScale {
// Small can be removed when either featureSingleAssetVault or featureLendingProtocol are
// retired
Small,
// LargeLegacy can be removed when fixCleanup3_2_0 is retired
LargeLegacy,
// Large320 can be removed when fixCleanup3_3_0 is retired
Large320,
// If Large330 is ever the only remaining "Large*" entry, it can be renamed to just "Large".
Large330,
// Large is a de-facto alias for "the latest", and is only here for backward compatibility
// in the extremely unlikely case that a downstream project made use of it. Note that
// because the behavior changed, this may still be a breaking change.
Large = Large330,
Large,
};
// NOLINTEND(readability-enum-initial-value)
// This entire enum can be removed when the last relevant amendment is retired
enum class CuspRoundingFix : std::uint8_t {
// Disabled can be removed when fixCleanup3_2_0 is retired
Disabled = 0,
// Enabled320 can be removed when fixCleanup3_3_0 is retired
Enabled320 = 1,
// If we ever get to the point that there's only one entry, remove the entire enum
Enabled330 = 2,
// Enabled is a de-facto alias for "the latest", and is only here for backward compatibility
// in the extremely unlikely case that a downstream project made use of it. Note that
// because the behavior changed, this may still be a breaking change.
Enabled = Enabled330,
// This entire enum can be removed when fixCleanup3_2_0 is retired
enum class CuspRoundingFix : bool {
Disabled = false,
Enabled = true,
};
explicit constexpr MantissaRange(MantissaScale sc) : scale(sc)
@@ -172,27 +141,13 @@ struct MantissaRange final
int const log{getExponent(scale)};
rep const min{getMin(scale, log)};
rep const max{(min * 10) - 1};
CuspRoundingFix const cuspRoundingFix{isCuspFixEnabled(scale)};
CuspRoundingFix const cuspRoundingFixEnabled{isCuspFixEnabled(scale)};
static MantissaRange const&
getMantissaRange(MantissaScale scale);
static std::set<MantissaScale> const&
getAllScales()
{
static std::set<MantissaRange::MantissaScale> const kScales = {
MantissaRange::MantissaScale::Small,
MantissaRange::MantissaScale::LargeLegacy,
MantissaRange::MantissaScale::Large320,
MantissaRange::MantissaScale::Large330,
};
return kScales;
}
class Access
{
static constexpr MantissaRange const&
mantissaRange(MantissaScale scale);
friend Number;
};
getAllScales();
private:
static constexpr int
@@ -203,8 +158,7 @@ private:
case MantissaScale::Small:
return 15;
case MantissaScale::LargeLegacy:
case MantissaScale::Large320:
case MantissaScale::Large330:
case MantissaScale::Large:
return 18;
// LCOV_EXCL_START
default:
@@ -233,24 +187,24 @@ private:
case MantissaScale::Small:
case MantissaScale::LargeLegacy:
return CuspRoundingFix::Disabled;
case MantissaScale::Large320:
return CuspRoundingFix::Enabled320;
case MantissaScale::Large330:
return CuspRoundingFix::Enabled330;
case MantissaScale::Large:
return CuspRoundingFix::Enabled;
default:
// If called in a constexpr context, this throw assures that the build fails if an
// invalid scale is used.
throw std::runtime_error("Unknown mantissa scale"); // LCOV_EXCL_LINE
}
}
static std::unordered_map<MantissaScale, MantissaRange> const&
getRanges();
};
// Like std::integral, but only 64-bit integral types.
template <class T>
concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::uint64_t>;
/**
* Number is a floating point type that can represent a wide range of values.
/** Number is a floating point type that can represent a wide range of values.
*
* It can represent all values that can be represented by an STAmount -
* regardless of asset type - XRPAmount, MPTAmount, and IOUAmount, with at least
@@ -304,7 +258,7 @@ concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::u
* on-ledger are non-negative. This is due to implementation details of
* several operations which use unsigned arithmetic internally. This is
* sufficient to represent all valid XRP values (where the absolute value
* can not exceed kInitialXRP: 10^17), and MPT values (where the absolute
* can not exceed INITIAL_XRP: 10^17), and MPT values (where the absolute
* value can not exceed maxMPTokenAmount: 2^63-1).
*
* ---- Mantissa Range Switching ----
@@ -346,6 +300,7 @@ concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::u
* disable the amendments that control the mantissa range choice
* (SingleAssetVault and LendingProtocol), and/or check if either of those
* amendments are enabled to determine which result to expect.
*
*/
class Number final
{
@@ -364,8 +319,6 @@ public:
static constexpr internalrep kMaxRep = std::numeric_limits<rep>::max();
static_assert(kMaxRep == 9'223'372'036'854'775'807);
static_assert(-kMaxRep == std::numeric_limits<rep>::min() + 1);
static constexpr internalrep kMaxRepUp = ((kMaxRep / 10) + 1) * 10;
static_assert(kMaxRepUp == 9'223'372'036'854'775'810ULL);
// May need to make unchecked private
struct Unchecked
@@ -433,11 +386,10 @@ public:
static Number
lowest() noexcept;
/**
* Conversions to Number are implicit and conversions away from Number
* are explicit. This design encourages and facilitates the use of Number
* as the preferred type for floating point arithmetic as it makes
* "mixed mode" more convenient, e.g. MPTAmount + Number.
/** Conversions to Number are implicit and conversions away from Number
* are explicit. This design encourages and facilitates the use of Number
* as the preferred type for floating point arithmetic as it makes
* "mixed mode" more convenient, e.g. MPTAmount + Number.
*/
explicit
operator rep() const; // round to nearest, even on tie
@@ -449,6 +401,12 @@ public:
x.exponent_ == y.exponent_;
}
friend constexpr bool
operator!=(Number const& x, Number const& y) noexcept
{
return !(x == y);
}
friend constexpr bool
operator<(Number const& l, Number const& r) noexcept
{
@@ -486,9 +444,7 @@ public:
return l.mantissa_ < r.mantissa_;
}
/**
* Return the sign of the amount
*/
/** Return the sign of the amount */
[[nodiscard]] constexpr int
signum() const noexcept
{
@@ -542,16 +498,14 @@ public:
static RoundingMode
setround(RoundingMode inMode);
/**
* Returns which mantissa scale is currently in use for normalization.
/** Returns which mantissa scale is currently in use for normalization.
*
* If you think you need to call this outside of unit tests, no you don't.
*/
static MantissaRange::MantissaScale
getMantissaScale();
/**
* Changes which mantissa scale is used for normalization.
/** Changes which mantissa scale is used for normalization.
*
* If you think you need to call this outside of unit tests, no you don't.
*/
@@ -587,13 +541,6 @@ public:
std::pair<T, int>
normalizeToRange() const;
// Safely convert rep (int64) mantissa to internalrep (uint64). If the rep
// is negative, returns the positive value. This takes a little extra work
// because converting std::numeric_limits<std::int64_t>::min() flirts with
// UB, and can vary across compilers.
static internalrep
externalToInternal(rep mantissa);
private:
static thread_local RoundingMode mode;
// The available ranges for mantissa
@@ -603,17 +550,10 @@ private:
// changing the values inside the range.
static thread_local std::reference_wrapper<MantissaRange const> kRange;
class Guard;
void
normalize(MantissaRange const& range);
// Guard has the fields that we need, as well as MantissaRange, so if we have a guard, use that
void
normalize(Guard const& guard);
/**
* Normalize Number components to an arbitrary range.
/** Normalize Number components to an arbitrary range.
*
* min/maxMantissa are parameters because this function is used by both
* normalize(), which reads from kRange, and by normalizeToRange,
@@ -627,7 +567,7 @@ private:
int& exponent,
internalrep const& minMantissa,
internalrep const& maxMantissa,
MantissaRange::CuspRoundingFix cuspRoundingFix);
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled);
template <class T>
friend void
@@ -637,7 +577,7 @@ private:
int& exponent,
MantissaRange::rep const& minMantissa,
MantissaRange::rep const& maxMantissa,
MantissaRange::CuspRoundingFix cuspRoundingFix,
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled,
bool dropped);
[[nodiscard]] bool
@@ -648,6 +588,15 @@ private:
// exponent could go out of range, so it will be checked.
[[nodiscard]] Number
shiftExponent(int exponentDelta) const;
// Safely convert rep (int64) mantissa to internalrep (uint64). If the rep
// is negative, returns the positive value. This takes a little extra work
// because converting std::numeric_limits<std::int64_t>::min() flirts with
// UB, and can vary across compilers.
static internalrep
externalToInternal(rep mantissa);
class Guard;
};
constexpr Number::Number(bool negative, internalrep mantissa, int exponent, Unchecked) noexcept
@@ -682,8 +631,7 @@ inline Number::Number(rep mantissa) : Number{mantissa, 0}
{
}
/**
* Returns the mantissa of the external view of the Number.
/** Returns the mantissa of the external view of the Number.
*
* Please see the "---- External Interface ----" section of the class
* documentation for an explanation of why the internal value may be modified.
@@ -704,8 +652,7 @@ Number::mantissa() const noexcept
return sign * static_cast<Number::rep>(m);
}
/**
* Returns the exponent of the external view of the Number.
/** Returns the exponent of the external view of the Number.
*
* Please see the "---- External Interface ----" section of the class
* documentation for an explanation of why the internal value may be modified.
@@ -911,11 +858,21 @@ squelch(Number const& x, Number const& limit) noexcept
return x;
}
std::string
to_string(MantissaRange::MantissaScale const& scale);
std::string
to_string(Number::RoundingMode const& round);
inline std::string
to_string(MantissaRange::MantissaScale const& scale)
{
switch (scale)
{
case MantissaRange::MantissaScale::Small:
return "small";
case MantissaRange::MantissaScale::LargeLegacy:
return "largeLegacy";
case MantissaRange::MantissaScale::Large:
return "large";
default:
throw std::runtime_error("Bad scale");
}
}
class SaveNumberRoundMode
{
@@ -954,10 +911,10 @@ public:
operator=(NumberRoundModeGuard const&) = delete;
};
/**
* Sets the new scale and restores the old scale when it leaves scope.
/** Sets the new scale and restores the old scale when it leaves scope.
*
* If you think you need to use this class outside of unit tests, no you don't.
*
*/
class NumberMantissaScaleGuard
{

View File

@@ -6,32 +6,29 @@
#include <boost/icl/closed_interval.hpp>
#include <boost/icl/interval_set.hpp>
#include <functional>
#include <optional>
#include <string>
#include <vector>
namespace xrpl {
/**
* A closed interval over the domain T.
*
* For an instance ClosedInterval c, this represents the closed interval
* (c.first(), c.last()). A single element interval has c.first() == c.last().
*
* This is simply a type-alias for boost interval container library interval
* set, so users should consult that documentation for available supporting
* member and free functions.
*/
/** A closed interval over the domain T.
For an instance ClosedInterval c, this represents the closed interval
(c.first(), c.last()). A single element interval has c.first() == c.last().
This is simply a type-alias for boost interval container library interval
set, so users should consult that documentation for available supporting
member and free functions.
*/
template <class T>
using ClosedInterval = boost::icl::closed_interval<T>;
/**
* Create a closed range interval
*
* Helper function to create a closed range interval without having to qualify
* the template argument.
*/
/** Create a closed range interval
Helper function to create a closed range interval without having to qualify
the template argument.
*/
template <class T>
ClosedInterval<T>
range(T low, T high)
@@ -39,30 +36,28 @@ range(T low, T high)
return ClosedInterval<T>(low, high);
}
/**
* A set of closed intervals over the domain T.
*
* Represents a set of values of the domain T using the minimum number
* of disjoint ClosedInterval<T>. This is useful to represent ranges of
* T where a few instances are missing, e.g. the set 1-5,8-9,11-14.
*
* This is simply a type-alias for boost interval container library interval
* set, so users should consult that documentation for available supporting
* member and free functions.
*/
/** A set of closed intervals over the domain T.
Represents a set of values of the domain T using the minimum number
of disjoint ClosedInterval<T>. This is useful to represent ranges of
T where a few instances are missing, e.g. the set 1-5,8-9,11-14.
This is simply a type-alias for boost interval container library interval
set, so users should consult that documentation for available supporting
member and free functions.
*/
template <class T>
using RangeSet = boost::icl::interval_set<T, std::less, ClosedInterval<T>>;
/**
* Convert a ClosedInterval to a styled string
*
* The styled string is
* "c.first()-c.last()" if c.first() != c.last()
* "c.first()" if c.first() == c.last()
*
* @param ci The closed interval to convert
* @return The style string
*/
/** Convert a ClosedInterval to a styled string
The styled string is
"c.first()-c.last()" if c.first() != c.last()
"c.first()" if c.first() == c.last()
@param ci The closed interval to convert
@return The style string
*/
template <class T>
std::string
to_string(ClosedInterval<T> const& ci)
@@ -72,15 +67,14 @@ to_string(ClosedInterval<T> const& ci)
return std::to_string(ci.first()) + "-" + std::to_string(ci.last());
}
/**
* Convert the given RangeSet to a styled string.
*
* The styled string representation is the set of disjoint intervals joined
* by commas. The string "empty" is returned if the set is empty.
*
* @param rs The rangeset to convert
* @return The styled string
*/
/** Convert the given RangeSet to a styled string.
The styled string representation is the set of disjoint intervals joined
by commas. The string "empty" is returned if the set is empty.
@param rs The rangeset to convert
@return The styled string
*/
template <class T>
std::string
to_string(RangeSet<T> const& rs)
@@ -96,16 +90,15 @@ to_string(RangeSet<T> const& rs)
return s;
}
/**
* Convert the given styled string to a RangeSet.
*
* The styled string representation is the set
* of disjoint intervals joined by commas.
*
* @param rs The set to be populated
* @param s The styled string to convert
* @return True on successfully converting styled string
*/
/** Convert the given styled string to a RangeSet.
The styled string representation is the set
of disjoint intervals joined by commas.
@param rs The set to be populated
@param s The styled string to convert
@return True on successfully converting styled string
*/
template <class T>
[[nodiscard]] bool
fromString(RangeSet<T>& rs, std::string const& s)
@@ -167,15 +160,14 @@ fromString(RangeSet<T>& rs, std::string const& s)
return result;
}
/**
* Find the largest value not in the set that is less than a given value.
*
* @param rs The set of interest
* @param t The value that must be larger than the result
* @param minVal (Default is 0) The smallest allowed value
* @return The largest v such that minV <= v < t and !contains(rs, v) or
* std::nullopt if no such v exists.
*/
/** Find the largest value not in the set that is less than a given value.
@param rs The set of interest
@param t The value that must be larger than the result
@param minVal (Default is 0) The smallest allowed value
@return The largest v such that minV <= v < t and !contains(rs, v) or
std::nullopt if no such v exists.
*/
template <class T>
std::optional<T>
prevMissing(RangeSet<T> const& rs, T t, T minVal = 0)

View File

@@ -3,7 +3,6 @@
#include <xrpl/beast/net/IPEndpoint.h>
#include <functional>
#include <string>
#include <vector>
namespace xrpl {
@@ -11,33 +10,26 @@ namespace xrpl {
class Resolver
{
public:
using HandlerType = std::function<void(std::string, std::vector<beast::ip::Endpoint>)>;
using HandlerType = std::function<void(std::string, std::vector<beast::IP::Endpoint>)>;
virtual ~Resolver() = 0;
/**
* Issue an asynchronous stop request.
*/
/** Issue an asynchronous stop request. */
virtual void
stopAsync() = 0;
/**
* Issue a synchronous stop request.
*/
/** Issue a synchronous stop request. */
virtual void
stop() = 0;
/**
* Issue a synchronous start request.
*/
/** Issue a synchronous start request. */
virtual void
start() = 0;
/**
* resolve all hostnames on the list
* @param names the names to be resolved
* @param handler the handler to call
*/
/** resolve all hostnames on the list
@param names the names to be resolved
@param handler the handler to call
*/
/** @{ */
template <class Handler>
void

Some files were not shown because too many files have changed in this diff Show More