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])