flake/dev/generate-all-maintainers: init

Used to generate a full maintainers.nix file that can be used for RFC39
invites. We will use these invites to support requesting reviews from
maintainers.
This commit is contained in:
Austin Horstman 2025-07-03 09:56:32 -05:00
parent 95f129ca65
commit 056cd86cc0
5 changed files with 328 additions and 0 deletions

View file

@ -51,6 +51,7 @@
packages = lib.optionalAttrs (partitionStack == [ ]) {
# Propagate `packages` from the `dev` partition:
inherit (config.partitions.dev.module.flake.packages.${system})
generate-all-maintainers
list-plugins
;
};

View file

@ -2,6 +2,7 @@
{
imports = [
./devshell.nix
./generate-all-maintainers
./list-plugins
./package-tests.nix
./template-tests.nix

View file

@ -0,0 +1,42 @@
{ self, ... }:
{
perSystem =
{
lib,
pkgs,
...
}:
let
package = pkgs.writers.writePython3Bin "generate-all-maintainers" {
# Disable flake8 checks that are incompatible with the ruff ones
flakeIgnore = [
# Thinks shebang is a block comment
"E265"
# line too long
"E501"
# line break before binary operator
"W503"
];
} (builtins.readFile ./generate-all-maintainers.py);
in
{
packages.generate-all-maintainers = package;
checks.generate-all-maintainers-test =
pkgs.runCommand "generate-all-maintainers-test"
{
nativeBuildInputs = [ package ];
}
''
generate-all-maintainers --root ${self} --output $out
'';
devshells.default.commands = [
{
name = "generate-all-maintainers";
command = ''${lib.getExe package} "$@"'';
help = "Generate a single nix file with all `nixvim` and `nixpkgs` maintainer entries";
}
];
};
}

View file

@ -0,0 +1,74 @@
# Extract maintainers from nixvim configuration using meta.maintainers
# This script evaluates an empty nixvimConfiguration and extracts the merged maintainer information
let
nixvim = import ../../..;
lib = nixvim.inputs.nixpkgs.lib.extend nixvim.lib.overlay;
emptyConfig = lib.nixvim.evalNixvim {
modules = [ { _module.check = false; } ];
extraSpecialArgs = {
pkgs = null;
};
};
inherit (emptyConfig.config.meta) maintainers;
extractMaintainerObjects =
maintainerData:
lib.pipe maintainerData [
lib.attrValues
lib.concatLists
lib.unique
];
allMaintainerObjects = extractMaintainerObjects maintainers;
allMaintainerNames = lib.filter (name: name != null) (
map (maintainer: maintainer.github) allMaintainerObjects
);
nixvimMaintainers = import ../../../lib/maintainers.nix;
nixvimMaintainerNames = lib.attrNames nixvimMaintainers;
partitionedMaintainers = lib.partition (nameValue: lib.elem nameValue.name nixvimMaintainerNames) (
lib.attrsToList maintainerDetails
);
maintainerDetails = lib.pipe allMaintainerObjects [
(map (obj: {
name = obj.github;
value = obj // {
source =
if categorizedMaintainers.nixvim ? ${obj.github} then
"nixvim"
else if categorizedMaintainers.nixpkgs ? ${obj.github} then
"nixpkgs"
else
throw "${obj.github} is neither a nixvim or nixpkgs maintainer";
};
}))
lib.listToAttrs
];
categorizedMaintainers = {
nixvim = lib.listToAttrs partitionedMaintainers.right;
nixpkgs = lib.listToAttrs partitionedMaintainers.wrong;
};
formattedMaintainers = lib.generators.toPretty {
multiline = true;
indent = "";
} maintainerDetails;
in
{
raw = maintainers;
names = allMaintainerNames;
details = maintainerDetails;
categorized = categorizedMaintainers;
formatted = formattedMaintainers;
stats = {
totalMaintainers = lib.length allMaintainerNames;
nixvimMaintainers = lib.length (lib.attrNames categorizedMaintainers.nixvim);
nixpkgsMaintainers = lib.length (lib.attrNames categorizedMaintainers.nixpkgs);
};
}

View file

@ -0,0 +1,210 @@
#!/usr/bin/env nix-shell
#!nix-shell -i python3 -p python3
"""
Generate all-maintainers.nix using meta.maintainers as source of truth.
This script uses the meta.maintainers system to extract maintainer information
by evaluating an empty nixvimConfiguration, which is much simpler and more
reliable than parsing files with regex.
"""
import argparse
import inspect
import json
import subprocess
import sys
from pathlib import Path
def get_project_root() -> Path:
"""
Find the project root directory.
Tries to find the git repository root. If that fails, falls back to
locating it relative to this script file.
"""
try:
# Ask git for the top-level directory of the current repository.
git_root_bytes = subprocess.check_output(
["git", "rev-parse", "--show-toplevel"], stderr=subprocess.DEVNULL
)
return Path(git_root_bytes.decode("utf-8").strip())
except (subprocess.CalledProcessError, FileNotFoundError):
# Fallback for when not in a git repo or git is not installed.
print(
"Warning: 'git rev-parse --show-toplevel' failed.",
"Falling back to script location to determine root.",
"This may not work correctly with flakes.",
file=sys.stderr,
)
# Assumes this script is at: <root>/flake/dev/generate-all-maintainers/
return Path(__file__).parent.parent.parent.parent.resolve()
class MetaMaintainerGenerator:
"""Generates maintainers list using meta.maintainers from nixvim evaluation."""
def __init__(self, nixvim_root: Path):
self.nixvim_root = nixvim_root
self.nixvim_maintainers_file = nixvim_root / "lib" / "maintainers.nix"
self.output_file = nixvim_root / "generated" / "all-maintainers.nix"
self.extractor_script = (
nixvim_root
/ "flake"
/ "dev"
/ "generate-all-maintainers"
/ "extract-maintainers-meta.nix"
)
def extract_maintainers_from_meta(self) -> dict:
"""Extract maintainer information using meta.maintainers."""
print("🔍 Extracting maintainers using meta.maintainers...")
try:
result = subprocess.run(
[
"nix",
"eval",
"--file",
str(self.extractor_script),
"--json",
],
capture_output=True,
text=True,
timeout=60,
)
if result.returncode == 0:
data = json.loads(result.stdout)
print("✅ Successfully extracted maintainers using meta.maintainers")
return data
else:
print(f"❌ Failed to extract maintainers: {result.stderr}")
sys.exit(1)
except subprocess.TimeoutExpired:
print("❌ Timeout while extracting maintainers")
sys.exit(1)
except Exception as e:
print(f"❌ Error extracting maintainers: {e}")
sys.exit(1)
def generate_maintainers_file(self) -> None:
"""Generate the complete all-maintainers.nix file."""
print("📄 Generating all-maintainers.nix using meta.maintainers...")
# Extract maintainers using meta.maintainers
maintainer_data = self.extract_maintainers_from_meta()
nixvim_maintainers = maintainer_data["categorized"]["nixvim"]
nixpkgs_maintainers = maintainer_data["categorized"]["nixpkgs"]
formatted_maintainers = maintainer_data["formatted"]
print(f"🏠 Nixvim maintainers: {len(nixvim_maintainers)}")
print(f"📦 Nixpkgs maintainers: {len(nixpkgs_maintainers)}")
with open(self.output_file, "w") as f:
f.write(inspect.cleandoc("""
# Nixvim all maintainers list.
#
# This file lists all referenced maintainers in Nixvim.
#
# This file is automatically generated using meta.maintainers from nixvim evaluation
# DO NOT EDIT MANUALLY
#
# To regenerate: nix run .#generate-all-maintainers
#
"""))
# Use the formatted maintainers from Nix evaluation
print(
"✨ Serializing formatted maintainers using lib.generators.toPretty..."
)
f.write("\n")
f.write(formatted_maintainers)
f.write("\n")
self.validate_generated_file()
self.print_statistics(maintainer_data)
def validate_generated_file(self) -> bool:
"""Validate the generated Nix file syntax."""
try:
result = subprocess.run(
["nix-instantiate", "--eval", str(self.output_file), "--strict"],
capture_output=True,
text=True,
timeout=10,
)
if result.returncode == 0:
print("✅ Generated file has valid Nix syntax")
return True
else:
print("❌ Warning: Generated file has Nix syntax errors")
print(result.stderr[:500])
return False
except Exception as e:
print(f"Warning: Could not validate file: {e}")
return False
def print_statistics(self, maintainer_data: dict) -> None:
"""Print generation statistics."""
stats = maintainer_data["stats"]
print(f"✅ Generated {self.output_file}")
print("📊 Statistics:")
print(f" - Total unique maintainers: {stats['totalMaintainers']}")
print(f" - Nixvim maintainers: {stats['nixvimMaintainers']}")
print(f" - Nixpkgs maintainers: {stats['nixpkgsMaintainers']}")
print()
print("🎉 Generation completed successfully using meta.maintainers!")
def main():
parser = argparse.ArgumentParser(
description="Generate Nixvim all-maintainers.nix using meta.maintainers"
)
parser.add_argument(
"--root",
type=Path,
default=None,
help="Path to Nixvim root (default: auto-detect)",
)
parser.add_argument(
"--output",
type=Path,
default=None,
help="Output file path (default: <root>/generated/all-maintainers.nix)",
)
args = parser.parse_args()
if args.root:
nixvim_root = args.root
else:
nixvim_root = get_project_root()
if not (nixvim_root / "lib" / "maintainers.nix").exists():
print(f"Error: Could not find maintainers.nix in {nixvim_root}")
print("Please specify --root or run from Nixvim directory")
sys.exit(1)
generator = MetaMaintainerGenerator(nixvim_root)
if args.output:
generator.output_file = args.output
print("🚀 Generating maintainers using meta.maintainers approach...")
try:
generator.generate_maintainers_file()
except KeyboardInterrupt:
print("\n❌ Generation cancelled by user")
sys.exit(1)
except Exception as e:
print(f"❌ Error generating maintainers file: {e}")
sys.exit(1)
if __name__ == "__main__":
main()