Aux Foundational Packages

Hey there @sig_core!

I recently announced the creation of Aux Labs for experiments which included a Nix library with a working module system. I have now updated the repository with Aux Foundation, a foundational package set that builds everything starting from a single binary seed file. This is based off of the minimal-bootstrap work done in NixPkgs, but this repository has no dependency on NixPkgs.

Currently, the initial Stage 0 is built and exported. This includes some important components such as:

  • kaem: a tool for running scripts on systems that donā€™t have a shell
  • mescc-tools: compiler tooling
  • mescc-tools-extra: additional helper commands such as cp and chmod

Everything in the repository is built using the module system. While I do not expect to reuse these modules in a larger package set (rather, we would import just the packages), I am envisioning a somewhat similar structure for a larger package set.

10 Likes

Can I ask that you communicate projects like these before starting? Some SIG Core members have been working on similar efforts (particularly, @Jeffā€™s work on this branch and this thread), and I would rather we not waste time on duplicated efforts. There needs to be open communication on whoā€™s doing what, especially this early on in bootstrapping.

7 Likes

This is still an early experiment, I wanted to try getting things to work. What stage would you like me to communicate about things like this? I figured that mentioning it now that there is something concrete would be the right time, but perhaps there is a better way?

Iā€™m not sure if the work Jeff has been doing overlaps with this. The foundational stuff is all about bootstrapping from a single binary seed, but the core boot requires things like bash to already exist. This foundational package set contains the primordial pieces that the core stuff would use to bootstrap :raised_hands:

5 Likes

The only thing Iā€™d say from my point of view is that because of your status as our ā€˜founderā€™ :) I tend to default to seeing your announcements as official ā€œweā€™re doing thisā€ rather than ā€œIā€™ve been experimentingā€ - you didnā€™t actually say that though, so itā€™s my fault rather than yours!

I agree that it probably good to prove that something is workable in a basic way before opening it up for comments etc - just in case a particular design proves to be a blind alley, or to avoid bandwidth overload if youā€™re still trying to hack in the face of lots of excited commentary :)

5 Likes

This does read a bit as an announcement rather than a proof of concept :sweat_smile: Mostly because it was posted in sig_core. Maybe reframing the post as ā€œhey all, I had this idea I wanted to try out, so I added it to auxlabs as an unofficial experiment. Check it out if youā€™re interested!ā€

6 Likes

Iā€™m actually fine with independent exploration. (No feelings hurt)

I think we should keep trying different things, criticizing, and then eventually work on merging, evolving, and deciding.

Iā€™ll take a look at it to see if theres any criticism I can give, especially on the detangling side. But so long as we donā€™t pull the trigger early on one experiment I think experimenting is fine.

And @jakehamilton I think you did a good job making clear this is an experiment :+1:

6 Likes

@jakehamilton

So, Iā€™m not nearly done, at lot of it looks fantastic and Iā€™m really impressed. In terms of criticism one of the first things I run into is ā€œwhere is lib defined?ā€. For example

  • first I went to src/staging/stage0
  • then that lead me to builders
  • not sure what kaem is, so I went to one of the other builders
  • got to lib.modules.override.default but I donā€™t know that behavior and I donā€™t/canā€™t know where that is defined, because lib is an argument and we (generally) donā€™t know everywhere this function is called

Which is a bigger design principle I think we should discuss.

_

When we read lib.doSomething I think we pretty much all expect it do the same thing every time. But lib.modules.override.default could return null every time for all we know; it is an argument after all. Instead of importing lib (like most languages), when we have it as an untyped argument we loose all ability to locally/statically reason about the code. As a reader, we canā€™t even assume lib.modules.override.default will be a function.

Readbility is one thing, but more importantly it makes code really really hard to refactor/maintain because, for example, if there is a null check on lib.modules.override.default then we canā€™t really ever remove that null check, because maybe ā€“ somewhere ā€“ an argument does cause it to be null. ā€œAnything is possible (thanks to arguments)ā€ might sound nice, but it does not help with understanding, debugging, and fully-testing code.

_

I know nix likes to be able to overwrite everything, that way (in theory) people donā€™t need to fork in order to modify. But after years of this approach, I think we can say it hasnā€™t really worked in practice. Perfect exampke is @ VlinkZ being unable to give an overlay of core stuff on nixpkgs because it breaks so many things. Even though heā€™s plenty experienced in using Nix.

Its easier to fork and modify, than it is to surgically try to override/hack-in behavior. So I think, at least for foundation, its okay to loose that benefit. If someone wants to change foundational behavior, just fork.

_

The alternative, I think, is having lib as a subrepo or submodule (so that we can have a direct import of lib rather than lib as an argument). I know that goes against my original plan, but hey, thatā€™s good. It means the prototypes are doing their job at changing my opinion/estimates. I now think a subrepo is the best way to make foundation maintainable and easy to reason about for new members, while still keeping lib pure and isolated in its own repo.

I think we could avoid the need for subrepos if the builtin import allowed for importing URLs. But thatā€™ll be a topic for another time.

5 Likes

Thanks for the feedback Jeff! Iā€™ll try and clarify some things here.

Lib comes from this experiment here, it is a replacement for NixPkgs lib and provides a fully functioning module system in addition to other helpers:

Foundation is built using module and just like the NixOS module system, lib is passed in as an argument:

This is mostly for convenience since otherwise every single module would have to import lib in order to declare options. That can get pretty difficult when working with flakes since you would need to point to the flake input. I think having this argument for the module system is a decent trade off here.

All lib functions are namespaces and (to some extent) documented. For example, lib.modules.overrides.default can be found in the modules namespace implementation here:

5 Likes

Agreed. Iā€™m just talking about foundation at the moment though so Iā€™m going to put the flakes input argument aside for now.

I think we should be very careful when doing things ā€œfor convenienceā€. Most of nix/nixpkgs, including stuff like with lib, were justified with ā€œfor convenienceā€ and now we have to clean much of all that stuff up.

Pretty much all other language import libs when defining a module; rust, python, java, C#, typescript, ruby, etc. I donā€™t think importing a library is inconvenient.

But beyond conveniences, I think we can agree it is imperative that this code is easy for other people to understand. Foundation is going to be complicated, and we shouldnt make it more difficult to newcomers.

In isolation, wouldnt you agree its easier to find the definition of an import compared to finding the definition of an argument?

6 Likes