1
0
Fork 0
mirror of https://github.com/nix-community/home-manager.git synced 2025-11-08 19:46:05 +01:00

ci: generate-all-maintainers use nix eval update

Previously, we had to hack together some string matching to identify and
retrieve the maintainers in the repo. We can just eval the modules to
retrieve the list of maintainers more accurately.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
This commit is contained in:
Austin Horstman 2025-07-03 15:13:18 -05:00
parent b46c693797
commit d03fa2d84c
2 changed files with 59 additions and 180 deletions

View file

@ -59,6 +59,8 @@ jobs:
run: | run: |
echo "📋 Generating updated all-maintainers.nix..." echo "📋 Generating updated all-maintainers.nix..."
./lib/python/generate-all-maintainers.py ./lib/python/generate-all-maintainers.py
echo "🎨 Formatting with nixfmt..."
nix fmt all-maintainers.nix
- name: Check for changes - name: Check for changes
id: check-changes id: check-changes
run: | run: |

View file

@ -1,134 +1,53 @@
#!/usr/bin/env nix-shell #!/usr/bin/env nix-shell
#!nix-shell -i python3 -p python3 #!nix-shell -i python3 -p python3
""" """
Generate all-maintainers.nix combining local and nixpkgs maintainers. Generate all-maintainers.nix using meta.maintainers as source of truth.
This script analyzes Home Manager modules to find maintainer references This script uses the meta.maintainers system to extract maintainer information
and combines them with local maintainers to create a master list. by evaluating Home Manager modules, which is much simpler and more
reliable than parsing files with regex.
""" """
import argparse import argparse
import json import json
import re
import subprocess import subprocess
import sys import sys
from pathlib import Path from pathlib import Path
from typing import Dict, List, Optional, Set from typing import Dict
class MaintainerGenerator: class MetaMaintainerGenerator:
"""Generates a comprehensive maintainers list from HM and nixpkgs sources.""" """Generates maintainers list using meta.maintainers from Home Manager evaluation."""
def __init__(self, hm_root: Path): def __init__(self, hm_root: Path):
self.hm_root = hm_root self.hm_root = hm_root
self.modules_dir = hm_root / "modules" self.hm_maintainers_file = hm_root / "modules" / "lib" / "maintainers.nix"
self.hm_maintainers_file = self.modules_dir / "lib" / "maintainers.nix"
self.output_file = hm_root / "all-maintainers.nix" self.output_file = hm_root / "all-maintainers.nix"
self.extractor_script = hm_root / "lib" / "nix" / "extract-maintainers-meta.nix"
def find_nix_files(self) -> List[Path]: def extract_maintainers_from_meta(self) -> Dict:
"""Find all .nix files in the modules directory.""" """Extract maintainer information using meta.maintainers."""
nix_files = list(self.modules_dir.rglob("*.nix")) print("🔍 Extracting maintainers using meta.maintainers...")
print(f"📁 Found {len(nix_files)} .nix files in modules")
return nix_files
def extract_maintainer_lines(self, file_path: Path) -> List[str]:
"""Extract lines containing maintainer references from a file."""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
lines = []
for line in content.splitlines():
if any(pattern in line for pattern in [
"meta.maintainers",
"lib.maintainers.",
"lib.hm.maintainers.",
"with lib.maintainers",
"with lib.hm.maintainers"
]):
lines.append(line.strip())
return lines
except Exception as e:
print(f"Warning: Could not read {file_path}: {e}")
return []
def parse_maintainer_names(self, lines: List[str]) -> Set[str]:
"""Parse maintainer names from extracted lines."""
nixpkgs_maintainers = set()
for line in lines:
matches = re.findall(r'lib\.maintainers\.([a-zA-Z0-9_-]+)', line)
nixpkgs_maintainers.update(matches)
if 'with lib.maintainers' in line:
bracket_match = re.search(r'\[([^\]]+)\]', line)
if bracket_match:
content = bracket_match.group(1)
names = re.findall(r'\b([a-zA-Z0-9_-]+)\b', content)
filtered_names = [
name for name in names
if name not in {'with', 'lib', 'maintainers', 'meta', 'if', 'then', 'else'}
]
nixpkgs_maintainers.update(filtered_names)
return nixpkgs_maintainers
def extract_all_maintainers(self) -> Dict[str, Set[str]]:
"""Extract all maintainer references from modules."""
print("🔎 Extracting maintainer references...")
nix_files = self.find_nix_files()
all_lines = []
hm_maintainers_used = set()
for file_path in nix_files:
lines = self.extract_maintainer_lines(file_path)
all_lines.extend(lines)
for line in lines:
hm_matches = re.findall(r'lib\.hm\.maintainers\.([a-zA-Z0-9_-]+)', line)
hm_maintainers_used.update(hm_matches)
print("📝 Parsing maintainer names...")
nixpkgs_maintainers = self.parse_maintainer_names(all_lines)
print(f"👥 Found potential nixpkgs maintainers: {len(nixpkgs_maintainers)}")
print(f"🏠 Found HM maintainers used: {len(hm_maintainers_used)}")
return {
'nixpkgs': nixpkgs_maintainers,
'hm_used': hm_maintainers_used
}
def load_hm_maintainers(self) -> Set[str]:
"""Load Home Manager maintainer names."""
try:
with open(self.hm_maintainers_file, 'r') as f:
content = f.read()
names = re.findall(r'^\s*"?([a-zA-Z0-9_-]+)"?\s*=', content, re.MULTILINE)
return set(names)
except Exception as e:
print(f"Error loading HM maintainers: {e}")
return set()
def fetch_nixpkgs_maintainers(self) -> Optional[Dict]:
"""Fetch nixpkgs maintainers data using nix eval."""
print("📡 Attempting to fetch nixpkgs maintainer information...")
try: try:
result = subprocess.run([ result = subprocess.run([
'nix', 'eval', '--file', '<nixpkgs>', 'lib.maintainers', '--json' "nix", "eval", "--impure", "--file", str(self.extractor_script), "--json"
], capture_output=True, text=True, timeout=30) ], capture_output=True, text=True, timeout=60)
if result.returncode == 0: if result.returncode == 0:
print("✅ Successfully fetched nixpkgs maintainers") data = json.loads(result.stdout)
return json.loads(result.stdout) print("✅ Successfully extracted maintainers using meta.maintainers")
return data
else: else:
print("⚠️ Could not fetch nixpkgs maintainers - will create placeholders") print(f"❌ Failed to extract maintainers: {result.stderr}")
return None sys.exit(1)
except (subprocess.TimeoutExpired, subprocess.CalledProcessError, FileNotFoundError) as e:
print(f"⚠️ Nix command failed: {e}") except subprocess.TimeoutExpired:
return None print("❌ Timeout while extracting maintainers")
sys.exit(1)
except Exception as e:
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.""" """Format a single maintainer entry with nix fmt compatible formatting."""
@ -184,24 +103,24 @@ class MaintainerGenerator:
def generate_maintainers_file(self) -> None: def generate_maintainers_file(self) -> None:
"""Generate the complete all-maintainers.nix file.""" """Generate the complete all-maintainers.nix file."""
print("📄 Generating all-maintainers.nix...") print("📄 Generating all-maintainers.nix using meta.maintainers...")
extracted = self.extract_all_maintainers() # Extract maintainers using meta.maintainers
nixpkgs_maintainers = extracted['nixpkgs'] maintainer_data = self.extract_maintainers_from_meta()
hm_maintainer_names = self.load_hm_maintainers()
nixpkgs_only = nixpkgs_maintainers - hm_maintainer_names
print(f"📦 Nixpkgs-only maintainers after deduplication: {len(nixpkgs_only)}")
nixpkgs_data = self.fetch_nixpkgs_maintainers() or {} hm_maintainers = maintainer_data["categorized"]["home-manager"]
nixpkgs_maintainers = maintainer_data["categorized"]["nixpkgs"]
formatted_maintainers = maintainer_data["formatted"]
print(f"🏠 Home Manager maintainers: {len(hm_maintainers)}")
print(f"📦 Nixpkgs maintainers: {len(nixpkgs_maintainers)}")
with open(self.output_file, 'w') as f: with open(self.output_file, 'w') as f:
f.write('''# Home Manager all maintainers list. f.write('''# Home Manager all maintainers list.
# #
# This file combines maintainers from: # This file lists all referenced maintainers in Home Manager.
# - Home Manager specific maintainers (modules/lib/maintainers.nix)
# - Nixpkgs maintainers referenced in Home Manager modules
# #
# This file is automatically generated by lib/python/generate-all-maintainers.py # This file is automatically generated using meta.maintainers from Home Manager evaluation
# DO NOT EDIT MANUALLY # DO NOT EDIT MANUALLY
# #
# To regenerate: ./lib/python/generate-all-maintainers.py # To regenerate: ./lib/python/generate-all-maintainers.py
@ -209,54 +128,16 @@ class MaintainerGenerator:
{ {
''') ''')
print("🏠 Adding Home Manager maintainers...") # Use the formatted maintainers from Nix evaluation
try: print("✨ Adding formatted maintainers using lib.generators.toPretty...")
with open(self.hm_maintainers_file, 'r') as hm_file: f.write(formatted_maintainers)
hm_content = hm_file.read() f.write("\n")
start = hm_content.find('{')
end = hm_content.rfind('}')
if start != -1 and end != -1:
inner_content = hm_content[start+1:end]
lines = inner_content.split('\n')
in_entry = False
for line in lines:
stripped = line.strip()
if not stripped or stripped.startswith('#') or 'keep-sorted' in stripped:
continue
if '= {' in line and not in_entry:
f.write(" # home-manager\n")
f.write(f"{line}\n")
in_entry = True
elif line.strip() == '};' and in_entry:
f.write(f"{line}\n")
in_entry = False
else:
f.write(f"{line}\n")
except Exception as e:
print(f"Warning: Could not process HM maintainers file: {e}")
print("📦 Adding referenced nixpkgs maintainers...")
for maintainer in sorted(nixpkgs_only):
if maintainer in nixpkgs_data:
entry = self.format_maintainer_entry(maintainer, nixpkgs_data[maintainer], "nixpkgs")
f.write(f"{entry}\n")
else:
placeholder = {
'name': maintainer,
'email': f'{maintainer}@example.com',
'github': maintainer,
'githubId': 0
}
entry = self.format_maintainer_entry(maintainer, placeholder, "nixpkgs (placeholder)")
f.write(f"{entry}\n")
f.write('''} f.write('''}
''') ''')
self.validate_generated_file() self.validate_generated_file()
self.print_statistics() self.print_statistics(maintainer_data)
def validate_generated_file(self) -> bool: def validate_generated_file(self) -> bool:
"""Validate the generated Nix file syntax.""" """Validate the generated Nix file syntax."""
@ -276,28 +157,24 @@ class MaintainerGenerator:
print(f"Warning: Could not validate file: {e}") print(f"Warning: Could not validate file: {e}")
return False return False
def print_statistics(self) -> None: def print_statistics(self, maintainer_data: Dict) -> None:
"""Print generation statistics.""" """Print generation statistics."""
try: stats = maintainer_data["stats"]
with open(self.output_file, 'r') as f:
content = f.read()
hm_count = content.count('# home-manager') print(f"✅ Generated {self.output_file}")
nixpkgs_count = content.count('# nixpkgs') print("📊 Statistics:")
total_entries = content.count(' = {') print(f" - Total files with maintainers: {stats['totalFiles']}")
print(f" - Total unique maintainers: {stats['totalMaintainers']}")
print(f"✅ Generated {self.output_file}") print(f" - Home Manager maintainers: {stats['hmMaintainers']}")
print("📊 Statistics:") print(f" - Nixpkgs maintainers: {stats['nixpkgsMaintainers']}")
print(f" - Home Manager maintainers: {hm_count}") print()
print(f" - Nixpkgs maintainers: {nixpkgs_count}") print("🎉 Generation completed successfully using meta.maintainers!")
print(f" - Total entries: {total_entries}")
print()
except Exception as e:
print(f"Could not generate statistics: {e}")
def main(): def main():
parser = argparse.ArgumentParser(description="Generate Home Manager all-maintainers.nix") parser = argparse.ArgumentParser(
description="Generate Home Manager all-maintainers.nix using meta.maintainers"
)
parser.add_argument( parser.add_argument(
'--root', '--root',
type=Path, type=Path,
@ -324,11 +201,11 @@ def main():
print("Please specify --root or run from Home Manager directory") print("Please specify --root or run from Home Manager directory")
sys.exit(1) sys.exit(1)
generator = MaintainerGenerator(hm_root) generator = MetaMaintainerGenerator(hm_root)
if args.output: if args.output:
generator.output_file = args.output generator.output_file = args.output
print("🔍 Analyzing Home Manager modules for maintainer references...") print("🚀 Generating maintainers using meta.maintainers approach...")
try: try:
generator.generate_maintainers_file() generator.generate_maintainers_file()