Comment by koito17

2 years ago

> MacPorts has an advantage over HomeBrew by having all dependencies vendored so OS updates have less impact.

I use MacPorts, but be careful stating this. libc is unstable between major releases of Mac OS, and the recommendation from the MacPorts project itself is to reinstall *all* of your ports after an OS upgrade.[1] This is not a fun process if you don't delay updating until the build bots for the latest OS go online. Also note it is literally impossible to statically link libc in Mac OS applications.[2]

Realistically speaking, I think Nix is likely to be a better alternative to both MacPorts and Homebrew for setting up dev environments, since you will have a declarative file format rather than ad-hoc Tcl (or Ruby) scripts running a function for fetch, verify, configure, build, destroot, etc. The only reason I personally don't use Nix is because MacPorts has more or less just worked for me and I haven't had the motivation to learn how Nix really works.

[1] https://trac.macports.org/wiki/Migration

[2] https://developer.apple.com/library/archive/qa/qa1118/_index...

Nix is great in theory, but the user experience is unacceptably bad, especially for anyone who isn’t a software engineer.

While it does do an excellent job of reproducibility if the inputs are the same, I’ve found it to be prone to be breaking if you switch to newer versions.

Part of the reason that it’s painful to use is because while it’s marketed as “declarative”, in actuality it’s functional, which results in a lot of convoluted syntax to modify parameters for a package, which varies based on the language of the package.

There seems to be some awareness of the usability issues, but the changes have seemed both a step forward and backwards. For instance, you used to be able to use the “nix search” command to look up package names; now it’s gated behind some arcane syntax because it’s “experimental” and something to do with flakes. And flakes seems like it has the consequence of fragmenting the package repositories and making it impractical to improve the language.

I still have helper functions to wrap desktop applications that I had to set aside, because some upstream changes broke it and neither I nor anyone on the nix forums could figure out if there was even a way to include them in the “darwin” namespace with an overlay. My goal was to make it as easy as homebrew to add an app to nix’s repository.

Another evening I sat down to make a minor feature to a Python library and decided to use a nix environment. In theory, this should have been better than a virtualenv. In practice, there’s no in-tree support for specifying specific versions of Python libraries, and mach-nix had trouble with the dependencies, so I wound up just filing a bug report and gave up on the feature I was trying to implement.

On the plus side, NixOS finally has a graphic installer, but I don’t think that helps macOS.

I’m still hopeful that the community will decide to prioritize usability, but after spending an aggregate of months of time trying to get things to work and continually running into time-consuming roadblocks, it’s not something I would recommend lightly to someone who wants to be more productive.

  • > Nix is great in theory, but the user experience is unacceptably bad, especially for anyone who isn’t a software engineer.

    This is a pretty extravagant claim. It was once very bad but there's now a large quantity of tooling that makes it as easy to work with as Homebrew.

    For managing their home, people can use Fleek, which makes Home Manager straightforward to work with: https://getfleek.dev/

    • It feels like every time someone complains about Nix being hard to use and understand, there's a response that claims it's great and that you just have to use X or Y. Oddly, what X or Y are seem to differ greatly.

      11 replies →

    • > now a large quantity of tooling that makes it as easy to work with as Homebrew.

      Now, that's a pretty extravagant claim. Homebrew can be used by basically anyone. It took me several attempts in the past few days to even get Home Manager installed with Nix on Fedora Silverblue because the Home Manager 23.05 channel package was broken and I had to use the master channel package to get it to work.

    • > This is a pretty extravagant claim

      If one wants to just replace Homebrew it's really straightforward:

      Install:

          /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
          sh <(curl -L https://nixos.org/nix/install) --daemon
      

      Usage:

          brew update              nix channel --update
          brew list                nix-env --query --installed
          brew search ruby         nix-env --query --available --status ruby  (or https://search.nixos.org/packages)
          brew install ruby@3.2    nix-env --install -A nixpkgs.ruby_3_2
          brew upgrade             nix-env --upgrade
          brew uninstall ruby      nix-env --uninstall ruby
      

      I really don't see how it is "unacceptably bad". You don't even have to understand anything about Nix and still be able to use the damn thing. Yes the real-version vs attribute-name-that-contains-a-version is a bit wonky but in practice that's seriously not an issue.

      But really, for versions you can actually pick whatever version you wish by hitting a specific channel state:

          nix-env -f https://github.com/NixOS/nixpkgs/archive/nixos-23.05.tar.gz -iA ruby
          nix-env -f https://github.com/NixOS/nixpkgs/archive/88f63d51109.tar.gz -iA ruby
      

      ... which is a) completely generalisable for anything even those packages that don't have versions and b) completely removes any problems with dependency version conflicts.

      ---

      (Screw naysayers that would tell you not to use nix-env, because that is just not productive to turn people away. In practice nix-env works, as in, it pragmatically solves the real-world above problem for people, who can later decide on their own if they want to stop there or move to "better" things should they feel any limitation with that or another use case; and at that stage they'll be more comfortable doing so than when they just start using the thing. Best way to climb a mountain is one step at a time.)

      ---

      And then from there you get the other benefit of being immediately able to ramp up to having a per-project `shell.nix` and just be done with it by typing `nix-shell`:

          {
            pkgs ? import <nixpkgs> {},
          }:
          pkgs.mkShell {
            buildInputs = [
              pkgs.ruby_3_2;
            ];
          }
      

      Or if like me you want to get fancy†:

          {
            pkgs ? import <nixpkgs> {},
          }:
          let
            # because I like it this way and can later reference ${ruby}
            ruby = pkgs.ruby_3_2;
            whatevs = pkgs.whatevs42;
          in pkgs.mkShell {
            buildInputs = [
              ruby
              whatevs
              pkgs.foobar
            ];
      
            # this is not nix-related, I just find that convenient
            shellHook = ''
              export RUBY_VERSION="$(ruby -e 'puts RUBY_VERSION.gsub(/\d+$/, "0")')"
              export GEM_HOME="$(pwd)/vendor/bundle/ruby/$RUBY_VERSION"
              export BUNDLE_PATH="$(pwd)/vendor/bundle"
              export PATH="$GEM_HOME/bin:$PATH"
            '';
          }
      

      Replace with this if you want to pin to a specific point in time:

          pkgs ? import(fetchTarball("https://github.com/NixOS/nixpkgs/archive/31b322916ae1.tar.gz")) {},
      

      You can even have multiple of those:

          pkgsA ? import(fetchTarball("https://github.com/NixOS/nixpkgs/archive/31b322916ae1.tar.gz")) {},
          pkgsB ? import(fetchTarball("https://github.com/NixOS/nixpkgs/archive/88f63d51109.tar.gz")) {},
      
          ...
      
            buildInputs = [
              pkgsA.foo
              pkgsB.bar
            ];
      

      Bonus: it also Just Works on your favorite Linux distro.

      In under 5min it can be immediately useful to anyone who knows how to use a package manager (may it brew or pacman or apt) without having to dive on any Nix detail. I will not buy that nixlang is a barrier in that case, you don't have to know nixlang to understand what is happening here.

      † Actually I just realised that I could probably use ${ruby} - which stringifies to the installed path on disk - and do:

          export RUBY_VERSION="$(${ruby}/bin/ruby -e 'puts RUBY_VERSION.gsub(/\d+$/, "0")')"
      

      to reference the actual path, or maybe even just ${ruby.version} or something and not even subshell capture. Not that it matters.

      2 replies →

    • > large quantity of tooling

      Please no. I want one tool that works well, not N tools each with their own idiomatic way of doing things that everybody has to install and learn.

      Looking over the install guide, this looks like it's just as bad as nix, it just hasn't been around as long. There are three approaches to installing nix that are suggested (the vanilla route, Determinate, and "this script") that are left up to the presumably new-to-nix user to research which one to use.

      Then it references flakes, as if you're expected to understand them, and links to the main nix article with dozens of pages. Then if you used the first or third approaches to install nix (but not the second), you need to run a couple shell commands.

      Then you need to run a nix command. Then edit a text file. Then set your "bling" level, which just isn't explained anywhere. Then another nix command. Then another two fleek commands, which aren't even provided, even though they're the first two fleek commands the user will ever issue.

      And then, finally, you've got fleek installed. I think. It could use a "Congratulations!" or some party emojis to let you know you're done, rather than immediately jumping into the deprecated install options (and why are these even here if they're deprecated? How am I as a complete n00b user supposed to make the judgment call that my need to use them outweighs them being deprecated?).

      Users that are comfortable with the level of shell involvement required to install Arch may find it familiar, but I would not expect someone accustomed to primarily using a macOS UI to find it reasonable.

      And this appears to mean you can manage packages (but not the version of them, nor the version of nix, so you've lost reproducibility), your path, and "bling". But presumably, not `shell.nix`. And I'm guessing anything more advanced requires you to rewrite your .yml in Nix anyway.

      So it's a lot of work to ask a first-time user to do, advanced users will find it of limited usefulness, and even the install process makes it glaringly obvious that it's a very incomplete abstraction with a lot of holes.

      This also means that people with Nix knowledge will be maintaining the tool and polishing its tools instead of Nix, so only a subset of downstream users will gain from any improvements. Essentially: https://xkcd.com/927/. To be fair, I realize it's not a zero-sum game, and it's probably a lot easier and more rewarding to contribute to an independent project.

      Sorry for the harshness of the reply, I realize a lot of work went into fleek. My frustration comes from a place of repeatedly losing a lot of time to tools that people think are user-friendly because they mentally excuse all the work they're offloading onto the end-user as justified.

      The fact of the matter is that when I reach for a tool, more often than not I want to immediately use that tool, then put it down as quickly as possible and get back to focusing on what I was doing. I don't want to execute a half-dozen commands or have to make uninformed decisions just to get started. This is why Nix itself is so frustrating to me; the end result is indeed as promised and reproducible, but getting there often involves so many edge cases, gotchas, exceptions to the rule, or simply flat-out broken stuff that it completely derails my focus from whatever I was trying to do.

      I think (though perhaps its my own bias) most users are the same for any popular tool. There are some niche users that use it every day or all the time, but in the case of a package manager like nix, I probably only interact with it briefly when I need to install a new program, change dependencies for a software package, and so forth. So, a few seconds every few days or weeks. Even as a developer.

  • > Part of the reason that it’s painful to use is because while it’s marketed as “declarative”, in actuality it’s functional

    You're correct, with a twist: NixOS is declarative, nix is not - it's indeed functional machinery.

    This exposes a declarative interface:

        https://github.com/NixOS/nixpkgs/tree/master/nixos/modules
        https://github.com/NixOS/nixos-hardware
        https://github.com/LnL7/nix-darwin/tree/master/modules
    

    This does not:

        https://github.com/NixOS/nixpkgs/tree/master/pkgs
    

    but being functional makes it easier for the declarative bits to exist, e.g the next step in this case (PR pending on my side to contribute just that upstream) is:

        - creating systemd.services."nqptp" with enabled = false as a default 
        - transforming services.shairport-sync to reference systemd.services."nqptp".enabled = true when enableAirplay2 = true
    
        https://github.com/NixOS/nixpkgs/issues/258643
    

    It also makes pinning/rollback to a specific version without touching the remainder of the system a spectacular non-event:

        https://github.com/NixOS/nixpkgs/issues/245769
    

    Even when `.package` is not made available it's only slightly harder to use another module with disabledModules + import.

    > Another evening I sat down to make a minor feature to a Python library and decided to use a nix environment. In theory, this should have been better than a virtualenv. In practice, there’s no in-tree support for specifying specific versions of Python libraries, and mach-nix had trouble with the dependencies

    Maybe you tried too hard to "nixify" everything, including managing the whole of python stuff. That's what I use:

        # shell.nix
        {
          pkgs ? import <nixpkgs> {},
        }:
        let
          # get these python packages from nix
          python_packages = python-packages: [
            python-packages.pip
          ];
        
          # use this pyton version, and include the above packages
          python = pkgs.python39.withPackages python_packages;
        in pkgs.mkShell {
          buildInputs = [
            python
          ];
        
          shellHook = ''
            # get python version
            export PYTHON_VERSION="$(python -c 'import platform; import re; print(re.sub(r"\.\d+$", "", platform.python_version()))')"
        
            # replicate virtualenv behaviour
            export PIP_PREFIX="$PWD/vendor/python/$PYTHON_VERSION/packages"
            export PYTHONPATH="$PIP_PREFIX/lib/python$PYTHON_VERSION/site-packages:$PYTHONPATH"
            unset SOURCE_DATE_EPOCH
            export PATH="$PIP_PREFIX/bin:$PATH"
          '';
        }
    

    And then just `pip -r requirements` or whatever poetry you fancy.

    On a specific project I needed a bit more control, and some fix because of a braindead build system. Fix once and be done with it.

        # shell.nix
        {
          pinned ? import(fetchTarball("https://github.com/NixOS/nixpkgs/archive/88f63d51109.tar.gz")) {},
        }:
        let
          # get these python packages from nix
          python_packages = python-packages: [
            python-packages.pip
          ];
        
          # use this pyton version, and include the above packages
          python = pinned.python39.withPackages python_packages;
        
          # control llvm/clang version (e.g for packages built from source)
          llvm = pinned.llvmPackages_12;
        in llvm.stdenv.mkDerivation {
          # unique project name for this environment derivation
          name = "whatevs.shell";
        
          buildInputs = [
            # version to use + default packages are declared above
            python
        
            # linters
            pinned.shellcheck
        
            # for scripts
            pinned.bash
            pinned.fswatch
            pinned.rsync
        
            # for c++ dependencies such as grpcio-tools
            llvm.libcxx.dev
          ];
        
          shellHook = ''
            # get python version
            export PYTHON_VERSION="$(python -c 'import platform; import re; print(re.sub(r"\.\d+$", "", platform.python_version()))')"
        
            # replicate virtualenv behaviour
            export PIP_PREFIX="$PWD/vendor/python/$PYTHON_VERSION/packages"
            export PYTHONPATH="$PIP_PREFIX/lib/python$PYTHON_VERSION/site-packages:$PYTHONPATH"
            unset SOURCE_DATE_EPOCH
            export PATH="$PIP_PREFIX/bin:$PATH"
        
            # for grpcio-tools, which is building from source but doesn't pick up the proper include
            export CFLAGS="-I${llvm.libcxx.dev}/include/c++/v1"
          '';
        }
    

    Sure that's not pure nix or flakesy or whatever, but simply delegating python things to python-land is a very pragmatic move, idealistic purity and reproducibility of everything be damned, it is instantly better than homebrew or docker because that setup gets you a consistent tooling environment on any Darwin (Intel or ARM, at whatever version) or Linux (whether it's NixOS or just nixpkgs).

    Also it's super amenable to collaborators who don't know the first thing about nix: they can blindly type `nix-shell` and be all the merrier, handling their python stuff as usual, and completely removing a whole class of "it works/breaks on my machine".

> libc is unstable between major releases of Mac OS

Really? I thought Golang on macOS switched from direct syscalls to linking against libc, because the former is unstable but the latter is stable.

  • I’d like to see a source for that too. If libc broke its ABI between OS releases there would be chaos.

> This is not a fun process if you don't delay updating until the build bots for the latest OS go online

The Sonoma installer came out for MacPorts less than a week after the release IIRC. The migration is like 4-5 shell commands. One of them takes a while as everything recompiles but it’s not interactive.