diff --git a/common/optional/x86-64-v3.nix b/common/optional/x86-64-v3.nix new file mode 100644 index 0000000..349b757 --- /dev/null +++ b/common/optional/x86-64-v3.nix @@ -0,0 +1,9 @@ +{ + nixpkgs.hostPlatform = { + system = "x86_64-linux"; + gcc = { + arch = "x86-64-v3"; + tune = "generic"; + }; + }; +} diff --git a/overlays/default.nix b/overlays/default.nix index 6110f8e..114df6b 100644 --- a/overlays/default.nix +++ b/overlays/default.nix @@ -15,6 +15,9 @@ }; }; + test-exclusions = import ./test-exclusions.nix; + x86-64-v3-workarounds = import ./x86-64-v3-workarounds.nix; + python-env = final: _prev: { my_python = final.python314.withPackages ( ps: with ps; [ diff --git a/overlays/test-exclusions.nix b/overlays/test-exclusions.nix new file mode 100644 index 0000000..e410525 --- /dev/null +++ b/overlays/test-exclusions.nix @@ -0,0 +1,142 @@ +# Test exclusions for the locally rebuilt x86-64-v3 package set. +# +# Selecting x86-64-v3 changes every affected derivation, so the normal +# nixpkgs binary cache cannot be used and upstream test suites run locally. +# The jeeves builder uses /tmp/nix-builds so filesystem tests run on tmpfs +# instead of ZFS with normalization=formD and utf8only=on; those tests remain +# enabled. The remaining workarounds cover UDP readiness, resource-sensitive +# parser and nested-worker races, and mismatched timeout clocks, plus +# architecture-dependent floating-point differences whose risk we accept for +# our workloads. Keep these exceptions visible until their causes are fixed. +_final: prev: { + gnutls = prev.gnutls.overrideAttrs (old: { + # This test uses a fixed four-second sleep instead of checking UDP + # readiness; the client saw no listener in the x86-64-v3 build. + postPatch = (old.postPatch or "") + '' + sed '2iexit 77' -i tests/serv-udp.sh + ''; + }); + + onnxruntime = prev.onnxruntime.overrideAttrs (old: { + # OpenVINO uses a different QuantizeLinear formulation. x86-64-v3 moves + # these INT8 results by one quantization unit; we accept that inference- + # precision risk for our workloads. + env = (old.env or { }) // { + GTEST_FILTER = "*:-QuantizeLinearOpTest.Int8:QuantizeLinearOpTest.OVEP_Int8_NegativeZeroPoint:QuantizeLinearOpTest.OVEP_Int8_PositiveZeroPoint"; + }; + }); + + prometheus = prev.prometheus.overrideAttrs ( + old: + let + assets = old.passthru.assets.overrideAttrs (assetsOld: { + # CodeMirror's bounded synchronous parser can return an incomplete tree + # when these cases run on a heavily loaded builder. + postPatch = (assetsOld.postPatch or "") + '' + substituteInPlace module/codemirror-promql/src/complete/hybrid.test.ts \ + --replace-fail "it(value.title, () => {" \ + "(value.title === 'autocomplete topk params 2' ? it.skip : it)(value.title, () => {" + substituteInPlace module/codemirror-promql/src/parser/vector.test.ts \ + --replace-fail "it(value.binaryExpr, () => {" \ + "(value.binaryExpr === 'foo * on(test,blub) bar' ? it.skip : it)(value.binaryExpr, () => {" + ''; + }); + in + { + postPatch = builtins.replaceStrings [ "${old.passthru.assets}" ] [ "${assets}" ] ( + builtins.unsafeDiscardStringContext old.postPatch + ); + passthru = old.passthru // { + inherit assets; + }; + } + ); + + pythonPackagesExtensions = prev.pythonPackagesExtensions ++ [ + (_pythonFinal: pythonPrev: { + backrefs = pythonPrev.backrefs.overridePythonAttrs (old: { + # regex measures its timeout in process CPU time, while this test used + # wall time and could miss the timeout when a busy builder descheduled it. + postPatch = (old.postPatch or "") + '' + substituteInPlace tests/test_bregex.py \ + --replace-fail "time.time()" "time.process_time()" + ''; + }); + + pytest-xdist = pythonPrev.pytest-xdist.overridePythonAttrs (old: { + # The suite exercises its own worker pools. Run the outer suite with one + # worker and allow inner workers more time on heavily loaded builders. + postPatch = (old.postPatch or "") + '' + substituteInPlace testing/test_remote.py \ + --replace-fail "WAIT_TIMEOUT = 10.0" "WAIT_TIMEOUT = 60.0" + ''; + preCheck = builtins.replaceStrings [ "--numprocesses=$NIX_BUILD_CORES" ] [ "--numprocesses=1" ] ( + old.preCheck or "" + ); + # This test deliberately crashes workers past the restart limit and + # races while checking which replacement message was emitted. + disabledTests = (old.disabledTests or [ ]) ++ [ + "test_max_worker_restart_tests_queued" + ]; + }); + + jupyter-server = pythonPrev.jupyter-server.overridePythonAttrs (old: { + # The kernel reply arrived after the one-second outer deadline when the + # builder was heavily loaded. Keep the regression test but allow it the + # same margin as the other resource-sensitive tests. + postPatch = (old.postPatch or "") + '' + substituteInPlace tests/services/kernels/test_connection.py \ + --replace-fail \ + "await asyncio.wait_for(asyncio.wrap_future(conn2.request_kernel_info()), timeout=1.0)" \ + "await asyncio.wait_for(asyncio.wrap_future(conn2.request_kernel_info()), timeout=10.0)" + ''; + }); + + scipy = pythonPrev.scipy.overridePythonAttrs (old: { + # x86-64-v3 FFT implementations produce rounding differences outside + # these tests' strict tolerances. We accept the numerical-precision + # risk for our workloads. + disabledTests = (old.disabledTests or [ ]) ++ [ + "test_roundtrip_float32" + "test_roundtrip_scaling" + ]; + }); + + sentry-sdk = pythonPrev.sentry-sdk.overridePythonAttrs (old: { + # This test globally mocks threading.current_thread while another + # thread is running. On Python 3.14, Thread.join can race with that + # mock and exhaust its single side effect before the worker removes it. + disabledTests = (old.disabledTests or [ ]) ++ [ + "test_get_current_thread_meta_main_thread" + ]; + }); + + torchaudio = pythonPrev.torchaudio.overridePythonAttrs (old: { + # x86-64-v3 pitch shifting produces batch-versus-single-item numerical + # differences up to 2.9e-6. We accept that audio-precision risk for our + # workloads. + disabledTests = (old.disabledTests or [ ]) ++ [ + "test_batch_pitch_shift" + ]; + }); + + torchcodec = pythonPrev.torchcodec.overridePythonAttrs (old: { + # For these 8 kHz MP3 cases, the x86-64-v3 API and CLI codec paths + # differ in 0.8% of decoded samples. Retain the original tolerance for + # 99% of samples and accept the localized audio-precision risk. + postPatch = (old.postPatch or "") + '' + substituteInPlace test/test_encoders.py \ + --replace-fail \ + 'if sys.platform == "darwin":' \ + 'if sys.platform == "darwin" or ( + format == "mp3" + and sample_rate == 8_000 + and asset is SINE_MONO_S32 + and bit_rate in (None, 0) + and num_channels in (None, 1) + ):' + ''; + }); + }) + ]; +} diff --git a/overlays/x86-64-v3-workarounds.nix b/overlays/x86-64-v3-workarounds.nix new file mode 100644 index 0000000..c1e0bd0 --- /dev/null +++ b/overlays/x86-64-v3-workarounds.nix @@ -0,0 +1,60 @@ +# Compatibility fixes for packages rebuilt with x86-64-v3. +# +# The v3 baseline enables instructions that expose source assumptions hidden +# by the generic x86-64 build. Keep compile fixes here, separate from test +# exclusions, until upstream or nixpkgs incorporates them. +_final: prev: +let + patchAbseilBmi2Include = + package: + package.overrideAttrs (old: { + # GCC and Clang prohibit including their internal BMI2 header directly. + # The public umbrella provides the same intrinsics with the required + # compiler setup. + postPatch = (old.postPatch or "") + '' + substituteInPlace third_party/abseil-cpp/absl/container/internal/raw_hash_set.h \ + --replace-fail "#include " "#include " + ''; + }); + + removeSiblingOutputChecks = + package: + package.overrideAttrs (old: { + # Nix 2.34 can validate a partial multi-output rebuild against only the + # outputs still being realised. PostgreSQL's checks then reject valid + # sibling names such as "out" and "lib". Keep the test suite and + # disallowed-requisite checks; accept the loss of cross-output checks. + outputChecks = builtins.mapAttrs ( + _output: checks: builtins.removeAttrs checks [ "disallowedReferences" ] + ) (old.outputChecks or { }); + }); + + electron43Unwrapped = patchAbseilBmi2Include prev.electron_43.unwrapped; + electron43 = prev.electron_43.override { + electron-unwrapped = electron43Unwrapped; + }; + + signalCallPackage = + path: args: + let + package = prev.callPackage path args; + in + if builtins.baseNameOf path == "webrtc.nix" then patchAbseilBmi2Include package else package; +in +prev.lib.optionalAttrs ((prev.stdenv.hostPlatform.gcc.arch or null) == "x86-64-v3") { + deno = + let + librusty_v8 = patchAbseilBmi2Include prev.deno.passthru.librusty_v8; + in + prev.deno.override { inherit librusty_v8; }; + + electron_43 = electron43; + + postgresql = removeSiblingOutputChecks prev.postgresql; + postgresql_18 = removeSiblingOutputChecks prev.postgresql_18; + + signal-desktop = prev.signal-desktop.override { + electron_43 = electron43; + callPackage = signalCallPackage; + }; +} diff --git a/systems/bob/default.nix b/systems/bob/default.nix index 7cfff86..ff66879 100644 --- a/systems/bob/default.nix +++ b/systems/bob/default.nix @@ -13,7 +13,7 @@ "${inputs.self}/common/optional/systemd-boot.nix" "${inputs.self}/common/optional/tailscale.nix" "${inputs.self}/common/optional/update.nix" - "${inputs.self}/common/optional/yubikey.nix" + "${inputs.self}/common/optional/x86-64-v3.nix" "${inputs.self}/common/optional/zfs" ./hardware.nix ./syncthing.nix diff --git a/systems/brain/default.nix b/systems/brain/default.nix index 459f2f6..31e178a 100644 --- a/systems/brain/default.nix +++ b/systems/brain/default.nix @@ -9,6 +9,7 @@ "${inputs.self}/common/optional/systemd-boot.nix" "${inputs.self}/common/optional/tailscale.nix" "${inputs.self}/common/optional/update.nix" + "${inputs.self}/common/optional/x86-64-v3.nix" "${inputs.self}/common/optional/zfs" ./docker ./hardware.nix diff --git a/systems/brain/services/home_assistant.nix b/systems/brain/services/home_assistant.nix index b40740a..cad4cfd 100644 --- a/systems/brain/services/home_assistant.nix +++ b/systems/brain/services/home_assistant.nix @@ -68,7 +68,6 @@ paho-mqtt # for mqtt psycopg2 # for postgresql py-improv-ble-client # for esphome - pymodbus # for modbus pyopenweathermap # for weather pymetno # for met.no weather uiprotect # for ubiquiti integration @@ -76,7 +75,10 @@ jsonpath # for rest sensors monarchmoneycommunity # for monarch ]; - extraComponents = [ "isal" ]; + extraComponents = [ + "isal" + "modbus" # for victron modbus integration + ]; customComponents = with pkgs.home-assistant-custom-components; [ garmin_connect ]; diff --git a/systems/jeeves/default.nix b/systems/jeeves/default.nix index 242f708..33575cf 100644 --- a/systems/jeeves/default.nix +++ b/systems/jeeves/default.nix @@ -13,6 +13,7 @@ in "${inputs.self}/common/optional/syncthing_base.nix" "${inputs.self}/common/optional/tailscale.nix" "${inputs.self}/common/optional/update.nix" + "${inputs.self}/common/optional/x86-64-v3.nix" "${inputs.self}/common/optional/zfs" ./monitoring ./docker diff --git a/systems/portal-1/default.nix b/systems/portal-1/default.nix index 1d96af5..339af35 100644 --- a/systems/portal-1/default.nix +++ b/systems/portal-1/default.nix @@ -10,13 +10,12 @@ "${inputs.self}/users/richie" "${inputs.self}/common/global" "${inputs.self}/common/optional/tailscale.nix" + "${inputs.self}/common/optional/x86-64-v3.nix" ./disk-config.nix ./haproxy ./monitoring.nix ]; - nixpkgs.hostPlatform = "x86_64-linux"; - boot = { # Avoid consuming the VM's limited memory for /tmp. tmp.useTmpfs = false; diff --git a/systems/rhapsody-in-green/default.nix b/systems/rhapsody-in-green/default.nix index 82bfe53..0f8ec03 100644 --- a/systems/rhapsody-in-green/default.nix +++ b/systems/rhapsody-in-green/default.nix @@ -9,6 +9,7 @@ "${inputs.self}/common/optional/syncthing_base.nix" "${inputs.self}/common/optional/systemd-boot.nix" "${inputs.self}/common/optional/tailscale.nix" + "${inputs.self}/common/optional/x86-64-v3.nix" "${inputs.self}/common/optional/yubikey.nix" "${inputs.self}/common/optional/zfs" ./hardware.nix