In general, when developing Haskell programs on Nix, it is nice to enter a Nix shell with the required packages as determined by cabal2nix; I typically have a Makefile in the root of my project that looks like this, and then I run make nix-shell to enter a shell. Inside the Nix shell, I can run make run, make repl, etc. and they all work fine.
Unfortunately, this workflow causes some problems with regard to Emacs. Because I'd rather not (and I think this is true of most users of this kind of workflow) run my Emacs inside the Nix shell -- especially considering that it has to be restarted every time the Cabal file changes -- flycheck-haskell, ghc-mod, and other Haskell/Emacs tools don't have access to the dependencies they need to work properly.
I'm wondering if in its current/planned state haskell-ide-engine will have support for a workflow in which something like a GHC server (the titular engine?) can be launched inside the Nix shell, and Emacs can connect to it outside of the shell. If not, and the architectural changes aren't big, I would advocate moving to such a design.
It’s not supported atm, but we could add something like haskell-process-wrapper-function in haskell-mode. A PR would be great, otherwise I’ll add it at some point, but as it is not a priority for me I can’t promise when I’ll do it.
Okay, I'll look into writing a PR if I have some free time.
I gather stack has just received updated nix support. Does that help here?
FWIW: I've got master building in Nix with this Nix expression:
{ stdenv, fetchFromGitHub, haskell, haskellPackages }:
with haskell.lib;
let hie-src = stdenv.mkDerivation {
name = "haskell-ide-engine";
src = fetchFromGitHub {
owner = "haskell";
repo = "haskell-ide-engine";
rev = "a4759cd3b8f3245cfafcf8beebada53273c12b77";
sha256 = "0ngi2cx3wyynxgyk2ggk54vb8c4kqdj2dn02mwzlgs7iiv6p43nf";
};
phases = ["unpackPhase" "buildPhase"];
buildPhase = ''
for p in hie-apply-refact hie-base hie-example-plugin2 hie-eg-plugin-async hie-plugin-api hie-ghc-mod hie-ghc-tree hie-hare hie-hoogle
do
cp ./LICENSE ./$p/LICENSE
sed -i "s/..\/LICENSE/LICENSE/" ./$p/$p.cabal
done
cp -r ./ $out
'';
};
in (haskellPackages.extend (self: super: {
ghc-exactprint = dontCheck super.ghc-exactprint;
lens = dontCheck self.lens_4_15_2;
process-extras = dontCheck super.process-extras;
haskell-lsp = dontHaddock (self.callCabal2nix "haskell-lsp" (fetchFromGitHub {
owner = "alanz";
repo = "haskell-lsp";
rev = "47d3ad66a5a73069a5c367672ff1fd06a521cc02";
sha256 = "1p179i7x8nrk6nzk8mjd9lgrcxm03lblifr0bz75drvpf4chagid";
}) {});
ghc-dump-tree = self.callCabal2nix "ghc-dump-tree" (fetchFromGitHub {
owner = "alanz";
repo = "ghc-dump-tree";
rev = "9dbca928ad5507b144f46bb95123a08d24f24808";
sha256 = "1n2p5iskc6764axafh2iw1v1n6wmkac5g5j2qhqs8j3q6hp6yij8";
}) {};
/*hspec-jenkins = self.callCabal2nix "hspec-jenkins" (fetchFromGitHub {
owner = "sol";
repo = "hspec-jenkins";
rev = "cb012b23c4e524dde9ea129b6146dda20a5ca505";
sha256 = "0ngi2cx3wyynxgyk2ggk54vb8c4kqdj2dn02mwzlgs7iiv6p43nf";
}) {};*/
HaRe = dontCheck (self.callCabal2nix "HaRe" (fetchFromGitHub {
owner = "alanz";
repo = "HaRe";
rev = "423bc0b235dc7ede961278f6e614813b851bfd0a";
sha256 = "0b0sbspbmilvf9d3kz5r084nh8qr4i7k7gbxibrpj16bp9zbb07p";
}) {});
ghc-mod = dontCheck (self.callCabal2nix "ghc-mod" (fetchFromGitHub {
owner = "alanz";
repo = "ghc-mod";
rev = "4f6160976dbc211ee325723a42124e5f032ff16a";
sha256 = "1axbgqr13ywdvmhnyban3kp06bxrab5ccyvwf2ni3d1kg72pd8vx";
}) { shelltest = null; });
haskell-ide-engine = dontCheck (self.callCabal2nix "haskell-ide-engine" hie-src {});
hie-apply-refact = self.callCabal2nix "hie-apply-refact" "${hie-src}/hie-apply-refact" {};
hie-base = self.callCabal2nix "hie-base" "${hie-src}/hie-base" {};
hie-example-plugin2 = self.callCabal2nix "hie-example-plugin2" "${hie-src}/hie-example-plugin2" {};
hie-eg-plugin-async = self.callCabal2nix "hie-eg-plugin-async" "${hie-src}/hie-eg-plugin-async" {};
hie-plugin-api = self.callCabal2nix "hie-plugin-api" "${hie-src}/hie-plugin-api" {};
hie-ghc-mod = self.callCabal2nix "hie-ghc-mod" "${hie-src}/hie-ghc-mod" {};
hie-ghc-tree = self.callCabal2nix "hie-ghc-tree" "${hie-src}/hie-ghc-tree" {};
hie-hare = self.callCabal2nix "hie-hare" "${hie-src}/hie-hare" {};
hie-hoogle = self.callCabal2nix "hie-hoogle" "${hie-src}/hie-hoogle" {};
/*hie-docs-generator = self.callCabal2nix "hie-docs-generator" "${hie-src}/hie-docs-generator" {};*/
})).haskell-ide-engine
I had a bunch of problems with the tests that I didn't want to spend the time to track down, so I just used dontCheck on them. I suggest that haskell-ide-engine put the LICENSE file in each of the individual subdirs, rather than linking to ../LICENSE. I had to do some crap with sed to get Nix to build those subpackages, since Nix needs the root directory to be the one with the cabal file, meaning there's no parent directory to get the license file from.
Anyway, in your haskell project's shell.nix, just nixpkgs.callPackage ./hie.nix {} to get the derivation, and put it in some buildInputs or something to get it on the path. The nice thing about this is that it guarantees that you can have several projects with their own versions of GHC all using the ghc-mod stuff, even thought ghc-mod is very particular about GHC versions.
Here's a newer version of above snippet that works on top of HIE commit cc71e5bd and nixpkgs release-17.03 (https://github.com/NixOS/nixpkgs/commit/1e30a7c6170a469b36186ad210f7a52532d6ba4e):
{ stdenv, fetchFromGitHub, haskell, haskellPackages }:
with haskell.lib;
let hie-src = stdenv.mkDerivation {
name = "haskell-ide-engine";
src = fetchFromGitHub {
owner = "haskell";
repo = "haskell-ide-engine";
rev = "cc71e5bdbb58351712d3bdf8b42caa4153bb6c78";
sha256 = "0w7mapn5yl9n4ymjwkf3cijacsgd1sfj2f795lv65yhrnxfrbymj";
};
phases = ["unpackPhase" "buildPhase"];
buildPhase = ''
for p in hie-apply-refact hie-base hie-build-plugin hie-example-plugin2 hie-eg-plugin-async hie-plugin-api hie-ghc-mod hie-ghc-tree hie-hare hie-hoogle hie-brittany hie-haddock
do
cp ./LICENSE ./$p/LICENSE
sed -i "s/..\/LICENSE/LICENSE/" ./$p/$p.cabal
done
cp -r ./ $out
'';
};
in (haskellPackages.extend (self: super: {
process-extras = dontCheck super.process-extras;
monad-memo = dontCheck super.monad-memo;
multistate = dontCheck super.multistate;
hlint = dontCheck (self.callCabal2nix "hlint" (fetchFromGitHub {
owner = "ndmitchell";
repo = "hlint";
rev = "v2.0.9";
sha256 = "1zljl8nfvj8r3z19gdfpldrvf1apznlvwrp96yf50akql0niam3n";
}) {});
apply-refact = dontCheck (self.callCabal2nix "apply-refact" (fetchFromGitHub {
owner = "mpickering";
repo = "apply-refact";
rev = "v0.3.0.1";
sha256 = "15cac2vg307k95bm1d02kgbs48vxbmfi7r4di1rpsgmabpg6jyp5";
}) {});
lens = dontCheck (self.callCabal2nix "lens" (fetchFromGitHub {
owner = "ekmett";
repo = "lens";
rev = "v4.15.3";
sha256 = "1vfdy6mbpari5ldhh6r054f9ab67svz8q2bapzzmkhbf90jyi5jl";
}) {});
ghc-exactprint = dontCheck (self.callCabal2nix "ghc-exactprint" (fetchFromGitHub {
owner = "alanz";
repo = "ghc-exactprint";
rev = "v0.5.4.0";
sha256 = "0x6s319s427l3p2j6bwfj59k4iii4kh214klmwm4b6bn385yfdqi";
}) {});
hoogle = dontCheck (self.callCabal2nix "hoogle" (fetchFromGitHub {
owner = "ndmitchell";
repo = "hoogle";
rev = "v5.0.13";
sha256 = "06nyjn17hcyp4i9llfzsqmiwygil8d1z7axwnaq933gscbf51gld";
}) {});
haskell-lsp = dontHaddock (self.callCabal2nix "haskell-lsp" (fetchFromGitHub {
owner = "alanz";
repo = "haskell-lsp";
rev = "beeab9fc8a98dee87ee7291e7191dee762d90b6d";
sha256 = "1mcpvvc493bq85pfa3gqf4kbg2zfa70k4189zzc01ngdjs90rixw";
}) {});
ghc-dump-tree = self.callCabal2nix "ghc-dump-tree" (fetchFromGitHub {
owner = "alanz";
repo = "ghc-dump-tree";
rev = "50f8b28fda675cca4df53909667c740120060c49";
sha256 = "0v3r81apdqp91sv7avy7f0s3im9icrakkggw8q5b7h0h4js6irqj";
}) {};
/*hspec-jenkins = self.callCabal2nix "hspec-jenkins" (fetchFromGitHub {
owner = "sol";
repo = "hspec-jenkins";
rev = "cb012b23c4e524dde9ea129b6146dda20a5ca505";
sha256 = "0ngi2cx3wyynxgyk2ggk54vb8c4kqdj2dn02mwzlgs7iiv6p43nf";
}) {};*/
HaRe = dontCheck (self.callCabal2nix "HaRe" (fetchFromGitHub {
owner = "alanz";
repo = "HaRe";
rev = "e325975450ce89d790ed3f92de3ef675967d9538";
sha256 = "0z7r3l4j5a1brz7zb2rgd985m58rs0ki2p59y1l9i46fcy8r9y4g";
}) {});
ghc-mod = dontCheck (self.callCabal2nix "ghc-mod" (fetchFromGitHub {
owner = "alanz";
repo = "ghc-mod";
rev = "c9b209c08779b2166300dc2ec69d7f5bb2955eb3";
sha256 = "182ggv5g766nfzlgz5dh6wkficl2rfxpys4dlg9bblwqc1q46asi";
}) { shelltest = null; });
ghc-mod-core = dontCheck (self.callCabal2nix "ghc-mod-core" ((fetchFromGitHub {
owner = "alanz";
repo = "ghc-mod";
rev = "c9b209c08779b2166300dc2ec69d7f5bb2955eb3";
sha256 = "182ggv5g766nfzlgz5dh6wkficl2rfxpys4dlg9bblwqc1q46asi";
}) + "/core") {});
cabal-helper = dontCheck (self.callCabal2nix "cabal-helper" (fetchFromGitHub {
owner = "nh2"; # change back to alanz when https://github.com/alanz/cabal-helper/pull/1 is merged
repo = "cabal-helper";
rev = "68f45e22554e90fbc332ba402236ec96d3139be7";
sha256 = "0l0ssmz0zb7m8hi1hzz371h02h5wa3v6f6m6pvg1ai42rkcdwzqb";
}) {});
brittany = dontCheck (self.callCabal2nix "brittany" (fetchFromGitHub {
owner = "alanz";
repo = "brittany";
rev = "32a193f0ce02ec36a6032852454db96573ab3a60";
sha256 = "0hnvzfgqpx9kr252iq1iwd8kchrf3lgiijp94m751l7zckkd352r";
}) {});
haskell-ide-engine = dontCheck (self.callCabal2nix "haskell-ide-engine" hie-src {});
hie-apply-refact = self.callCabal2nix "hie-apply-refact" "${hie-src}/hie-apply-refact" {};
hie-base = self.callCabal2nix "hie-base" "${hie-src}/hie-base" {};
hie-build-plugin = dontHaddock (self.callCabal2nix "hie-build-plugin" "${hie-src}/hie-build-plugin" {});
hie-example-plugin2 = self.callCabal2nix "hie-example-plugin2" "${hie-src}/hie-example-plugin2" {};
hie-eg-plugin-async = self.callCabal2nix "hie-eg-plugin-async" "${hie-src}/hie-eg-plugin-async" {};
hie-plugin-api = self.callCabal2nix "hie-plugin-api" "${hie-src}/hie-plugin-api" {};
hie-ghc-mod = self.callCabal2nix "hie-ghc-mod" "${hie-src}/hie-ghc-mod" {};
hie-ghc-tree = self.callCabal2nix "hie-ghc-tree" "${hie-src}/hie-ghc-tree" {};
hie-hare = self.callCabal2nix "hie-hare" "${hie-src}/hie-hare" {};
hie-hoogle = self.callCabal2nix "hie-hoogle" "${hie-src}/hie-hoogle" {};
hie-brittany = self.callCabal2nix "hie-brittany" "${hie-src}/hie-brittany" {};
hie-haddock = self.callCabal2nix "hie-haddock" "${hie-src}/hie-haddock" {};
/*hie-docs-generator = self.callCabal2nix "hie-docs-generator" "${hie-src}/hie-docs-generator" {};*/
})).haskell-ide-engine
I'm having trouble getting ghc-mod when called by HIE to work inside nix-shell:
Got error while processing diagnostics: myfile.hs:56:17: fatal:
cannot find object file ‘/run/user/1000/ghc-mod10620/ghc10619_0/ghc_79.dyn_o’
while linking an interpreted expression
I tried this but on VSCode I get this error
[Error - 11:15:03 AM] Request textDocument/hover failed.
Message: IdeError {ideCode = PluginError, ideMessage = "getSymbolAtPoint: \"module not loaded\"", ideInfo = Null}
Code: -32603
And there was one more error reported regarding failure to load .so/.dll for lens library due to undefined data.text.breakOn closure..
Is there a way to debug what is going wrong here...
Once we fix https://github.com/NixOS/cabal2nix/issues/313 then https://github.com/input-output-hk/stack2nix could be quick way to achieve this.
@domenkozar I'm not sure about stack2nix in this case. I think you really want HIE built with your project's Haskell package set, not the one in this repo's stack.yaml, if for no other reason than to ensure ghc-mod is built with the same GHC version as your project (which may be something exotic, like HEAD or even a fork).
To me this just means we need proper Hackage releases for everything. Once that's done, we can get it all in Stackage and nixpkgs and follow the ordinary channels for this sort of stuff.
@ElvishJerricco wouldn't it be better to just ship each project with ghc-mod and cabal-helper?
@domenkozar I don't understand. What does "ship" mean in this context?
We could build hie with stack2nix and then each project can have ghc-mod and cabal-helper in dependency closure.
IIRC, hie has a compile time dependency on ghc-mod, since it uses ghc-mod as a library, not an executable. That ghc-mod ought to be one built with your project's GHC version, which sort of mandates that hie be as well.
Regardless, I'm just not seeing a downside to the typical flow of things here. Get it on Hackage, then get it in stable package sets, then it's easy for anyone to drop it into their project. The only downside is having to wait for things to stabilize enough for a release, but that's arguably an upside ;)
@ElvishJerricco I was just talking to @alanz on IRC.
You're right, best is to get hie on hackage. As a stop gap, we could use stack2nix for each stackage LTS.
Then all that's needed to work within nix-shell is some glue, which will pick hie based on some convention. For example, by default it runs hie, but if ghc version available is X.X.X, then hie tries to run hie-X.X.X.
Going back to the issue I originally raised, I think it might actually be advantageous _not_ to use haskell-process-wrapper-function, since I think a lot of the brittleness involved in running processes under emacs could be entirely avoided if there were some way to run the server separately, and just have emacs connect to it.
Ideally, you could have multiple instances of the server open, and they would put their sockets in some known directory, and then emacs would use something like inotify to monitor this directory. When a socket is added, emacs will send a request to the server asking for its current working directory, and it will be added to a global hashtable variable. When a socket is removed, emacs will delete the corresponding entry in the hashtable. Then, when a haskell file is opened, emacs will check if the path is a subpath of any of the hashtable entries, and if it is, the socket will be used to provide IDE features.
This is relatively similar to the approach used by hdevtools, except hdevtools will put its socket in the directory in which it is launched, which means it can pollute the project directory (e.g.: if the server crashes, and it _will_ :^)). If inotify is too cumbersome, then we could instead have emacs create a socket in that directory, and the HIE server when launched would send a message along that socket to notify emacs that it has been launched. Whatever scheme you choose, it should support multiple HIE servers, multiple independent editor processes, and most importantly, the environment in which the HIE server runs should be fully user-controlled (and don't do nonsense like what hdevtools does, where if you run an hdevtools command and the server isn't running, it will start automatically, since the whole point is to get the user in the habit of launching the server in a terminal when they want it; it's fine to provide something like C-c C-l for users that want that workflow, but it should be opt-in IMO).
I've packaged hie for Nix: https://github.com/domenkozar/hie-nix
[comment moved] https://github.com/haskell/haskell-ide-engine/issues/357#issuecomment-366567730
hie-wrapper now exists and I think that would be a good place to put in a check for Nix. There's already a check for Stack: https://github.com/haskell/haskell-ide-engine/blob/master/app/HieWrapper.hs#L103
Does that sound reasonable?
@puffnfresh hie-wrapper is definitely the place to put it.
I'd like to add another suggestion - use direnv-mode with a .envrc that contains use nix. If you do this, Emacs will essentially source .envrc whenever you change buffers, and change PATH accordingly. Now, anything that needs an executable on PATH will get stuff from nix-shell. I haven't tried this with hie, but this works really well with things like compile-command, where you can just cabal build as normal.
Should this issue stay open as the general "nix" discussion point?
I think we can close it and open specific nix issues. One can now run hie-nix within nix-shell and their editor separately, which fixes the original request.
Most helpful comment
I've packaged hie for Nix: https://github.com/domenkozar/hie-nix