diff --git a/.github/workflows/build_msrv.yml b/.github/workflows/build_msrv.yml index c10ccc66..c5beb1ea 100644 --- a/.github/workflows/build_msrv.yml +++ b/.github/workflows/build_msrv.yml @@ -142,7 +142,15 @@ jobs: cargo-build-msrv-${{ runner.os }}-${{ runner.arch }}- - name: Build workspace - run: cargo build --workspace --features full + run: | + cargo build --workspace --exclude dash-pow --features full + cargo build -p dash-pow --features std,aes_hw + env: + RUSTUP_TOOLCHAIN: 1.85.0 - name: Test workspace - run: cargo test --workspace --features full + run: | + cargo test --workspace --exclude dash-pow --features full + cargo test -p dash-pow --features std,aes_hw + env: + RUSTUP_TOOLCHAIN: 1.85.0 diff --git a/.github/workflows/build_stable.yml b/.github/workflows/build_stable.yml index ac933da2..f73bee01 100644 --- a/.github/workflows/build_stable.yml +++ b/.github/workflows/build_stable.yml @@ -75,6 +75,10 @@ jobs: - name: Build package run: cargo build -p ${{ inputs.package }} --features ${{ inputs.features }} + env: + RUSTUP_TOOLCHAIN: stable - name: Test package run: cargo test -p ${{ inputs.package }} --features ${{ inputs.features }} + env: + RUSTUP_TOOLCHAIN: stable diff --git a/.taplo.toml b/.taplo.toml new file mode 100644 index 00000000..16e2f0cc --- /dev/null +++ b/.taplo.toml @@ -0,0 +1,9 @@ +exclude = [ + "**/.venv/**", + "**/node_modules/**", + "**/target/**", +] + +[formatting] +array_auto_collapse = false +column_width = 80 diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 8a18b0f4..5edfba6d 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -6,5 +6,6 @@ "ms-python.vscode-pylance", "ms-vscode-remote.remote-containers", "rust-lang.rust-analyzer", + "tamasfe.even-better-toml", ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 9dc54fb9..c3cec8ca 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,6 +11,10 @@ "[ql]": { "editor.rulers": [100] }, + "[toml]": { + "editor.defaultFormatter": "tamasfe.even-better-toml", + "editor.rulers": [80] + }, "editor.formatOnSave": true, "editor.minimap.enabled": true, "editor.rulers": [120], diff --git a/Cargo.lock b/Cargo.lock index 7ecbd362..3476ee6b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1075,23 +1075,21 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.2.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ "displaydoc", - "potential_utf", - "utf8_iter", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locale_core" -version = "2.2.0" +name = "icu_locid" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ "displaydoc", "litemap", @@ -1100,61 +1098,99 @@ dependencies = [ "zerovec", ] +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" + [[package]] name = "icu_normalizer" -version = "2.2.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" dependencies = [ + "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", + "utf16_iter", + "utf8_iter", + "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "2.2.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" [[package]] name = "icu_properties" -version = "2.2.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ + "displaydoc", "icu_collections", - "icu_locale_core", + "icu_locid_transform", "icu_properties_data", "icu_provider", - "zerotrie", + "tinystr", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.2.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" [[package]] name = "icu_provider" -version = "2.2.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" dependencies = [ "displaydoc", - "icu_locale_core", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", "writeable", "yoke", "zerofrom", - "zerotrie", "zerovec", ] +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "idna" version = "1.1.0" @@ -1168,9 +1204,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.2" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" dependencies = [ "icu_normalizer", "icu_properties", @@ -1303,9 +1339,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.2" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "log" @@ -1435,15 +1471,6 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" -[[package]] -name = "potential_utf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" -dependencies = [ - "zerovec", -] - [[package]] name = "ppv-lite86" version = "0.2.21" @@ -1905,9 +1932,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.3" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ "displaydoc", "zerovec", @@ -2014,6 +2041,12 @@ dependencies = [ "serde", ] +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -2347,11 +2380,17 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + [[package]] name = "writeable" -version = "0.6.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "xxhash-rust" @@ -2361,10 +2400,11 @@ checksum = "aee1b19627c7c60102ab80d3a9cbe18de90bfe03bfa6c3715447681f0e8c8af6" [[package]] name = "yoke" -version = "0.8.3" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ + "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -2372,9 +2412,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.2" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", @@ -2443,22 +2483,11 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "zerotrie" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - [[package]] name = "zerovec" -version = "0.11.6" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" dependencies = [ "yoke", "zerofrom", @@ -2467,9 +2496,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +checksum = "3e3c6377872d72510393f688a555d7097b0f741995c7a00f0407f786dd486b2d" dependencies = [ "proc-macro2", "quote", diff --git a/contrib/build_docs.py b/contrib/build_docs.py index 392603f2..cb3d33d1 100755 --- a/contrib/build_docs.py +++ b/contrib/build_docs.py @@ -19,9 +19,19 @@ import sys from functools import partial from pathlib import Path +from typing import TYPE_CHECKING import rjsmin -from common import RETCODE_ERR, RETCODE_PASS, require_bin, root_dir +from common import ( + RETCODE_ERR, + RETCODE_PASS, + declare_verbs, + require_bin, + root_dir, +) + +if TYPE_CHECKING: + from collections.abc import Callable SITE_DIR = Path("public") PREVIEW_PORT = 8000 @@ -198,22 +208,19 @@ def _preview(root: Path) -> None: srv.server_close() -VERBS = {"build": _build, "preview": _preview} +VERBS: dict[str, tuple[Callable[[Path], None], str]] = { + "build": (_build, f"render the site into {SITE_DIR}/"), + "preview": (_preview, "render the site, then serve it over localhost"), +} def main() -> int: """Entry point.""" - verb = sys.argv[1] if len(sys.argv) > 1 else "build" - action = VERBS.get(verb) - if action is None: - print( - f"unknown verb: {verb} (expected: {', '.join(VERBS)})", - file=sys.stderr, - ) - return RETCODE_ERR - - root = root_dir() - action(root) + args = declare_verbs( + "Build the documentation site.", + {verb: what for verb, (_, what) in VERBS.items()}, + ).parse_args(sys.argv[1:]) + VERBS[args.verb][0](root_dir()) return RETCODE_PASS diff --git a/contrib/common.py b/contrib/common.py index c1f61e62..60d208ed 100644 --- a/contrib/common.py +++ b/contrib/common.py @@ -11,15 +11,16 @@ from __future__ import annotations +import argparse import os import shutil import subprocess import sys from pathlib import Path -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, NoReturn if TYPE_CHECKING: - from collections.abc import Callable + from collections.abc import Callable, Mapping # ANSI escape codes for terminal output. ANSI_BOLD = "\033[1m" @@ -28,6 +29,9 @@ ANSI_RED = "\033[31m" ANSI_RESET = "\033[0m" +# Cargo workspace roots, relative to the repository root. +CARGO_WORKSPACES: tuple[str, ...] = (".", "contrib/samples") + # Assumed base branch for codebase. DEFAULT_BASE = "develop" @@ -37,6 +41,86 @@ RETCODE_SKIP = 77 +class _VerbParser(argparse.ArgumentParser): + """Parser spelling a usage fault in the harness' return codes.""" + + def exit(self, status: int = 0, message: str | None = None) -> NoReturn: + if message: + self._print_message(message, sys.stderr) + sys.exit(RETCODE_ERR if status else RETCODE_PASS) + + +def declare_verbs( + description: str, + verbs: Mapping[str, str], +) -> argparse.ArgumentParser: + """Return a parser taking one of *verbs*. + + *verbs* maps each verb to what it does, and insertion order picks the + default, so the first entry must avoid mutating effects. + """ + if not verbs: + raise ValueError("no verbs declared") + default = next(iter(verbs)) + parser = _VerbParser( + description=description, + formatter_class=argparse.RawTextHelpFormatter, + ) + parser.add_argument( + "verb", + choices=tuple(verbs), + default=default, + nargs="?", + help="\n".join( + f"{name}: {what}" + (" (default)" if name == default else "") + for name, what in verbs.items() + ), + ) + return parser + + +def is_plain_file(root: Path, name: str) -> bool: + """Whether *name* is a regular file inside *root*, reached without links.""" + path = root / name + if not path.is_file(): + return False + try: + relative = path.relative_to(root) + except ValueError: + return False + probe = root + for part in relative.parts: + probe = probe / part + if probe.is_symlink(): + return False + return path.resolve().is_relative_to(root.resolve()) + + +def touched(repo_root: Path, suffixes: tuple[str, ...]) -> list[str]: + """Return the files matching *suffixes* that this branch has changed.""" + git = require_bin("git") + + def run(args: list[str]) -> str: + result = subprocess.run( # noqa: S603 + [git, *args], + capture_output=True, + check=False, + cwd=str(repo_root), + text=True, + ) + if result.returncode != 0: + raise RuntimeError( + f"git {args[0]}: {result.stderr.strip() or result.returncode}", + ) + return result.stdout + + base = run(["merge-base", DEFAULT_BASE, "HEAD"]).strip() + return [ + name for name in run(["diff", "--name-only", base]).splitlines() + if name.endswith(suffixes) and is_plain_file(repo_root, name) + ] + + def format_table( headers: tuple[str, ...], rows: list[tuple[str, ...]], diff --git a/contrib/lint/lint_cargo.py b/contrib/lint/lint_cargo.py new file mode 100755 index 00000000..151e8e39 --- /dev/null +++ b/contrib/lint/lint_cargo.py @@ -0,0 +1,300 @@ +#!/usr/bin/env python3 +# coding: latin-1 + +# +# Copyright (c) 2026-present, The Dash Core developers +# SPDX-License-Identifier: MIT +# See the accompanying file LICENSE or https://opensource.org/license/MIT +# + +"""Validate and enforce constraints across Rust's build system, cargo. + +Includes a TOML formatter using taplo that affects all TOML files regardless of +provenance or origin, exclusions must be defined in '.taplo.toml' +""" + +from __future__ import annotations + +import json +import re +import subprocess +import sys +import tomllib +from pathlib import Path + +from common import ( + CARGO_WORKSPACES, + DEFAULT_BASE, + RETCODE_ERR, + RETCODE_PASS, + RETCODE_SKIP, + declare_verbs, + format_table, + require_bin, + root_dir, + touched, +) + +SCRIPT = Path(__file__).stem + +TARGET_TRIPLES: tuple[str, ...] = ( + "x86_64-unknown-linux-gnu", + "aarch64-unknown-linux-gnu", +) + +# Leading `name vX.Y.Z` of a `cargo tree --prefix none` line. Anything after +# the version (source, ` (*)` dedupe marker, feature list) is ignored. +TREE_ENTRY = re.compile(r"^(\S+) v(\S+)") + +Coord = tuple[str, str] +Version = tuple[int, ...] + + +def _check_format( + repo_root: Path, + *, + fix: bool, + only: list[str] | None = None, +) -> int | None: + """Format or check TOML, or None when taplo is absent.""" + try: + taplo = require_bin("taplo") + except FileNotFoundError as e: + print(f"{e}, skipping the format check", file=sys.stderr) + return None + + if only is not None and not only: + print(f"{SCRIPT}: no TOML file was touched") + return RETCODE_PASS + + argv = [taplo, "fmt"] + ([] if fix else ["--check", "--diff"]) + (only or []) + result = subprocess.run( # noqa: S603 + argv, + capture_output=True, + check=False, + cwd=str(repo_root), + text=True, + ) + prefix = str(repo_root) + "/" + for line in result.stdout.splitlines(): + print(line.replace(prefix, "")) + + # Taplo reports the file count on stderr at INFO, so only the lines that + # name a fault should be emitted. + for line in result.stderr.splitlines(): + if not line.lstrip().startswith("INFO"): + print(line.replace(prefix, ""), file=sys.stderr) + + if result.returncode != 0: + if not fix: + print( + f"hint: run 'python3 contrib/lint/{SCRIPT}.py apply-all' to rewrite", + file=sys.stderr, + ) + return RETCODE_ERR + scope = ( + f"{len(only)} touched TOML file(s)" if only is not None + else "every TOML file" + ) + print(f"{SCRIPT}: rewrote {scope}" if fix else f"{SCRIPT}: {scope} conforms") + return RETCODE_PASS + + +def _parse_version(text: str) -> Version: + """Return *text* as a comparable triple. + + Manifests write the same floor as `1.85` or `1.85.0`; padding to three + components keeps those two spellings equal. A prerelease is not a floor + cargo accepts, so it is raised on rather than trimmed to one. + """ + parts = text.split(".") + try: + fields = [int(p) for p in parts] + except ValueError as exc: + raise ValueError(f"malformed rust version {text!r}") from exc + if not 1 <= len(fields) <= 3: + raise ValueError(f"malformed rust version {text!r}") + return tuple(fields + [0] * (3 - len(fields))) + + +def _workspace_cap(repo_root: Path) -> tuple[str, Version]: + """Return the workspace `rust-version` as written and as a tuple.""" + manifest = tomllib.loads( + (repo_root / "Cargo.toml").read_text(encoding="utf-8"), + ) + declared = manifest.get("workspace", {}).get("package", {}).get( + "rust-version", + ) + if not isinstance(declared, str): + raise ValueError("workspace.package.rust-version is not set") + return declared, _parse_version(declared) + + +def _cargo(cargo_bin: str, repo_root: Path, args: list[str]) -> str: + """Run cargo with *args*, echoing stderr and raising on failure.""" + result = subprocess.run( # noqa: S603 + [cargo_bin, *args], + capture_output=True, + check=False, + cwd=str(repo_root), + text=True, + ) + prefix = str(repo_root) + "/" + for line in result.stderr.splitlines(): + print(line.replace(prefix, ""), file=sys.stderr) + if result.returncode != 0: + raise RuntimeError(f"cargo {args[0]} failed with {result.returncode}") + return result.stdout + + +def _build_graph( + cargo_bin: str, + repo_root: Path, + workspace: str, + triple: str, +) -> set[Coord]: + """Return the crates cargo compiles, as `(name, version)` pairs.""" + stdout = _cargo(cargo_bin, repo_root, [ + "tree", + "--manifest-path", str(Path(workspace) / "Cargo.toml"), + "--workspace", + "--all-features", + "--locked", + "--target", triple, + "--edges", "normal,build,dev", + "--prefix", "none", + "--quiet", + ]) + graph: set[Coord] = set() + for raw in stdout.splitlines(): + line = raw.strip() + if not line: + continue + entry = TREE_ENTRY.match(line) + if entry is None: + raise ValueError(f"unparsed cargo tree line {line!r}") + graph.add((entry.group(1), entry.group(2))) + return graph + + +def _declared( + cargo_bin: str, + repo_root: Path, + workspace: str, + triple: str, +) -> dict[Coord, str]: + """Return the `rust-version` each resolved package declares.""" + stdout = _cargo(cargo_bin, repo_root, [ + "metadata", + "--manifest-path", str(Path(workspace) / "Cargo.toml"), + "--format-version", "1", + "--all-features", + "--locked", + "--filter-platform", triple, + "--quiet", + ]) + return { + (pkg["name"], pkg["version"]): pkg["rust_version"] + for pkg in json.loads(stdout)["packages"] + if pkg.get("rust_version") + } + + +def _check_msrv(repo_root: Path) -> int | None: + """Fail on a crate above the cap, or None when cargo is absent.""" + try: + cargo_bin = require_bin("cargo") + except FileNotFoundError as e: + print(f"{e}, skipping the msrv check", file=sys.stderr) + return None + + cap_text, cap = _workspace_cap(repo_root) + print(f"checking msrv: cap {cap_text} ({', '.join(TARGET_TRIPLES)})") + + graph: set[Coord] = set() + declared: dict[Coord, str] = {} + for workspace in CARGO_WORKSPACES: + for triple in TARGET_TRIPLES: + graph |= _build_graph(cargo_bin, repo_root, workspace, triple) + declared |= _declared(cargo_bin, repo_root, workspace, triple) + + # Parsed floor first, so that sorting a crate is sorting its rust-version + # and the name only breaks ties. + rated = sorted( + (_parse_version(text), name, version, text) + for (name, version), text in declared.items() + if (name, version) in graph + ) + if not rated: + raise ValueError("no crate in the build graph declares a rust-version") + + highest = max(rated)[0] + peak = [r for r in rated if r[0] == highest] + + # `rated` is already sorted, and this sort is stable, so the worst overrun + # leads and crates sharing a floor stay in name order. + over = sorted( + (r for r in rated if r[0] > cap), key=lambda r: r[0], reverse=True, + ) + summary = ", ".join(f"{n} {v}" for _, n, v, _ in peak[:3]) + if len(peak) > 3: + summary += f", +{len(peak) - 3} more" + top = ".".join(str(p) for p in highest) + silent = len(graph - declared.keys()) + print( + f"build graph of {len(graph)} crates ({silent} declaring no floor) " + f"peaks at {top}: {summary}", + ) + + if not over: + return RETCODE_PASS + + print( + format_table( + ("package", "version", "rust-version", "status"), + [(n, v, t, "fail") for _, n, v, t in over], + ), + file=sys.stderr, + ) + print( + f"error: {len(over)} crate(s) declare a rust-version above the " + f"{cap_text} cap", + file=sys.stderr, + ) + print( + "hint: pin the offending package with " + "'cargo update --precise '", + file=sys.stderr, + ) + return RETCODE_ERR + + +def main() -> int: + args = declare_verbs( + "Validate the crate graph is compatible with the MSRV.", + { + "check": "report every fault, changing nothing", + "apply": f"also rewrite TOML this branch changed vs {DEFAULT_BASE}", + "apply-all": "also rewrite every TOML file in the tree", + }, + ).parse_args(sys.argv[1:]) + fix = args.verb.startswith("apply") + repo_root = root_dir() + only = touched(repo_root, (".toml",)) if args.verb == "apply" else None + + verdicts: list[int | None] = [ + _check_format(repo_root, fix=fix, only=only), + _check_msrv(repo_root), + ] + ran = [v for v in verdicts if v is not None] + if not ran: + return RETCODE_SKIP + return RETCODE_ERR if any(v != RETCODE_PASS for v in ran) else RETCODE_PASS + + +if __name__ == "__main__": + try: + sys.exit(main()) + except Exception as exc: # noqa: BLE001 + print(exc, file=sys.stderr) + sys.exit(RETCODE_ERR) diff --git a/contrib/lint/lint_codeql.py b/contrib/lint/lint_codeql.py index 5422958a..251a079a 100755 --- a/contrib/lint/lint_codeql.py +++ b/contrib/lint/lint_codeql.py @@ -11,7 +11,6 @@ from __future__ import annotations -import argparse import contextlib import csv import datetime @@ -24,12 +23,14 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: + import argparse from collections.abc import Iterator from common import ( RETCODE_ERR, RETCODE_PASS, RETCODE_SKIP, + declare_verbs, require_bin, root_dir, usable_mem, @@ -181,8 +182,9 @@ def _workspace_dirs( def _parse_args(argv: list[str]) -> argparse.Namespace: - parser = argparse.ArgumentParser( - description="Run CodeQL queries against the workspace.", + parser = declare_verbs( + "Run CodeQL queries against the workspace.", + {"run": "build a database and evaluate every query"}, ) parser.add_argument( "-c", diff --git a/contrib/lint/lint_rust.py b/contrib/lint/lint_rust.py old mode 100644 new mode 100755 index 14b7d730..a6987085 --- a/contrib/lint/lint_rust.py +++ b/contrib/lint/lint_rust.py @@ -13,12 +13,14 @@ import subprocess import sys +from pathlib import Path -from common import RETCODE_ERR, RETCODE_PASS, require_bin, root_dir - -CARGO_MANIFESTS: tuple[str, ...] = ( - "Cargo.toml", - "contrib/samples/Cargo.toml", +from common import ( + CARGO_WORKSPACES, + RETCODE_ERR, + RETCODE_PASS, + require_bin, + root_dir, ) @@ -28,7 +30,8 @@ def main() -> int: repo_root = root_dir() failed = False - for manifest in CARGO_MANIFESTS: + for workspace in CARGO_WORKSPACES: + manifest = Path(workspace) / "Cargo.toml" manifest_path = repo_root / manifest print(f"checking formatting: {manifest}") cmd = [cargo_bin, "fmt", "--check", "--all"] diff --git a/contrib/lint/lint_unconv.py b/contrib/lint/lint_unconv.py index 43b2924e..ea5e2efa 100755 --- a/contrib/lint/lint_unconv.py +++ b/contrib/lint/lint_unconv.py @@ -9,7 +9,6 @@ from __future__ import annotations -import argparse import re import subprocess import sys @@ -17,6 +16,8 @@ from dataclasses import dataclass, field from pathlib import Path +from common import declare_verbs + CONFIG_FILENAME = "unconv.toml" # namespace%type[(scope)][!]: description @@ -173,11 +174,10 @@ def _subjects_from_range(git_range: str) -> list[str]: def main() -> int: - parser = argparse.ArgumentParser( - description=( - "Lint commit messages against the unconventional commits format.\n" - "Format: [namespace]%type[(scope)]: description" - ), + parser = declare_verbs( + "Lint commit messages against the unconventional commits format.\n" + "Format: [namespace]%type[(scope)]: description", + {"run": "lint whichever messages the options below select"}, ) source = parser.add_mutually_exclusive_group(required=False) source.add_argument( diff --git a/contrib/lint_all.py b/contrib/lint_all.py index 74072c24..86f16a30 100755 --- a/contrib/lint_all.py +++ b/contrib/lint_all.py @@ -9,13 +9,15 @@ from __future__ import annotations -import argparse import asyncio import sys import time from dataclasses import dataclass, field from pathlib import Path -from typing import cast +from typing import TYPE_CHECKING, cast + +if TYPE_CHECKING: + import argparse from common import ( ANSI_BOLD, @@ -25,6 +27,7 @@ RETCODE_ERR, RETCODE_PASS, RETCODE_SKIP, + declare_verbs, format_table, ) @@ -72,8 +75,9 @@ async def _read_stream( def _parse_args(argv: list[str]) -> argparse.Namespace: - parser = argparse.ArgumentParser( - description="Run contrib/lint/lint_*.py concurrently.", + parser = declare_verbs( + "Run contrib/lint/lint_*.py concurrently.", + {"run": "run every linter and summarise the results"}, ) parser.add_argument( "--exclude", diff --git a/contrib/samples/parser/Cargo.toml b/contrib/samples/parser/Cargo.toml index e4982605..0df863a8 100644 --- a/contrib/samples/parser/Cargo.toml +++ b/contrib/samples/parser/Cargo.toml @@ -23,7 +23,9 @@ dash-primitives = { version = "0.0.0", path = "../../../pkgs/primitives", defaul "serde", ] } dash-types = { version = "0.0.0", path = "../../../pkgs/types", default-features = false } -hex-conservative = { version = "0.3", default-features = false, features = ["alloc"] } +hex-conservative = { version = "0.3", default-features = false, features = [ + "alloc", +] } serde_json = { version = "1", default-features = false, features = ["alloc"] } wasm-bindgen = "0.2" diff --git a/contrib/samples/solver/Cargo.toml b/contrib/samples/solver/Cargo.toml index 46bd839f..e22450aa 100644 --- a/contrib/samples/solver/Cargo.toml +++ b/contrib/samples/solver/Cargo.toml @@ -23,12 +23,19 @@ bitcoin-consensus-encoding = { workspace = true, features = ["alloc"] } bitcoin-primitives = { workspace = true, features = ["alloc"] } bitcoin-units = { workspace = true, features = ["alloc"] } dash-num = { version = "0.0.0", path = "../../../pkgs/num", default-features = false } -dash-pow = { version = "0.0.0", path = "../../../pkgs/pow", default-features = false, features = ["simd"] } +dash-pow = { version = "0.0.0", path = "../../../pkgs/pow", default-features = false, features = [ + "simd", +] } dash-primitives = { version = "0.0.0", path = "../../../pkgs/primitives", default-features = false } dash-script = { version = "0.0.0", path = "../../../pkgs/script", default-features = false } dash-types = { version = "0.0.0", path = "../../../pkgs/types", default-features = false } -hex-conservative = { version = "0.3", default-features = false, features = ["alloc"] } -serde = { version = "1", default-features = false, features = ["derive", "alloc"] } +hex-conservative = { version = "0.3", default-features = false, features = [ + "alloc", +] } +serde = { version = "1", default-features = false, features = [ + "derive", + "alloc", +] } serde_json = { version = "1", default-features = false, features = ["alloc"] } wasm-bindgen = "0.2" diff --git a/pkgs/pkc/Cargo.toml b/pkgs/pkc/Cargo.toml index cca4f7fa..7f7b73cf 100644 --- a/pkgs/pkc/Cargo.toml +++ b/pkgs/pkc/Cargo.toml @@ -50,8 +50,20 @@ serde = { version = "1", features = ["derive"] } [features] default = [] std = ["base58ck?/std", "bitcoin_hashes?/std", "dash-types/std"] -bls = ["dep:aes", "dep:blst", "dep:ff", "dep:group", "dep:rand_core", "dep:sha2"] -codec = ["dep:base58ck", "dep:bitcoin_hashes", "dep:dash-num", "dash-types/codec"] +bls = [ + "dep:aes", + "dep:blst", + "dep:ff", + "dep:group", + "dep:rand_core", + "dep:sha2", +] +codec = [ + "dep:base58ck", + "dep:bitcoin_hashes", + "dep:dash-num", + "dash-types/codec", +] ecdsa = ["codec", "dep:k256", "dep:rand_core"] serde = ["codec", "dep:serde", "dash-num/serde", "dash-types/serde"] full = ["bls", "codec", "ecdsa", "serde", "std", "tests"] diff --git a/pkgs/primitives/Cargo.toml b/pkgs/primitives/Cargo.toml index 2748f6d1..fd2bf232 100644 --- a/pkgs/primitives/Cargo.toml +++ b/pkgs/primitives/Cargo.toml @@ -46,7 +46,9 @@ dash-types = { version = "0.0.0", path = "../types", default-features = false, f "codec", ] } cfg-if = "1" -hex-conservative = { version = "0.3", default-features = false, features = ["alloc"] } +hex-conservative = { version = "0.3", default-features = false, features = [ + "alloc", +] } libm = { version = "0.2", default-features = false } serde = { version = "1", default-features = false, features = [ "alloc", diff --git a/pyproject.toml b/pyproject.toml index faa6d302..d8e532ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,7 @@ dev = [ "rjsmin>=1.2", "ruff>=0.9", "semgrep>=1.118.0", + "taplo>=0.9.3; sys_platform != 'linux' or platform_machine != 'aarch64'", "zensical>=0.0.33", ]