Skip to content

Remove systems and flake-parts dependencies#3

Closed
Andrew15-5 wants to merge 1 commit intotypst:init-flakefrom
Andrew15-5:less-deps
Closed

Remove systems and flake-parts dependencies#3
Andrew15-5 wants to merge 1 commit intotypst:init-flakefrom
Andrew15-5:less-deps

Conversation

@Andrew15-5
Copy link

@Andrew15-5 Andrew15-5 commented Dec 8, 2025

As a minimalist, and after seeing how "convenient" inputs can only complicate the config, rather than simplify it, I don't like that there are flake-parts and systems.

In short, it creates unnecessary dependencies, which in turn

  • makes the config be written in a certain, more restrictive way
    • makes it harder to read, force people to learn/use yet another level of abstraction on top of flakes
  • less portable between native-flake-schema-based configs
  • any potential issues with dependencies, you have to depend on the upstream to solve them
  • 3 more small or big blobs to download in flake.lock, slowing down the initial download, and increasing Internet traffic for little to no benefit

First time I discovered this from @PartyWumpus here, and that flake config can be shorter without flake-utils.

Since then, I wrote several flakes without any of those systems/flake-parts/flake-utils nonsense, and also rewrote some other ones (like ejecting from devenv, that is also restrictive). This made my flakes minimal and more accessible to be copied partly or fully by others, without using any new tech someone doesn't like/know about.


I didn't reformat everything yet, only the new part.

As you can see, it produces 34 - 62 = -28 lines total, plus removing the overhead of downloading, extracting and evaluating 3 dependencies.

This shows that it works the exact same way, transparently substituting one wrapper with another, while all the other lines stay untouched. After reformat, it won't look the same.

The unfortunate part is that I don't know if there is a more straightforward ways to transform system.name.value to name.system.value. Using nixpkgs.lib will produce the same amount of code, but part of it will be inheriting a bunch of pre-made functions, instead of using a more native way without any lib stuff.

@drupol
Copy link
Collaborator

drupol commented Dec 8, 2025

I took the time to carefully check the diff of your PR. From what I can see, you essentially re-implemented what flake-parts already provides... except now it’s custom code, specific to this repository, and not documented anywhere.

If I had to choose between the two options today, I would absolutely pick the one that is well documented, actively maintained, and widely used across the community. In this case, that’s flake-parts.

I’m being honest here: I don’t really understand the reasoning.
Replacing a small, well-tested framework with a handcrafted subset of its features doesn’t feel like an improvement. It increases the maintenance burden and reduces clarity for future contributors.

As a minimalist, and after seeing how “convenient” inputs can only complicate the config, rather than simplify it, I don't like that there are flake-parts and systems.

The way you're phrasing this feels quite subjective which is fine, of course. But I might be misunderstanding your point. Could you elaborate on how those inputs are complicating the configuration in practice?

In short, it creates unnecessary dependencies, which in turn

  • makes the config be written in a certain, more restrictive way

That part is indeed true of any framework or higher-level abstraction: you trade some freedom for structure.

  • makes it harder to read, force people to learn/use yet another level of abstraction on top of flakes

This can certainly feel true depending on one’s experience. Personally, I find that the benefits of the flake-parts module system outweigh the cost of learning it, but I agree it introduces an additional layer.

  • less portable between native-flake-schema-based configs

I don't fully agree here. Flake-parts mainly helps structure the project; it doesn’t inherently reduce portability. The underlying flake interface remains the same.

  • any potential issues with dependencies, you have to depend on the upstream to solve them

I would say this isn’t necessarily the case. Nix allows overriding and pinning pretty much anything, whether you use flake-parts or not.

  • 3 more small or big blobs to download in flake.lock, slowing down the initial download, and increasing Internet traffic for little to no benefit

This is probably the clearest drawback. Still, systems adds almost nothing (it doesn’t depend on nixpkgs), and flake-parts already depends on it, so the overhead is mitigated.

Since then, I wrote several flakes without any of those systems/flake-parts/flake-utils nonsense…

I think this mostly comes down to personal preference. Some people prioritise minimalism; others prioritise maintainability, structure, or familiarity.

As you can see, it produces 34 - 62 = -28 lines total…

Line count matters to some extent, but in my experience the real question is maintainability. Flake-parts doesn’t meaningfully increase LoC for most projects, and the benefit of bringing the Nix module system to flakes can be significant... especially in larger or more complex repositories where structure becomes important.

One last thing: it looks like you're recommending adopting Alejandra, while the Nix project itself uses the official formatter (nixfmt). For someone who values minimalism and reducing dependencies, suggesting a completely different formatting tool with its own conventions feels a bit contradictory. It’s a confusing message for people trying to keep things simple.


To conclude: I do not agree with removing those two dependencies. If we start removing them in the name of minimalism, then we should logically keep the door open to even more minimalism. In that case, why stop there? Perhaps we should also consider removing flakes entirely (which is exactly what I experimented with here: drupol/infra#122), because Flake is actually nothing else than a framework (sadly, it is as of Dec 2025 still considered as experimental).

Frameworks exist for a reason: to help us manage structure and complexity.
Flake-parts does not invent anything new; it simply brings the Nix module system to flakes. Nothing more, nothing less. Given that, I would advise anyone working with flakes to use it by default.

@Andrew15-5
Copy link
Author

Andrew15-5 commented Dec 8, 2025

The problem I faced is not being able to use perSystem for all top-level attribute names at ones. Most of my flakes only have devShells, which makes perSystem very short and shows a strong use case where any extra dependencies are not worth it.

It's very sad that Nixpkgs library still doesn't have a wrapper just for this. IMO, it will 100% make flake-parts and alike useless in the vast majority of use cases. Do you think we can add this?

I now tried using a simpler perSystem for each top-level attribute name separately, and I think it's way simpler and more maintainable than this implementation. Which makes it a more strong case of not needing extra dependencies. https://github.com/typst/typst-flake/pull/4/files

And to prove the claims, just look at the flake in Nixpkgs. It uses the exact same thing, but it can only provide system argument, instead of pkgs, lib, self', typst, craneLib, etc. But the idea and interface is identical. So I wanna know if you are against an approach that doesn't require 3rd party libraries, and it's the default one in Nixpkgs.


Could you elaborate on how those inputs are complicating the configuration in practice?

Basically, it's not part of flake schema/feature, nor part of builtins/nixpkgs.lib. The main goal of the flake is to provide the dev shell and the Typst package. For that, you need to bring up all of the tooling, that is required to compile Typst and give the working dev shell.

Rust in Nix is pretty wild, so I don't care which of ten different ways everything will be compiled/build. Separately building all the dependencies in hopes that you don't work on a PR that constantly changes some of them (https://github.com/typst/typst/pull/7519)...is smart, but probably only hurts if you do work on such a change. So for most stuff, it's a pretty good solution to reduce re-compilation time.

But this makes flake-parts and systems the only dependencies that have nothing to do with Rust compilation. You can do this natively with Nix (and nixpkgs.lib, since it's already a required input): #4.

For me, it's almost as if you want to use is-number instead of just doing the same thing directly in your project (IMO, it's ridiculous, same as is-odd or is-even library!). Not everything has to be part of some library. It has over 100 million weekly downloads, which
you can count as "widely used across the community", similar to flake-parts. Though not all flakes use flake-parts, as flake-utils is more or less the alternative to that, hence why some flakes use one or the other.

However, unlike is-number, you don't just download a small mkFlake wrapper, you also download a whole separate Nixpkgs, which (with sometimes slow Internet) can take near minute of additional time and 30–70 MiB of Internet traffic (which is not an issue, unless you are on metered/capped plan, which is a real thing in some places).

And systems is just a whole dependency to get one small list of strings that never changes and there is no sign if it will change any time soon. And if it will, someone will definitely let us know. Literally go through all the flake/TCP/IP hoops just to fetch a near constant 65 char long list.

In other words,

  • more Internet traffic
  • slower initial time-until-ready
  • more disk space usage
  • bigger nix/lock files because of the dependencies
    for me, are not worth it at all over
  • ~10 pure lines of local straightforward wrapper (if we diff though, then it's not 10 lines, but a small reduction in LoC, even not considering flake.lock)

This newer implementation is simple enough that there is no maintenance overhead. Technically, both have some maintenance overhead, but both are about the same and both are very negligible.

I didn't really answer everything, but I think the new implementation shifts some stuff, so maybe some of that is irrelevant now.


If we start removing them in the name of minimalism, then we should logically keep the door open to even more minimalism. In that case, why stop there? Perhaps we should also consider removing flakes entirely (which is exactly what I experimented with here: drupol/infra#122), because Flake is actually nothing else than a framework (sadly, it is as of Dec 2025 still considered as experimental).

Why? Because the point of the repository is to have a flake. No flake, no need for repository. As simple as that. Moreover, I've heard somewhere that flakes will probably forever be in the experimental state, so for years this doesn't hold any weight in not using them. Everyone uses it, if not, then some use other pinning frameworks to have a lock file with all the dependencies for reproducibility. Flakes is a native framework, so it's much more convenient and very widely used, including Nixpkgs, and many other projects (Typst, Dioxus, to name just a few).

One last thing: it looks like you're recommending adopting Alejandra, while the Nix project itself uses the official formatter (nixfmt). For someone who values minimalism and reducing dependencies, suggesting a completely different formatting tool with its own conventions feels a bit contradictory. It’s a confusing message for people trying to keep things simple.

How can you talk about minimalism, if flake already has a formatter set as a dependency from Nixpkgs? Choosing formatter doesn't change anything, other than the way of formatting. Have you read the README of the project? In short, it's much faster than the "official" one and more patch-friendly as it moves arguments on separate lines. And overall, the readability improves. And I'm not the first to say that. After I shared some code snippets in Nix / NixOS room somewhat said that I should use it, and after reading I did, because I don't see any reason not to. You still will format with nix fmt (or same way as always in your IDE). No contradictions, I just know that it's better in many ways, while only being "not official" one as a "downside", that's it.

@drupol
Copy link
Collaborator

drupol commented Dec 8, 2025

Thanks for taking the time to expand on your position. I appreciate it. Let me try to address this for the very last time, as clearly as possible.

  1. Your implementation is a re-invention of flake-parts, not an alternative to it

    I looked at your updated PR (Remove systems and flake-parts dependencies (simpler alternative) #4) as well. It still does exactly what flake-parts already does:

    • define a perSystem wrapper
    • route attributes based on system
    • inject some shared context
    • expose them at the top level

    Except now it is:

    • tied to this single repository
    • undocumented
    • only tested by one person
    • missing years of refinement
    • unfamiliar to contributors

    This is not minimalism: it is fragmentation. Minimalism is removing duplication, not introducing it.

  2. “Native flakes” vs “frameworks” is a false dichotomy

    A recurring theme in your comments is the idea that anything not in the flake schema is somehow illegitimate or “non-native”. That is not how the Nix ecosystem works.

    Nixpkgs, lib, modules: none of these are “part of the flake schema”, yet they define how the vast majority of practical Nix code is structured. Flake-parts does not replace flakes. It provides the module system, which has existed for over a decade.

    It is not a new abstraction: it is the same one used across NixOS, Home Manager, and countless other projects.

    In other words: flake-parts brings consistency, and handwritten glue code removes it.

  3. The “dependencies and traffic” argument simply does not hold

    You repeatedly highlight network traffic, disk usage, download size, etc.

    But:

    • systems is a tiny repo with no dependencies.
    • flake-parts depends only on nixpkgs, which is already required for the Typst build.
    • The additional cost in the lockfile is negligible compared with the full build closure of Typst, Rust toolchains, cargo dependencies, LLVM, and so on.

    The idea that this PR meaningfully reduces bandwidth or disk footprint is simply not correct.

  4. The argument based on the Nixpkgs flake is misleading

    You claim the Nixpkgs flake “proves” that handwritten wrappers are better. That comparison does not apply:

    • Nixpkgs is a monorepo with a decades-old internal architecture
    • Its flake interface is intentionally minimal because the module system already lives inside the repo
    • External projects do not have that luxury

    You are comparing a Formula 1 engine to a bicycle chain.

  5. Line count is not a design metric

    I will say this clearly: line count is not a proxy for maintainability.

    If it were, we would all:

    • stop using NixOS modules,
    • stop using lib,
    • stop using overlays,
    • and go back to single 1,000-line flakes with repeated boilerplate.

    Minimalism is not “fewer lines at all costs”. Minimalism is “fewer concepts repeated across the ecosystem”.

    Flake-parts reduces conceptual duplication. Your approach increases it.

  6. About formatters

    Just to clarify this point: in this repository there is no debate about formatters. We simply follow the convention used by Nixpkgs and use the default formatter (nixfmt).

    With fewer than 10 Nix files in this project, any theoretical speed gain from a different formatter is irrelevant anyway. This choice is purely about consistency and reducing cognitive load.

    To be honest, and at this point, formatter discussions are firmly in bikeshed territory.


To conclude, and to be completely transparent:

  • I will not support replacing a well-tested, well-documented, community-standard framework
    • with bespoke glue code that duplicates its function,
    • based on subjective preference,
    • for negligible or non-existent technical benefit.
  • I do not intend to participate in yet another debate about:
    • “formatter A vs formatter B”,
    • “framework vs no framework”,
    • or “this abstraction is one layer too many”.
  • I will not continue engaging in debates framed around minimalism-for-minimalism’s-sake.

These conversations tend to generate heat, not light.

All I care about is:

  • clarity
  • efficiency
  • reliability
  • maintainability
  • consistency with the broader ecosystem
  • reducing long-term friction for contributors

Given all that, my stance remains unchanged from my previous comment.

Thanks again for the discussion.

Comment on lines -37 to -40
imports = [
inputs.flake-parts.flakeModules.easyOverlay
let
systems = [
"aarch64-darwin"
"aarch64-linux"
"x86_64-darwin"
"x86_64-linux"
];

Copy link
Collaborator

@PgBiel PgBiel Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's worth noting that easyOverlay is doing something. Do we know if we can easily replace it?

@laurmaedje laurmaedje closed this Dec 15, 2025
@typst typst locked and limited conversation to collaborators Dec 15, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants