188 lines
6.5 KiB
Python
Executable file
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()
|