HackerTrans
TopNewTrendsCommentsPastAskShowJobs

kylewlacy

no profile record

Submissions

Show HN: Brioche – A new Nix-like package manager

brioche.dev
168 points·by kylewlacy·2 वर्ष पहले·87 comments

comments

kylewlacy
·2 वर्ष पहले·discuss
There are also quite a few attempts at "Nix without NixLang". In the ecosystem, I can think of Nickel (a different configuration language, but one with strong typing), Garn (Nix but using TypeScript), and Guix (Nix but using Scheme, and also kind of an ecosystem fork I think?). The common link behind all of them is that they all still have Nix in their DNA, i.e. using a Nix store with Nix derivations under the hood

Outside Nix, there's Brioche (my project!), and there's also Vorpal and Tangram, all of which are "Nix-like" but are from-scratch projects instead of using any Nix tooling

So I definitely agree that Nix as an _idea_ is right, and I definitely also agree that NixLang is a blocker for mass adoption. Personally though, I think if you dig deeper, there are other places that could use improvement beyond just the language itself (but nothing that would necessarily be a show-stopper for adoption IMO). But that's why my project is a "Nix-like"-- there are things that I think Nix could / should do, but which aren't really compatible with the way Nix itself works. One example: Brioche works without having a global store path-- it's per-user instead so that it can be installed rootlessly-- which doesn't really map well to how Nix itself handles store paths and dependencies

So point is, I agree Nix is the right model, but I think the _real_ gold lies outside Nix itself (...with the side note that I'm obviously biased!)
kylewlacy
·2 वर्ष पहले·discuss
Very cool! I've been watching the "toolchains in Wasm" landscape for a while, and seeing a Clang/LLVM toolchain running in Wasm is awesome!

YoWASP has also had an LLVM toolchain working in Wasm for a while too[1], although it seems like this version solves the subprocess problem by providing an implementation of `posix_spawn` whereas the YoWASP one uses some patches to avoid subprocesses altogether

My biggest question marks around this version are about runtime/platform support. As I understand it, this toolchain uses WASIX, which (AFAICT) works with Wasmer's own runtime and with a browser shim, but with none of the other runtimes. Are there plans to get WASIX more widely adopted across more runtimes, or to get WASIX caught up to the latest WASI standard (preview2)? Or maybe even better, bring the missing features from WASIX to mainline WASI like `posix_spawn`[2]? I'd love to be able to adopt this toolchain, but it doesn't seem like WASIX support has really caught on across the other runtimes

[1]: https://discourse.llvm.org/t/rfc-building-llvm-for-webassemb... [2]: https://github.com/WebAssembly/WASI/issues/414
kylewlacy
·2 वर्ष पहले·discuss
I believe Deno's LSP does use this push model, the missing ingredient is that the LSP server itself is implemented in Rust, but bridges to TypeScript's Language Service API in JavaScript/TypeScript. The TypeScript side calls functions like `readFile()` that Deno then needs to provide, so they need to get the in-memory file contents from the Rust side to the JS side.
kylewlacy
·2 वर्ष पहले·discuss
I think garn is really cool! If I were still in the Nix ecosystem, garn would probably be my go-to

Building something on Nix obviously has a ton of benefits: well-proven tooling, lots of mindshare already, TONS of packages. Everyone knows that Nix-the-language is fairly unintuitive for beginners, and tools like garn, Devenv, Devbox, etc. all seem to be trying to let folks use Nix without exposing them to Nix-the-language. I think this is a good goal to have and helps make Nix more accessible

But I really thought it was worth exploring the Nix-free design space too. For example, I think derivations in Nix are... kinda complicated and weird! Having a fixed set of phases specifically designed for autotools builds always seemed a little weird to me. By ditching Nix, I was able to see what the world looks like when you ditch the idea of phases completely, for example

Another interesting one was ditching the absolute path for the store path. Getting everything working by resolving relative paths was surprisingly tricky, but the end result is that you don't need to be root to install Brioche, and you can also take a Brioche-built binary (along with the resource dir it outputs) and ship it to another Linux machine and it'll just work. That's only something you can do if you see what happens if you explore outside the Nix space

So I'm hoping Brioche can _at least_ be an interesting case study in how you can take a lot of Nix's ideas and get a lot of the same value without having to take Nix's approach directly (or who knows-- maybe some of those ideas end up back in Nix someday!)
kylewlacy
·2 वर्ष पहले·discuss
I'd definitely like to have good performance even for large build graphs! I'm hoping the laziness exists "where it counts". To walk through an example, if you build your backend, and your backend calls the function `postgres()`, and that calls `openssl()`, and THAT calls `gcc()`, etc., etc., each function is basically building an object to represent its chunk of the build graph (each function returns a "recipe"). Nothing gets built until that object gets returned from the top-level function and the runtime does something with it

In other words, the eager part is basically constructing the build graph. Maybe I'm wrong but I don't that this would necessarily be slower than the lazy version. In practice the most complex build graph I've made is basically the full chain of Linux From Scratch builds (that's the basis for my toolchain currently), and I think that takes about 400-500ms to evaluate. It's about 160 build steps, so it's not _simple_ but I know build graphs can also get a lot more complex, so I'll just have to keep an eye on performance as I start to get into more and more complex builds

Maybe I'm missing something but intuitively I'd expect this approach to be fairly efficient-- as long as build scripts only call these functions when they're used as part of the build graph
kylewlacy
·2 वर्ष पहले·discuss
Nushell has definitely been on my radar for a while! but I missed DetSys was playing around with it, I'll have to give that a look

One neat thing about Brioche is that Bash isn't really baked into it the same way as the Nix ecosystem. In a lot of the docs and examples, I used the `std.runBash` Brioche function for running scripts, but that's a fairly simple 11-line function[^1]. I wanted to make it just as easy to add `runZsh`, `runFish`, `runNushell`, etc. functions that work just as well as Bash (and they could either be put in the std library or in their own packages). So hopefully, there will be a Brioche `runNushell` function in the near future :)

[^1]: https://github.com/brioche-dev/brioche-packages/blob/9fd5109...
kylewlacy
·2 वर्ष पहले·discuss
I've been asked about overrides and attributes a lot! That was one of the sacrifices I had to make to go with a more traditional language, so it's definitely a negative point compared to Nix. That said, I'm hoping to have some conventions and features that will cover _most_ of the use-cases that overrides give you, but that's definitely still future work at this point and I probably won't be able to cover everything overrides do

And yep, I think having a "Brioche for Nix users" guide makes a lot of sense, although it's not the first time that question so I'll probably stand up a first-pass version of it sooner rather than later (my Nix skills are also pretty rusty-- I'll need to brush up a bit first before I write it!)
kylewlacy
·2 वर्ष पहले·discuss
ooh, porkg looks pretty interesting, will definitely keep an eye on the project!

Definitely agreed about the lockfile ideas! I went with a fairly similar design, although I haven't really escaped the "rebuild the world" situation yet and I've rebuilt a _lot_ of copies of gcc from source by now! (that's also partially because I set up the packages repo as a workspace[1], which I felt was easier to iterate on in the early days... I might eventually split each package into their own projects or separate repos so they can keep lockfiles independent of each other)

and yeah, I definitely felt the temptation to write my own DSL but I stayed strong! I just knew that it'd be a huge uphill battle, especially because I wanted to provide good editor support (IMO implementing a language is (relatively) easy, but implementing a language with good error messages and LSP support is crazy hard)

[^1]: https://brioche.dev/docs/core-concepts/workspaces/
kylewlacy
·2 वर्ष पहले·discuss
> What are your plans for cross-compilation or heavy package customization? One of nixpkgs coolest party tricks imo is that you can just change the stdenv and get a musl static binary or cross-compiled binary.

So in general, I don't think I'm going to have anything quite as powerful as Nix's overrides. But I'm hoping most of the use-cases for it will be covered by some simpler options:

- Since build definitions are functions, package authors can just take arguments for the things they want downstream users to be able to customize (e.g. `configure` flags, optional features and plugins, etc.)

- I haven't built it yet, but I think adding support for dependency overrides would be fairly easy, a la Cargo. Basically, you'd just fork or clone the package you want to tweak, make your tweaks, then set an "overrides" option to use it instead. I know that's not a super satisfying answer, but that should help cover a lot of use cases

- For toolchains specifically, I have an idea in mind for this as well (also not implemented at all). At a high level, the idea is that packages could use "dynamic bindings", which you can then override for downstream recipes (this would require some new runtime features in Brioche itself). The toolchain itself would effectively be a dynamic binding, letting you pick a different recipe (so you could swap glibc for musl, or gcc for clang, etc). Cross-compilation would also be built on this same feature
kylewlacy
·2 वर्ष पहले·discuss
This was one of my bigger pain points with Nix as well: there was a lot of "reinventing the world" just to avoid network access. With Brioche, I stuck with disabling network access by default, but there's an escape hatch to specifically opt-in to networking (by calling `.unsafe({ networking: true })` on a process recipe). My thoughts are that Cargo, NPM, Poetry, etc. have all done a great job building amazing tooling to download and verify resources from the network against a lockfile, and I wanted to be able to leverage as much of that as possible. So, for example, `npm clean-install` will give you more-or-less the same guarantees that Nix does, so my thought was as long as it's encapsulated properly, I'd rather lean on existing tooling (that's also why I used the term "unsafe", you need to make sure you only access the network with tools that do sufficient verification!)

I've generally stayed away from using the term "reproducible build" when talking about Brioche, because I don't feel like it fits the reproducible-builds.org definition (though I don't think Nix does either). But, if a build is cached locally or in the registry, then you're guaranteed to get the same result, since it'll just re-use the cache

The sandboxing also gives pretty strong guarantees around hermetic builds[1]. So I think you could do reproducible builds _within_ Brioche (and I'd like to add tools to make this even easier), but I'd say Brioche itself doesn't give you reproducible builds out of the box

[1]: https://bazel.build/basics/hermeticity