summaryrefslogtreecommitdiff
path: root/src/sailfish_devel_mcp/config.py.template
diff options
context:
space:
mode:
authorAndrew Branson <andrew.branson@jolla.com>2026-08-09 17:37:37 +0200
committerAndrew Branson <andrew.branson@jolla.com>2026-08-09 17:37:37 +0200
commitffb64c45b42dda1edf44a0fbaf42c78f790d6869 (patch)
tree290ee2607c5679f628cd673b3dfb875aefd41f1b /src/sailfish_devel_mcp/config.py.template
parentb9746bfa320cdd1464eb6c91fbadad35c84d2459 (diff)
Keep machine-local MCP config untrackedHEADmaster
Track a generic config.py.template and ignore the operational config.py so machine-specific build host defaults remain local. Document the bootstrap copy and replace personal OBS and Android test fixtures with neutral examples.
Diffstat (limited to 'src/sailfish_devel_mcp/config.py.template')
-rw-r--r--src/sailfish_devel_mcp/config.py.template284
1 files changed, 284 insertions, 0 deletions
diff --git a/src/sailfish_devel_mcp/config.py.template b/src/sailfish_devel_mcp/config.py.template
new file mode 100644
index 0000000..198b09d
--- /dev/null
+++ b/src/sailfish_devel_mcp/config.py.template
@@ -0,0 +1,284 @@
+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"
+)
+DEFAULT_ANDROID_BUILD_HOST = "android-builder"
+DEFAULT_ANDROID_BUILD_SSH_TARGET = "builder@example.invalid"
+DEFAULT_ANDROID_BUILD_PROJECT_DIR = ""
+DEFAULT_ANDROID_BUILD_STATE_DIR = "/tmp/sailfish-devel-mcp/android-builds"
+
+
+@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 AndroidBuildHostConfig:
+ name: str
+ ssh_target: str
+ project_dir: str
+ state_dir: str = DEFAULT_ANDROID_BUILD_STATE_DIR
+
+ def public_dict(self) -> dict[str, object]:
+ return {
+ "name": self.name,
+ "ssh_target": self.ssh_target,
+ "project_dir": self.project_dir,
+ "state_dir": self.state_dir,
+ }
+
+
+def default_android_build_hosts() -> dict[str, AndroidBuildHostConfig]:
+ return {
+ DEFAULT_ANDROID_BUILD_HOST: AndroidBuildHostConfig(
+ name=DEFAULT_ANDROID_BUILD_HOST,
+ ssh_target=DEFAULT_ANDROID_BUILD_SSH_TARGET,
+ project_dir=DEFAULT_ANDROID_BUILD_PROJECT_DIR,
+ state_dir=DEFAULT_ANDROID_BUILD_STATE_DIR,
+ )
+ }
+
+
+@dataclass(frozen=True)
+class PathConfig:
+ git_root: Path = Path.home() / "git"
+ obs_root: Path = Path.home() / "OBS"
+ 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),
+ "obs_root": str(self.obs_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)
+ default_android_build_host: str = DEFAULT_ANDROID_BUILD_HOST
+ android_build_hosts: Mapping[str, AndroidBuildHostConfig] = field(
+ default_factory=default_android_build_hosts
+ )
+
+ 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 android_build_host(self, name: str | None = None) -> AndroidBuildHostConfig:
+ key = name or self.default_android_build_host
+ if key in self.android_build_hosts:
+ return self.android_build_hosts[key]
+ if name and "@" in name:
+ return AndroidBuildHostConfig(
+ name=name,
+ ssh_target=name,
+ project_dir=DEFAULT_ANDROID_BUILD_PROJECT_DIR,
+ )
+ raise KeyError(f"unknown Android build host: {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(),
+ "default_android_build_host": self.default_android_build_host,
+ "android_build_hosts": {
+ name: host.public_dict()
+ for name, host in self.android_build_hosts.items()
+ },
+ }
+
+
+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", {}))
+ android_build_hosts = _load_android_build_hosts(raw.get("android_build_hosts", {}))
+ default_android_build_host = str(
+ raw.get("default_android_build_host") or next(iter(android_build_hosts))
+ )
+ return Config(
+ path=config_path if config_path.exists() else None,
+ default_device=default_device,
+ devices=devices,
+ paths=paths,
+ default_android_build_host=default_android_build_host,
+ android_build_hosts=android_build_hosts,
+ )
+
+
+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_android_build_hosts(raw_hosts: Any) -> dict[str, AndroidBuildHostConfig]:
+ if not isinstance(raw_hosts, dict):
+ raise ValueError("android_build_hosts must be a JSON object")
+
+ hosts: dict[str, AndroidBuildHostConfig] = {}
+ for name, value in raw_hosts.items():
+ if isinstance(value, str):
+ value = {"ssh_target": value}
+ if not isinstance(value, dict):
+ raise ValueError(f"android build host {name!r} must be a string or object")
+ hosts[str(name)] = AndroidBuildHostConfig(
+ name=str(name),
+ ssh_target=str(value.get("ssh_target") or name),
+ project_dir=str(
+ value.get("project_dir") or DEFAULT_ANDROID_BUILD_PROJECT_DIR
+ ),
+ state_dir=str(value.get("state_dir") or DEFAULT_ANDROID_BUILD_STATE_DIR),
+ )
+
+ if not hosts:
+ name = os.environ.get("SAILFISH_MCP_ANDROID_BUILD_HOST", DEFAULT_ANDROID_BUILD_HOST)
+ hosts[name] = AndroidBuildHostConfig(
+ name=name,
+ ssh_target=os.environ.get(
+ "SAILFISH_MCP_ANDROID_BUILD_SSH_TARGET",
+ DEFAULT_ANDROID_BUILD_SSH_TARGET,
+ ),
+ project_dir=os.environ.get(
+ "SAILFISH_MCP_ANDROID_BUILD_PROJECT_DIR",
+ DEFAULT_ANDROID_BUILD_PROJECT_DIR,
+ ),
+ state_dir=os.environ.get(
+ "SAILFISH_MCP_ANDROID_BUILD_STATE_DIR",
+ DEFAULT_ANDROID_BUILD_STATE_DIR,
+ ),
+ )
+ return hosts
+
+
+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(),
+ obs_root=Path(
+ str(
+ raw_paths.get("obs_root")
+ or os.environ.get("SAILFISH_MCP_OBS_ROOT")
+ or defaults.obs_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
+ ),
+ )