feat: dist_tag boolean for distro-aware release strings
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/build-fedora42 Pipeline was successful
ci/woodpecker/pr/build-fedora44 Pipeline was successful
ci/woodpecker/pr/build-fedora43 Pipeline was successful
ci/woodpecker/pr/build-almalinux8 Pipeline was successful
ci/woodpecker/pr/build-almalinux9 Pipeline was successful

Adds a per-package dist_tag: true/false metadata flag (default false).
When enabled the build tool appends the RPM dist tag to the release
at build time so each distro produces a unique version in the Gitea
package registry:

  release: 1  +  almalinux/el9  ->  PACKAGE_RELEASE=1.el9
  release: 1  +  fedora/43      ->  PACKAGE_RELEASE=1.fc43

This ensures Gitea package existence checks don't confuse packages built
for one distro with those built for another (the original bug).

Changes:
- Add effective_release() and get_rpm_dist_tag() helpers
- Revert the broken files-endpoint check from the previous commit;
  dist disambiguation is now handled purely via the release string
- discover_packages and build_single both compute the effective release
  before constructing PackageInfo
- check_package_exists simplified back to a single version-level check
- dist_tag added to METADATA_SCHEMA and PackageMetadata dataclass
- All 69 metadata.yaml files updated with dist_tag: true
- Exclude jellyfin from dist_tags
This commit is contained in:
2026-05-17 11:07:49 +10:00
parent 651a38392e
commit f487365f96
73 changed files with 421 additions and 352 deletions
+43 -54
View File
@@ -113,6 +113,11 @@ METADATA_SCHEMA = {
'required': False,
'empty': False
},
'dist_tag': {
'type': 'boolean',
'required': False,
'default': False
},
'builds': {
'type': 'list',
'required': True,
@@ -178,6 +183,7 @@ class PackageMetadata:
maintainer: str = ""
homepage: str = ""
license: str = ""
dist_tag: bool = False
builds: List[Build] = None
def __post_init__(self):
@@ -534,39 +540,48 @@ def normalize_version(version: str) -> str:
return ''.join(normalized_parts)
def get_rpm_dist_suffix(distro: str) -> str:
def get_rpm_dist_tag(distro: str) -> str:
"""
Map a distro path to the RPM dist tag suffix used in package filenames.
Map a distro path to its RPM dist tag string.
Examples:
'almalinux/el9' -> '.el9'
'fedora/43' -> '.fc43'
'almalinux/el9' -> 'el9'
'fedora/43' -> 'fc43'
"""
if distro.startswith('almalinux/'):
return '.' + distro.split('/', 1)[1]
return distro.split('/', 1)[1]
if distro.startswith('fedora/'):
return '.fc' + distro.split('/', 1)[1]
return 'fc' + distro.split('/', 1)[1]
return ''
def check_package_exists(package_name: str, version: str, release: str, distro: str = '') -> bool:
def effective_release(base_release: str, distro: str, use_dist_tag: bool) -> str:
"""Return the release string to use for building and registry checks.
When use_dist_tag is True the dist tag is appended so different distros
produce distinct entries in the package registry (e.g. '1.el9', '1.fc43').
"""
if not use_dist_tag:
return base_release
tag = get_rpm_dist_tag(distro)
return f"{base_release}.{tag}" if tag else base_release
def check_package_exists(package_name: str, version: str, release: str) -> bool:
"""
Check if a package version exists in the Gitea package registry.
When distro is provided the check is distro-aware: it lists the files
attached to the package version and returns True only when a file whose
name contains the expected RPM dist suffix (e.g. '.el9', '.fc43') is
found. This prevents a package built for almalinux/el9 from being
mistaken for a fedora/43 build (and vice-versa).
Distro disambiguation is handled by the caller via the release string:
when dist_tag is enabled the caller appends the dist tag to release
(e.g. '1.el9', '1.fc43') so each distro maps to a unique version entry.
Args:
package_name: Name of the package
version: Version string
release: Release number
distro: Target distro path (e.g. 'almalinux/el9', 'fedora/43')
release: Effective release (may include dist tag suffix)
Returns:
True if package exists for the given distro, False otherwise
True if package exists, False otherwise
"""
logger = logging.getLogger(__name__)
@@ -595,40 +610,9 @@ def check_package_exists(package_name: str, version: str, release: str, distro:
if response.status_code == 200:
package_info = response.json()
if not package_info.get('id'):
logger.debug(f"Package {package_name}:{full_version} not found")
return False
# No distro filter treat any file as a match
if not distro:
logger.debug(f"Package {package_name}:{full_version} exists")
return True
# Distro-aware check: inspect individual file names for the dist suffix
dist_suffix = get_rpm_dist_suffix(distro)
if not dist_suffix:
logger.debug(f"Package {package_name}:{full_version} exists (no dist suffix for {distro})")
return True
files_url = f"{base_url}/api/v1/packages/{owner}/{package_type}/{package_name}/{full_version}/files"
logger.debug(f"Checking distro-specific files: {files_url}")
files_response = _gitea_session.get(files_url, timeout=10)
if files_response.status_code == 200:
files = files_response.json()
for file_info in files:
if dist_suffix in file_info.get('name', ''):
logger.debug(f"Package {package_name}:{full_version} exists for {distro}")
return True
logger.debug(f"Package {package_name}:{full_version} not found for {distro} (no {dist_suffix} file)")
return False
else:
# If files endpoint fails fall back to treating version existence as a match
logger.warning(
f"Could not list files for {package_name}:{full_version}: "
f"{files_response.status_code} assuming package exists"
)
return True
exists = bool(package_info.get('id'))
logger.debug(f"Package {package_name}:{full_version} {'exists' if exists else 'not found'}")
return exists
elif response.status_code == 404:
logger.debug(f"Package {package_name}:{full_version} not found (404)")
@@ -1324,6 +1308,7 @@ class Builder:
package_name = metadata.get('name', package_dir.name)
build_configs = metadata.get('builds', [])
use_dist_tag = metadata.get('dist_tag', False)
if not build_configs:
self.logger.warning(f"No builds in metadata.yaml for {package_name}")
@@ -1342,7 +1327,8 @@ class Builder:
if repositories and base_image and version and release:
# Use the first repository as the distro identifier
build_distro = repositories[0] if repositories else 'unknown'
packages.append(PackageInfo(package_name, version, str(release), package_dir, build_distro, base_image))
rel = effective_release(str(release), build_distro, use_dist_tag)
packages.append(PackageInfo(package_name, version, rel, package_dir, build_distro, base_image))
else:
# Build for specific distro
for build_config in build_configs:
@@ -1354,7 +1340,8 @@ class Builder:
# Check if the target distro matches any repository
if distro in repositories and base_image and version and release:
packages.append(PackageInfo(package_name, version, str(release), package_dir, distro, base_image))
rel = effective_release(str(release), distro, use_dist_tag)
packages.append(PackageInfo(package_name, version, rel, package_dir, distro, base_image))
break
else:
# If no matching distro found, log a warning
@@ -1407,6 +1394,8 @@ class Builder:
with open(metadata_file, 'r') as f:
metadata = yaml.safe_load(f)
use_dist_tag = metadata.get('dist_tag', False)
# Find base image and validate version/release for the specified distro
build_configs = metadata.get('builds', [])
base_image = None
@@ -1450,7 +1439,8 @@ class Builder:
self.logger.error(f"Error reading metadata.yaml for {package}: {e}")
return False
package_info = PackageInfo(package, version, release, package_dir, distro, base_image)
rel = effective_release(release, distro, use_dist_tag)
package_info = PackageInfo(package, version, rel, package_dir, distro, base_image)
return self._build_package(package_info, dry_run, force, native, buildah)
def build_all(self, dry_run: bool = False, force: bool = False, parallel: int = 4, distro: str = 'el/9', native: bool = False, buildah: bool = False) -> bool:
@@ -1532,8 +1522,7 @@ class Builder:
if check_package_exists(
package_info.name,
package_info.version,
package_info.release,
package_info.distro
package_info.release
):
self.logger.info(
f"Skipping {package_info} (already exists in repository)"