Adding New Plugins
The plugin architecture makes it easy to add support for new version sources and spec managers.
Adding a Spec Manager
To add a new spec manager (e.g., for a new language ecosystem):
Create the spec manager file
Create
src/dist_git_manager/spec_managers/new_manager.pyInherit from SpecManager base class
from pathlib import Path from typing import Optional import subprocess from dist_git_manager.spec_managers.base import SpecManager from dist_git_manager.exceptions import SpecManagerError from dist_git_manager.logger import get_logger class NewSpecManager(SpecManager): """Spec manager for XYZ packages using xyz2rpm.""" def __init__(self, dry_run: bool = False): super().__init__(dry_run) self.logger = get_logger() def update_spec( self, repo_path: Path, branch: str, package_name: str, version: str, dry_run: bool, ) -> None: """Generate spec file using xyz2rpm.""" spec_path = self.find_spec_file(repo_path, package_name) # Call the tool cmd = ["xyz2rpm", package_name, "--version", version] if dry_run: self.logger.info(f"[DRY-RUN] Would run: {' '.join(cmd)}") return try: result = subprocess.run( cmd, cwd=repo_path, capture_output=True, text=True, check=True, ) self.logger.info(f"Generated spec for {package_name} {version}") except subprocess.CalledProcessError as e: raise SpecManagerError(f"xyz2rpm failed: {e.stderr}") def parse_version(self, spec_path: Path) -> Optional[str]: """Extract version from spec file using rpmspec.""" return self._parse_version_rpmspec(spec_path) def find_spec_file( self, repo_path: Path, package_name: str ) -> Optional[Path]: """Find spec file in repository.""" # Standard locations candidates = [ repo_path / f"{package_name}.spec", repo_path / f"xyz-{package_name}.spec", # Language prefix repo_path / "SPECS" / f"{package_name}.spec", ] for path in candidates: if path.exists(): return path return None def ensure_prerequisites( self, repo_path: Path, project: dict ) -> None: """Ensure necessary files exist (if needed).""" # Some spec managers need config files, others don't pass
Register in DistGitManager
Edit
src/dist_git_manager/core/manager.pyand add to_get_spec_manager():from dist_git_manager.spec_managers.new_manager import NewSpecManager def _get_spec_manager(self, project: Dict[str, Any]) -> SpecManager: # ... existing code ... elif manager_type == "xyz2rpm": return NewSpecManager(dry_run=self.dry_run) else: raise ConfigError(f"Unknown spec manager type: {manager_type}")
Add to CLI choices
Edit
src/dist_git_manager/cli.pyand add to spec manager choices:cli_group.add_argument( "--spec-manager", choices=["packit", "rust2rpm", "pyp2spec", "xyz2rpm"], # Add here help="Spec manager type (required for CLI-only mode)", )
Update list-plugins command
Edit
src/dist_git_manager/cli.pyinlist_plugins():def list_plugins() -> None: print("Available spec managers:") print(" - packit : Packit (updates existing specs)") print(" - rust2rpm : rust2rpm (generates specs from crates.io)") print(" - pyp2spec : pyp2spec (generates specs from PyPI)") print(" - xyz2rpm : xyz2rpm (generates specs from XYZ registry)")
Add tests
Create
tests/unit/test_new_manager.pyfollowing the pattern of existing spec manager tests.
Adding a Version Source
To add a new version source (e.g., for a new package registry):
Create the version source file
Create
src/dist_git_manager/version_sources/new_source.pyInherit from VersionSource base class
from typing import List, Optional import requests from dist_git_manager.version_sources.base import VersionSource from dist_git_manager.exceptions import VersionSourceError from dist_git_manager.logger import get_logger class NewVersionSource(VersionSource): """Fetch versions from XYZ package registry.""" def __init__(self, api_url: str = "https://api.xyz-registry.org"): super().__init__() self.logger = get_logger() self.api_url = api_url def get_latest_version( self, identifier: str, release_type: str = "semver" ) -> Optional[str]: """Get latest version for a package.""" try: versions = self._fetch_all_versions(identifier) except Exception as e: raise VersionSourceError(f"Failed to fetch versions: {e}") if not versions: return None # Filter and sort based on release_type if release_type == "semver": # Return latest stable semantic version return self._get_latest_semver(versions) else: # Return most recent version return versions[0] if versions else None def _fetch_all_versions(self, package_name: str) -> List[str]: """Fetch all versions from API.""" url = f"{self.api_url}/packages/{package_name}/versions" try: response = requests.get(url, timeout=30) response.raise_for_status() data = response.json() # Extract versions from response versions = data.get("versions", []) return versions except requests.RequestException as e: raise VersionSourceError(f"API request failed: {e}")
Register in DistGitManager
Edit
src/dist_git_manager/core/manager.pyand add to_get_version_source():from dist_git_manager.version_sources.new_source import NewVersionSource def _get_version_source(self, project: Dict[str, Any]) -> VersionSource: # ... existing code ... elif source_type == "xyz_registry": return NewVersionSource() else: raise ConfigError(f"Unknown version source type: {source_type}")
Add to CLI choices and documentation (same pattern as spec manager)
Add tests (same pattern as spec manager)
Fedora Spec Generators
Here are the additional Fedora spec generators that could be added:
R Packages (r2spec)
Tool: https://pagure.io/r2spec
Version Source: CRAN (Comprehensive R Archive Network)
API: https://cran.r-project.org/web/packages/{package}/index.html
Implementation:
# Version Source
class CRANVersionSource(VersionSource):
def get_latest_version(self, identifier: str, release_type: str) -> str:
url = f"https://cran.r-project.org/web/packages/{identifier}/index.html"
# Parse HTML to extract version
# Or use: https://crandb.r-pkg.org/{identifier}
# Spec Manager
class R2SpecManager(SpecManager):
def update_spec(self, repo_path, branch, package_name, version, dry_run):
cmd = ["r2spec", "update", package_name, version]
# Run r2spec command
Go Packages (go2rpm)
Tool: https://gitlab.com/fedora/sigs/go/go2rpm
Version Source: GitHub releases or Go package proxy
API: https://proxy.golang.org/{module}/@v/list
Implementation:
# Version Source
class GoProxyVersionSource(VersionSource):
def get_latest_version(self, identifier: str, release_type: str) -> str:
# identifier is module path like "github.com/user/repo"
url = f"https://proxy.golang.org/{identifier}/@v/list"
# Fetch and parse version list
# Spec Manager
class Go2RpmManager(SpecManager):
def update_spec(self, repo_path, branch, package_name, version, dry_run):
cmd = ["go2rpm", "convert", "--version", version, package_name]
# Run go2rpm command
Octave Packages (oct2spec)
Tool: https://pagure.io/oct2spec
Version Source: Octave Forge
API: https://octave.sourceforge.io/packages.php
Implementation:
# Version Source
class OctaveForgeVersionSource(VersionSource):
def get_latest_version(self, identifier: str, release_type: str) -> str:
url = f"https://octave.sourceforge.io/{identifier}/index.html"
# Parse package page for version
# Spec Manager
class Oct2SpecManager(SpecManager):
def update_spec(self, repo_path, branch, package_name, version, dry_run):
cmd = ["oct2spec", package_name, version]
# Run oct2spec command
Ruby Gems (gem2rpm)
Tool: https://github.com/fedora-ruby/gem2rpm
Version Source: RubyGems.org
API: https://rubygems.org/api/v1/versions/{gem}.json
Implementation:
# Version Source
class RubyGemsVersionSource(VersionSource):
def get_latest_version(self, identifier: str, release_type: str) -> str:
url = f"https://rubygems.org/api/v1/versions/{identifier}.json"
response = requests.get(url)
versions = response.json()
# Find latest stable version
# Spec Manager
class Gem2RpmManager(SpecManager):
def update_spec(self, repo_path, branch, package_name, version, dry_run):
cmd = ["gem2rpm", f"{package_name}-{version}.gem",
"--output", f"{package_name}.spec"]
# Run gem2rpm command
Quick Start: Adding R Package Support
Would you like me to implement R package support (CRAN + r2spec) as a working example?
The steps would be:
Create
src/dist_git_manager/version_sources/cran.pyCreate
src/dist_git_manager/spec_managers/r2spec.pyRegister both in
core/manager.pyAdd to CLI choices
Create tests
Update documentation
This would demonstrate the complete process and make it easy to add the others following the same pattern.
Testing New Plugins
When developing a new plugin:
Unit tests: Mock all external dependencies (HTTP, subprocess)
Integration tests: Test with real git repos in tempdir
Manual testing: Use
--dry-runfirst
Example test structure:
# tests/unit/test_new_source.py
import responses
import pytest
from dist_git_manager.version_sources.new_source import NewVersionSource
@responses.activate
def test_fetch_version():
responses.add(
responses.GET,
"https://api.xyz-registry.org/packages/test/versions",
json={"versions": ["1.0.0", "2.0.0"]},
status=200,
)
source = NewVersionSource()
version = source.get_latest_version("test", "semver")
assert version == "2.0.0"
See Also
Architecture - Understanding the plugin architecture
Testing - Testing guidelines
Existing plugin implementations in
src/dist_git_manager/version_sources/andsrc/dist_git_manager/spec_managers/