summaryrefslogtreecommitdiff
path: root/src/sailfish_devel_mcp/config.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/sailfish_devel_mcp/config.py')
-rw-r--r--src/sailfish_devel_mcp/config.py178
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
+ ),
+ )