# flake.nix - Nix-defined package and devel env for the Mercurial project.
#
# Copyright 2021-2023 Pacien TRAN-GIRARD <pacien.trangirard@pacien.net>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.

# Usage summary, from the root of this repository:
#
# Enter a shell with development tools:
#   nix develop 'hg+file:.?dir=contrib/nix'
#
# Running mercurial:
#   nix run 'hg+file:.?dir=contrib/nix' -- version
#
# Running the test suite in a sandbox:
#   nix build 'hg+file:.?dir=contrib/nix#mercurial-tests' -L

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-22.11";
    nixpkgs-black.url = "github:NixOS/nixpkgs/c7cb72b0";  # black 20.8b1
    # rust-overlay.url = "github:oxalica/rust-overlay";
    flake-utils.url = "github:numtide/flake-utils";
    flaky-utils.url = "git+https://cgit.pacien.net/libs/flaky-utils";
  };

  outputs = {
    self
  , nixpkgs
  , nixpkgs-black
  # , rust-overlay
  , flake-utils
  , flaky-utils
  }:
  flake-utils.lib.eachDefaultSystem (system:
  let
    # overlays = [ (import rust-overlay) ];
    pkgs = import nixpkgs { inherit system; };

    # We're in the contrib/nix sub-directory.
    src = ../..;

    # For snapshots, to satisfy extension minimum version requirements.
    dummyVersion = "99.99";

    pin = {
      # The test suite has issues with the latest/current versions of Python.
      # Use an older recommended version instead, matching the CI.
      python = pkgs.python39;

      # The project uses a pinned version (rust/clippy.toml) for compiling,
      # but uses formatter features from nightly.
      # TODO: make cargo use the formatter from nightly automatically
      #       (not supported by rustup/cargo yet? workaround?)
      # rustPlatform = pkgs.rust-bin.stable."1.85.1".default;
      # rustPlatformFormatter = pkgs.rust-bin.nightly."2023-04-20".default;

      # The CI uses an old version of the Black code formatter,
      # itself depending on old Python libraries.
      # The formatting rules have changed in more recent versions.
      inherit (import nixpkgs-black { inherit system; }) black;
    };

  in rec {
    apps.mercurial = apps.mercurial-rust;
    apps.default = apps.mercurial;
    apps.mercurial-c = flake-utils.lib.mkApp {
      drv = packages.mercurial-c;
    };
    apps.mercurial-rust = flake-utils.lib.mkApp {
      drv = packages.mercurial-rust;
    };

    packages.mercurial = packages.mercurial-rust;
    packages.default = packages.mercurial;

    packages.mercurial-c = pin.python.pkgs.buildPythonApplication {
      format = "other";
      pname = "mercurial";
      version = "SNAPSHOT";
      passthru.exePath = "/bin/hg";
      inherit src;

      postPatch = ''
        echo 'version = b"${toString dummyVersion}"' \
          > mercurial/__version__.py

        patchShebangs .

        for f in **/*.{py,c,t}; do
          # not only used in shebangs
          substituteAllInPlace "$f" '/bin/sh' '${pkgs.stdenv.shell}'
        done
      '';

      buildInputs = with pin.python.pkgs; [
        docutils
      ];

      nativeBuildInputs = with pkgs; [
        gettext
        installShellFiles
      ];

      makeFlags = [
        "PREFIX=$(out)"
      ];

      buildPhase = ''
        make local
      '';

      # Test suite is huge ; run on-demand in a separate package instead.
      doCheck = false;
    };

    packages.mercurial-rust = packages.mercurial-c.overrideAttrs (super: {
      cargoRoot = "rust";
      cargoDeps = pkgs.rustPlatform.importCargoLock {
        lockFile = "${src}/rust/Cargo.lock";
      };

      nativeBuildInputs = (super.nativeBuildInputs or []) ++ (
        with pkgs.rustPlatform; [
          cargoSetupHook
          rust.cargo
          rust.rustc
        ]
      );

      makeFlags = (super.makeFlags or []) ++ [
        "PURE=--rust"
      ];
    });

    packages.mercurial-tests = pkgs.stdenv.mkDerivation {
      pname = "mercurial-tests";
      version = "SNAPSHOT";
      inherit src;

      buildInputs = with pkgs; [
        pin.python
        pin.black
        unzip
        which
        sqlite
      ];

      postPatch = (packages.mercurial.postPatch or "") + ''
        # * paths emitted by our wrapped hg look like ..hg-wrapped-wrapped
        # * 'hg' is a wrapper; don't run using python directly
        for f in **/*.t; do
          substituteInPlace 2>/dev/null "$f" \
            --replace '*/hg:' '*/*hg*:' \
            --replace '"$PYTHON" "$BINDIR"/hg' '"$BINDIR"/hg'
        done
      '';

      buildPhase = ''
        export HGTEST_REAL_HG="${packages.mercurial}/bin/hg"
        export HGMODULEPOLICY="rust+c"
        export HGTESTFLAGS="--blacklist blacklists/nix"
        make check 2>&1 | tee "$out"
      '';
    };

    devShell = flaky-utils.lib.mkDevShell {
      inherit pkgs;

      tools = [
        pin.python
        pin.black
      ];
    };
  });
}
