mirror of
https://github.com/kevinveenbirkenbach/cli-gnome-extension-manager.git
synced 2026-08-24 23:24:33 +00:00
Replaces main.sh with the 'cli-gnome-extension-manager' distribution in a src layout, shipping both the cli-gnome-extension-manager and the older goexma command. sync_repository keeps the three states of the shell original apart: clone when the folder is missing, pull when it is a checkout, leave it alone otherwise, so a manually installed extension is never overwritten. build_extension still moves the checkout away before make install, but into a TemporaryDirectory instead of a fixed /tmp/<name>, which removes the collision between parallel runs and cleans up on abort. Every step passes through the exit code of the failing tool, replacing the shell chain of || exit 1; a missing gnome-extensions aborts with exit 127. The e2e suite builds a real git repository, clones it with real git and runs real make install. Adds --extensions-dir, unit, integration and container-based e2e tests, ruff and markdown/mermaid linting, CodeQL and Dependabot, and pins the core metadata to 2.4 so twine accepts the artifacts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
100 lines
2.8 KiB
Python
100 lines
2.8 KiB
Python
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
REQUIRED_BINARIES = ("gnome-extensions",)
|
|
REPOSITORY_BINARIES = ("git", "make")
|
|
EXIT_MISSING_BINARY = 127
|
|
|
|
|
|
class MissingBinaryError(RuntimeError):
|
|
"""A required external command is not installed.
|
|
|
|
Args:
|
|
binary: Name of the missing executable.
|
|
"""
|
|
|
|
def __init__(self, binary):
|
|
super().__init__(f"required command '{binary}' is not installed")
|
|
self.binary = binary
|
|
|
|
|
|
def default_extensions_dir():
|
|
"""Return the per-user GNOME shell extensions directory."""
|
|
return Path.home() / ".local" / "share" / "gnome-shell" / "extensions"
|
|
|
|
|
|
def run(cmd, cwd=None):
|
|
"""Run a command and return its exit code.
|
|
|
|
Args:
|
|
cmd: Command argument list.
|
|
cwd: Working directory for the command.
|
|
"""
|
|
try:
|
|
return subprocess.run(cmd, cwd=cwd, check=False).returncode
|
|
except FileNotFoundError as error:
|
|
raise MissingBinaryError(cmd[0]) from error
|
|
|
|
|
|
def sync_repository(folder, repository_path):
|
|
"""Clone the extension repository, or pull it when already checked out.
|
|
|
|
Args:
|
|
folder: Target folder of the extension.
|
|
repository_path: Git URL or path of the extension repository.
|
|
|
|
Returns:
|
|
Exit code, non-zero when git failed.
|
|
"""
|
|
if not folder.exists():
|
|
print("Cloning repository...")
|
|
return run(["git", "clone", repository_path, str(folder)])
|
|
|
|
if not (folder / ".git").is_dir():
|
|
print("No git repository. Extension will not be updated.")
|
|
return 0
|
|
|
|
print("Pulling changes from git...")
|
|
return run(["git", "pull"], cwd=str(folder))
|
|
|
|
|
|
def build_extension(folder):
|
|
"""Build an extension shipping a Makefile, then drop the build folder.
|
|
|
|
The checkout is moved into a scratch directory before 'make install' runs,
|
|
mirroring the original shell implementation, which installed from /tmp and
|
|
removed the checkout afterwards.
|
|
|
|
Args:
|
|
folder: Folder holding the checked out extension.
|
|
|
|
Returns:
|
|
Exit code, non-zero when the build failed.
|
|
"""
|
|
if not (folder / "Makefile").is_file():
|
|
print("No Makefile found. Skipping compilation...")
|
|
return 0
|
|
|
|
with tempfile.TemporaryDirectory() as build_root:
|
|
build_dir = Path(build_root) / folder.name
|
|
shutil.move(str(folder), str(build_dir))
|
|
|
|
print("Compiling extension...")
|
|
returncode = run(["make", "install"], cwd=str(build_dir))
|
|
if returncode != 0:
|
|
print("Compilation failed.")
|
|
|
|
return returncode
|
|
|
|
|
|
def gnome_extensions(action, extension_name):
|
|
"""Enable or disable an extension through the gnome-extensions tool.
|
|
|
|
Args:
|
|
action: Either 'enable' or 'disable'.
|
|
extension_name: UUID of the GNOME extension.
|
|
"""
|
|
return run(["gnome-extensions", action, extension_name])
|