diff options
| author | Andrew Branson <andrew.branson@jolla.com> | 2026-05-15 12:21:39 +0200 |
|---|---|---|
| committer | Andrew Branson <andrew.branson@jolla.com> | 2026-05-15 12:21:39 +0200 |
| commit | 1f14c5483ee111105f94d66fb3b82208946d914a (patch) | |
| tree | d931715d7a3335324fbcdc51fcef1e0cef590235 /src/sailfish_devel_mcp/config.py | |
Initial Sailfish devel MCP
Diffstat (limited to 'src/sailfish_devel_mcp/config.py')
| -rw-r--r-- | src/sailfish_devel_mcp/config.py | 178 |
1 files changed, 178 insertions, 0 deletions
diff --git a/src/sailfish_devel_mcp/config.py b/src/sailfish_devel_mcp/config.py new file mode 100644 index 0000000..6515918 --- /dev/null +++ b/src/sailfish_devel_mcp/config.py @@ -0,0 +1,178 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +import json +import os +from pathlib import Path +from typing import Any, Mapping + + +DEFAULT_RUNTIME_DIR = "/run/user/100000" +DEFAULT_BUS_ADDRESS = "unix:path=/run/user/100000/dbus/user_bus_socket" +BUNDLED_BUILD_HELPER = ( + Path(__file__).resolve().parent / "vendor" / "build_sailfishos.py" +) + + +@dataclass(frozen=True) +class DeviceConfig: + name: str + ssh_target: str + username: str = "defaultuser" + architecture: str = "" + release: str = "" + user_bus_runtime_dir: str = DEFAULT_RUNTIME_DIR + user_bus_address: str = DEFAULT_BUS_ADDRESS + + def public_dict(self) -> dict[str, object]: + return { + "name": self.name, + "ssh_target": self.ssh_target, + "username": self.username, + "architecture": self.architecture, + "release": self.release, + "user_bus_runtime_dir": self.user_bus_runtime_dir, + "user_bus_address": self.user_bus_address, + } + + +@dataclass(frozen=True) +class PathConfig: + git_root: Path = Path.home() / "git" + ssh_config: Path = Path.home() / ".ssh" / "config" + build_sailfishos: Path = BUNDLED_BUILD_HELPER + local_sdk: Path | None = None + osc_api_alias: str = "" + + def public_dict(self) -> dict[str, str | None]: + return { + "git_root": str(self.git_root), + "ssh_config": str(self.ssh_config), + "build_sailfishos": str(self.build_sailfishos), + "local_sdk": str(self.local_sdk) if self.local_sdk else None, + "osc_api_alias": self.osc_api_alias, + } + + +@dataclass(frozen=True) +class Config: + path: Path | None + default_device: str + devices: Mapping[str, DeviceConfig] = field(default_factory=dict) + paths: PathConfig = field(default_factory=PathConfig) + + def device(self, name: str | None = None) -> DeviceConfig: + key = name or self.default_device + if key in self.devices: + return self.devices[key] + if name and "@" in name: + return DeviceConfig(name=name, ssh_target=name) + raise KeyError(f"unknown Sailfish device: {key}") + + def public_dict(self) -> dict[str, Any]: + return { + "path": str(self.path) if self.path else None, + "default_device": self.default_device, + "devices": { + name: device.public_dict() for name, device in self.devices.items() + }, + "paths": self.paths.public_dict(), + } + + +def default_config_path() -> Path: + if "SAILFISH_MCP_CONFIG" in os.environ: + return Path(os.environ["SAILFISH_MCP_CONFIG"]).expanduser() + return Path.home() / ".config" / "sailfish-devel-mcp" / "config.json" + + +def load_config(path: str | os.PathLike[str] | None = None) -> Config: + config_path = Path(path).expanduser() if path else default_config_path() + raw: dict[str, Any] = {} + + if config_path.exists(): + with config_path.open("r", encoding="utf-8") as handle: + loaded = json.load(handle) + if not isinstance(loaded, dict): + raise ValueError(f"{config_path} must contain a JSON object") + raw = loaded + + devices = _load_devices(raw.get("devices", {})) + if not devices: + default_name = os.environ.get("SAILFISH_MCP_DEFAULT_DEVICE", "device") + devices = { + default_name: DeviceConfig( + name=default_name, + ssh_target=os.environ.get("SAILFISH_MCP_SSH_TARGET", "root@device"), + ) + } + + default_device = str(raw.get("default_device") or next(iter(devices))) + paths = _load_paths(raw.get("paths", {})) + return Config( + path=config_path if config_path.exists() else None, + default_device=default_device, + devices=devices, + paths=paths, + ) + + +def _load_devices(raw_devices: Any) -> dict[str, DeviceConfig]: + if not isinstance(raw_devices, dict): + raise ValueError("devices must be a JSON object") + + devices: dict[str, DeviceConfig] = {} + for name, value in raw_devices.items(): + if isinstance(value, str): + value = {"ssh_target": value} + if not isinstance(value, dict): + raise ValueError(f"device {name!r} must be a string or object") + ssh_target = str(value.get("ssh_target") or name) + devices[str(name)] = DeviceConfig( + name=str(name), + ssh_target=ssh_target, + username=str(value.get("username") or "defaultuser"), + architecture=str(value.get("architecture") or ""), + release=str(value.get("release") or ""), + user_bus_runtime_dir=str( + value.get("user_bus_runtime_dir") or DEFAULT_RUNTIME_DIR + ), + user_bus_address=str(value.get("user_bus_address") or DEFAULT_BUS_ADDRESS), + ) + return devices + + +def _load_paths(raw_paths: Any) -> PathConfig: + if not isinstance(raw_paths, dict): + raise ValueError("paths must be a JSON object") + defaults = PathConfig() + raw_local_sdk = raw_paths.get("local_sdk") or os.environ.get("SAILFISH_MCP_LOCAL_SDK") + return PathConfig( + git_root=Path( + str( + raw_paths.get("git_root") + or os.environ.get("SAILFISH_MCP_GIT_ROOT") + or defaults.git_root + ) + ).expanduser(), + ssh_config=Path( + str( + raw_paths.get("ssh_config") + or os.environ.get("SAILFISH_MCP_SSH_CONFIG") + or defaults.ssh_config + ) + ).expanduser(), + build_sailfishos=Path( + str( + raw_paths.get("build_sailfishos") + or os.environ.get("SAILFISH_MCP_BUILD_HELPER") + or defaults.build_sailfishos + ) + ).expanduser(), + local_sdk=Path(str(raw_local_sdk)).expanduser() if raw_local_sdk else None, + osc_api_alias=str( + raw_paths.get("osc_api_alias") + or os.environ.get("SAILFISH_MCP_OSC_API_ALIAS") + or defaults.osc_api_alias + ), + ) |
