Sunday, February 08, 2015

Developing in golang with Nix package manager

I've been using Go since several months. It's a pleasant language, even though it has its own drawbacks.

In our Nixpkgs repository we have support for several programming languages: perl, python, ruby, haskell, lua, ... We've merged a better support for Go.

What kind of support are we talking about? In Nix, you never install libraries. Instead, you define an environment in which to use a certain library compiled for a certain version of the language. The library will be available only within this environment.

Think of it like virtualenv for python, except for any language, and also being able to mix them.
On the other hand Nix requires the src url and the checksum of every dependency of your project. So before starting, make sure you are willing to write nix packages that are not currently present in nixpkgs.

Also you probably have to wait a couple of days before this PR will be available in the unstable channel, at the time of this writing (otherwise git clone https://github.com/NixOS/nixpkgs.git).

Using nix-shell -p

First a quick example using nix-shell -p for your own project:
$ nix-shell '<nixpkgs>' -p goPackages.go goPackages.net goPackages.osext
[nix-shell]$ echo $GOPATH
/nix/store/kw9dryid364ac038zmbzq72bnh3zsinz-go-1.4.1-go.net-3338d5f109e9/share/go:...
That's how nix mostly works, it's as simple as that. The GOPATH is set for every package that provides a directory share/go.

What is goPackages? Currently it's go14Packages, which is all the go packages we have, compiled with go 1.4. There's also go13Packages, you know some particular packages don't work with go 1.4 yet.

Writing a nix file

A more structured example by writing a default.nix file in your project:
with import <nixpkgs> {}; with goPackages;

buildGoPackage rec {
  name = "yourproject";
  buildInputs = [ net osext ];
  goPackagePath = "github.com/you/yourproject";
}
Then you can just run nix-shell in your project directory and have your dev environment ready to compile your code.
The goPackagePath is something needed by buildGoPackage, in case you are going to run nix-build. Ignore it for now.

Writing a dependency

But nixpkgs doesn't have listed all the possible go projects. What if you need to use a particular library?
Let's take for example github.com/kr/pty. Write something like this in a pty.nix file:
{ goPackages, fetchFromGitHub }:

goPackages.buildGoPackage rec {
  rev = "67e2db24c831afa6c64fc17b4a143390674365ef";
  name = "pty-${rev}";
  goPackagePath = "github.com/kr/pty";
  src = fetchFromGitHub {
    inherit rev;
    owner = "kr";
    repo = "pty";
    sha256 = "1l3z3wbb112ar9br44m8g838z0pq2gfxcp5s3ka0xvm1hjvanw2d";
  };
}
Then in your default.nix:
with import <nixpkgs> {}; with goPackages;

let
  pty = callPackage ./pty.nix {};
in
buildGoPackage rec {
  name = "yourproject";
  buildInputs = [ net osext pty ];
  goPackagePath = "github.com/you/yourproject";
}
Type nix-shell and now you will also have pty in your dev environment.
So as you can see, for each go package nix requires a name, the go path, where to fetch the sources, and the hash.

You may be wondering how do you get the sha256: a dirty trick is to write a wrong sha, then nix will tell you the correct sha.

Conclusion and references

Nix looks a little complex and boring due to writing a package for each dependency. On the other hand you get for free:
  • Exact build and runtime dependencies
  • Sharing build and runtime dependencies between multiple projects
  • Easily test newer or older versions of libraries, without messing with system-wide installations
  • Mix with other programming languages, using a similar approach
  • Packages using C libraries don't need to be compiled manually by you: define the nix package once, reuse everywhere
For installing nix, follow the manual. Make sure you read the entire document to learn the nix syntax.

For more examples on how to write dependencies, you can look at nixpkgs goPackages itself.

Drop by #nixos on irc.freenode.net for any doubts.


18 comments:

Craig Swank said...

Thanks for the article. When I copy try to use the default.nix example you provide I get an error:

➜ nix-shell
error: undefined variable ‘goPackages’ at "/private/tmp/gavels/default.nix":1:32

Am I doing something wrong?

Luca Bruno aka Lethalman said...

goPackages has landed when I posted this article. Try updating your nix channel and let me know.

Craig Swank said...

Still the same thing:

➜ gavels nix-channel --update
downloading Nix expressions from ‘https://nixos.org/releases/nixpkgs/nixpkgs-15.05pre57176.b9cc043/nixexprs.tar.xz’...
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
unpacking channels...
➜ gavels nix-shell
error: undefined variable ‘goPackages’ at "/private/tmp/gavels/default.nix":1:32

I'm really just learning how to do use nix-shell, so I may be missing something. I'm having a hard time getting up to speed with nix-shell.

Luca Bruno aka Lethalman said...

Ok I think the problem is that nixpkgs channel is old because stuck at a darwin regression since several days.

Can you try the unstable nixos channel instead?
Try removing nixpkgs with nix-channel --remove nixpkgs and then nix-channel --add https://nixos.org/channels/nixos-unstable nixos and update again.

You can add back the nixpkgs channel at any time with nix-channel --add http://nixos.org/channels/nixpkgs-unstable nixpkgs .

Just don't have both of them, only one.

Craig Swank said...

➜ /tmp nix-channel --remove nixpkgs
➜ /tmp nix-channel --add https://nixos.org/channels/nixos-unstable nixos
➜ /tmp nix-channel --update
downloading Nix expressions from ‘https://nixos.org/releases/nixos/unstable/nixos-15.05pre58123.9775f46/nixexprs.tar.xz’...
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
unpacking channels...
➜ /tmp nix-shell
error: getting status of ‘/private/tmp/default.nix’: No such file or directory
➜ /tmp cd gavels
➜ gavels nix-shell
error: file ‘nixpkgs’ was not found in the Nix search path (add it using $NIX_PATH or -I)

I'm not sure what that last line means. This dialog will be nice for others who try this.

Luca Bruno aka Lethalman said...

Right, give me a couple of minutes to get back to my box. The NIX_PATH must be fixed to point to nixos.

Luca Bruno aka Lethalman said...

Ok try: export NIX_PATH=~/.nix-defexpr/channels/nixos

Craig Swank said...

A bunch of stuff is happening now, so that appears to be the missing part. Thanks for the help.

I'm really looking forward to learning more.

Craig Swank said...

After about 1.5 hours of building, the nix shell command failed with:

go install runtime/cgo: open /nix/store/8pl62z9jizl283big2c012xxvchilzas-go-1.4.1/share/go/pkg/darwin_amd64/runtime/cgo.a: permission denied

Is that due to something else I did wrong or is it a bug I need to report to someone?

Luca Bruno aka Lethalman said...

I'm really sorry, seems like you are on darwin but I never tested that goPackages on darwin.
So if you can, please open an issue at https://github.com/NixOS/nixpkgs/issues/new and please paste your default.nix expression, that's very important. I will take a look better there.

Anonymous said...

How can I pull the submodules in a repo with fetchFromGitHub?

Luca Bruno aka Lethalman said...

With fetchFromGitHub I don't know. I don't think it's possible because it fetches github archives.
But you can try with fetchgit which has a deepClone option.

Anonymous said...

Ended up using fetchgit which has a fetchSubmodules option.

Anonymous said...

I am getting an error about being outside the GOPATH when installing git2go with this method. Does nix not put the source in the GOPATH?

Luca Bruno aka Lethalman said...

Can you explain better what you mean by "installing git2go"? Feel free to report an issue in nixpkgs.

Anonymous said...

If I have a built package that is not on github but in a private git repository does NIX support pulling from that.
There is fetchURL but there does not seem to be a fetchGit.

Alex said...

Thanks for the writeup!

I am a bit confused as to how to develop go packages:
- How do I build and test my code? nix-build complains on not having src or srcs set
- Do I still need a Makefile like a lot go projects go, how to support non-nix users to allow them to build a package from the source?
- Using nix-shell breaks my emacs configuration, which is loaded as a systemd service and is always running, is there a sort of catch-all way to allow GOPATH to be correctly set at emacs startup, without going to nix-shell?

Thanks a lot!

Luca Bruno aka Lethalman said...

I think few people use nix for developing golang currently. In nixpkgs it's mostly used to build applications and their related libraries.
There's a lot of room for improvement on this side, just the community still didn't catch up on the idea of using nix instead of go get.

As for nix-build: try setting src = ./. and somehow inhibit the unpackPhase.
As for nix-shell, the problem there is usually because PS1 is broken after launching another shell.

If you come on irc.freenode.net #nixos we can sort all the things out. Perhaps creating an issue and incrementally improve the golang development experience on nix.