diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 0a4ae73830..47980566d4 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -74,8 +74,36 @@ jobs: # Use the compiler fork in flake.lock, including both Android ABIs. # Stock GHC installed by ghcup is not interchangeable with this compiler. - name: Build existing Haskell outputs + id: haskell_build run: make nix-build + # Diagnose the same native library after failure. Keep the canonical + # failure and its flags intact; this is a separate diagnostic derivation. + - name: Trace native Linux GHC phases after failure + id: ghc_phase_trace + if: ${{ failure() && matrix.system == 'x86_64-linux' && steps.haskell_build.outcome == 'failure' }} + shell: bash + run: | + if [ -d "result-nix/$NIX_SYSTEM/lib:simplex-chat" ]; then + echo 'Native library already built; skipping its diagnostic trace.' + exit 0 + fi + echo 'attempted=true' >> "$GITHUB_OUTPUT" + flake_ref="git+file://$GITHUB_WORKSPACE?rev=$(git rev-parse HEAD)" + original="(builtins.getFlake \"$flake_ref\").hydraJobs.x86_64-linux.\"lib:simplex-chat\"" + timeout --kill-after=30s 7200s nice -n 10 nix build \ + --file scripts/nix/trace-native-ghc.nix --arg original "$original" \ + --no-link -L --keep-failed --max-jobs 1 --cores 1 --timeout 7200 \ + 2>&1 | tee native-linux-ghc-phase-trace.log + + - name: Upload native Linux GHC phase log + if: ${{ always() && steps.ghc_phase_trace.outputs.attempted == 'true' }} + uses: actions/upload-artifact@v4 + with: + name: nix-ghc-phase-trace-${{ github.sha }} + path: native-linux-ghc-phase-trace.log + if-no-files-found: warn + - name: Check native CLI encryption and profile passwords run: python3 scripts/nix/smoke-cli.py "result-nix/$NIX_SYSTEM/exe:simplex-chat/bin/simplex-chat" diff --git a/scripts/nix/README.md b/scripts/nix/README.md index 80c2e1e9fc..c43a1b2f8d 100644 --- a/scripts/nix/README.md +++ b/scripts/nix/README.md @@ -110,6 +110,16 @@ Darwin ARM64/x86_64, with a six-hour job limit. It uploads the existing library and CLIs under an artifact named for the source SHA. Each native CLI also runs the encrypted-store and profile-password smoke test, which can be repeated with `python3 scripts/nix/smoke-cli.py result-nix/BUILD_SYSTEM/exe:simplex-chat/bin/simplex-chat`. + +After a failed native x86_64 Linux library build, CI tries a separate derivation +with `-v2 -ddump-timings` on compile calls and uploads +`native-linux-ghc-phase-trace.log`, including when that trace fails. Compiler +queries and the original source, plan, dependency units and GHC arguments stay +unchanged. The canonical build remains failed. The trace has one job/core and a +two-hour wall-clock/per-derivation bound; the job's six-hour limit stays in place. +`trace-native-ghc.nix` accepts the original component via `--arg original` for the +same diagnostic locally. + Hydra continues to consume the same `hydraJobs` attributes. The older application release workflow still uses its existing GHCup/Docker compiler settings; migrating that packaging and diff --git a/scripts/nix/trace-native-ghc.nix b/scripts/nix/trace-native-ghc.nix new file mode 100644 index 0000000000..0a6cd72941 --- /dev/null +++ b/scripts/nix/trace-native-ghc.nix @@ -0,0 +1,30 @@ +# Compile-phase diagnostics for an existing, already-planned native component. +# This produces a different derivation; it does not change source, plan, compiler, +# dependency units or the original GHC arguments. Query output stays unchanged. +{ original }: +let + needle = " printf '#!/bin/sh\\nexec %s \"$@\"\\n' \"$g\" > \"$wrapBin/$gName\""; + replacement = " printf '#!/bin/sh\\n" + + "ghcTraceCompile=\\n" + + "for arg do\\n" + + " case \"$arg\" in\\n" + + " --numeric-version|--version|-V|--info|--print-*|--show-*|--supported-*|--abi-hash|-M)\\n" + + " exec \"%s\" \"$@\"\\n" + + " ;;\\n" + + " @*|--make|-c) ghcTraceCompile=1 ;;\\n" + + " esac\\n" + + "done\\n" + + "if [ -n \"$ghcTraceCompile\" ]; then\\n" + + " exec \"%s\" \"$@\" -v2 -ddump-timings\\n" + + "fi\\n" + + "exec \"%s\" \"$@\"\\n" + + "' \"$g\" \"$g\" \"$g\" > \"$wrapBin/$gName\""; + occurrences = phase: builtins.div + (builtins.stringLength phase - builtins.stringLength + (builtins.replaceStrings [ needle ] [ "" ] phase)) + (builtins.stringLength needle); +in original.overrideAttrs (old: + # Fail visibly if the pinned v2 builder changes its wrapper-generation seam. + assert occurrences old.buildPhase == 1; { + buildPhase = builtins.replaceStrings [ needle ] [ replacement ] old.buildPhase; + })