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

treefmt: add configuration wrappers #390147

Merged
merged 2 commits into from
Mar 18, 2025
Merged

Conversation

MattSturgeon
Copy link
Contributor

@MattSturgeon MattSturgeon commented Mar 15, 2025

Summary

This PR adds some functions to the treefmt package that allow users to produce a treefmt config file and/or a wrapped treefmt package from structured settings modules.

  • treefmt.buildConfig builds a treefmt.toml file using a freeform module
  • treefmt.withConfig builds a treefmt package wrapped with a config

Also:

  • Added myself as a package maintainer
  • Added basic tests for the new functions

Motivation

It is inspired by treefmt-nix and the new nixfmt-tree wrapper package recently added by @infinisil.

Hopefully, the implementation here could be used by the new nixfmt-tree package, ideally simplifying its implementation. To keep this PR's scope minimal, I've not done that here.

Freeform options module

I also experimented locally with a settings.nix module that defined some options & defaults for the treefmt.toml file. I've not included that in this PR to keep the scope minimal. It also sees somewhat pointless to add module options without a clear way to document them; currently the nixpkgs manual does use nixos-render-docs, but it is hard-coded to render only the top-level pkgs config options.

Local settings.nix commit

commit f23edc22568005eae80e3a4ca411b4c4cb20af46
Author: Matt Sturgeon <matt@sturgeon.me.uk>
Date:   Wed Mar 5 03:12:47 2025 +0000

    treefmt: add default settings module
    
    Based on the config scheme in treefmt-nix:
    https://github.com/numtide/treefmt-nix/blob/94ae23/module-options.nix

diff --git a/pkgs/by-name/tr/treefmt/build-config.nix b/pkgs/by-name/tr/treefmt/build-config.nix
index 6f8b672d8e54..8a37961f3233 100644
--- a/pkgs/by-name/tr/treefmt/build-config.nix
+++ b/pkgs/by-name/tr/treefmt/build-config.nix
@@ -7,6 +7,8 @@ let
   settingsFormat = formats.toml { };
   configuration = lib.evalModules {
     modules = [
+      # TODO: document the settings options in the nixpkgs manual
+      ./settings.nix
       {
         freeformType = settingsFormat.type;
       }
diff --git a/pkgs/by-name/tr/treefmt/settings.nix b/pkgs/by-name/tr/treefmt/settings.nix
new file mode 100644
index 000000000000..cb8b24f5903e
--- /dev/null
+++ b/pkgs/by-name/tr/treefmt/settings.nix
@@ -0,0 +1,59 @@
+{ lib, config, ... }:
+let
+  inherit (lib) types;
+
+  # Coerce paths and packages into exe strings
+  toExeStr = p: if builtins.isPath p then "${p}" else lib.getExe p;
+  exeType = types.coercedTo (types.either types.package types.path) toExeStr types.str;
+
+  # Type for `formatter.*`
+  formatterType = types.submodule {
+    inherit (config._module) freeformType;
+    options = {
+      command = lib.mkOption {
+        description = "Executable obeying the treefmt formatter spec";
+        type = exeType;
+      };
+      options = lib.mkOption {
+        description = "List of arguments to pass to the command";
+        type = types.listOf types.str;
+        default = [ ];
+      };
+      includes = lib.mkOption {
+        description = "List of files to include for formatting. Supports globbing.";
+        type = types.listOf types.str;
+      };
+      excludes = lib.mkOption {
+        description = "List of files to exclude for formatting. Supports globbing. Takes precedence over the includes.";
+        type = types.listOf types.str;
+        default = [ ];
+      };
+    };
+  };
+in
+{
+  options = {
+    excludes = lib.mkOption {
+      description = "A global list of paths to exclude. Supports glob.";
+      type = types.listOf types.str;
+      default = [ ];
+      example = [ "./node_modules/**" ];
+    };
+    on-unmatched = lib.mkOption {
+      description = "Log paths that did not match any formatters at the specified log level.";
+      type = types.enum [
+        "debug"
+        "info"
+        "warn"
+        "error"
+        "fatal"
+      ];
+      default = "warn";
+    };
+    formatter = lib.mkOption {
+      type = types.attrsOf formatterType;
+      default = { };
+      description = "Set of formatters to use";
+    };
+  };
+}

Things done

  • Built on platform(s)
    • x86_64-linux
    • aarch64-linux
    • x86_64-darwin
    • aarch64-darwin
  • For non-Linux: Is sandboxing enabled in nix.conf? (See Nix manual)
    • sandbox = relaxed
    • sandbox = true
  • Tested, as applicable:
  • Tested compilation of all packages that depend on this change using nix-shell -p nixpkgs-review --run "nixpkgs-review rev HEAD". Note: all changes have to be committed, also see nixpkgs-review usage
  • Tested basic functionality of all binary files (usually in ./result/bin/)
  • 25.05 Release Notes (or backporting 24.11 and 25.05 Release notes)
    • (Package updates) Added a release notes entry if the change is major or breaking
    • (Module updates) Added a release notes entry if the change is significant
    • (Module addition) Added a release notes entry if adding a new NixOS module
  • Fits CONTRIBUTING.md.

Add a 👍 reaction to pull requests you find important.

@MattSturgeon MattSturgeon changed the title treefmt config treefmt: add configuration wrappers Mar 15, 2025
@github-actions github-actions bot added 10.rebuild-darwin: 0 This PR does not cause any packages to rebuild on Darwin 10.rebuild-linux: 0 This PR does not cause any packages to rebuild on Linux labels Mar 15, 2025
Copy link
Contributor

@jfly jfly left a comment

Choose a reason for hiding this comment

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

Interesting! I'll take a detailed look later, but my initial reaction is that I don't totally understand the motivation here. My thinking is that people can already get a nice, modular configuration of treefmt if they use treefmt-nix with flake-parts. Just thinking out loud, here are some reasons that this PR could be nice:

  1. As you mention in your OP, this could simplify the implementation nixfmt-tree package, which by virtue of living in nixpkgs, cannot depend on treefmt-nix.
  2. There are folks out there who might not be using flakes or flake-parts, and this would let them modularize their configuration.
    • Wouldn't it be better to address this in treefmt-nix itself, though?
  3. Anything else?

@MattSturgeon
Copy link
Contributor Author

MattSturgeon commented Mar 15, 2025

Thanks for your initial thoughts!

Anything else?

treefmt-nix has a much larger scope than what is proposed in this PR. For example, it provides sane configuration for many formatting programs. On the other hand, this PR is focused solely on defining treefmt.toml in an rfc42 structural style.

treefmt-nix is great at what it does, and I personally use it whenever I need a non-trivial treefmt config.

Offering simple wrappers and structural settings feels within scope for nixpkgs and (imo) shouldn't necessarily require external dependencies like treefmt-nix or flake-parts.

Perhaps at some point this could be done in such a way that treefmt-nix can also build on top of the impl here? Although I don't think that should be our first consideration.

Copy link
Member

@zimbatm zimbatm left a comment

Choose a reason for hiding this comment

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

Thanks, that looks like a great addition to treefmt.

We can't have nixpkgs depend on treefmt-nix so it makes sense to have a liteweight version in nixpkgs directly.

Comment on lines 10 to 18
{
freeformType = settingsFormat.type;
}
{
_file = "<treefmt2.buildConfig args>";
imports = lib.toList module;
}
Copy link
Member

Choose a reason for hiding this comment

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

Can't those two be merged together?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They could, but I think that'd be more confusing.

The first module is definitions by us (this file) while the second is wrapping user-supplied module(s) and setting a default file location.

freeformType = settingsFormat.type;
}
{
_file = "<treefmt2.buildConfig args>";
Copy link
Member

Choose a reason for hiding this comment

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

Best to use ./build-config.nix to provide the actual context.

Copy link
Contributor Author

@MattSturgeon MattSturgeon Mar 18, 2025

Choose a reason for hiding this comment

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

Normally I'd agree, but in this case we don't want to specify _file = ./build-config.nix, because we are wrapping the user-supplied modules. Kinda like how deferredModule wraps definitions with _file = "${def.file}, via option ${showOption loc}".

I don't think we have access to the actual location here,1 so the default _file only indicates the function name, to which the user's modules were passed.


I've added _file = ./build-config.nix to the other module, along with some clarifying comments and a typo fix (treefmt2 -> treefmt).

Footnotes

  1. I tried playing with builtins.unsafeGetAttrPos, but it always returned null. I'm guessing makeOverridable turning buildConfig into a functor is messing with attr locations?

@brianmcgee
Copy link
Contributor

I like it; we should also port this into the package exposed by treefmt's flake.

- `treefmt.withConfig` builds a treefmt package wrapped with a config
- `treefmt.buildConfig` builds a treefmt.toml file using a freeform module
@MattSturgeon
Copy link
Contributor Author

MattSturgeon commented Mar 18, 2025

we should also port this into the package exposed by treefmt's flake.

Would it make sense for treefmt's flake to inherit and/or override the impl directly from nixpkgs, instead of re-implementing or copy-pasting it?

Something like:

{
  passthru = {
    withConfig = pkgs.treefmt.withConfig.override {
      treefmt = finalAttrs.finalPackage;
    };
    buildConfig = pkgs.treefmt.buildConfig.override {
      treefmt = finalAttrs.finalPackage;
    };
  };
}

I like the idea or re-using/sharing implementations wherever possible, to ease maintenance and keep related projects in sync with each other.

Copy link
Contributor

@jfly jfly left a comment

Choose a reason for hiding this comment

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

🚢

Copy link
Member

@infinisil infinisil left a comment

Choose a reason for hiding this comment

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

Reviewed together in the Nix formatting team, looking good, let's merge!

@infinisil infinisil merged commit 7d76b1a into NixOS:master Mar 18, 2025
21 of 22 checks passed
@nixos-discourse
Copy link

This pull request has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/formatting-team-meeting-2025-03-18/61868/1

@MattSturgeon MattSturgeon deleted the treefmt-config branch March 18, 2025 20:02
MattSturgeon added a commit to MattSturgeon/treefmt that referenced this pull request Mar 19, 2025
Inherit the `withConfig` and `buildConfig` wrapper functions added in
NixOS/nixpkgs#390147

If the current nixpkgs revision does not have these attrs, a sane error
message is thrown when attempting to use them.

The `treefmt` argument passed is overridden to refer to
`perSystem.self.treefmt`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
10.rebuild-darwin: 0 This PR does not cause any packages to rebuild on Darwin 10.rebuild-linux: 0 This PR does not cause any packages to rebuild on Linux
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

6 participants