C Package Manager
github.com
github.com
[1]: Unfortunately documentation is famously bad, the repo that holds package declarations is full of undocumented conventions, there often isn't a "blessed" way of doing things, and there are some other warts, such as all the difficulty one may experience from having to learn a new functional, lazy, dynamically typed language. I'm not aware of active, coordinated efforts to improve on these fronts, except for new languages such as Nickel.
[2]: https://edolstra.github.io/pubs/phd-thesis.pdf (don't be discouraged by the number of pages, it's fine if you read just the first part)
But what irks me is that it doesn't fundamentally change anything. There are clearly major inherent limitations in how software is developed that led us here. Nix is intended to find complicated ways to work around the problems in those development patterns. But we can't call out the pink elephant. Rather, let's make pink wallpaper, pink sofas, pink chairs, pink rugs, and then say that we've solved the problem, because you can barely even see the elephant now.
The problem isn't packaging. The problem is software design itself. The dependency hell, the conflicting versions, the filesystem hash trees, the DSL. It all exists because the software itself has no way to express its own compatibility with other functions or dependencies. We just kick the can down the road to a random package manager and hope for the best. But clearly the solution needs to happen in the software, not in how we install it.
The filesystem is also a limitation. We need to refer to versions in files and directories in the same way, due to application dependence on files. This can be accomplished probably just by adding versions in paths and then defining version compatibility in code. Would be nice if the filesystems all got versioning built in, though, so we could just call file functions with version arguments rather than hacking around path names.
Data formats and protocols also need explicit versions for all operations, so they can advertise their compatibility and thus be backwards and forwards compatible.
We must also be able to execute software with a specific combination of versioned functions at exec time. This requires kernel modifications / new syscalls.
Libraries and applications should keep old versions of functions indefinitely, and only load code when needed. This will cause some consequences which we need solutions for, such as how to handle the reduced disk space and shared memory. It will require rethinking how applications are built, stored, and executed.
All of this rethinking of software design requires a lot of effort. But it would provide a world of benefits that we currently need hacks for.
We could upgrade randomly to bleeding edge software without any change to existing applications; any software interacting would continue to function as before. But newer function versions would be available and could be enabled via feature flags. We would no longer need containers, maybe not even package managers. Software could be upgraded continuously and remain perfectly stable. Users could define what versions they will execute depending on their wishes. And legacy software could be supported for decades, maybe centuries, with no extra effort.
> Unison’s core idea is that code is immutable and identified by its content. This lets us reimagine many aspects of how a programming language works. We simplify codebase management — Unison has no builds, no dependency conflicts, and renaming things is trivial.
FWIW, I agree with you that the way we do things now is, uh, not the best of all possible worlds. I think Nix (and Guix) is a step in the right direction, though, but they don't go far enough.
A library has some functions. Later, the library gets refactored, and those functions are thereby rewritten; but since no changes have actually been made, the version number stays the same. However, there exists some legacy code that horrendously breaks when given a non-ABI-compatible function from this library, _even though_ the functionality and version of those functions the same. Oh dear! What can we do? Well, clearly exact binary compatibility is important, so someone has an idea: we'll specify functions with exact hashes of their code instead of versioning, so this problem doesn't happen.
This is all well and good, but then the library decides to update some internal utility subroutine, changing its hash, but _not_ changing its call sites. Oh dear, now some other functions have hashes that are still the same, but they have different functionality due to that utility update, and legacy code breaks horribly again! In order to make sure this doesn't happen, someone comes up with the idea that a function's hash should be dependent on the hashes of all of the functions, variables, etc. it depends on _and_ its code, so all of the "inputs" that go towards defining the function end up updating its hash, thereby insuring legacy code stability.
Now we run into another problem: where do we put all of these functions? They need to be stored somewhere on the system so applications can have access to them, so we'll put them in a repository that we'll call a "store". Now we can do cool stuff with shared stores such as "only load code when needed"!
Oh dear, now the user sitting at the shell of the system wants to run programs too. Our function infrastructure works so well, why not extend it to programs too?
Some programs really like filesystems and environments and depend on specific structures. We can't really encode these structures as an "input" to our programs' hashes, because they could be highly dynamic (or even undescribable), so instead we can put software into a little box where it sees the filesystem structure it wants, so it is happy, and the box itself could be specified with input programs/functions in the same way that programs or functions can!
Hmm.. we seem to be recreating this "hash" structure that depends on inputs a lot. We need a common name for this... hmm, how about "derivations"?
Congratulations, you have just reinvented Nix! The solutions in your comment that you talk about are things that Nix already was designed to provide. The only discrepancy is that Nix doesn't actually provide hashes for individual functions, rather they work for libraries, but this is because of a limitation on how libraries work: that they can have shared, global, mutable state, making the idea of individual function versions useless.
The main problem why Nix doesn't obviously seem to solve these problems is that Nix's tools are not very well designed. For example, it's very non-obvious how to specify older versions of packages. However, flakes makes this easier, and although I don't believe _Nix_ specifically will be the solution, the holy grail at the end of the road will be very similar to Nix in core ideas.
But I think the biggest issue is in your last paragraph where you don't describe why or how the solution needs to happen in the software itself rather than at a higher level.
I am curious about how a system designed from the ground up with this in mind might be better.
1) foo.lang: class foo { func bar(a) { return b } }
2) baz.lang: import foo && print( foo.bar(1) )
Generally `foo` needs to have unit tests (self-tests), but `baz` needs to have "acceptance tests" for `foo` because `baz` imports `foo` (external validation / fit-for-purpose). Blah blah "law of demeter" aka: self.do_foo_bar() { foo.bar(1) }.
I've come to the mental model that code should test itself, and (some) code should test its dependencies. Code fundamentally can't test code that uses it, but can attempt to be "well-behaved" and share (via documentation or checked interface) how it can be / should be used.
eg: `foo_test.lang: assert( foo.bar(1) == 42 );`
But if `baz` calls `foo.bar(2)` or `foo.bar(0.3)` or `foo.bar("four")`, then `foo` cannot possibly be responsible for how `baz` interoperates with it in all cases.
"Software has no way to express its own compatibility with other functions or dependencies".
I've heard that at a certain point all internal behavior can turn out to "bake" and be depended on by an external user. An extreme example is `*.append(...)` having O(n), O(log n), O(n^2) performance characteristics in either memory or speed. Some (ab-)use means that you may get a bug report b/c your performance characteristics changed in a way that the _user_ of your code didn't expect, and I struggle to see how anything other than snapshotting the world [never mind network-call dependencies] would ever "fix" things?
I think it's an impossible problem:
1) code can test itself
2) by definition, code can't [exhaustively] test how it is used by "others"
3) therefore: any changes to dependent code are potentially "breaking" changes from the perspective of the user of the dependent code (even bugfixes!)
4) a generalized "really good" outcome is possible (eg: imagine something like TypeScript but for dependency management), but once you loop around to point #1 again... you either exactly match like a puzzle piece (eg: frozen dependency), or there's some sort of wiggle-room where any sort of change to the dependency has the _potential_ to break the user, or there is guaranteed slack/wastage in between the two dependencies (ie: size, performance, memory, operating range, etc) where changes can be relatively safely made.
5) therefore: code that uses a dependency should have acceptance tests (fit-for-purpose) of how the dependency behaves.
We're relying on version numbers and transitive dependencies at the moment, but I can't imagine anything other than "freeze everything" being a viable, generalized solution?
https://nixos.wiki/wiki/Overlays#Overriding_a_version
With Nix flakes it's even easier and you can just mix and match inputs as you want. For example if you want to replace libfoo with your own fork you just do something like:
$ nix build --override-input libfoo /home/juser/src/libfoo
And the best part is that none of this requires any changes to your system. If you need multiple different specific versions at once, Nix can just do it, as every software is sitting around in /nix/store/{HASH} and there is no risk of anything colliding.
Windows shouldn't be treated like a freak, something too outside the norm to support compiling on.
> The best C and [insert any language here] package manager is Nix.
I raise you a docker.Seriously, the reproducibility and ability to just share a Dockerfile is amazing, especially with version pinning.
Seriously, try it. I personally use docker-compose, even for single containers.
And don't forget that all new Macs are now ARM-based, so x86-based Docker images don't work.
My gripe with C is that everyone pretends it's portable, but only care about porting to one (their) platform. It's easy: just use pkg-config/docker/nix. Windows? macOS? Who cares!? Just run a Linux VM!
But god help you if you care about MacOS and Windows
Seriously, the reproducibility and ability to share a vm is amazing, especially with gzip.
Personally, I prefer VMs in GCP, Azure, or AWS. VMs can be stopped when not in use so their use can be inexpensive enough for both hobby projects and work. Instead of writing a Dockerfile, etc. for a new project, I create a VM. This has the advantage of using VMs much more powerful that any laptop I own. This does require being OK with a mosh/ssh, Emacs/Vim, development style.
I would love to see an evolution of nixpkgs with more of a starlark-style for package declarations.
This just might be me as a functional-programming-maximalist, but I do not like imperative languages for configuration management. You implicitly have to hold the execution state of all actions to know what is going on.
Maybe nix is more lazy, but I would be surprised if nix ends up doing substantially less work than bazel when a single target in a large graph is built.
If you can just take all of your packages from a tag of nixpkgs, then that experience is very nice. On the other hand, if you end up wanting a package that is but up to date or missing entirely, that can be a bit more daunting.
It's heading in exactly that direction. In fact we specifically want to start using Starlark for module declaration (mind -- optionally. It's still all declarative JSON API at the bottom! APIs FTW!).
We're also going full hermetic and aiming for reproducible-by-default. Those should be "duh" things in modern world. The comparisons with the good parts of Nix should be obvious.
The starlark-adjacent parts are still (very) early, but you'll find some notes about the intention in our Notion already: https://www.notion.so/warpforge/Data-Execution-and-Formulas-...
Get in touch if you'd like to collaborate, we'd be thrilled to have more company working on it, or starting to package things!
This seems to be an extremely bad idea. Since everyone can edit the GitHub wiki pages, it means its registry is vulnerable to all kinds of malicious attack.
The C ecosystem desperately needs an npm, it's actually coming in 10 years too late. I don't mind the naysaying.
Yeah sure, "you could have done it better", but then you didn't so this is better than none. I'll take real things over illusions any day :D!
[2] https://github.com/microsoft/vcpkg/tree/master/ports/glib
That’s just unacceptable.
It is otherwise so very close…
- How do plan to address the issues of trust, security, auditing, copyright and licensing, trough the life-cycle of the artifacts in your registry.
- How do you plan to enforce the artifacts integrity both registry side as well as client side while using unreliable hardware.
- How do you plan to manage your dependencies and limit the blast radius of unforeseen events.
- Do you have a community model for namespace management.
- Do you have a model to implement Two or Three Person/Group rule for critical changes?
Edit: this latest is usually called the Two-Man Rule...Ehrm I don't see how that issue should be solved. I gave it a first look and there's clearly no way of filtering by license. Most of the sources in deps are MIT license but there are also BSD. So after you copy all the libs together you have to go through the package.json to find all the different licenses and if obliged copy them into your archive. Same can hold for the code from various sources. I don't think they all follow the same standard (It's an assumption. I didn't bother checking it.).
[1] The Advent of Clib - https://web.archive.org/web/20200128184218/http://blog.ashwo...
https://github.com/xmake-io/xmake#supported-package-reposito...
I like the side conversation here about Nix. I have not tried Nix in its own, but when I used to use non-M1 Macs, I had NixOS running in VirtualBox.
I may be wrong about this, but I love the huge variety in programming languages, operating systems, package managers, development tools, etc. A world with a tech monoculture would be boring.
Nix/Guix are nice, but they’re much heavier than Conan.
Ok but not too small. No one wants a left-pad.c