Files
2026-06-30 16:56:49 -03:00

202 lines
7.7 KiB
Nix

{
description = "Logos Libp2p Module";
inputs = {
logos-module-builder.url = "github:logos-co/logos-module-builder";
libp2p.url = "github:vacp2p/nim-libp2p/feat/cbind/xpr-decode";
openmetrics-module = {
url = "github:logos-co/openmetrics-module";
inputs.logos-module-builder.follows = "logos-module-builder";
};
# logoscore + lgpm binaries the openmetrics e2e drives.
logoscore-cli.url = "github:logos-co/logos-logoscore-cli";
package-manager.url = "github:logos-co/logos-package-manager";
};
outputs = inputs@{ logos-module-builder, ... }:
let
externalLibInputs = {
libp2p = {
input = inputs.libp2p;
packages.default = "cbind";
};
};
module = logos-module-builder.lib.mkLogosModule {
src = ./.;
configFile = ./metadata.json;
flakeInputs = inputs;
inherit externalLibInputs;
tests = {
dir = ./tests;
};
};
nixpkgs = logos-module-builder.inputs.nixpkgs;
systems = [ "aarch64-darwin" "x86_64-darwin" "aarch64-linux" "x86_64-linux" ];
forEachSystem = f: builtins.listToAttrs (map (system: {
name = system;
value = f system;
}) systems);
# Pre-resolved store paths for the two .lgx bundles the e2e installs.
e2eEnv = system: {
LIBP2P_LGX_DIR = "${module.packages.${system}.lgx}";
OPENMETRICS_LGX_DIR = "${inputs.openmetrics-module.packages.${system}.lgx}";
};
# mkLogosModuleTests splats extraCmakeFlags into cmake unquoted, so
# multi-token values get shell-split. Inject sanitizer flags via env vars.
baseSanitizerTests = logos-module-builder.lib.mkLogosModuleTests {
src = ./.;
testDir = ./tests;
configFile = ./metadata.json;
flakeInputs = inputs;
inherit externalLibInputs;
extraCmakeFlags = [ "-DCMAKE_BUILD_TYPE=Debug" ];
};
mkSanitized = system: sanitizer: runtimeOpts:
let
pkgs = import nixpkgs { inherit system; };
clang = pkgs.clang;
llvm = pkgs.llvm; # llvm-symbolizer; symbol-based suppressions need it
sanFlags = "-fsanitize=${sanitizer} -fno-omit-frame-pointer -g -O1";
tsanSuppressions = pkgs.writeText "tsan.supp" ''
race:libp2p.so
'';
# Wrapper code (Libp2pModuleImpl, src/*.cpp) is NOT suppressed —
# real wrapper leaks (e.g. src/plugin.cpp:121 privkey) still surface.
lsanSuppressions = pkgs.writeText "lsan.supp" ''
leak:libp2p.so
leak:std::promise
leak:std::__future_base
leak:QCoreApplication
leak:QMetaObject
leak:QArrayData
leak:QObjectPrivate
leak:LogosTestRunner
leak:LogosTestContext
'';
extraRuntime =
if sanitizer == "address" then ''
export ASAN_SYMBOLIZER_PATH=${llvm}/bin/llvm-symbolizer
export LSAN_OPTIONS="suppressions=${lsanSuppressions}:print_suppressions=0"
'' else ''
export TSAN_OPTIONS="$TSAN_OPTIONS:suppressions=${tsanSuppressions}:external_symbolizer_path=${llvm}/bin/llvm-symbolizer"
'';
in baseSanitizerTests.${system}.unit-tests.overrideAttrs (old: {
nativeBuildInputs = (old.nativeBuildInputs or []) ++ [ clang llvm ];
hardeningDisable = (old.hardeningDisable or []) ++ [ "all" ];
preBuild = (old.preBuild or "") + ''
export CC=${clang}/bin/clang
export CXX=${clang}/bin/clang++
export CFLAGS="${sanFlags}"
export CXXFLAGS="${sanFlags}"
export LDFLAGS="-fsanitize=${sanitizer}"
export ${runtimeOpts}
${extraRuntime}
'';
});
sanitizerPackages = builtins.listToAttrs (map (system: {
name = system;
value = {
unit-tests-asan = mkSanitized system "address"
"ASAN_OPTIONS=detect_leaks=1:abort_on_error=1:print_stacktrace=1:strict_string_checks=1:check_initialization_order=1";
unit-tests-tsan = mkSanitized system "thread"
"TSAN_OPTIONS=halt_on_error=1:second_deadlock_stack=1:history_size=7";
};
}) systems);
perSystem = forEachSystem (system:
let
pkgs = import nixpkgs { inherit system; };
unitTests = module.packages.${system}.unit-tests;
runner = pkgs.writeShellScript "run-tests" ''
filter="''${1:-}"
ran=0
for bin in ${unitTests}/bin/*; do
name="$(basename "$bin")"
if [ -n "$filter" ] && ! echo "$name" | grep -q "$filter"; then
continue
fi
echo "=== $name ==="
"$bin"
ran=$((ran + 1))
done
if [ "$ran" -eq 0 ] && [ -n "$filter" ]; then
echo "No test binary matched filter: $filter" >&2
exit 1
fi
'';
env = e2eEnv system;
e2eRuntime = [ pkgs.coreutils pkgs.gnugrep pkgs.bash pkgs.iproute2 pkgs.jq ];
e2eScript = ./tests/integration_e2e/openmetrics_e2e.sh;
standaloneE2eScript = ./tests/integration_e2e/standalone_e2e.sh;
# `nix run .#openmetrics-e2e`: standalone, runs a live logoscore
# daemon so it can't be a hermetic flake check. LOGOSCORE_BIN /
# LGPM_BIN override the vendored binaries when set.
logoscoreBin = "${inputs.logoscore-cli.packages.${system}.default}/bin/logoscore";
lgpmBin = "${inputs.package-manager.packages.${system}.cli}/bin/lgpm";
openmetricsE2eApp = pkgs.writeShellScript "openmetrics-e2e" ''
export PATH=${pkgs.lib.makeBinPath e2eRuntime}:$PATH
export LIBP2P_LGX_DIR=${env.LIBP2P_LGX_DIR}
export OPENMETRICS_LGX_DIR=${env.OPENMETRICS_LGX_DIR}
export LOGOSCORE_BIN="''${LOGOSCORE_BIN:-${logoscoreBin}}"
export LGPM_BIN="''${LGPM_BIN:-${lgpmBin}}"
exec ${e2eScript} "$@"
'';
standaloneE2eApp = pkgs.writeShellScript "standalone-e2e" ''
export PATH=${pkgs.lib.makeBinPath e2eRuntime}:$PATH
export LIBP2P_LGX_DIR=${env.LIBP2P_LGX_DIR}
export LOGOSCORE_BIN="''${LOGOSCORE_BIN:-${logoscoreBin}}"
export LGPM_BIN="''${LGPM_BIN:-${lgpmBin}}"
exec ${standaloneE2eScript} "$@"
'';
in {
# *-e2e run a live logoscore daemon, so they can't be hermetic flake
# checks; they're standalone apps run as their own CI step.
apps = {
tests = { type = "app"; program = toString runner; };
} // pkgs.lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux {
openmetrics-e2e = { type = "app"; program = toString openmetricsE2eApp; };
standalone-e2e = { type = "app"; program = toString standaloneE2eApp; };
};
}
);
existingApps = module.apps or {};
mergedApps = forEachSystem (system:
(existingApps.${system} or {}) // (perSystem.${system}.apps or {})
);
mergedPackages = builtins.listToAttrs (map (system: {
name = system;
value = (module.packages.${system} or {}) // (sanitizerPackages.${system} or {});
}) systems);
mergedDevShells = forEachSystem (system:
let pkgs = import nixpkgs { inherit system; };
in (module.devShells.${system} or {}) // {
default = pkgs.mkShell {
inputsFrom = [ module.devShells.${system}.default ];
buildInputs = [ pkgs.boost.dev ];
};
});
in module // {
apps = mergedApps;
checks = module.checks or {};
packages = mergedPackages;
devShells = mergedDevShells;
};
}