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:
parent
b46c693797
commit
d03fa2d84c
2 changed files with 59 additions and 180 deletions
2
.github/workflows/update-maintainers.yml
vendored
2
.github/workflows/update-maintainers.yml
vendored
|
|
@ -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: |
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue