Files
testium/src/testium/interpreter/utils/version.py
francois 7b569df202 flatpak: route gitpython through flatpak-spawn for host git
Inside a Flatpak the host /usr/bin/git is reachable at
/run/host/usr/bin/git but linked against host glibc/zlib, which the
sandbox cannot load (libz-ng.so.2 missing). gitpython resolves git
eagerly on import and crashed the whole validation run.

Install a tiny shell wrapper under /tmp at module load
(``exec flatpak-spawn --host git "$@"``) and point gitpython at it via
GIT_PYTHON_GIT_EXECUTABLE so test_version / test_modifs work in
flatpak mode. No-op outside Flatpak.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 23:16:05 +02:00

183 lines
5.6 KiB
Python

import atexit
import os
import stat
import sys
import tempfile
from importlib import import_module
import interpreter.utils.settings as prefs
import api.testium as tm
# When running inside a Flatpak, the host /usr/bin/git is reachable at
# /run/host/usr/bin/git but linked against host glibc/zlib, which the
# sandbox can't load (``libz-ng.so.2`` not found). gitpython resolves git
# eagerly on import and would crash the whole test run. We install a
# tiny shell wrapper under /tmp that forwards to ``flatpak-spawn --host
# git``, and point gitpython at it via ``GIT_PYTHON_GIT_EXECUTABLE``.
_HOST_GIT_WRAPPER = None
def _setup_flatpak_git():
global _HOST_GIT_WRAPPER
if not os.path.isfile("/.flatpak-info"):
return
if _HOST_GIT_WRAPPER is not None:
return
fd, path = tempfile.mkstemp(prefix="testium-git-host-", suffix=".sh", dir="/tmp")
with os.fdopen(fd, "w") as f:
f.write('#!/bin/sh\nexec flatpak-spawn --host git "$@"\n')
os.chmod(path, stat.S_IRWXU)
_HOST_GIT_WRAPPER = path
atexit.register(_cleanup_flatpak_git)
os.environ["GIT_PYTHON_GIT_EXECUTABLE"] = path
# Silence gitpython's warning if its refresh probe ever still fails;
# the wrapper itself should make the probe succeed.
os.environ.setdefault("GIT_PYTHON_REFRESH", "quiet")
def _cleanup_flatpak_git():
global _HOST_GIT_WRAPPER
if _HOST_GIT_WRAPPER and os.path.isfile(_HOST_GIT_WRAPPER):
try:
os.unlink(_HOST_GIT_WRAPPER)
except OSError:
pass
_HOST_GIT_WRAPPER = None
_setup_flatpak_git()
_cached_versions = {}
def repo_rev(path):
ret = _cached_versions.get(path, None)
if ret:
return ret
git = import_module("git")
repo = git.Repo(path, search_parent_directories=True)
if repo.bare:
ret ="Warning Bare repo: {}, modifications cannot be tracked !".format(path)
else:
ret = getSubmoduleVersion(git, repo)
_cached_versions.update({path: ret})
repo.close()
return ret
def get_version(path :str)-> str:
if prefs.settings.git_supported:
try:
return repo_rev(path)
except:
return "Warning : {} not versioned".format(path)
else:
return "Warning git not supported in your settings, version of {} unknown".format(path)
def get_testium_version():
# Flatpak bundle
if os.path.isfile('/.flatpak-info'):
ver = os.environ.get('TESTIUM_VERSION', '').strip()
return (ver if ver else 'unknown') + " (flatpak release)"
# AppImage
if 'APPIMAGE' in os.environ:
ver = os.environ.get('TESTIUM_VERSION', '').strip()
return (ver if ver else 'unknown') + " (binary release)"
# PyInstaller frozen exe
if getattr(sys, 'frozen', False):
file_path = os.path.join(sys._MEIPASS, "VERSION")
try:
with open(file_path, 'r') as f:
ver = f.read().strip()
return ver + " (binary release)"
except OSError:
return "unknown (binary release)"
# Source checkout: prefer git revision when available
if prefs.settings.git_supported:
try:
git = import_module("git")
return repo_rev(tm.get_main_dir())
except Exception:
# Not a git repo (typical pip install): fall through.
pass
# Pip-installed wheel: use the package metadata baked from VERSION
try:
from importlib.metadata import version as _pkg_version
from importlib.metadata import PackageNotFoundError
try:
return _pkg_version("testium") + " (wheel release)"
except PackageNotFoundError:
pass
except ImportError:
pass
return "unknown"
def get_modifications(path : str)-> str:
if prefs.settings.git_supported:
git = import_module("git")
modifs = ""
try:
repo = git.Repo(path, search_parent_directories=True)
for item in repo.index.diff(None):
modifs = modifs + '"' + item.a_path + '"' + ' (modified)\n'
for item in repo.untracked_files:
modifs = modifs + '"' + item + '"' + ' (untracked)\n'
repo.close()
return modifs
except git.InvalidGitRepositoryError:
return "Warning : {} not versioned".format(path)
else:
return "Warning git not supported in your settings, version of {} unknown".format(path)
def getSubmoduleVersion(git, repo) -> str:
v = ""
for subM in repo.iter_submodules(ignore_self=False):
try:
v = v + getCommitVsTag(subM.module()) + "\n"
except git.InvalidGitRepositoryError:
v = v +"{} not versioned".format(subM.module().git_dir) + "\n"
return v
def getCommitVsTag(repo) -> str:
sha = repo.head.object.hexsha
short_sha = repo.git.rev_parse(sha, short=12)
url = change = ''
# check if a tag or no
t = None
for tag in repo.tags:
# Try excepted added after crash encountered because of strange tag
try:
if tag.commit == repo.head.commit:
t = tag
except:
pass
if repo.is_dirty():
change = '(M)'
try:
url = "".join(repo.remote().urls)
except:
pass
if t:
ret = "tag {}".format(t.name)
else:
branch = ""
if not repo.head.is_detached:
branch = repo.active_branch.name
else:
for h in repo.heads:
if h.commit == repo.head.commit:
branch = "detached from " + h.name
ret = "{}{}, commit {}".format(branch, change, short_sha)
if url:
ret = ret + " from : " + url
repo.close()
return ret