Edit to add, if you or her want help with this, I wouldn’t mind helping, reach out to my username at gmail.
Edit to add, if you or her want help with this, I wouldn’t mind helping, reach out to my username at gmail.
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...
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.
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/
That's why I use dead simple nix stuff, which gets me 90% of the way (more like 140% if compared to Homebrew). If one's goal is to replace - and solve a few problems inherent to - homebrew or apt it's really not hard, see my sibling comment.
Which part?
For the commands, update install upgrade uninstall are straight the same.
There's an additional -A on install, but you don't need to understand it. The commands are even handed over to you on https://search.nixos.org/packages.
There's an additional nixpkgs prefix but it's perfectly understandable that it's coming from the channel name:
# nix-channel --list
nixpkgs https://nixos.org/channels/nixpkgs-23.05-darwin
It's really not out of this world to mentally map this to homebrew taps or apt repos.nix-env --query is different from homebrew but its design is much the same as pacman -Q and people have no trouble with that (I won't even dive on apt-get vs dpkg vs apt-cache)
nix-env -f <github archive url> --install nixos-23.05.tar.gz is such because it's the branch name, 88f63d51109.tar.gz is just the commit sha. These are GitHub things. Providing the source of packages as a --file option is hardly inscrutable nor even a nix-only thing.
But if you really want a parallel with homebrew, then homebrew has taps, and nixpkgs has channels: nix-channel --add <url> name is directly equivalent to brew tap user/repo <url>, down to installing with brew install user/repo/package vs nix-env --install -A name.package.
And there it is, you have your 1:1 nixism-free homebrew:nixpkgs replacement, and can manage your packages with nix just like you do with homebrew.
> there's now a large quantity of tooling that makes it as easy to work with as Homebrew
My point is that there is no tool that could "make it as easy to work with as homebrew" because homebrew:nixpkgs is already 1:1, and people confused by using nix this way would equally be confused by using homebrew.
You mentioned Fleek, which seems like a nice tool, but managing home is outside the scope of homebrew so I don't see how it follows that it makes nix "as easy as homebrew". It apparently has some homebrew bits, but it's a third party tool to both homebrew and nix.
Don't get me wrong, tools can be nice, I use nix-darwin myself, not too sure about going home-manager yet.
---
Now that second part where I talk about shell.nix, maybe it's this one you took issue with? I aimed it at being a next step, not addressing the above point, but attempting to demonstrate the immediate value it can give without understanding the slightest bit of nix and especially eschewing all the nix-specific vernacular.
The first time it is encountered, this is probably what a reasonable non-nixer vaguely interested in would see:
{
pkgs ? import <nixpkgs> {},
# ^^^^^^^^^^^^^^^^^^^^^^^^
# okay so I've seen nixpkgs before, that's my channel, where the packages come from
# that `?` looks odd but well, whatever, there's a "pkgs" thing and an import of sorts.
}:
pkgs.mkShell {
# ^^^^^^^^^^
# this is:
# - a shell.nix file
# - that has been described to me as being used by a `nix-shell` command
# - and that thing is name mkShell
# so probably it's going to tell things how to make that shell
buildInputs = [
pkgs.ruby_3_2;
# here's that pkgs that I've seen above
# it has a dot plus a package name just like when I did nix-env --install
# except it had nixpkgs before the dot there
# oh, I see "pkgs ? import ..." sort of creates that variable or whatever from nixpkgs
# so this is a list of packages from nixpkgs
];
# and so that make a shell thing uses that list to get its dependencies
# and it's getting named buildInputs, whatever
}
# so that's it, it makes a shell using a list of packages from nixpkgs
And proceed to add whatever package they see fit by looking them up on the search page. Understanding of nixisms doesn't matter.Second example: "let .. in" isn't a very special nix construct, "let" is extremely frequently used in a number of languages (including JS) to define variables. "whatever .. in" is a very common construct as well. It's quite obvious that the example hoists out a few elements by assigning variables between "let" and "in", possibly creating some form of scope after "in". It also introduces "shellHook", which literally contains a multiline bash string; again I feel like it's quite obvious that the "make shell" thingy is going to call that hook at some point.
Last bits shows that the nixpkgs channel can be replaced with fetchTarball + a GitHub tarball archive URL, and that you can have multiple of these at once with different names, referencing packages from one or the other.
> that most people used to imperative thinking would not grok.
I can hear that the syntax is an oddball, but even then that example is really not that hard to wrap one's head around for anyone who has a passing familiarity with programming, whether functional or imperative.
Doesn't mean that they would fully appreciate that { foo ? "bleh" }: whatevs = 42 is actually a function definition equivalent to JS function(foo = "bleh") { return { "whatevs": 42 }; } but that's immaterial to understanding what is happening here and even being able to add/remove packages, change versions, pin sources, or hack that shell bit and ultimately have it be pragmatically useful.
So I don't think the nixisms are any problem in this case because they can be completely ignored. I'm also wondering if people have an epidermic reaction to the syntax, combined with the constant rumour that nix is hard and hardly usable does not set them up for success.
I mean, would people be as hung up if it were written this way?
args:
pkgs: <nixpkgs>
let:
ruby: pkgs.ruby_3_2;
whatevs: pkgs.whatevs42;
in:
pkgs.mkShell:
buildInputs:
- ruby
- whatevs
- pkgs.foobar
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"
or this way? function(pkgs = import(NixPath.resolve('nixpkgs'))) {
let ruby = pkgs.ruby_3_2;
let whatevs = pkgs.whatevs42;
return pkgs.mkShell({
buildInputs: [
ruby,
whatevs,
pkgs.foobar,
],
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"
`
});
}
Because at the end of the day, it's not that different in complexity, from, say, a Gemfile, a PKGBUILD, or a homebrew formula.To me the big stumbling block in the language is that it at first appears declarative, like you're writing a data structure that describes your configuration. However, when you start modifying anything about packages, you need to call functions to make changes because you're actually writing a function that's transforming input.
So, you're thinking about what you're trying to do in a declarative, descriptive sense and certain parts of what you're writing are structured as such; but then other parts are structured as a transformation.
Eg you write out a list of packages, but then if you want to change one of those packages, you need to start calling functions. As I mentioned in the Python example below, that can wind up requiring calling `override`, `overrideAttrs`, `overridePythonAttrs`, etc.
Consider this:
{
packageOverrides = pkgs: {
python3 = pkgs.python3.override {
packageOverrides = python-self: python-super: {
twitch-python = python-super.twitch-python.overrideAttrs (attrs: {
patches = (attrs.patches or []) ++ [
./twitch-allow-no-token.patch
];
});
};
};
twitch-chat-downloader = pkgs.twitch-chat-downloader.overrideAttrs (attrs: {
patches = (attrs.patches or []) ++ [
./twitch-chat-downloader-no-oauth.patch
];
});
};
}
Versus this: {
packageOverrides: pkgs {
python3: {
twitch-python: pkgs.python3.twitch-python {
patches: pkgs.python3.twitch-python.patches or ./twitch-allow-no-token.patch
}
},
twitch-chat-downloader: pkgs.twitch-chat-downloader {
patches: pkgs.twitch-chat-downloader.patches or ./twitch-chat-downloader-no-oauth.patch
}
}
The latter is less "functionally perfect", but there is vastly less cognitive overhead required to lay it out because you are basically just describing your goal, rather than having to keep your goal in mind while implementing it functionally (and keeping exactly how nixpkgs is implemented in mind to correctly use those overrides).This is just off-the-cuff of what I intuitively expected when I first started using Nix, but it's more what I'd expect of a no-holds-barred purpose-built language for declarative package management. All the override stuff seems like needlessly noisy syntax; you know it's going to be everywhere, you might as well build it into the language.
And it can probably be made even simpler with effort.
That's a very fair point to make. I have noticed the same and it seems like it's borne out of the way Nix (NixOS more precisely) is built, which is in two layers:
- a first layer of packages and whatnot, which is by and large functional programming, and has the gritty implementation you mention which get exposed when you want to alter some specific things
- a second layer of configuration modules, which takes packages and turns them into a declarative interface
From a Ruby analogy I would compare the first to some form of monkey-patching or otherwise forceful injection and the second one to a nice DSL which exposes clear properties to change some bits
For example, on modules there's `.package` which allows one to override the package to use fairly easily:
services.tailscale.enable = true;
services.tailscale.package = let
old = import(fetchTarball("https://github.com/NixOS/nixpkgs/archive/a695c109a2c.tar.gz")) {};
in
old.tailscale;
(taken from this issue https://github.com/NixOS/nixpkgs/issues/245769)Frequently you get additionalPackages or extraConfig or something in this "DSL", which handles the whacky non-descriptive stuff behind the scenes which really is an implementation detail that should not leak through.
So indeed I feel like Nix packages in general should benefit from a more descriptive interface similar to what NixOS modules expose, so that appending patches or adding configure flags would not be such an ordeal.
Basically this (pseudocode) should be generalised and hidden away:
# iterate over patch map => mypackage, mypatches
packageOverrides = python-self: python-super: {
${mypackage} = python-super.${mypackage}.overrideAttrs (attrs: {
patches = (attrs.patches or []) ++ ${mypatches};
});
};
So you then would just descriptively pass python3.packagePatches = { twitch-python = [./twitch-allow-no-token.patch] } or something and be done with it. Not saying it's easy for Nix folks to achieve that but it should be doable one way or another. I mean, there's already: python_packages = python-packages: [
python-packages.pip
];
python = pkgs.python39.withPackages python_packages;
It's not out of this world to think there chould be a generalisable extension of that for patches (and more) to fill in the various overrides that exist around Nix derivations.That would certainly make nixpkgs more approachable.
You have an entire industry building on top of Nix. Nix itself is complex because it has to be. Its DSL didn't need to be so terrible but the ideas Nix packages are not easy.
So use the layer above the thing that is complex to make that thing easy. That's what tools are for.
This is a grossly misleading comparison. Or at least, it's maybe not the comparison you want to make.
btrfs being unstable persists for a couple of reasons:
- Even casual users have pretty much zero tolerance for a filesystem that self-destructs. Especially when the filesystem integrates RAID and snapshot functionality, so self-destructing takes backup infrastructure with it.
- There are features that are legitimately unstable (RAID5/6), have unexpected side effects (defragmenting breaking reflinks), and seem unfinished (dedup requires external tools)
Nix being too hard to use comes from, well, the stuff I mentioned, which is all from the last few years. Or, the laptop I installed NixOS on just this year, where the install finished, and then the user is left without any guidance how to set up their system. For me, that's OK; I can fire up a terminal and run nano then rebuild the system.
But for any first-time user, I'd expect them to be SOL and stuck spending a Linux evening digging through the Nix manuals to figure out how to configure their system. (I just checked, and "First steps with Nix" starts with "Ad hoc shell environments", not anything to do with `/etc/nixos/configuration.nix`)
If they're a DevOps user who's going to be using it for every day of their job, that's probably time well spent; but if they're a casual user who only wants to maintain the same set of software and settings whenever they get a new computer, it may just be more time-efficient to go through some pain every several years.
For even a power user, they probably need to be working with a lot of software projects with native dependencies, or switching between computers a lot. Or just specifically curious about Nix.
But in any case, I very much disagree that it's just an out-of-date reputation that's holding Nix back. Its functionality is really, really useful and widespread adoption would make language-specific tools somewhat redundant (eg virtualenv). I'm pretty sure the main thing holding it back is that it's just too hard to use and people silently abandon it.
Fedora uses it by default as does openSUSE. Meta uses it for thousands of their own servers. The very specific RAID configuration is a non-issue for 99.9 percent of people. If they have need of it they'll use something else.
> I'm pretty sure the main thing holding it back is that it's just too hard to use and people silently abandon it.
That's why you use the tools to make it easy. Use the Determinate Nix installer, then install Fleek. I haven't read the Nix manual in at least 5 years.
> But for any first-time user, I'd expect them to be SOL and stuck spending a Linux evening digging through the Nix manuals to figure out how to configure their system. (I just checked, and "First steps with Nix" starts with "Ad hoc shell environments", not anything to do with `/etc/nixos/configuration.nix`)
What? This is not what most users will be doing, no.
"Very specific" being the most common RAID configuration used outside of personal computing (when you have more than 2-3 disks, you aren't running RAID1/RAID0/RAID10, it's all RAID 5/6). And funnily, one of the main scenarios where an advanced filesystem is actually genuinely needed and not just a nice to have, is when you have lots of disks.
Yeah, but that's what the Nix website has.
Let's assume I already know I want to try Nix, but I don't know anything about it. I probably either:
(1) Click "Download" (https://nixos.org/download) (2) Click "Get Started" (https://nixos.org/learn), then "Install Nix" (https://nixos.org/download#download-nix)
Either way, those instructions don't offer any clear next step. I might get it installed, but what now?
Going back to "Get Started" (https://nixos.org/learn), the next options is "First steps with Nix" (https://nix.dev/tutorials/first-steps/). The options here are:
Ad hoc shell environments
Reproducible interpreted scripts
Towards reproducibility: pinning Nixpkgs
Declarative shell environments with shell.nix
None of which covers what you'd probably want to do after you've freshly installed Nix or NixOS, eg system configuration and package management.Having officially ordained instructions isn't just a convenience for n00bs, it's also useful for knowing what needs to be maintained whenever there are changes, and consolidating effort to continually improve upon the presentation (rather than everybody having their own blog post just from their perspective).
So many people get started with Nix, not NixOS. Once see how useful it is, they then begin migrating their existing systems over to NixOS.
Not only that, but ad hoc shell environments are one the most common use cases. Putting that in the forefront of the official documentation is helpful. I also somehow can't help but think that if the official documentation followed your advice and started off with configuration.nix before explaining the Nix basics, you'd have a problem with that just the same.
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.> I really don't see how it is "unacceptably bad"
1) Like you pointed out, documentation will advise against this approach. Is it it even portable (what the original thread of discussion was about)? After my initial usage of nix, I switched to following the "best practice" of writing derivations and specifying system dependencies in the configuration.nix .
2) The nix commands are undeniably more complex than their brew equivalents. If you've used nix enough to memorize them, this probably goes away. But to a casual user who only interacts with nix once in awhile, it's way easier to remember "search" than "--query --available" or "-qa". "search" also feels like a higher-level unstructured-human-readable command.
3) Even "nix-env" is sort of weird as a name, because it begs explanation of why "-env", and that starts pulling in more advanced concepts that maybe a user doesn't initially need to be exposed to. It also means then you have to remember when to use "nix" and when to use "nix-env".
As for the rest, consider the use case of setting up an environment with Python packages:
https://nixos.wiki/wiki/Python
This requires memorizing more commands and syntax.
And then if you want to change something about the configuration of that Python package:
https://stackoverflow.com/questions/70395839/how-to-globally...
And the problem is, by this point you've long ago lost most software developers. They gave up, wrote a dockerfile with `pip install` commands, and used `sed` or `patch` or something to make the small-scale changes.
And I have to admit, while the procedural solution is not as elegant nor reproducible nor crossplatform, there's less cognitive overhead and novel syntactic constructs required than switching to a language and library that forces you to distinguish between `override`, `overrideAttrs`, and `overridePythonAttrs`.
If by "documentation" you mean "community documentation", indeed people like to point out that You-Re-Holding-It-Wrong-And-Should-Not-Do-This-As-It-Is-Not-The-Proper-Nix-Way, with which I disagree because Nix is a tool, and wielding it in a way that solves one's problem is one's prerogative. I do agree that doing it in other more complete ways (e.g configuration.nix, shell.nix, flakes) unlocks further helpful stuff but that does not mean doing it this way is not helpful in its own right, especially as the more complete ways come with more knowledge needed, thus facing newcomers with a steeper learning curve.
If by "documentation" you mean "official documentation", it certainly does _not_ advise against this approach: https://nixos.org/manual/nix/stable/package-management/basic...
> After my initial usage of nix, I switched to following the "best practice" of writing derivations and specifying system dependencies in the configuration.nix .
Which is how it should be, walking along the learning curve: start small, walk forward, and stop at any point that solves your problems in satisfactory ways. configuration.nix is nixos/nix-darwin, but to me that's already a bridge too far to get simple early adoption from newcoming folks.
I find it more friendly to let people start with nix-env, possibly shoving a sequence of nix-env -iA foo in a shell script, then as they get familiar with it, progressively have them realise that they can set up their dev packages per project easily with shell.nix, or their system ones with nixos/nix-darwin configuration.nix instead of lecturing them in doing it "The Right Way".
> If you've used nix enough to memorize them, this probably goes away.
That's my "learning curve" point, and true for any tool. I've seen the same resistance with Docker back then and now everyone takes it for granted, but it was equally mystifying for people at first.
> it's way easier to remember "search" than "--query --available" or "-qa"
I would agree, nonetheless the situation is really not much better for some other package managers that folks seem to have no problem with, e.g pacman is loved yet has an eerily similar --query/-Q, apt-get/apt-cache/dpkg are all over the place in general Debian-based management, including Dockerfiles.
By "have no problem with" I mean I never heard anyone saying "OMG apt/pacman is absolutely inscrutable I cannot use this", which Nix seems to trigger every time.
I will readily admit though is that whatever https://search.nixos.org/packages is doing should be available via command line. Big gripe on my side but it's not an insurmountable barrier.
> Even "nix-env" is sort of weird as a name, because it begs explanation of why "-env"
Is it? People do not seem that taken aback about using "rbenv" or "pyenv" or "virtualenv". I don't think "env" is exactly uncharted territory.
> As for the rest, consider the use case of setting up an environment with Python packages [...] And the problem is, by this point you've long ago lost most software developers
Whoa, this is going much farther than what prompted my initial comment. Fair enough though, I'm the one who made it devolve into project-level management with Python/Ruby dependencies. That said, folding language dependency management into Nix is definitely a very rough area of Nix, one that needs much development to be made easier.
That is exactly why I am advocating for this less dogmatic approach of doing things: have Nix handle your systemwide (via simple nix-env) or per-project (via shell.nix) tools and delegate language dependencies to the dependency tool that people use, reaping both the benefit of nix for setting up a dev env and the benefit of people knowing their tool of choice for their language.
Language dependencies have a huge impedance mismatch between dependency management tools and package managers. There's exactly the same kind of impedance mismatch problem with trying to package some or all language dependencies with a package manager such as apt or pacman or homebrew. Trying to package a gem or wheel in a deb is an exercise in frustration. I don't know if it's still the case but there was a time where npm install -g, pip install --global, or gem install would install stuff where e.g apt or pacman would, and thus screw things up badly.
So I would recommend for a long while: do not attempt to have nix handle everything down to language dependencies at the beginning because the impedance mismatch between these dependency management tools and nix is making it hard. The current state of Nix has no descriptive abstraction on top of it so you are faced with injecting stuff and getting to grips with Nix internals.
I do believe that over time this is solvable though, e.g NixOS modules generally provide nice override points that don't require one to dance with overrideAttrs and self: super.
> They gave up, wrote a dockerfile with `pip install` commands
Interestingly enough, that's sort of what I recommend for newcomers, except not with Docker:
# shell.nix
{
pkgs ? import <nixpkgs> {},
}:
let
# get these python packages from nix
python_packages = python-packages: [
python-packages.pip
];
# use this pyhton 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"
'';
}
Which makes nix-shell an equivalent of source venv/bin/activate, and then just pip install -r requirements.txt. The interesting bit is that one can control non-python dependencies for python things, e.g if a native python package depends on some C library or a compiler or a CFLAGS. Also, compared to a Dockerfile it is not RUN order dependent. There's also much less fuss about darwin vs linux or intel vs arm. You can stuff CLI stuff in there, be it shellcheck or rsync or fswatch and be sure that everyone is on the same page, no it-fails-oh-wait-my-homebrew-python-is-not-on-the-same-version-as-everyone-else, no it-fails-oh-wait-I-forgot-to-docker-pull-or-docker-build. It walks around a ton of little issues that increase friction with extremely little fuss.My point is not that Nix is not hard, Nix can be hard... if you go full-tilt, but it can also be immediately useful even with only small, approachable bits here and there. I remain convinced that the people that spend hours on end cobbling up Dockerfiles full of sed hacks and conjuring contrived docker run commands to run on a dog-slow Docker for Mac should not be objectively turned away by 'nix-env -iA' instead of 'brew install' or even making sense of that shell.nix.
That's why I feel like the community's response in the lines of "don't use nix-env" or "flakes!" combined with "here's this nixlang eldritch horror and I can't seem to be able to do this" is unhelpful in reducing the general sentiment that Nix is only approachable - and thus useful - if you write Haskell in your sleep.
That's why I'm trying to make it better with my limited ways so that curious newcomers are not scared away and can benefit from a little bit of nix goodness with little effort.
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.
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.
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".
Really? I thought Golang on macOS switched from direct syscalls to linking against libc, because the former is unstable but the latter is stable.
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.
NOTE: I briefly tried Gentoo Prefix, so I could use the same setup for both Linux and macOS, but that required quite a bit more time investment than I'm willing to deal with. I spent even less time trying out Nix, but the learning curve was steeper than I had the time for, so I gave up on it pretty quickly.
did you mean versioned? if you really meant vendored, could you elaborate?
I don't know about brew, but MacPorts wasn't ready when Sonoma came out. So that's a bit of a bummer for early adopter types, or folks that need to develop their own app against beta macOS and depend on ports for their setup.
The point of using a package manager is to avoid "manually handling the dependency tree". Whether you build from source or not is orthogonal.
It just implies code collected into a bundle. Whether it's compiled into binary or not, is open.
There are lots of environments where the package manager just brings "code to be compiled", sometimes as an optional feature, other times as the preferred or only mode. Gentoo, Arch, FreeBSD Ports are classic examples of "source first" approaches. IIRC that was the case for Python packages that needed binary (e.g. C) dependencies: they were downloaded as dependencies and compiled locally, until the introduction of "wheels" (pre-built).
either as the prefered more, or as the only mode. MacPorts
Golang, where it is (or used to be) a common practice, even has special tooling for this, and dedicates the "vendor" directory to that.