feat(version): show installed pkgmgr version when no repo is selected
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / codesniffer-shellcheck (push) Has been cancelled
Mark stable commit / codesniffer-ruff (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / codesniffer-shellcheck (push) Has been cancelled
Mark stable commit / codesniffer-ruff (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
- Add installed version detection for Python environments and Nix profiles - Display pkgmgr’s own installed version when run outside a repository - Improve version command output to include installed vs source versions - Prefer editable venv setup as default in Makefile setup target https://chatgpt.com/share/693e9f02-9b34-800f-8eeb-c7c776b3faa7
This commit is contained in:
@@ -1,21 +1,3 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Helpers to extract version information from various packaging files.
|
||||
|
||||
All functions take a repository directory and return either a version
|
||||
string or None if the corresponding file or version field is missing.
|
||||
|
||||
Supported sources:
|
||||
- pyproject.toml (PEP 621, [project].version)
|
||||
- flake.nix (version = "X.Y.Z";)
|
||||
- PKGBUILD (pkgver / pkgrel)
|
||||
- debian/changelog (first entry line: package (version) ...)
|
||||
- RPM spec file (package-manager.spec: Version / Release)
|
||||
- Ansible Galaxy (galaxy.yml or meta/main.yml)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
@@ -30,46 +12,61 @@ def read_pyproject_version(repo_dir: str) -> Optional[str]:
|
||||
Read the version from pyproject.toml in repo_dir, if present.
|
||||
|
||||
Expects a PEP 621-style [project] table with a 'version' field.
|
||||
Returns the version string or None.
|
||||
"""
|
||||
path = os.path.join(repo_dir, "pyproject.toml")
|
||||
if not os.path.exists(path):
|
||||
if not os.path.isfile(path):
|
||||
return None
|
||||
|
||||
try:
|
||||
try:
|
||||
import tomllib # Python 3.11+
|
||||
except ModuleNotFoundError: # pragma: no cover
|
||||
tomllib = None
|
||||
|
||||
if tomllib is None:
|
||||
return None
|
||||
import tomllib # Python 3.11+
|
||||
except Exception:
|
||||
import tomli as tomllib # type: ignore
|
||||
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
data = tomllib.load(f)
|
||||
|
||||
project = data.get("project", {})
|
||||
if isinstance(project, dict):
|
||||
version = project.get("version")
|
||||
if isinstance(version, str):
|
||||
return version.strip() or None
|
||||
project = data.get("project") or {}
|
||||
version = project.get("version")
|
||||
return str(version).strip() if version else None
|
||||
except Exception:
|
||||
# Intentionally swallow errors and fall back to None.
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
def read_pyproject_project_name(repo_dir: str) -> Optional[str]:
|
||||
"""
|
||||
Read distribution name from pyproject.toml ([project].name).
|
||||
|
||||
This is required to correctly resolve installed Python package
|
||||
versions via importlib.metadata.
|
||||
"""
|
||||
path = os.path.join(repo_dir, "pyproject.toml")
|
||||
if not os.path.isfile(path):
|
||||
return None
|
||||
|
||||
try:
|
||||
import tomllib # Python 3.11+
|
||||
except Exception:
|
||||
import tomli as tomllib # type: ignore
|
||||
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
data = tomllib.load(f)
|
||||
project = data.get("project") or {}
|
||||
name = project.get("name")
|
||||
return str(name).strip() if name else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def read_flake_version(repo_dir: str) -> Optional[str]:
|
||||
"""
|
||||
Read the version from flake.nix in repo_dir, if present.
|
||||
|
||||
Looks for a line like:
|
||||
version = "1.2.3";
|
||||
and returns the string inside the quotes.
|
||||
Looks for:
|
||||
version = "X.Y.Z";
|
||||
"""
|
||||
path = os.path.join(repo_dir, "flake.nix")
|
||||
if not os.path.exists(path):
|
||||
if not os.path.isfile(path):
|
||||
return None
|
||||
|
||||
try:
|
||||
@@ -81,22 +78,21 @@ def read_flake_version(repo_dir: str) -> Optional[str]:
|
||||
match = re.search(r'version\s*=\s*"([^"]+)"', text)
|
||||
if not match:
|
||||
return None
|
||||
version = match.group(1).strip()
|
||||
return version or None
|
||||
|
||||
return match.group(1).strip() or None
|
||||
|
||||
|
||||
def read_pkgbuild_version(repo_dir: str) -> Optional[str]:
|
||||
"""
|
||||
Read the version from PKGBUILD in repo_dir, if present.
|
||||
Read the version from PKGBUILD in repo_dir.
|
||||
|
||||
Expects:
|
||||
pkgver=1.2.3
|
||||
pkgrel=1
|
||||
|
||||
Returns either "1.2.3-1" (if both are present) or just "1.2.3".
|
||||
Combines pkgver and pkgrel if both exist:
|
||||
pkgver=1.2.3
|
||||
pkgrel=1
|
||||
-> 1.2.3-1
|
||||
"""
|
||||
path = os.path.join(repo_dir, "PKGBUILD")
|
||||
if not os.path.exists(path):
|
||||
if not os.path.isfile(path):
|
||||
return None
|
||||
|
||||
try:
|
||||
@@ -121,15 +117,13 @@ def read_pkgbuild_version(repo_dir: str) -> Optional[str]:
|
||||
|
||||
def read_debian_changelog_version(repo_dir: str) -> Optional[str]:
|
||||
"""
|
||||
Read the latest Debian version from debian/changelog in repo_dir, if present.
|
||||
Read the latest version from debian/changelog.
|
||||
|
||||
The first non-empty line typically looks like:
|
||||
package-name (1.2.3-1) unstable; urgency=medium
|
||||
|
||||
We extract the text inside the first parentheses.
|
||||
Expected format:
|
||||
package (1.2.3-1) unstable; urgency=medium
|
||||
"""
|
||||
path = os.path.join(repo_dir, "debian", "changelog")
|
||||
if not os.path.exists(path):
|
||||
if not os.path.isfile(path):
|
||||
return None
|
||||
|
||||
try:
|
||||
@@ -140,8 +134,7 @@ def read_debian_changelog_version(repo_dir: str) -> Optional[str]:
|
||||
continue
|
||||
match = re.search(r"\(([^)]+)\)", line)
|
||||
if match:
|
||||
version = match.group(1).strip()
|
||||
return version or None
|
||||
return match.group(1).strip() or None
|
||||
break
|
||||
except Exception:
|
||||
return None
|
||||
@@ -151,81 +144,72 @@ def read_debian_changelog_version(repo_dir: str) -> Optional[str]:
|
||||
|
||||
def read_spec_version(repo_dir: str) -> Optional[str]:
|
||||
"""
|
||||
Read the version from a RPM spec file.
|
||||
Read the version from an RPM spec file.
|
||||
|
||||
For now, we assume a fixed file name 'package-manager.spec'
|
||||
in repo_dir with lines like:
|
||||
|
||||
Version: 1.2.3
|
||||
Release: 1%{?dist}
|
||||
|
||||
Returns either "1.2.3-1" (if Release is present) or "1.2.3".
|
||||
Any RPM macro suffix like '%{?dist}' is stripped from the release.
|
||||
Combines:
|
||||
Version: 1.2.3
|
||||
Release: 1%{?dist}
|
||||
-> 1.2.3-1
|
||||
"""
|
||||
path = os.path.join(repo_dir, "package-manager.spec")
|
||||
if not os.path.exists(path):
|
||||
return None
|
||||
for fn in os.listdir(repo_dir):
|
||||
if not fn.endswith(".spec"):
|
||||
continue
|
||||
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
text = f.read()
|
||||
except Exception:
|
||||
return None
|
||||
path = os.path.join(repo_dir, fn)
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
text = f.read()
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
ver_match = re.search(r"^Version:\s*(.+)$", text, re.MULTILINE)
|
||||
if not ver_match:
|
||||
return None
|
||||
version = ver_match.group(1).strip()
|
||||
ver_match = re.search(r"^Version:\s*(.+)$", text, re.MULTILINE)
|
||||
if not ver_match:
|
||||
return None
|
||||
version = ver_match.group(1).strip()
|
||||
|
||||
rel_match = re.search(r"^Release:\s*(.+)$", text, re.MULTILINE)
|
||||
if rel_match:
|
||||
release_raw = rel_match.group(1).strip()
|
||||
# Strip common RPM macro suffix like %... (e.g. 1%{?dist})
|
||||
release = release_raw.split("%", 1)[0].strip()
|
||||
# Also strip anything after first whitespace, just in case
|
||||
release = release.split(" ", 1)[0].strip()
|
||||
if release:
|
||||
return f"{version}-{release}"
|
||||
rel_match = re.search(r"^Release:\s*(.+)$", text, re.MULTILINE)
|
||||
if rel_match:
|
||||
release_raw = rel_match.group(1).strip()
|
||||
release = release_raw.split("%", 1)[0].split(" ", 1)[0].strip()
|
||||
if release:
|
||||
return f"{version}-{release}"
|
||||
|
||||
return version or None
|
||||
return version or None
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def read_ansible_galaxy_version(repo_dir: str) -> Optional[str]:
|
||||
"""
|
||||
Read the version from Ansible Galaxy metadata, if present.
|
||||
Read the version from Ansible Galaxy metadata.
|
||||
|
||||
Supported locations:
|
||||
- galaxy.yml (preferred for modern roles/collections)
|
||||
- meta/main.yml (legacy style roles; uses galaxy_info.version or version)
|
||||
Supported:
|
||||
- galaxy.yml
|
||||
- meta/main.yml (galaxy_info.version or version)
|
||||
"""
|
||||
# 1) galaxy.yml in repo root
|
||||
galaxy_path = os.path.join(repo_dir, "galaxy.yml")
|
||||
if os.path.exists(galaxy_path):
|
||||
galaxy_yml = os.path.join(repo_dir, "galaxy.yml")
|
||||
if os.path.isfile(galaxy_yml):
|
||||
try:
|
||||
with open(galaxy_path, "r", encoding="utf-8") as f:
|
||||
with open(galaxy_yml, "r", encoding="utf-8") as f:
|
||||
data = yaml.safe_load(f) or {}
|
||||
version = data.get("version")
|
||||
if isinstance(version, str) and version.strip():
|
||||
return version.strip()
|
||||
except Exception:
|
||||
# Ignore parse errors and fall through to meta/main.yml
|
||||
pass
|
||||
|
||||
# 2) meta/main.yml (classic Ansible role)
|
||||
meta_path = os.path.join(repo_dir, "meta", "main.yml")
|
||||
if os.path.exists(meta_path):
|
||||
meta_yml = os.path.join(repo_dir, "meta", "main.yml")
|
||||
if os.path.isfile(meta_yml):
|
||||
try:
|
||||
with open(meta_path, "r", encoding="utf-8") as f:
|
||||
with open(meta_yml, "r", encoding="utf-8") as f:
|
||||
data = yaml.safe_load(f) or {}
|
||||
|
||||
# Preferred: galaxy_info.version
|
||||
galaxy_info = data.get("galaxy_info") or {}
|
||||
if isinstance(galaxy_info, dict):
|
||||
version = galaxy_info.get("version")
|
||||
if isinstance(version, str) and version.strip():
|
||||
return version.strip()
|
||||
|
||||
# Fallback: top-level 'version'
|
||||
version = data.get("version")
|
||||
if isinstance(version, str) and version.strip():
|
||||
return version.strip()
|
||||
|
||||
Reference in New Issue
Block a user