A Nix Merge Request

1. Introduction

Most everyone I talk to about computers and software want things that “Just work!” The older I get, the more sympathetic I am to this view. There just isn’t enough time in the day to be futzing around with broken things; however, when it comes to my personal computers, I tend to run in the opposite direction. I just love to tinker, and my PCs provide the perfect playground for that. Whenever I find that they “Just work,” why, that’s perfect time to break them with some new project! As a consequence, these days I find myself running the unstable branch of NixOS.

One of the neat things about running on the unstable branch of an new-ish distro is that I often get to see bugs pop up before too many other people. When I used to run a very popular distro, like Arch Linux, solving a bug was mostly a matter of tracking down the forum post containing its fix. With so many nerds on the case, all the fun bugs are fixed before I’ve even finished my work day. With NixOS, if your system configuration is… unique enough, you could easily bump into a bug a day or two before anyone else does.

Earlier this week I ran into such a bug. When doing a system upgrade, I encountered an error for a package that I’m somewhat familiar with, and the error looked pretty easy, so why not take a shot at fixing it?

2. The error

When updating my system to a more recent commit from nixos-unstable, and running nixos-rebuild switch --recreate‑lock‑file, I hit the following error:

error: builder for '/nix/store/iaajnwfiwxwmkfjkpj8csmzs1lin1cax-nixpkgs-firefox-addons-0.8.0.drv' failed with exit code 1;
       last 10 log lines:
       >     It could refer to
       >        either ‘Prelude.getArgs’,
       >               imported from ‘Prelude’ at src/Main.hs:5:8-11
       >               (and originally defined in ‘Relude.Lifted.Env’)
       >            or ‘System.Environment.getArgs’,
       >               imported from ‘System.Environment’ at src/Main.hs:13:28-34
       >    |
       > 48 | parseArgs = getArgs >>= parse
       >    |             ^^^^^^^
       >
       For full logs, run 'nix log /nix/store/iaajnwfiwxwmkfjkpj8csmzs1lin1cax-nixpkgs-firefox-addons-0.8.0.drv'.
error: 1 dependencies of derivation '/nix/store/cihwc6c2ya2icfp10ch2b56kry2nr6ac-home-manager-path.drv' failed to build
error: 1 dependencies of derivation '/nix/store/iihzy9jsbispd6n0m0iv3izcbbkpv7ak-home-manager-path.drv' failed to build
error: 1 dependencies of derivation '/nix/store/j4lli5sd58g6l2ahmm1kfgykjyfx4ibz-home-manager-generation.drv' failed to build
error: 1 dependencies of derivation '/nix/store/yn2ygc4zz3ch0yiam4g162rarlqf3a95-home-manager-generation.drv' failed to build
error: 1 dependencies of derivation '/nix/store/23qwfk4hcb37d2jnsglxn535v22as5wa-user-environment.drv' failed to build
error: 1 dependencies of derivation '/nix/store/3w27sfjv2z4ky9nx5npq71xcvh2vylrm-etc.drv' failed to build
error: 1 dependencies of derivation '/nix/store/nbhij1q45y3l93njr5di18gjkrwv20bj-nixos-system-desktop-21.11.20210802.c464dc8.drv' failed to build

I was familiar enough with this package (and Haskell) to recognise this as a Haskell project. Running the suggested command gives more information both about the nix build process, and the cabal build output:

nix log /nix/store/iaajnwfiwxwmkfjkpj8csmzs1lin1cax-nixpkgs-firefox-addons-0.8.0.drv
Over-long nix log output…
@nix { "action": "setPhase", "phase": "setupCompilerEnvironmentPhase" }
setupCompilerEnvironmentPhase
Build with /nix/store/kp4hnhfkd7c6gf3fnb8z897nwnpvg5w7-ghc-8.10.4.
@nix { "action": "setPhase", "phase": "unpackPhase" }
unpacking sources
unpacking source archive /nix/store/9rj3r36n59wfb355wzvpq8n9jc7548qf-source
source root is source
@nix { "action": "setPhase", "phase": "patchPhase" }
patching sources
@nix { "action": "setPhase", "phase": "compileBuildDriverPhase" }
compileBuildDriverPhase
setupCompileFlags: -package-db=/build/setup-package.conf.d -j16 +RTS -A64M -RTS -threaded -rtsopts
[1 of 1] Compiling Main             ( Setup.hs, /build/Main.o )
Linking Setup ...
/nix/store/hy3lz2vfv9qq2v5jz9nzlx6mmiaq79rj-binutils-2.35.1/bin/ld.gold: warning: /nix/store/cvr0kjg2q7z2wwhjblx6c73rv422k8cm-glibc-2.33-47/lib/crt1.o: unknown program property type 0xc0008002 in .note.gnu.property section
@nix { "action": "setPhase", "phase": "configurePhase" }
configuring
configureFlags: --verbose --prefix=/nix/store/cgdlkljgp57ljlif1wgi3i2r9iwrmih3-nixpkgs-firefox-addons-0.8.0 --libdir=$prefix/lib/$compiler --libsubdir=$abi/$libname --datadir=/nix/store/w08y4297xxx5c06asl0ffvj25dhl3lrp-nixpkgs-firefox-addons-0.8.0-data/share/ghc-8.10.4 --docdir=/nix/store/k96z8cag9fssyfgay3s58m1b2js9ig6i-nixpkgs-firefox-addons-0.8.0-doc/share/doc/nixpkgs-firefox-addons-0.8.0 --with-gcc=gcc --package-db=/build/package.conf.d --ghc-options=-j16 +RTS -A64M -RTS --disable-split-objs --enable-library-profiling --profiling-detail=exported-functions --disable-profiling --enable-shared --disable-coverage --enable-static --disable-executable-dynamic --enable-tests --disable-benchmarks --enable-library-vanilla --disable-library-for-ghci --ghc-option=-split-sections --extra-lib-dirs=/nix/store/yn7nd41grg2mzvzliphalk70hs0rfpdr-ncurses-6.2/lib --extra-lib-dirs=/nix/store/j74nwv61ghpv6zjzjvc14awggaahgm81-libffi-3.3/lib --extra-lib-dirs=/nix/store/fx58y0jdzd2xggdy0pqag2ffj72l7w33-gmp-6.2.1/lib
Using Parsec parser
Configuring nixpkgs-firefox-addons-0.8.0...
Dependency aeson -any: using aeson-1.5.6.0
Dependency base-noprelude >=4.11: using base-noprelude-4.13.0.0
Dependency directory -any: using directory-1.3.6.0
Dependency hnix >=0.5: using hnix-0.14.0.1
Dependency microlens-aeson -any: using microlens-aeson-2.3.1
Dependency microlens-platform -any: using microlens-platform-0.4.2
Dependency relude >=0.4.0: using relude-1.0.0.1
Dependency text -any: using text-1.2.4.1
Dependency wreq >=0.5: using wreq-0.5.3.3
Source component graph: component exe:nixpkgs-firefox-addons
Configured component graph:
    component nixpkgs-firefox-addons-0.8.0-4QMZH9kcWWAKNTz4vXzQeX-nixpkgs-firefox-addons
        include aeson-1.5.6.0-2IvrCnYKbWb9QZgZlQVX57
        include base-noprelude-4.13.0.0-FLZf4L5KDywLYv1iU9OSd6
        include directory-1.3.6.0
        include hnix-0.14.0.1-2y4msCoFrQrD5OYIt9LvpL
        include microlens-aeson-2.3.1-EmfAYy2jjBp4khxw5WE8xe
        include microlens-platform-0.4.2-FgxjmmP12DB2jdeWMZJi4x
        include relude-1.0.0.1-8azty5ILzKwBtkhgSOVYQs
        include text-1.2.4.1
        include wreq-0.5.3.3-3ESYfxTmnLk7ONFvKZI7MC
Linked component graph:
    unit nixpkgs-firefox-addons-0.8.0-4QMZH9kcWWAKNTz4vXzQeX-nixpkgs-firefox-addons
        include aeson-1.5.6.0-2IvrCnYKbWb9QZgZlQVX57
        include base-noprelude-4.13.0.0-FLZf4L5KDywLYv1iU9OSd6
        include directory-1.3.6.0
        include hnix-0.14.0.1-2y4msCoFrQrD5OYIt9LvpL
        include microlens-aeson-2.3.1-EmfAYy2jjBp4khxw5WE8xe
        include microlens-platform-0.4.2-FgxjmmP12DB2jdeWMZJi4x
        include relude-1.0.0.1-8azty5ILzKwBtkhgSOVYQs
        include text-1.2.4.1
        include wreq-0.5.3.3-3ESYfxTmnLk7ONFvKZI7MC
Ready component graph:
    definite nixpkgs-firefox-addons-0.8.0-4QMZH9kcWWAKNTz4vXzQeX-nixpkgs-firefox-addons
        depends aeson-1.5.6.0-2IvrCnYKbWb9QZgZlQVX57
        depends base-noprelude-4.13.0.0-FLZf4L5KDywLYv1iU9OSd6
        depends directory-1.3.6.0
        depends hnix-0.14.0.1-2y4msCoFrQrD5OYIt9LvpL
        depends microlens-aeson-2.3.1-EmfAYy2jjBp4khxw5WE8xe
        depends microlens-platform-0.4.2-FgxjmmP12DB2jdeWMZJi4x
        depends relude-1.0.0.1-8azty5ILzKwBtkhgSOVYQs
        depends text-1.2.4.1
        depends wreq-0.5.3.3-3ESYfxTmnLk7ONFvKZI7MC
Using Cabal-3.2.1.0 compiled by ghc-8.10
Using compiler: ghc-8.10.4
Using install prefix:
/nix/store/cgdlkljgp57ljlif1wgi3i2r9iwrmih3-nixpkgs-firefox-addons-0.8.0
Executables installed in:
/nix/store/cgdlkljgp57ljlif1wgi3i2r9iwrmih3-nixpkgs-firefox-addons-0.8.0/bin
Libraries installed in:
/nix/store/cgdlkljgp57ljlif1wgi3i2r9iwrmih3-nixpkgs-firefox-addons-0.8.0/lib/ghc-8.10.4/x86_64-linux-ghc-8.10.4/nixpkgs-firefox-addons-0.8.0
Dynamic Libraries installed in:
/nix/store/cgdlkljgp57ljlif1wgi3i2r9iwrmih3-nixpkgs-firefox-addons-0.8.0/lib/ghc-8.10.4/x86_64-linux-ghc-8.10.4
Private executables installed in:
/nix/store/cgdlkljgp57ljlif1wgi3i2r9iwrmih3-nixpkgs-firefox-addons-0.8.0/libexec/x86_64-linux-ghc-8.10.4/nixpkgs-firefox-addons-0.8.0
Data files installed in:
/nix/store/w08y4297xxx5c06asl0ffvj25dhl3lrp-nixpkgs-firefox-addons-0.8.0-data/share/ghc-8.10.4/x86_64-linux-ghc-8.10.4/nixpkgs-firefox-addons-0.8.0
Documentation installed in:
/nix/store/k96z8cag9fssyfgay3s58m1b2js9ig6i-nixpkgs-firefox-addons-0.8.0-doc/share/doc/nixpkgs-firefox-addons-0.8.0
Configuration files installed in:
/nix/store/cgdlkljgp57ljlif1wgi3i2r9iwrmih3-nixpkgs-firefox-addons-0.8.0/etc
No alex found
Using ar found on system at:
/nix/store/hy3lz2vfv9qq2v5jz9nzlx6mmiaq79rj-binutils-2.35.1/bin/ar
No c2hs found
No cpphs found
No doctest found
Using gcc version 10.3.0 given by user at:
/nix/store/qvv5y4fx4x879rbsbs4g27mypl9wxbb9-gcc-wrapper-10.3.0/bin/gcc
Using ghc version 8.10.4 found on system at:
/nix/store/kp4hnhfkd7c6gf3fnb8z897nwnpvg5w7-ghc-8.10.4/bin/ghc
Using ghc-pkg version 8.10.4 found on system at:
/nix/store/kp4hnhfkd7c6gf3fnb8z897nwnpvg5w7-ghc-8.10.4/bin/ghc-pkg
No ghcjs found
No ghcjs-pkg found
No greencard found
Using haddock version 2.24.0 found on system at:
/nix/store/kp4hnhfkd7c6gf3fnb8z897nwnpvg5w7-ghc-8.10.4/bin/haddock
No happy found
Using haskell-suite found on system at: haskell-suite-dummy-location
Using haskell-suite-pkg found on system at: haskell-suite-pkg-dummy-location
No hmake found
Using hpc version 0.68 found on system at:
/nix/store/kp4hnhfkd7c6gf3fnb8z897nwnpvg5w7-ghc-8.10.4/bin/hpc
Using hsc2hs version 0.68.7 found on system at:
/nix/store/kp4hnhfkd7c6gf3fnb8z897nwnpvg5w7-ghc-8.10.4/bin/hsc2hs
No hscolour found
No jhc found
Using ld found on system at:
/nix/store/rs4jynk5rpvag2b2f8m7nrzsypjss4w1-binutils-wrapper-2.35.1/bin/ld.gold
No pkg-config found
Using runghc version 8.10.4 found on system at:
/nix/store/kp4hnhfkd7c6gf3fnb8z897nwnpvg5w7-ghc-8.10.4/bin/runghc
Using strip version 2.35 found on system at:
/nix/store/hy3lz2vfv9qq2v5jz9nzlx6mmiaq79rj-binutils-2.35.1/bin/strip
Using tar found on system at:
/nix/store/87l7j4jcsl6x50nzpii751cdbsa26b0f-gnutar-1.34/bin/tar
No uhc found
@nix { "action": "setPhase", "phase": "buildPhase" }
building
Preprocessing executable 'nixpkgs-firefox-addons' for nixpkgs-firefox-addons-0.8.0..
Building executable 'nixpkgs-firefox-addons' for nixpkgs-firefox-addons-0.8.0..
[1 of 3] Compiling Prelude          ( src/Prelude.hs, dist/build/nixpkgs-firefox-addons/nixpkgs-firefox-addons-tmp/Prelude.o, dist/build/nixpkgs-firefox-addons/nixpkgs-firefox-addons-tmp/Prelude.dyn_o )
[2 of 3] Compiling System.Nixpkgs.FirefoxAddons ( src/System/Nixpkgs/FirefoxAddons.hs, dist/build/nixpkgs-firefox-addons/nixpkgs-firefox-addons-tmp/System/Nixpkgs/FirefoxAddons.o, dist/build/nixpkgs-firefox-addons/nixpkgs-firefox-addons-tmp/System/Nixpkgs/FirefoxAddons.dyn_o )
[3 of 3] Compiling Main             ( src/Main.hs, dist/build/nixpkgs-firefox-addons/nixpkgs-firefox-addons-tmp/Main.o, dist/build/nixpkgs-firefox-addons/nixpkgs-firefox-addons-tmp/Main.dyn_o )

src/Main.hs:35:40: error:
    Ambiguous occurrence ‘span’
    It could refer to
       either ‘Prelude.span’,
              imported from ‘Prelude’ at src/Main.hs:5:8-11
              (and originally defined in ‘GHC.List’)
           or ‘Data.Text.span’,
              imported from ‘Data.Text’ at src/Main.hs:10:19-22
   |
35 |                                      . span isUpper
   |                                        ^^^^

src/Main.hs:48:13: error:
    Ambiguous occurrence ‘getArgs’
    It could refer to
       either ‘Prelude.getArgs’,
              imported from ‘Prelude’ at src/Main.hs:5:8-11
              (and originally defined in ‘Relude.Lifted.Env’)
           or ‘System.Environment.getArgs’,
              imported from ‘System.Environment’ at src/Main.hs:13:28-34
   |
48 | parseArgs = getArgs >>= parse
   |             ^^^^^^^

Most of the above output is just build-noise, but the important bit is at the end—the full list of errors:

src/Main.hs:35:40: error:
    Ambiguous occurrence ‘span’
    It could refer to
       either ‘Prelude.span’,
              imported from ‘Prelude’ at src/Main.hs:5:8-11
              (and originally defined in ‘GHC.List’)
           or ‘Data.Text.span’,
              imported from ‘Data.Text’ at src/Main.hs:10:19-22
   |
35 |                                      . span isUpper
   |                                        ^^^^

src/Main.hs:48:13: error:
    Ambiguous occurrence ‘getArgs’
    It could refer to
       either ‘Prelude.getArgs’,
              imported from ‘Prelude’ at src/Main.hs:5:8-11
              (and originally defined in ‘Relude.Lifted.Env’)
           or ‘System.Environment.getArgs’,
              imported from ‘System.Environment’ at src/Main.hs:13:28-34
   |
48 | parseArgs = getArgs >>= parse
   |             ^^^^^^^

Two errors, and both are “ambiguous occurrences.” This is Haskell-speak for “you’ve imported 2 modules that export the same name.” For example, both Prelude and System.Environment export the getArgs function. In Haskell, like most languages, this is an easy bug to fix. You just need to tidy up your imports!

3. Investigation

Before we run in and make some quick changes to fix these errors, why are they only popping up now? Version 0.8.0 of nixpkgs-firefox-addons was released half a year ago. I’ve been using it fine up until now and, me aside, it’s a very popular package: if it was busted a lot of people would be making noise about it. More importantly, both errors mention the Prelude module. Prelude is like ruby’s Kernel module: it’s included by default and imports a large collection of useful functions. As you use Haskell, you get more and more familiar with this module and I’m pretty sure it doesn’t provide span and getArgs functions. And what’s this business about “originally defined in ‘Relude.Lifted.Env’?” I don’t remember Prelude re-exporting functions from entirely separate packages.

Looking back at the nix log output from above, we can see a few more clues about what is going on here:

Configuring nixpkgs-firefox-addons-0.8.0...
Dependency aeson -any: using aeson-1.5.6.0
Dependency base-noprelude >=4.11: using base-noprelude-4.13.0.0
Dependency directory -any: using directory-1.3.6.0
Dependency hnix >=0.5: using hnix-0.14.0.1
Dependency microlens-aeson -any: using microlens-aeson-2.3.1
Dependency microlens-platform -any: using microlens-platform-0.4.2
Dependency relude >=0.4.0: using relude-1.0.0.1
Dependency text -any: using text-1.2.4.1
Dependency wreq >=0.5: using wreq-0.5.3.3

Two unfamiliar Haskell dependencies jump out to me and this whole thing starts to make more sense:

base-noprelude

base is Haskell’s stdlib, so this is probably some fork of that which excludes the auto-imported Prelude module.

This package simplifies defining custom Preludes without having to use -XNoImplicitPrelude by re-exporting the full module-hierarchy of the base-4.13.0.0 package except for the Prelude module.

Hackage

relude

This package is mentioned by name in the error message. Note that major version of the derivation that fulfills this dependency is a major version greater than the minimum version we asked for (1.0 vs 0.4).

relude is an alternative prelude library. If you find the default Prelude unsatisfying, despite its advantages, consider using relude instead.

Hackage

At this point, we have a pretty good candidate for the root cause of this bug: A new version of relude was released and this version imports functions that the author was already importing from elsewhere. We can verify this with a quick visit to the relude GitHub repo. These exports are added in issue #305 and issue #309.

4. The Fix and Lessons Learnt

The fix to this bug is dead-simple: remove the superfluous imports mentioned in the compiler error and fire a pull-request at the original author. Easy-peasy!

This bug shows yet another example of why you should always specify both a lower bound and and upper bound for your dependencies. In the traditional Semantic Versioning scheme, when the major version changes, you’re all but guaranteed to see some breaking changes. If you say “I’ll take any version of libyz over version 1.2.3,” you’re bound to run into trouble eventually. Maybe libxyz-2.0.0 works fine, but since you left it open-ended, who knows what happens when libxyz-3.0.0 shows up or libxyz-50.0.0!

Some languages provide a convenient syntax to specify min/max bounds for your dependencies1, but in Haskell you need to do it explicitly: relude >= 0.4.0 && < 1.0.0.

Footnotes: