summaryrefslogtreecommitdiff
path: root/src/sailfish_devel_mcp/config.py
diff options
context:
space:
mode:
authorAndrew Branson <andrew.branson@jolla.com>2026-06-29 16:40:30 +0200
committerAndrew Branson <andrew.branson@jolla.com>2026-06-29 17:50:20 +0200
commiteb92561bd685961889cc553cd96db4a569eefec5 (patch)
treea4df385847e712d1dfb428f28d23df58b9ed298d /src/sailfish_devel_mcp/config.py
parent21dcc7e6413cf1e93a37cce2098a86c2a3ab5e5a (diff)
Add remote Android build tools and request logging
Add Android build host config and MCP tools for remote AppSupport builds. Extend device RPM installs to copy dependency sets and install them together. Log MCP request and tool-call lifecycle events, and document the config.
Diffstat (limited to 'src/sailfish_devel_mcp/config.py')
-rw-r--r--src/sailfish_devel_mcp/config.py97
1 files changed, 97 insertions, 0 deletions
diff --git a/src/sailfish_devel_mcp/config.py b/src/sailfish_devel_mcp/config.py
index 73740d1..198b09d 100644
--- a/src/sailfish_devel_mcp/config.py
+++ b/src/sailfish_devel_mcp/config.py
@@ -12,6 +12,10 @@ 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)
@@ -37,6 +41,33 @@ class DeviceConfig:
@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"
@@ -62,6 +93,10 @@ class Config:
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
@@ -71,6 +106,18 @@ class Config:
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,
@@ -79,6 +126,11 @@ class Config:
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()
+ },
}
@@ -111,11 +163,17 @@ def load_config(path: str | os.PathLike[str] | None = None) -> Config:
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,
)
@@ -144,6 +202,45 @@ def _load_devices(raw_devices: Any) -> dict[str, DeviceConfig]:
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")