Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] Initial Proposal for flake-parts-cli #1

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions rfcs/00-initial-proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Initial Proposal for `flake-parts-cli`

## Background

`flake-parts` modularizes project `flake.nix` ([example](https://github.com/srid/haskell-template/blob/master/flake.nix)) sufficiently enough to make it almost look like YAML, due to the way the module system works. Developers set "options", with config implementation taken care of by upstream modules ([such as these](https://community.flake.parts/modules)).

Can we go one step further and substantially improve the DX by allowing developers to achieve typical Nix uses cases without having to write Nix?

## Approach

To achieve this, we propose a CLI that takes as input some YAML or TOML (file formats familiar to many people) and "adjust" (if not auto-generate) the top-level `flake.nix`.

## Spec & Implementation

While the details of the spec & implementation are to be worked out, here's a good starting point:

Let's say our CLI generates an initial `flake.nix` with "placeholder" comments:

```nix
{
inputs = {
# START inputs managed by flake-parts cli
<generate whatever we want here>
# END inputs managed by flake-parts cli
}:
outputs = inputs: inputs.flake-parts.mkFlake {
inherit inputs;
# A toml file is easier to manipulate programmatically
specialArgs.flake-parts-toml = ./nix/flake-parts.toml;
modules = [
# and this module could translate the toml into module definitions
inputs.flake-parts-cli.flakeModule
Comment on lines +31 to +32
Copy link

Choose a reason for hiding this comment

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

We don't have eval in Nix, making this slightly inefficient. Something like

withSystem system (import (builtins.toFile "something descriptive" "args: with args; ${exprStr}"))

withSystem would only apply to perSystem things, but can be "ported" to the top level. Also going through allSystems is a bit backwards if you're defining something in perSystem, so that part could be ripped out when writing the right function for this.

];
};
Copy link

Choose a reason for hiding this comment

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

I think it should be acceptable for users to write configs in plain Module System style here.
The CLI should try not to assume that the TOML is the only input to evaluation, because that would be anti-modular and a problem for e.g. modules that auto-configure other modules. In other words there is no boundary between user definitions and modular definitions, and generally all reading of the configuration should be done through the Module System.

Copy link
Member Author

Choose a reason for hiding this comment

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

I imagine the user can hand-edit (or through the cli) the modules list to include custom .nix modules. Shouldn't this the case for inputs as well? The START...END suggests, however, that the user is not supposed to hand-edit the inputs.

}
```

This flake points to a TOML file that, for a Haskell project, may look like this:

```toml
# ./nix/flake-parts.toml
# flake-parts TOML

[flake.registry]
url = "https://github.com/juspay/flake-registry"

[flake.inputs]
nixpkgs.url = "nixpkgs"
haskell-flake.url = "haskell-flake"
euler-hs.url = "github:juspay/euler-hs"
euler-hs.follow-auto = ["nixpkgs", "haskell-flake"]

[flake.parts]
imports = "haskell-flake"

[flake.parts.haskellProjects.default]
projectRoot = "."
[flake.parts.haskellProjects.default.settings]
euler-hs.check = false
[flake.parts.haskellProjects.default.defaults.settings.local]
buildFromSdist = false
haddock = true

[flake.outputs.packages]
default = "${self'.packages.hello-world}"

[flake.outputs.devShells.default]
packages = ["${pkgs.haskellPackages.ghcid}", "${pkgs.node}"]
Copy link
Member Author

Choose a reason for hiding this comment

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

Is ${...} the best we can do for referring to packages? Do we have to support arbitrary Nix expressions with variables in scope?

Copy link
Member Author

Choose a reason for hiding this comment

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

In some cases, we also need conditionals, like:

      perSystem = { config, self', pkgs, lib, ... }: {
        rust-project.crane.args = {
          buildInputs = lib.optionals pkgs.stdenv.isDarwin (
            with pkgs.darwin.apple_sdk.frameworks; [
              IOKit
            ]
          );
        };
      };

How would the TOML represent this case?

```

Now, the user can simply edit the TOML file (`./nix/flake-parts.toml`) to make any changes, without writing a line of Nix. The `flake-parts-cli` app will support commands for "adding" or "removing" modules, which automatically will modify the `inputs` part of the flake. Developers can then configure their new modules in the TOML file. The CLI can also wrap the various Nix commands like `build`, `develop` and so forth, whilst even being ambitious enough to provide custom subcommands to be specified by upstream modules (for e.g., [services-flake](https://github.com/juspay/services-flake) can provide custom subcommands to add/remove services).
Copy link

Choose a reason for hiding this comment

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

I'd like nix itself to fulfill the "run arbitrary project-specific command" role; see NixOS/nix#6241. Not a reason not to plan this for a flake-parts CLI, but doing it in Nix itself further improves standardization.
Also I can review as a Nix team member nowadays, and it's a reasonable "good first issue".

Copy link
Member Author

Choose a reason for hiding this comment

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


Developers can write custom modules and have them imported in top-level flake for those (rare) advanced use-cases; thus they don't have to move away from being able to use the full power of Nix.