gentoo-overlay/ebuild-repo-tools.py
2025-09-27 13:07:18 +03:00

188 lines
6.5 KiB
Python
Executable file

#!/usr/bin/env python3
import os
import re
import subprocess
import sys
from pathlib import Path
from portage.versions import vercmp
def get_ebuilds(repo_path):
"""Find all ebuild files in the repository."""
ebuild_files = list(Path(repo_path).glob('*/*/*.ebuild'))
packages = {}
for ebuild in ebuild_files:
category = ebuild.parent.parent.name
package_name = ebuild.parent.name
version = ebuild.name.replace(f"{package_name}-", "").replace(".ebuild", "")
if (category, package_name) not in packages:
packages[(category, package_name)] = []
packages[(category, package_name)].append((version, ebuild))
return packages
def get_upstream_info(category, package_name, repo_path):
"""Get upstream location and available versions from equery meta output."""
try:
result = subprocess.run(
['equery', 'meta', f"{category}/{package_name}"],
capture_output=True,
text=True,
check=True
)
location = None
versions = []
repo_name = None
for line in result.stdout.split('\n'):
if line.startswith('Location:'):
location = line.split(':', 1)[1].strip()
elif line.startswith('Keywords:'):
# Format: "Keywords: 3.0.67-r1:0:"
parts = line.split()
if len(parts) >= 2:
version_part = parts[1] # "3.0.67-r1:0:"
version = version_part.split(':')[0]
versions.append(version)
# Extract repository name from location path
if location:
# Location format: /var/db/repos/gentoo/sys-apps/portage
# or /var/db/repos/local/dev-util/intel_clc
path_parts = location.split('/')
if len(path_parts) >= 5 and path_parts[3] == 'repos':
repo_name = path_parts[4] # gentoo, local, etc.
# Check if this package exists in the main Gentoo repository
# or if it's only in our overlay
repo_path_abs = Path(repo_path).resolve()
location_path = Path(location) if location else None
# Package is only in our overlay if the location is within our repo
is_only_in_overlay = (location_path and
(repo_path_abs in location_path.parents or
location_path.parent.parent == repo_path_abs))
return location, versions, repo_name, is_only_in_overlay
except subprocess.CalledProcessError:
return None, [], None, False # Package might not exist upstream
except FileNotFoundError:
print("Error: equery command not found. Please install app-portage/gentoolkit.", file=sys.stderr)
sys.exit(1)
def is_version_obsolete(ebuild_version, upstream_versions, repo_path, upstream_location, is_only_in_overlay):
"""Check if ebuild version is obsolete compared to upstream versions."""
# If package only exists in our overlay, it can't be obsolete
if is_only_in_overlay:
return False
if not upstream_versions or not upstream_location:
return False
# Check if upstream location is outside our repo
upstream_path = Path(upstream_location)
repo_path_abs = Path(repo_path).resolve()
# If upstream is in our repo, skip comparison
try:
if repo_path_abs in upstream_path.parents or upstream_path == repo_path_abs:
return False
except:
pass # If path comparison fails, continue with version check
# Find the highest upstream version
if not upstream_versions:
return False
highest_upstream = upstream_versions[0]
for version in upstream_versions[1:]:
try:
if vercmp(version, highest_upstream) > 0:
highest_upstream = version
except:
continue # Skip versions that can't be compared
# Compare our version with the highest upstream
try:
return vercmp(ebuild_version, highest_upstream) < 0
except:
return False
def list_obsolete(repo_path=Path('.')):
"""Main function to list obsolete packages."""
repo_path_abs = Path(repo_path).resolve()
packages = get_ebuilds(repo_path_abs)
obsolete_packages = {}
for (category, package_name), ebuilds in packages.items():
upstream_location, upstream_versions, repo_name, is_only_in_overlay = get_upstream_info(
category, package_name, repo_path_abs
)
# Skip packages that only exist in our overlay
if is_only_in_overlay:
continue
# Skip if no upstream info or no versions found
if not upstream_location or not upstream_versions:
continue
obsolete_ebuilds = []
all_obsolete = True
for version, ebuild_path in ebuilds:
if is_version_obsolete(version, upstream_versions, repo_path_abs,
upstream_location, is_only_in_overlay):
obsolete_ebuilds.append(ebuild_path)
else:
all_obsolete = False
if obsolete_ebuilds:
if all_obsolete:
obsolete_packages[(category, package_name)] = ebuilds[0][1].parent
else:
obsolete_packages[(category, package_name)] = obsolete_ebuilds
return obsolete_packages
def main():
if len(sys.argv) < 3 or sys.argv[1] != 'list' or sys.argv[2] != 'obsolete':
print("Usage: ebuild-repo-tools.py list obsolete [REPO_PATH]")
print(" REPO_PATH: Path to the overlay repository (default: current directory)")
sys.exit(1)
repo_path = Path(sys.argv[3] if len(sys.argv) > 3 else '.')
if not os.path.exists(repo_path):
print(f"Error: Repository path '{repo_path}' does not exist.", file=sys.stderr)
sys.exit(1)
try:
obsolete = list_obsolete(repo_path)
if not obsolete:
print("No obsolete packages found.")
return
repo_path = repo_path.absolute()
for (category, package_name), paths in obsolete.items():
if isinstance(paths, Path): # Directory (all ebuilds obsolete)
print(paths.relative_to(repo_path))
else: # List of ebuild paths
for path in paths:
print(path)
except KeyboardInterrupt:
print("\nOperation cancelled by user.", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == '__main__':
main()