Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 28 additions & 9 deletions hermes_cli/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2187,27 +2187,46 @@ def _setup_matrix():
save_env_value("MATRIX_ENCRYPTION", "true")
print_success("E2EE enabled")

matrix_pkg = "mautrix[encryption]" if want_e2ee else "mautrix"
try:
__import__("mautrix")
except ImportError:
print_info(f"Installing {matrix_pkg}...")
install_specs: tuple[str, ...]
manual_specs: tuple[str, ...]
if want_e2ee:
try:
from tools.lazy_deps import feature_missing, feature_specs

install_specs = tuple(feature_missing("platform.matrix"))
manual_specs = tuple(feature_specs("platform.matrix"))
except Exception:
install_specs = ("mautrix[encryption]",)
manual_specs = install_specs
else:
try:
__import__("mautrix")
install_specs = ()
except ImportError:
install_specs = ("mautrix",)
manual_specs = ("mautrix",)

install_label = "Matrix E2EE dependencies" if want_e2ee else "mautrix"
if install_specs:
print_info(f"Installing {install_label}...")
import subprocess

uv_bin = shutil.which("uv")
if uv_bin:
result = subprocess.run(
[uv_bin, "pip", "install", "--python", sys.executable, matrix_pkg],
[uv_bin, "pip", "install", "--python", sys.executable, *install_specs],
capture_output=True, text=True,
)
else:
result = subprocess.run(
[sys.executable, "-m", "pip", "install", matrix_pkg],
[sys.executable, "-m", "pip", "install", *install_specs],
capture_output=True, text=True,
)
if result.returncode == 0:
print_success(f"{matrix_pkg} installed")
print_success(f"{install_label} installed")
else:
print_warning(f"Install failed — run manually: pip install '{matrix_pkg}'")
quoted_specs = " ".join(f"'{spec}'" for spec in manual_specs)
print_warning(f"Install failed — run manually: pip install {quoted_specs}")
if result.stderr:
print_info(f" Error: {result.stderr.strip().splitlines()[-1]}")

Expand Down
59 changes: 57 additions & 2 deletions tests/hermes_cli/test_setup_matrix_e2ee.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Test that setup.py has shutil available for Matrix E2EE auto-install."""
import ast

import pytest
from types import SimpleNamespace


def _parse_setup_imports():
Expand Down Expand Up @@ -29,3 +28,59 @@ def test_shutil_imported_at_module_level(self):
"This causes a NameError when the Matrix E2EE auto-install "
"tries to call shutil.which('uv')."
)


def test_setup_matrix_e2ee_installs_missing_companion_deps(monkeypatch):
"""E2EE setup must install missing companion deps, not just mautrix itself."""
import hermes_cli.setup as setup_mod
import tools.lazy_deps as lazy_deps

saved_env = {}
prompts = iter([
"https://matrix-client.matrix.org",
"secret-token",
"@bot:matrix.org",
"",
"",
])
install_calls = []

monkeypatch.setattr(setup_mod, "get_env_value", lambda key: saved_env.get(key, ""))
monkeypatch.setattr(setup_mod, "save_env_value", lambda key, value: saved_env.__setitem__(key, value))
monkeypatch.setattr(setup_mod, "prompt", lambda *args, **kwargs: next(prompts))
monkeypatch.setattr(setup_mod, "prompt_yes_no", lambda *args, **kwargs: True)
monkeypatch.setattr(setup_mod, "print_header", lambda *args, **kwargs: None)
monkeypatch.setattr(setup_mod, "print_info", lambda *args, **kwargs: None)
monkeypatch.setattr(setup_mod, "print_success", lambda *args, **kwargs: None)
monkeypatch.setattr(setup_mod, "print_warning", lambda *args, **kwargs: None)
monkeypatch.setattr(setup_mod.shutil, "which", lambda name: "/usr/bin/uv" if name == "uv" else None)
monkeypatch.setattr(lazy_deps, "feature_missing", lambda feature: ("asyncpg==0.31.0",))
monkeypatch.setattr(
lazy_deps,
"feature_specs",
lambda feature: (
"mautrix[encryption]==0.21.0",
"Markdown==3.10.2",
"aiosqlite==0.22.1",
"asyncpg==0.31.0",
"aiohttp-socks==0.11.0",
),
)

def fake_run(args, capture_output=True, text=True):
install_calls.append(args)
return SimpleNamespace(returncode=0, stdout="", stderr="")

monkeypatch.setattr("subprocess.run", fake_run)

setup_mod._setup_matrix()

assert install_calls == [[
"/usr/bin/uv",
"pip",
"install",
"--python",
setup_mod.sys.executable,
"asyncpg==0.31.0",
]]
assert saved_env["MATRIX_ENCRYPTION"] == "true"
Loading