Files
revng-revng/python/revng/project/local_daemon_project.py
Giacomo Vercesi 3e3779498e Implement python interface for revng
Add a python interface (`revng.profile`) for interacting with the rev.ng
infrastructure as a whole; either through the CLI (`CLIProject`) or the
GraphQL API (`DaemonProject`).
2025-05-07 10:48:51 +02:00

84 lines
2.5 KiB
Python

#
# This file is distributed under the MIT License. See LICENSE.md for details.
#
import os
import socket
from signal import SIGINT
from subprocess import DEVNULL, STDOUT, Popen
from time import sleep
from typing import Optional
import requests
from .daemon_project import DaemonProject
from .project import CLIProjectMixin, ResumeProjectMixin
class LocalDaemonProject(DaemonProject, CLIProjectMixin, ResumeProjectMixin):
"""
This class extends the DaemonProject. When initialized it starts the revng daemon
server and setups the DaemonProject client used to connect to the server.
"""
def __init__(
self,
resume_path: Optional[str] = None,
revng_executable_path: Optional[str] = None,
*,
connection_retries: int = 10,
):
CLIProjectMixin.__init__(self, revng_executable_path)
ResumeProjectMixin.__init__(self, resume_path)
self.port: int = self._get_port()
self.daemon_process: Optional[Popen] = None
self.start_daemon(connection_retries)
super().__init__(f"http://127.0.0.1:{self.port}/graphql/")
def __del__(self):
self.stop_daemon()
def start_daemon(self, connection_retries: int):
"""
Start the `revng` daemon and wait for it to be ready.
"""
env = os.environ
env["REVNG_DATA_DIR"] = self.resume_path
cli_args = [self.revng_executable_path, "daemon", "-b", f"tcp:127.0.0.1:{self.port}"]
self.daemon_process = Popen(cli_args, stdout=DEVNULL, stderr=STDOUT, env=env)
failed_retries = 0
while failed_retries <= connection_retries:
if self._is_server_running():
return
failed_retries += 1
sleep(1)
raise RuntimeError(f"Couldn't connect to daemon server at http://127.0.0.1:{self.port}")
def stop_daemon(self) -> int:
"""
Stop the daemon server.
"""
if self.daemon_process:
self.daemon_process.send_signal(SIGINT)
status_code = self.daemon_process.wait(30.0)
self.daemon_process = None
return status_code
return 0
def _is_server_running(self) -> bool:
try:
requests.get(f"http://127.0.0.1:{self.port}/status", timeout=5)
return True
except requests.exceptions.ConnectionError:
return False
def _get_port(self) -> int:
s = socket.socket()
s.bind(("127.0.0.1", 0))
free_socket = s.getsockname()[1]
s.close()
return int(free_socket)