41 comments

[ 4.6 ms ] story [ 97.2 ms ] thread
> "This is the automation of dependency hell. The problem is that not everything needs to be automated, especially hell. Dependency hell is a real thing which anyone who has worked on a large project has experienced. Projects having thousands, if not tens of thousands, of dependencies where you don’t know if they work properly, where are the bugs, you don’t how anything is being handled—it’s awful.

This the wrong thing to automate. You can do this manually, however it doesn’t stop you getting into hell, rather just slow you down, as you can put yourself into hell (in fact everyone puts themselves into hell voluntarily). The point is it makes you think how you get there, so if you have to download manually, you will start thinking “maybe I don’t want this” or “maybe I can do this instead”. And when you need to update packages, being manual forces you to be very careful."

I sympathise with this, but I have to respond that we have to live within existing ecosystems. Getting rid of npm and doing things manually won't make building SPAs have fewer dependencies, build would be incredibly slow and painful.

> Each dependency is a potential liability.

I mean, sure. So what does the solution look like? From my perspective it looks like a tool that is able to update your dependencies so that you can easily pick up bug fixes in your dependencies, which sounds an awful lot like a package manager.

> JavaScript is great example of this as there are multiple different package managers for the language (npm being one of the most popular), but because each package manager defines the concept of a package differently, it results in the need for a package manager manager.

This doesn't seem like a strong point to me. Yes, there are things like yarn, pnpm, etc. But IIUC practically all npm alternatives still define packages in the same way (a package.json at the root hosted by npmjs (or your private repo)), and the differences are ergonomic/performance related.

> [that each package manager defines the concept of a package differently] is why I am saying it is evil, as it will send you to hell quicker.

Then I think it's more of a language problem, not a problem with the concept of a package manager.

"When using Go for example, you don’t need any third-party libraries to make a web server, Go has it all there and you are done."

Fine, now what if you need to connect to a database, or parse a PDF, or talk to a grpc backend. What a hilariously short-sighted example.

To me, this whole article just screams inexperience.

To me, this whole comment just screams inability to steelman.
Not sure why this argument doesn't also apply to operating systems. Maybe everyone should be writing all their programs to run on a custom micro-kernel. Surely we can't trust other programmers to write something as complicated as an operating system.
> Dependency hell [0] is a real thing which anyone who has worked on a large project has experienced. Projects having thousands, if not tens of thousands, of dependencies where you don’t know if they work properly, where are the bugs, you don’t how anything is being handled—it’s awful.

[0] https://en.wikipedia.org/wiki/Dependency_hell

I find it strange that they use a term with a common meaning, link to that meaning, and then talk about something else?

Same. The first thing I thought was "wait a second, that isn't dependency hell".

The second thing is that their version of dependency hell - having lots of dependencies introducing lots of bugs that you would not have written - is not my experience. 99% of the time, my bugs are in my own code, lol. Maybe once you become a much better programmer than me, you stop writing bugs in your own code and instead start having to deal with bugs in the PNG parsing library you depend on or something, and at that point writing your own PNG parsing library becomes a good use of your time. But I'm certainly not at that point.

I've had to fix bugs in dependencies of course. Here is one I fixed yesterday [0]. But it's much closer to the exception than the rule.

[0]: https://github.com/sanity/pav.rs/pull/4

This reads much more like a critique of traditional open-source development than package managers themselves.

The author asserts that most open-source projects don't hit the quality standards so that their libraries can be just included, and they'll do what they say.

I assert that this is because there's no serious product effort behind most libraries (as in no dedicated QA/test/release cycle), no large commercial products use it (or if they do, either they do it in a very limited fashion, or just fork it).

Hobbyists do QA as long as it interests them/fits their usecase, but only the big vendors do bulletproof releases (which in the desktop realm seems to be only MS/Apple)

This might have to do with the domain the author chose - desktop development has unfortunately had the life sucked out of it with every dev either being a fullstack/cloud/ML/mobile dev, its mindshare and the resources going toward it have plummeted.

(I also have a sneaking suspicion the author might've encountered those bugs on desktop Linux, which, despite all the cheerleading (and policing negative opinions), is as much as a buggy mess as ever.

In my experience, it's quite likely to run into a bug that nobody has written about on the internet ever.

Missed one of the biggest problems: Most assume the world is simpler than it really is. Not all the world is Rust, Python, Go, C++, or whatever language you advocate. I have millions of lines of C++, I'm interested in other languages, but they needs to interoperate with our C++. When we have a C++ library that does what you want I don't want you adding a new package to do the same thing as now we need to support both - sometimes will accept that using the package is the right answer, but often we need bug compatibility.
I see this a lot with Rust where I will depend on one or two external crates for a simple application and then I am shocked to see dozens of dependencies being pulled in when I go to build. I actually think Cargo's support for feature gates and conditional compilation could in theory be a strong mitigation against this as crates can avoid pulling in dependencies unless you actually need a feature that relies on them, but in practice it doesn't seem to work that way as I often see these complaints about Rust.

I sympathise with the arguments but IMO laziness will always win out. If Rust didn't have Cargo to automate dependency hell, someone would create a third party script to fill the gap.

I wouldn't say I'm a dependency maximalist but it not far off.

Yes, shared code has costs

- more general than you likely need, affecting complexity, compile times, etc

- comes with risks for today (code) and the future (governace)

But the benefits are big. My theory for one of the causes for Rust having so many good cli's is Cargo because it keeps the friction low for pulling in high quality building blocks so you can better focus on your actual problem.

Instead of resisting dependencies, I think it would be better to spend time finding ways to mitigate the costs, e.g.

- I'd love for crates.io to integrate diff.rs, provenance reporting (https://lawngno.me/blog/2024/06/10/divine-provenance.html), etc

- More direct support for security checking in cargo

- Integrating cargo-vet and/or cargo-crev into cargo

In general, I think the dependency hate is overblown. People hear about problems with dependencies because dependencies are usually open source code used by a lot of people so it is public and relevant. You don't hear as much about problems in the random code of one particular company unless it ends up in a high profile leak. For example, something like the heartbleed bug was a huge deal and got a lot of press, but imagine how many issues we would be in if everyone was implementing their own SSL. Programmers often don't follow best practices when they do things on their own. That is how you end up with things like SQL injection attacks in 2025.

Dependencies do suck but it is because managing a lot of complicated code sucks. You need some way to find issues over time and keep things up to date. Dependencies and package managers at least offer us a path to deal with problems. If you are managing your own dependencies, which I imagine would mean vendoring, then you aren't going to keep these dependencies up to date. You aren't going to find out about exploits in the dependencies and apply them.

> imagine how many issues we would be in if everyone was implementing their own SSL.

No, the alternative is to imagine how many issues we would be in if every project pulled in 5 different SSL libraries. Having one that everybody uses and that is already installed on everyone's system is avoiding dependency hell. Even better if it's in stdlib.

> How do I manage my code without a “package manager”? [...] Through manual dependency management.

Slackware Linux does precisely that.

I'm a Slackware user. Slackware does have a package manager that can install or remove packages, and even a frontend that can use repositories (slackpkg), but it does have manual dependency resolution. Sure, there are 3rd-party managers that can add dependency resolution, but they do not come with the distro as default.

This is a very personal opinion, but manual dependency management is a feature. Back in the day, I remember installing Mandrake Linux 9.2 and activating the (then new-ish) framebuffer console. The distro folks had no better idea than to force a background "9.2" image on framebuffer consoles, which I hated. I finally found the package responsible for that. Removing it with urpmi, however, meant removing all the graphical desktop components (including X11) because that stupid package was listed as a dependency of everything graphical.

That prompted me to seek alternatives to Mandrake and ended up using Slackware. Its simplicity had the added bonus of offering manual dependency resolution.

Honestly, he's not wrong. I use Ruby and 99% of the gems on rubygems.org are absolute trash. I use Rails and stuff like Nokogiri or Faraday, also RubyLLM, but little else because of reasons.

NPM is even worse, you import one thing and get 1000s of trash libraries so nowadays the only JS I write is vanilla and I import ES Modules manually.

Also, Odin doesn't make adding dependencies that difficult, you can literally just throw an Odin library into your project as a folder and it's available. The Odin compiler does everything else for you.

There's a fair bit of semantic quibbling here.

Regardless of how they define these terms, producing a list of hashes which function as a commitment to specific versions of dependencies is a technique essential to modern software development. Whatever the tools are called, and whatever they do, they need to spit out a list of hashes that can be checked into version control.

You could just use git submodules, but in practice there are better user experiences provided by language package managers (`go mod` works great).

A good amount of this ranting can probably be attributed to projects and communities that aren't even playing the list of hashes game. They are resolving or upgrading dependencies in CI or at runtime or something crazy like that.

The argument here is (in brief) "Package management is hell, package managers are evil. So let's handle the hell manually to feel the pain better".

And honestly speaking: It is plain stupid.

We can all agree that abusing package management with ~10000 of micro packages everywhere like npm/python/ruby does is completely unproductive and brings its own considerable maintenance burden and complexity.

But ignoring the dependency resolution problem entirely by saying "You do not need dependencies" is even dumber.

Not every person is working in an environment where shipping a giant blob executable built out of vendored static dependencies is even possible. This is a privilege of the Gamedev industry has and the author forgets a bit too easily it is domain specific.

Some of us works in environment where the final product is an agglomerate of >100 of components developed by >20 teams around the world. Versioned over ~50 git repositories. Often mixed with some proprietary libraries provided by third-party providers. Gluing, assembling and testing all of that is far beyond the "LOL, just stick to the SDL" mindset proposed here.

Some of us are developing libraries/frameworks that are used embedded in >50 products with other libraries with a hell of multiples combinations of compilers / ABI / platforms. This is not something you want to test nor support without automation.

Some of us have to maintain cathedrals that are constructed over decades of domain specific knowhow (Scientific simulators, solvers, Petrol prospection tools, financial frameworks, ... ) in multiple languages (Fortran, C, C++, Python, Lua, ...) that can not just be re-written in few weeks because "I tell you: dependencies sucks, Bro"

Managing all of that manually is just insane. And generally finishes with an home-made half-baked bunch of scripts that try to badly mimic the behavior of a proper package manager.

So no, there is no replacement for a proper package manager: Instead of hating the tool, just learn to use it.

Package manager are tools, and like every tool, they should be used Wisely and not as a Maslow's Hammer.

> The problem is that not everything needs to be automated, especially hell.

What a great quote.

I don't know what the solution to this problem is, but I do remember a time (around 20 years ago) when this wasn't a big problem. Was working on a fairly large (each module between 50k - 100k LOC) C++ system. The process for using libraries:

1) Have problem that feels too complicated to hand-code.

2) Go on Internet/forums, find a library. The library is usually a small, flat collection of atomic functions.

3) A senior engineer vets the library and approves it for use.

4) Download the stable version: header file, and the lib file for our platform (on rare occasions, build it from source)

5) Place the .h file in the header path, and the lib file in the lib path; update the Makefile.

6) #include the header and call functions.

7) Update deployment scripts (bash script) to scp the lib file to target environment, or in some cases, use static linking.

8) Subscribe to a mailing list and very occasionally receive news of a breaking change that requires a rebuild.

This may sound like a lot of work, but somehow, it was a lot less stressful than dealing with NPM and node_modules today.

I think the main thing that makes this workable is "The library is usually a small, flat collection of atomic functions."

I find that it's the hell of transitive dependencies--you as a developer can reasonably vet a single layer of 10-30 standalone libraries. But if those libraries depend on other libraries, etc, then it balloons into hundreds or thousands of dependencies, and then you're sunk.

For what it's worth, I don't think much of this is essential complexity. Often a library is complicated because it supports 10 different ways of using it, but when you use the library, you're only using 1 of those ways. If everyone is only using 10% of thousands of transitive dependencies, the overall effect is incredibly complicated, but could have been achieved with 10-100% more short-term effort. Sure, "it took twice as long to develop but at least we don't have 10x the dependencies" is a hard sell to management (and often to ourselves), but that's because we usually choose to ignore the costs of depending on software we don't understand and don't control. We think that we're cleverly avoiding having to maintain and secure those libraries we outsourced, but most open-source developers aren't doing a great job of that anyway.

Often it really is easier to develop something from scratch, rather than learn and integrate a library. Not always though, of course.

The post goes on to say that random packages are not necessarily better than what members of your team could make. At the end it gets to:

> Through manual dependency management. Regardless of the language, it is a very good idea that you know what you are depending on in your project. Copying and vendoring each package manually, and fixing the specific versions down is the most practical approach to keeping a code-base stable, reliable, and maintainable. Automated systems such as generic package managers hide the complexity and complications in a project which are much better not hidden away.

So that makes all of us human package managers. It's also true that you can get a package manager from internet folk that works better than the processes and utilities your team cobbles together to ease the burden.

I'm very thankful for the Debian team's efforts to include most of my most commonly software packages in their repo. Out of all the differences between me and my colleagues workflows on MacOS and windows, this is the most impactful one. I don't remember the last time I had any kinds of dependency issues. I keep updating my packages when I log on and there are no version and/or dependency issues whatsoever.
I’ve had major Nissan Altima effect with this lately. A few weeks ago I set out to make a simple C and C++ package manager that just ignores dependency hell in favor of you explicitly specifying packages. And no binaries, just build from source and use Git as a backend for managing what source maps to what builds.

Plus Lua for package recipes. It’s going really well!

There are three points of prioritization here: you can use other peoples' code, manually vet all the code you're running, or accept that you need to trust a social network to vet stuff for you. Pick two. This is not a solvable problem.

EDIT: I've been rate limited, so the point is: unless you're Terry Davis, you're not going to be able to write software of any real complexity. Few people are going to even bother to vet the standard library, let alone the compiler, the runtime, etc etc.

if only people knew how easy it is to implement the standard library and make it way simpler than what is usually provided, everyone would be writing their own standard libraries; you can implement one with string manipulation, files, memory management, threading, and basic timing, in less than 1000loc of c code, as i have done before, and the biggest parts by far were console printing and filesystem stuff, and it's mostly because of windows utf-16 conversion nonsense
i had this idea for vendor based “package manager”

what if packages were meant to be read, and config was set inside the file directly

what if we transitioned to think of packages as templates, rather than generic black boxes

i think it would drastically reduce dependencies, package complexity, and improve understanding

> SDL3 might fix it all but the time to integrate SDL3 would be the same time I could write it from scratch.

The Programmers’ Credo: we do these things not because they are easy, but because we thought they were going to be easy

I don't see the value in making it even harder to build software. I want to make things. Downloading a dependency manually and then cursing at the compiler because "it's right there! why won't it load!!" is just gonna make me want to build software less.

Anyone I want to work with on a project is going to have to have the same frustration and want to work on the project less. Only even more because you see they downloaded version 2.7.3-2 but the version I use is 2.7.3-1.

Package managers are constraint solvers. You could manually figure out if XYZ shared library works with somebody else's code, or, you could expect that they would label the range of shared library versions their code needs.

Note to self... don't use Odin.