воскресенье, 26 апреля 2015 г.

Overriding hackage-packages in HaskellNG on NixOS

On #nixos, Benno Fünfstück (aka bennofs) has produced a very exhaustive explanation of how to override a hackage package in the new Haskell-NG infrastructure. (Here's the original gist).

I immediately felt that it's actually more of a blog post than a snippet, and so here it is:
{}: # nix-env expects a function

let 
  # Get nixpkgs (in configuration.nix, use pkgs for this, but this file is standalone
  # to test it easier so we have to manually import nixpkgs)
  pkgs = import  {};

  # First, get the haskell packages from nixpkgs. In configuration.nix, you
  # can use pkgs.haskellngPackages for this of course.
  haskellngPackages = pkgs.haskellngPackages;
  # (this could also be written as inherit (pkgs) haskellngPackages; )


  # The ghc-mod expression is defined in nixpkgs/pkgs/development/haskell-modules/hackage-packages.nix as:
  #    "ghc-mod" = callPackage
  #    ({ mkDerivation, async, base, Cabal, ......}:
  #     mkDerivation {
  #       pname = "ghc-mod";
  #       version = "5.2.1.2";
  #       sha256 = "11wnrdb6blw169w6kd49ax9h1r9qkka5329lmdhimvki8amv8riv";
  #       isLibrary = true;
  #       isExecutable = true;
  #       buildDepends = [ async base Cabal ..... ];
  #       testDepends = [ base Cabal ...... ];
  #       buildTools = [ emacs makeWrapper ];
  #       configureFlags = "--datasubdir=ghc-mod-5.2.1.2";
  #       postInstall = ''
  #         cd $out/share/ghc-mod-5.2.1.2
  #         make
  #         rm Makefile
  #         cd ..
  #         ensureDir "$out/share/emacs"
  #         mv ghc-mod-5.2.1.2 emacs/site-lisp
  #       '';
  #       homepage = "http://www.mew.org/~kazu/proj/ghc-mod/";
  #       description = "Happy Haskell Programming";
  #       license = stdenv.lib.licenses.bsd3;
  #     }) { inherit (pkgs) emacs;  inherit (pkgs) makeWrapper;};
  #  (..... marks parts that were abbreviated)
  #
  # Note that the package is build by a call to the `mkDerivation` function. We would like to pass an additional
  # argument to mkDerivation such that the expression instead looks like this:
  #     mkDerivation {
  #       pname = "ghc-mod";
  #       version = "5.2.1.2";
  #       src = pkgs.fetchgit {
  #         url = https://github.com/kazu-yamamoto/ghc-mod;
  #         rev = "247e4e0e7616fe1fecc68fdcf80d6249ac4cee4f";
  #         sha256 = "2a23271d0e6907351a246f095040ba18c3ab6bf1cba08a14338d701defa55474";
  #         # sha256 and rev can be determined using 'nix-prefetch-git https://github.com/kazu-yamamoto/ghc-mod'
  #       };
  #       .... # rest like above
  #     })
  #
  # This is what the haskell-ng.lib.overrideCabal function allows us to do.
  # `overrideCabal` expects a function that transforms the old argument set passed
  # to `mkDerivation` to a new argument set that will be passed to `mkDerivation`.
  #
  # So, we can define a new ghc-mod package that overrides the old haskellngPackages.ghc-mod package:
  ghc-mod-git = pkgs.haskell-ng.lib.overrideCabal haskellngPackages.ghc-mod (oldAttrs: {
    src = pkgs.fetchgit {
      url = https://github.com/kazu-yamamoto/ghc-mod;
      rev = "247e4e0e7616fe1fecc68fdcf80d6249ac4cee4f";
      sha256 = "2a23271d0e6907351a246f095040ba18c3ab6bf1cba08a14338d701defa55474";
     };
    # the new ghc mod also requires some new dependencies. Add them to buildDepends:
    buildDepends = oldAttrs.buildDepends ++ [ cabal-helper-new haskellngPackages.cereal ];
  });

  cabal-helper-new = pkgs.haskell-ng.lib.overrideCabal haskellngPackages.cabal-helper (oldAttrs: {
    version = "0.3.2.0";
    sha256 = "06igjmr0n8418wid1pr74cgvlsmwni7ar72g9bddivlbxax1pfli";
  });

  # The problem with this approach is: all packages that depend on ghc-mod will need to be
  # changed if they should use the new ghc-mod!
  #
  # Also, if haskellngPackages.ghc-mod also depends on cabal-helper, then ghc-mod-git will now
  # have two versions of cabal-helper in it's build environment!
  #
  # If we want reverse dependencies of ghc-mod to see the new ghc-mod too, we need to override
  # the haskellngPackage set. 
  
  # To do this, we need an override function. This function takes two arguments:
  #   self: this is the final package set, after all customizations have been applied
  #         (note that this is recursive: it is like 'fix $ \self -> ...' in Haskell)
  #   super: this is the "previous" package set, where previous means before our 
  #           customizations have been applied.
  overrideFunction = self: super: {
    # Here we just override ghc-mod like above, with one small difference: the package we override is
    # `super.ghc-mod`, not `haskellngPackages.ghc-mod`.
    ghc-mod = pkgs.haskell-ng.lib.overrideCabal super.ghc-mod (oldAttrs: {
      src = pkgs.fetchgit {
        url = https://github.com/kazu-yamamoto/ghc-mod;
        rev = "247e4e0e7616fe1fecc68fdcf80d6249ac4cee4f";
        sha256 = "2a23271d0e6907351a246f095040ba18c3ab6bf1cba08a14338d701defa55474";
      };

      # the new ghc mod also requires cereal and cabal-helper now. Add it to buildDepends:
      # note: we use self.cereal and self.cabal-helper here, so if the package set if 
      # overriden *again* in the future, this package will use the overriden cereal and cabal-helper.
      buildDepends = oldAttrs.buildDepends ++ [ self.cabal-helper self.cereal ];
    });

    # ghc-mod from git also requires a newer version of cabal-helper than nixos-unstable contains.
    cabal-helper = pkgs.haskell-ng.lib.overrideCabal super.cabal-helper (oldAttrs: {
      version = "0.3.2.0";
      sha256 = "06igjmr0n8418wid1pr74cgvlsmwni7ar72g9bddivlbxax1pfli";
    });
  };

  # Now we only need to apply our override function to the haskell package set.
  #
  # 
  # We can do this using `.override`.
  # `.override` allows to change the arguments given to callPackage, and the
  # haskellngPackage set is just defined as:
  #     haskellngPackages = callPackage ../development/haskell-modules {
  #       ghc = compiler.ghc784;
  #       packageSetConfig = callPackage ../development/haskell-modules/configuration-ghc-7.8.x.nix { };
  #     }
  # (it's an alias for packages.ghc784 defined in nixpkgs/pkgs/top-level/haskell-ng.nix)
  #
  # With .override, we pass an additional argument when calling ../development/haskell-modules 
  # (which is just nixpkgs/pkgs/development/haskell-modules/default.nix, the file that hooks everything together) called
  # overrides. This means that customizedPackages is essentially:
  #     haskellngPackages = callPackage ../development/haskell-modules {
  #       ghc = compiler.ghc784;
  #       packageSetConfig = callPackage ../development/haskell-modules/configuration-ghc-7.8.x.nix { };
  #       overrides = overrideFunction
  # `haskell-modules/default.nix` will then apply our custom overrides when building the package set.
  customizedPackages = haskellngPackages.override {
    overrides = overrideFunction;
  };

# Now we just take ghc-mod from the customized package set:
# By applying the overrides to all of haskellngPackages, all packages that
# depend on ghc-mod will also see the new ghc-mod.
in customizedPackages.ghc-mod 
#in ghc-mod-git # ghc-mod-git also works, as explained above.

# As said above, 

# You can test this using:
#
# nix-build /path/to/this/file.nix
# 
# Or install it to your user environment with:
#
# nix-env -i -f /path/to/this/file.nix
#
# You can even see that ghc-mod-git and customizedPackages.ghc-mod are exactly identical:
# just change customizedPackages.ghc-mod to ghc-mod-git above, and rebuild. Nix will not
# rebuild ghc-mod, but instead just use the already build ghc-mod, since the packages are identical.

Комментариев нет:

Отправить комментарий