From b8b7e5ec3570eb003d55f4947dd39af15c3ca98d Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Fri, 11 Jul 2025 12:47:42 -0500 Subject: [PATCH] ci: extract-maintainers-meta tweaks (#7434) Simplify extraction and generation of file. We dont need the comments and can leverage the appropriate lib.generator function. Signed-off-by: Austin Horstman --- lib/nix/extract-maintainers-meta.nix | 68 ++++++++++---------------- lib/python/generate-all-maintainers.py | 67 ++++++++++++++++--------- 2 files changed, 71 insertions(+), 64 deletions(-) diff --git a/lib/nix/extract-maintainers-meta.nix b/lib/nix/extract-maintainers-meta.nix index dba3bc4a0..de9a0cc66 100644 --- a/lib/nix/extract-maintainers-meta.nix +++ b/lib/nix/extract-maintainers-meta.nix @@ -13,6 +13,7 @@ let moduleMaintainersJson = builtins.fromJSON (builtins.readFile docsLib.jsonModuleMaintainers); maintainers = moduleMaintainersJson; + # TODO: Find a better solution for extracting maintainers outside `modules` additionalFiles = [ ../../docs/home-manager-manual.nix ]; @@ -58,22 +59,30 @@ let allMaintainerObjects = extractMaintainerObjects maintainers ++ additionalMaintainerObjects; - getMaintainerName = maintainer: maintainer.github or maintainer.name or null; - - allMaintainerNames = lib.filter (name: name != null) (map getMaintainerName allMaintainerObjects); - - maintainerDetails = lib.pipe allMaintainerObjects [ - (lib.filter (obj: getMaintainerName obj != null)) - (map (obj: { - name = getMaintainerName obj; - value = obj; - })) - lib.listToAttrs - ]; + allMaintainerNames = lib.filter (name: name != null) ( + map (maintainer: maintainer.github or maintainer.name or null) allMaintainerObjects + ); hmMaintainers = import ../../modules/lib/maintainers.nix; hmMaintainerNames = lib.attrNames hmMaintainers; + maintainerDetails = lib.pipe allMaintainerObjects [ + (lib.filter (obj: (obj.github or obj.name or null) != null)) + (map (obj: { + name = obj.github or obj.name; + value = obj // { + source = + if categorizedMaintainers.home-manager ? ${obj.github} then + "home-manager" + else if categorizedMaintainers.nixpkgs ? ${obj.github} then + "nixpkgs" + else + throw "${obj.github} is neither a home-manager or nixpkgs maintainer"; + }; + })) + lib.listToAttrs + ]; + partitionedMaintainers = lib.partition (nameValue: lib.elem nameValue.name hmMaintainerNames) ( lib.attrsToList maintainerDetails ); @@ -83,35 +92,10 @@ let nixpkgs = lib.listToAttrs partitionedMaintainers.wrong; }; - formatMaintainer = - name: info: source: - let - quotedName = - if lib.match "[0-9].*" name != null || lib.match "[^a-zA-Z0-9_-].*" name != null then - ''"${name}"'' - else - name; - - filteredInfo = lib.filterAttrs (k: v: !lib.hasPrefix "_" k) info; - in - " ${quotedName} = ${ - lib.generators.toPretty { - multiline = true; - indent = " "; - } filteredInfo - };"; - - formatAllMaintainers = - let - hmEntries = lib.mapAttrsToList ( - name: info: formatMaintainer name info "home-manager" - ) categorizedMaintainers.home-manager; - - nixpkgsEntries = lib.mapAttrsToList ( - name: info: formatMaintainer name info "nixpkgs" - ) categorizedMaintainers.nixpkgs; - in - lib.concatStringsSep "\n" (hmEntries ++ nixpkgsEntries); + formattedMaintainers = lib.generators.toPretty { + multiline = true; + indent = ""; + } maintainerDetails; in { @@ -119,7 +103,7 @@ in names = allMaintainerNames; details = maintainerDetails; categorized = categorizedMaintainers; - formatted = formatAllMaintainers; + formatted = formattedMaintainers; stats = { totalFiles = lib.length (lib.attrNames maintainers); diff --git a/lib/python/generate-all-maintainers.py b/lib/python/generate-all-maintainers.py index a7ed09c23..aee662260 100755 --- a/lib/python/generate-all-maintainers.py +++ b/lib/python/generate-all-maintainers.py @@ -9,13 +9,37 @@ reliable than parsing files with regex. """ import argparse +import inspect import json import subprocess import sys from pathlib import Path -from typing import Dict +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: /flake/dev/generate-all-maintainers/ + return Path(__file__).parent.parent.parent.parent.resolve() + class MetaMaintainerGenerator: """Generates maintainers list using meta.maintainers from Home Manager evaluation.""" @@ -25,13 +49,13 @@ class MetaMaintainerGenerator: self.output_file = hm_root / "all-maintainers.nix" self.extractor_script = hm_root / "lib" / "nix" / "extract-maintainers-meta.nix" - def extract_maintainers_from_meta(self) -> Dict: + 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", "--impure", "--file", str(self.extractor_script), "--json" + "nix", "eval", "--file", str(self.extractor_script), "--json" ], capture_output=True, text=True, timeout=60) if result.returncode == 0: @@ -49,7 +73,7 @@ class MetaMaintainerGenerator: print(f"❌ Error extracting maintainers: {e}") sys.exit(1) - def format_maintainer_entry(self, name: str, info: Dict, source: str) -> str: + def format_maintainer_entry(self, name: str, info: dict, source: str) -> str: """Format a single maintainer entry with nix fmt compatible formatting.""" lines = [f" # {source}"] lines.append(f" {name} = {{") @@ -116,26 +140,26 @@ class MetaMaintainerGenerator: print(f"📦 Nixpkgs maintainers: {len(nixpkgs_maintainers)}") with open(self.output_file, 'w') as f: - f.write('''# Home Manager all maintainers list. -# -# This file lists all referenced maintainers in Home Manager. -# -# This file is automatically generated using meta.maintainers from Home Manager evaluation -# DO NOT EDIT MANUALLY -# -# To regenerate: ./lib/python/generate-all-maintainers.py -# -{ -''') + f.write( + inspect.cleandoc(""" + # Home Manager all maintainers list. + # + # This file lists all referenced maintainers in Home Manager. + # + # This file is automatically generated using meta.maintainers from Home Manager evaluation + # DO NOT EDIT MANUALLY + # + # To regenerate: ./lib/python/generate-all-maintainers.py + # + """) + ) # Use the formatted maintainers from Nix evaluation print("✨ Adding formatted maintainers using lib.generators.toPretty...") + f.write("\n") f.write(formatted_maintainers) f.write("\n") - f.write('''} -''') - self.validate_generated_file() self.print_statistics(maintainer_data) @@ -143,7 +167,7 @@ class MetaMaintainerGenerator: """Validate the generated Nix file syntax.""" try: result = subprocess.run([ - 'nix', 'eval', '--file', str(self.output_file), '--json' + 'nix-instantiate', '--eval', str(self.output_file), '--strict' ], capture_output=True, text=True, timeout=10) if result.returncode == 0: @@ -157,7 +181,7 @@ class MetaMaintainerGenerator: print(f"Warning: Could not validate file: {e}") return False - def print_statistics(self, maintainer_data: Dict) -> None: + def print_statistics(self, maintainer_data: dict) -> None: """Print generation statistics.""" stats = maintainer_data["stats"] @@ -193,8 +217,7 @@ def main(): if args.root: hm_root = args.root else: - script_dir = Path(__file__).parent - hm_root = script_dir.parent.parent + hm_root = get_project_root() if not (hm_root / "modules" / "lib" / "maintainers.nix").exists(): print(f"Error: Could not find maintainers.nix in {hm_root}")