I AM SCREAMING

This commit is contained in:
firestar5683
2026-07-02 16:38:13 -05:00
parent 5bbe5f1daf
commit ea2aa532af
18 changed files with 173 additions and 1187 deletions
+4 -1
View File
@@ -30,7 +30,7 @@ from websocket import (ABNF, WebSocket, WebSocketException, WebSocketTimeoutExce
import cereal.messaging as messaging
from cereal import log
from cereal.services import SERVICE_LIST
from openpilot.common.api import Api, get_key_pair, use_konik_server
from openpilot.common.api import Api, get_key_pair
from openpilot.common.utils import CallbackReader, get_upload_stream
from openpilot.common.params import Params
from openpilot.common.realtime import set_core_affinity
@@ -41,6 +41,9 @@ from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID
from openpilot.system.version import get_build_metadata
from openpilot.system.hardware.hw import Paths
from openpilot.starpilot.common.starpilot_utilities import use_konik_server
ATHENA_HOST = os.getenv('ATHENA_HOST', f"wss://athena.{'konik.ai' if use_konik_server() else 'comma.ai'}")
HANDLER_THREADS = int(os.getenv('HANDLER_THREADS', "4"))
LOCAL_PORT_WHITELIST = {22, } # SSH
+16 -49
View File
@@ -1,57 +1,29 @@
#!/usr/bin/env python3
import time
import json
import jwt
import random
import string
from typing import cast
from pathlib import Path
from datetime import datetime, timedelta, UTC
from openpilot.common.api import api_get, get_key_pair
from openpilot.common.params import Params
from openpilot.common.spinner import Spinner
from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert
from openpilot.system.hardware import HARDWARE, PC
from openpilot.system.hardware.hw import Paths
from openpilot.common.swaglog import cloudlog
UNREGISTERED_DONGLE_ID = "UnregisteredDevice"
OFFROAD_UNREGISTERED_ALERT = "Offroad_UnregisteredHardware"
KEYS = {"id_rsa": "RS256",
"id_ecdsa": "ES256"}
def api_get(*args, **kwargs):
from openpilot.common.api import api_get as api_get_impl
return api_get_impl(*args, **kwargs)
def get_key_pair() -> tuple[str, str, str] | tuple[None, None, None]:
for key, algorithm in KEYS.items():
private_path = Path(Paths.persist_root()) / "comma" / key
public_path = Path(Paths.persist_root()) / "comma" / f"{key}.pub"
if private_path.is_file() and public_path.is_file():
return algorithm, private_path.read_text(), public_path.read_text()
return None, None, None
def _has_key_pair() -> bool:
for key in KEYS:
private_path = Path(Paths.persist_root()) / "comma" / key
public_path = Path(Paths.persist_root()) / "comma" / f"{key}.pub"
if private_path.is_file() and public_path.is_file():
return True
return False
def is_registered_device() -> bool:
dongle = Params().get("DongleId")
return dongle not in (None, UNREGISTERED_DONGLE_ID)
def _set_registration_alert(params: Params, dongle_id: str | None) -> None:
show_alert = (dongle_id == UNREGISTERED_DONGLE_ID) and not PC
if show_alert:
from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert
set_offroad_alert(OFFROAD_UNREGISTERED_ALERT, True)
else:
params.remove(OFFROAD_UNREGISTERED_ALERT)
def register(show_spinner=False, register_konik=False) -> str | None:
"""
All devices built since March 2024 come with all
@@ -70,18 +42,13 @@ def register(show_spinner=False, register_konik=False) -> str | None:
with open(Paths.persist_root()+"/comma/dongle_id") as f:
dongle_id = f.read().strip()
if not _has_key_pair() and not register_konik:
# Create registration token, in the future, this key will make JWTs directly
jwt_algo, private_key, public_key = get_key_pair()
if not public_key and not register_konik:
dongle_id = UNREGISTERED_DONGLE_ID
cloudlog.warning("missing public key")
elif dongle_id is None or register_konik:
import json
import jwt
from datetime import datetime, timedelta, UTC
from openpilot.common.spinner import Spinner
# Create registration token, in the future, this key will make JWTs directly
jwt_algo, private_key, public_key = get_key_pair()
if show_spinner:
spinner = Spinner()
spinner.update("registering device")
@@ -106,14 +73,14 @@ def register(show_spinner=False, register_konik=False) -> str | None:
while True:
try:
register_token = jwt.encode({'register': True, 'exp': datetime.now(UTC).replace(tzinfo=None) + timedelta(hours=1)},
private_key, algorithm=jwt_algo)
cast(str, private_key), algorithm=jwt_algo)
cloudlog.info("getting pilotauth")
resp = api_get("v2/pilotauth/", method='POST', timeout=15,
imei=imei1, imei2=imei2, serial=serial, public_key=public_key, register_token=register_token)
if resp.status_code in (402, 403):
cloudlog.info(f"Unable to register device, got {resp.status_code}")
dongle_id = UNREGISTERED_DONGLE_ID
dongle_id = ''.join(random.choices(string.ascii_lowercase + string.digits, k=16))
else:
dongleauth = json.loads(resp.text)
dongle_id = dongleauth["dongle_id"]
@@ -132,7 +99,7 @@ def register(show_spinner=False, register_konik=False) -> str | None:
if not register_konik and dongle_id != params.get("KonikDongleId"):
params.put("DongleId", dongle_id)
params.put("StockDongleId", dongle_id)
_set_registration_alert(params, dongle_id)
set_offroad_alert("Offroad_UnregisteredHardware", (dongle_id == UNREGISTERED_DONGLE_ID) and not PC)
return dongle_id
+21 -27
View File
@@ -1,11 +1,18 @@
import errno
import fcntl
import os
import sys
import pathlib
import shutil
import signal
import subprocess
import tempfile
import threading
from openpilot.common.basedir import BASEDIR
from openpilot.common.params import Params
def unblock_stdout() -> None:
import errno
import fcntl
import signal
import sys
# get a non-blocking stdout
child_pid, child_pty = os.forkpty()
if child_pid != 0: # parent
@@ -44,30 +51,17 @@ def write_onroad_params(started, params):
def save_bootlog():
import threading
# copy current params
tmp = tempfile.mkdtemp()
params_dirname = pathlib.Path(Params().get_param_path()).name
params_dir = os.path.join(tmp, params_dirname)
shutil.copytree(Params().get_param_path(), params_dir, dirs_exist_ok=True)
def fn():
import pathlib
import shutil
import subprocess
import tempfile
from openpilot.common.basedir import BASEDIR
from openpilot.common.params import Params
tmpdir = tempfile.mkdtemp()
def fn(tmpdir):
env = os.environ.copy()
env['PARAMS_COPY_PATH'] = tmpdir
try:
params = Params()
params_dirname = pathlib.Path(params.get_param_path()).name
params_dir = os.path.join(tmpdir, params_dirname)
shutil.copytree(params.get_param_path(), params_dir, dirs_exist_ok=True)
subprocess.call("./bootlog", cwd=os.path.join(BASEDIR, "system/loggerd"), env=env)
finally:
shutil.rmtree(tmpdir, ignore_errors=True)
t = threading.Thread(target=fn)
subprocess.call("./bootlog", cwd=os.path.join(BASEDIR, "system/loggerd"), env=env)
shutil.rmtree(tmpdir)
t = threading.Thread(target=fn, args=(tmp, ))
t.daemon = True
t.start()
+40 -216
View File
@@ -2,13 +2,12 @@
import datetime
import json
import os
import shutil
from pathlib import Path
import signal
import sys
import threading
import time
import traceback
from types import SimpleNamespace
_MANAGER_IMPORT_START = time.monotonic()
_BOOT_TIMING_LOG_PATH = os.environ.get("SP_BOOT_TIMING_LOG", "/tmp/starpilot_boot_timing.log")
@@ -24,29 +23,37 @@ def _append_boot_timing_line(line: str) -> None:
from cereal import car, log
import cereal.messaging as messaging
import openpilot.system.sentry as sentry
from openpilot.common.utils import atomic_write
from openpilot.common.params import Params, ParamKeyFlag, ParamKeyType
from openpilot.common.text_window import TextWindow
from openpilot.system.hardware import HARDWARE
from openpilot.system.manager.helpers import unblock_stdout, write_onroad_params, save_bootlog
from openpilot.system.manager.process import ensure_running
from openpilot.system.manager.process_config import managed_processes
from openpilot.common.swaglog import cloudlog
from openpilot.system.athena.registration import register, UNREGISTERED_DONGLE_ID
from openpilot.common.swaglog import cloudlog, add_file_handler
from openpilot.system.version import get_build_metadata, terms_version, training_version
from openpilot.system.hardware.hw import Paths
_MANAGER_CORE_IMPORT_DONE = time.monotonic()
from openpilot.starpilot.common.starpilot_functions import starpilot_boot_functions, install_starpilot, uninstall_starpilot
from openpilot.starpilot.common.starpilot_variables import (
LEGACY_STARPILOT_PARAM_RENAMES,
LEGACY_STARPILOT_STATS_KEY_RENAMES,
get_starpilot_toggles,
)
_MANAGER_IMPORT_DONE = time.monotonic()
if __name__ == "__main__":
_manager_import_timing_line = (
"SP_BOOT_TIMING manager_import "
f"core={_MANAGER_CORE_IMPORT_DONE - _MANAGER_IMPORT_START:.3f}s "
f"starpilot={_MANAGER_IMPORT_DONE - _MANAGER_CORE_IMPORT_DONE:.3f}s "
f"total={_MANAGER_IMPORT_DONE - _MANAGER_IMPORT_START:.3f}s"
)
print(_manager_import_timing_line, flush=True)
_append_boot_timing_line(_manager_import_timing_line)
_manager_import_timing_line = (
"SP_BOOT_TIMING manager_import "
f"core={_MANAGER_CORE_IMPORT_DONE - _MANAGER_IMPORT_START:.3f}s "
f"starpilot={_MANAGER_IMPORT_DONE - _MANAGER_CORE_IMPORT_DONE:.3f}s "
f"total={_MANAGER_IMPORT_DONE - _MANAGER_IMPORT_START:.3f}s"
)
print(_manager_import_timing_line, flush=True)
_append_boot_timing_line(_manager_import_timing_line)
LEGACY_BOLT_FP_MIGRATION_FLAG = Path("/data") / "legacy_bolt_fp_migration_v1"
@@ -60,30 +67,9 @@ STARPILOT_PC_ROOT_MIGRATION_FLAG = Path("/data") / "starpilot_pc_root_v1"
STARPILOT_PARAMS_CACHE_MIGRATION_FLAG = Path("/data") / "starpilot_params_cache_v1"
STARPILOT_LEGACY_CACHE_MARKER_KEYS = ("RemapCancelToDistance",)
STARPILOT_REMOVED_PARAM_KEYS = ("HumanFollowing",)
PARAMS_CACHE_RESTORE_SKIP_FLAGS = (
ParamKeyFlag.CLEAR_ON_MANAGER_START
| ParamKeyFlag.CLEAR_ON_ONROAD_TRANSITION
| ParamKeyFlag.CLEAR_ON_OFFROAD_TRANSITION
| ParamKeyFlag.CLEAR_ON_IGNITION_ON
)
UNREGISTERED_DONGLE_ID = "UnregisteredDevice"
POWER_WATCHDOG_PATH = "/var/tmp/power_watchdog"
_PENDING_PARAMS_CACHE_SYNC: tuple[str, list[str]] | None = None
LEGACY_CARMODEL_MIGRATIONS = {
"CHEVROLET_BOLT_CC_2019_2021": "CHEVROLET_BOLT_CC_2018_2021",
}
LEGACY_STARPILOT_PARAM_RENAMES = {
"FrogPilotApiToken": "StarPilotApiToken",
"FrogPilotCarParams": "StarPilotCarParams",
"FrogPilotCarParamsPersistent": "StarPilotCarParamsPersistent",
"FrogPilotDongleId": "StarPilotDongleId",
"FrogPilotStats": "StarPilotStats",
}
LEGACY_STARPILOT_STATS_KEY_RENAMES = {
"FrogPilotDrives": "StarPilotDrives",
"FrogPilotMeters": "StarPilotMeters",
"FrogPilotSeconds": "StarPilotSeconds",
}
STARPILOT_STATS_DROP_KEYS = {"CurrentMonthsKilometers", "ResetStats"}
STARPILOT_STATS_MAX_KEYS = {"LongestDistanceWithoutOverride", "MaxAcceleration"}
@@ -97,154 +83,6 @@ def _log_boot_timing(scope: str, label: str, start: float, previous: float | Non
return now
def _sync_params_cache_keys_async(cache_params_path: str, keys: list[str]) -> None:
try:
params = Params()
params_cache = Params(cache_params_path, return_defaults=True)
for key in keys:
current_value = params.get(key)
if current_value is not None and params_cache.get(key) != current_value:
params_cache.put(key, current_value)
except Exception:
cloudlog.exception("failed to sync params cache")
def _schedule_params_cache_sync(cache_params_path: str, keys: list[str]) -> None:
timer = threading.Timer(5.0, _sync_params_cache_keys_async, args=(cache_params_path, keys))
timer.daemon = True
timer.start()
def _schedule_pending_params_cache_sync() -> None:
global _PENDING_PARAMS_CACHE_SYNC
pending_sync = _PENDING_PARAMS_CACHE_SYNC
_PENDING_PARAMS_CACHE_SYNC = None
if pending_sync is not None and pending_sync[1]:
_schedule_params_cache_sync(*pending_sync)
def _iter_param_store_keys(store_path: str | Path) -> set[str]:
try:
path = Path(store_path)
if not path.is_dir():
return set()
with os.scandir(path) as entries:
return {
entry.name
for entry in entries
if entry.is_file(follow_symlinks=False) and entry.name != ".lock" and not entry.name.startswith(".tmp_")
}
except Exception:
cloudlog.exception(f"failed to list params store: {store_path}")
return set()
def _param_key_to_text(key: bytes | str) -> str:
return key.decode("utf-8", errors="ignore") if isinstance(key, bytes) else str(key)
def _should_restore_param_from_cache(params: Params, key: bytes | str) -> bool:
try:
return not (params.get_key_flag(key) & PARAMS_CACHE_RESTORE_SKIP_FLAGS)
except Exception:
return False
def _restore_missing_params_from_cache(params: Params, params_cache: Params, active_keys: set[str] | None = None) -> list[str]:
active_keys = active_keys if active_keys is not None else _iter_param_store_keys(params.get_param_path())
restored_keys: list[str] = []
for raw_key in params.all_keys():
key = _param_key_to_text(raw_key)
if key in active_keys:
continue
if not _should_restore_param_from_cache(params, raw_key):
continue
cached_value = params_cache.get(raw_key)
if cached_value is None:
continue
try:
params.put(raw_key, cached_value)
restored_keys.append(key)
except Exception:
cloudlog.exception(f"failed to restore param from cache: {key}")
return restored_keys
def _init_sentry_async() -> None:
def fn() -> None:
try:
import openpilot.system.sentry as sentry
sentry.init(sentry.SentryProject.SELFDRIVE)
except Exception:
cloudlog.exception("failed to initialize sentry")
threading.Thread(target=fn, daemon=True).start()
def _capture_manager_exception() -> None:
try:
import openpilot.system.sentry as sentry
sentry.capture_exception()
except Exception:
cloudlog.exception("failed to capture manager exception")
def _write_power_watchdog(timestamp: float) -> None:
import tempfile
tmp_file_name = None
try:
with tempfile.NamedTemporaryFile("w", dir=os.path.dirname(POWER_WATCHDOG_PATH), delete=False) as f:
tmp_file_name = f.name
f.write(str(timestamp))
os.replace(tmp_file_name, POWER_WATCHDOG_PATH)
tmp_file_name = None
finally:
if tmp_file_name is not None:
try:
os.unlink(tmp_file_name)
except FileNotFoundError:
pass
def _get_starpilot_toggles(sm=None):
from openpilot.starpilot.common.starpilot_variables import get_starpilot_toggles
return get_starpilot_toggles(sm)
def _get_manager_startup_toggles(params: Params | None = None) -> SimpleNamespace:
params = params or Params()
force_onroad = params.get_bool("ForceOnroad")
device_management = params.get_bool("DeviceManagement")
vetting_branch = os.environ.get("GIT_BRANCH") == "StarPilot-Vetting"
no_logging = False
no_uploads = False
if device_management and not vetting_branch:
no_logging = params.get_bool("NoLogging")
no_uploads = params.get_bool("NoUploads")
return SimpleNamespace(
force_offroad=params.get_bool("ForceOffroad"),
force_onroad=force_onroad,
no_logging=no_logging or (force_onroad and HARDWARE.get_device_type() == "pc"),
no_uploads=no_uploads,
no_onroad_uploads=params.get_bool("DisableOnroadUploads") if no_uploads else False,
speed_limit_filler=params.get_bool("SpeedLimitFiller"),
vision_speed_limit_detection=params.get_bool("VisionSpeedLimitDetection"),
)
def _to_text(value):
if value is None:
return None
@@ -416,8 +254,6 @@ def _copy_param_store_without_overwrite(source: Path, destination: Path) -> int:
if not source.is_dir():
return 0
import shutil
destination.mkdir(parents=True, exist_ok=True)
copied_entries = 0
for path in source.iterdir():
@@ -535,8 +371,6 @@ def _merge_tree_without_overwrite(source: Path, destination: Path) -> int:
if not source.exists():
return moved_entries
import shutil
if not destination.exists():
destination.parent.mkdir(parents=True, exist_ok=True)
shutil.move(str(source), str(destination))
@@ -926,12 +760,11 @@ def migrate_legacy_experimental_longitudinal(params: Params, params_cache: Param
def manager_init() -> None:
global _PENDING_PARAMS_CACHE_SYNC
manager_init_start = time.monotonic()
last_timing = _log_boot_timing("manager_init", "start", manager_init_start, manager_init_start)
last_timing = _log_boot_timing("manager_init", "save_bootlog_deferred", manager_init_start, last_timing)
save_bootlog()
last_timing = _log_boot_timing("manager_init", "save_bootlog", manager_init_start, last_timing)
build_metadata = get_build_metadata()
last_timing = _log_boot_timing("manager_init", "build_metadata", manager_init_start, last_timing)
@@ -977,11 +810,14 @@ def manager_init() -> None:
last_timing = _log_boot_timing("manager_init", "starpilot_migrations", manager_init_start, last_timing)
# set unset params to their default value
active_param_keys = _iter_param_store_keys(params.get_param_path())
last_timing = _log_boot_timing("manager_init", f"params_store_snapshot_{len(active_param_keys)}", manager_init_start, last_timing)
restored_param_keys = _restore_missing_params_from_cache(params, params_cache, active_param_keys)
last_timing = _log_boot_timing("manager_init", f"params_restore_{len(restored_param_keys)}", manager_init_start, last_timing)
params_cache_update_keys = sorted(active_param_keys)
for k in params.all_keys():
current_value = params.get(k)
if current_value is None:
cached_value = params_cache.get(k)
if cached_value is not None:
params.put(k, cached_value)
else:
params_cache.put(k, current_value)
last_timing = _log_boot_timing("manager_init", "params_defaults_cache_sync", manager_init_start, last_timing)
# Create folders needed for msgq
@@ -1011,8 +847,6 @@ def manager_init() -> None:
last_timing = _log_boot_timing("manager_init", "version_params", manager_init_start, last_timing)
# set dongle id
from openpilot.system.athena.registration import register
reg_res = register(show_spinner=True)
if reg_res:
dongle_id = reg_res
@@ -1028,6 +862,7 @@ def manager_init() -> None:
os.environ['CLEAN'] = '1'
# init logging
sentry.init(sentry.SentryProject.SELFDRIVE)
cloudlog.bind_global(dongle_id=dongle_id,
version=build_metadata.openpilot.version,
origin=build_metadata.openpilot.git_normalized_origin,
@@ -1037,22 +872,16 @@ def manager_init() -> None:
device=HARDWARE.get_device_type())
last_timing = _log_boot_timing("manager_init", "logging_ready", manager_init_start, last_timing)
# Preimporting every process serializes a lot of import work before manager can
# start the always-on processes. Keep the old behavior available for debugging,
# but optimize normal boot by letting children import their modules in parallel.
if os.getenv("SP_MANAGER_PREIMPORT", "0").lower() in ("1", "true", "yes", "on"):
for p in managed_processes.values():
p.prepare()
last_timing = _log_boot_timing("manager_init", "preimport_processes", manager_init_start, last_timing)
else:
last_timing = _log_boot_timing("manager_init", "preimport_processes_skipped", manager_init_start, last_timing)
# preimport all processes
for p in managed_processes.values():
p.prepare()
last_timing = _log_boot_timing("manager_init", "preimport_processes", manager_init_start, last_timing)
# StarPilot variables
install_starpilot(build_metadata, params)
last_timing = _log_boot_timing("manager_init", "install_starpilot", manager_init_start, last_timing)
starpilot_boot_functions(build_metadata, params)
_log_boot_timing("manager_init", "starpilot_boot_functions", manager_init_start, last_timing)
_PENDING_PARAMS_CACHE_SYNC = (cache_params_path, params_cache_update_keys)
def manager_cleanup() -> None:
@@ -1090,7 +919,7 @@ def manager_thread() -> None:
last_timing = _log_boot_timing("manager_thread", "messaging", manager_thread_start, last_timing)
write_onroad_params(False, params)
initial_toggles = _get_manager_startup_toggles(params)
initial_toggles = get_starpilot_toggles()
last_timing = _log_boot_timing("manager_thread", "initial_toggles", manager_thread_start, last_timing)
ensure_running(managed_processes.values(), False, params=params, CP=sm['carParams'], not_run=ignore, starpilot_toggles=initial_toggles)
last_timing = _log_boot_timing("manager_thread", "initial_ensure_running", manager_thread_start, last_timing)
@@ -1106,12 +935,9 @@ def manager_thread() -> None:
params_memory = Params(memory=True)
starpilot_toggles = _get_manager_startup_toggles(params)
starpilot_toggles = get_starpilot_toggles()
last_timing = _log_boot_timing("manager_thread", "loop_toggles", manager_thread_start, last_timing)
_log_boot_timing("manager_thread", "loop_ready", manager_thread_start, last_timing)
save_bootlog()
_init_sentry_async()
_schedule_pending_params_cache_sync()
while True:
sm.update(1000)
@@ -1159,7 +985,8 @@ def manager_thread() -> None:
# kick AGNOS power monitoring watchdog
try:
if sm.all_checks(['deviceState']):
_write_power_watchdog(time.monotonic())
with atomic_write("/var/tmp/power_watchdog", "w", overwrite=True) as f:
f.write(str(time.monotonic()))
except Exception:
pass
@@ -1182,7 +1009,7 @@ def manager_thread() -> None:
break
# StarPilot variables
starpilot_toggles = _get_starpilot_toggles(sm)
starpilot_toggles = get_starpilot_toggles(sm)
def main() -> None:
@@ -1197,7 +1024,7 @@ def main() -> None:
manager_thread()
except Exception:
traceback.print_exc()
_capture_manager_exception()
sentry.capture_exception()
finally:
manager_cleanup()
@@ -1221,9 +1048,6 @@ if __name__ == "__main__":
except KeyboardInterrupt:
print("got CTRL-C, exiting")
except Exception:
from openpilot.common.swaglog import add_file_handler
from openpilot.common.text_window import TextWindow
add_file_handler(cloudlog)
cloudlog.exception("Manager failed to start")
+7 -48
View File
@@ -6,8 +6,6 @@ import struct
import threading
import time
import subprocess
import multiprocessing
import sys
from pathlib import Path
from collections.abc import Callable, ValuesView
from abc import ABC, abstractmethod
@@ -18,6 +16,7 @@ from setproctitle import setproctitle
from cereal import car, log
import cereal.messaging as messaging
import openpilot.system.sentry as sentry
from openpilot.common.basedir import BASEDIR
from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
@@ -430,6 +429,7 @@ def launcher(proc: str, name: str, nice: int | None = None) -> None:
# add daemon name tag to logs
cloudlog.bind(daemon=name)
sentry.set_tag("daemon", name)
# exec the process
mod.main()
@@ -438,13 +438,7 @@ def launcher(proc: str, name: str, nice: int | None = None) -> None:
except Exception:
# can't install the crash handler because sys.excepthook doesn't play nice
# with threads, so catch it here.
try:
import openpilot.system.sentry as sentry
sentry.set_tag("daemon", name)
sentry.capture_exception()
except Exception:
cloudlog.exception(f"failed to capture exception for child {proc}")
sentry.capture_exception()
raise
@@ -467,33 +461,11 @@ def join_process(process: Process, timeout: float) -> None:
time.sleep(0.001)
class SubprocessProcess:
def __init__(self, proc: subprocess.Popen):
self._proc = proc
@property
def pid(self) -> int | None:
return self._proc.pid
@property
def exitcode(self) -> int | None:
return self._proc.poll()
def is_alive(self) -> bool:
return self._proc.poll() is None
def join(self, timeout: float | None = None) -> None:
try:
self._proc.wait(timeout=timeout)
except subprocess.TimeoutExpired:
pass
class ManagerProcess(ABC):
daemon = False
sigkill = False
should_run: Callable[[bool, Params, car.CarParams, SimpleNamespace], bool]
proc: Process | SubprocessProcess | None = None
proc: Process | None = None
enabled = True
name = ""
@@ -643,7 +615,7 @@ class NativeProcess(ManagerProcess):
class PythonProcess(ManagerProcess):
def __init__(self, name, module, should_run, enabled=True, sigkill=False, watchdog_max_dt=None, nice=None, start_method=None):
def __init__(self, name, module, should_run, enabled=True, sigkill=False, watchdog_max_dt=None, nice=None):
self.name = name
self.module = module
self.should_run = should_run
@@ -651,7 +623,6 @@ class PythonProcess(ManagerProcess):
self.sigkill = sigkill
self.watchdog_max_dt = watchdog_max_dt
self.nice = nice
self.start_method = start_method
self.launcher = launcher
def prepare(self) -> None:
@@ -682,20 +653,8 @@ class PythonProcess(ManagerProcess):
name = self.name if "modeld" not in self.name else "MainProcess"
cloudlog.info(f"starting python {self.module}")
start_method = self.start_method or os.getenv("PYTHON_PROCESS_START_METHOD", "fork")
if start_method == "subprocess":
launcher_code = (
"from openpilot.system.manager.process import launcher; "
f"launcher({self.module!r}, {self.name!r}, {self.nice!r})"
)
self.proc = SubprocessProcess(subprocess.Popen([sys.executable, "-c", launcher_code]))
else:
self.proc = multiprocessing.get_context(start_method).Process(
name=name,
target=self.launcher,
args=(self.module, self.name, self.nice),
)
self.proc.start()
self.proc = Process(name=name, target=self.launcher, args=(self.module, self.name, self.nice))
self.proc.start()
self.last_watchdog_time = 0
self.watchdog_seen = False
self.shutting_down = False
+8 -56
View File
@@ -7,42 +7,10 @@ from types import SimpleNamespace
from cereal import car
from openpilot.common.params import Params
from openpilot.system.hardware import HARDWARE, PC, TICI
from openpilot.system.manager.process import PythonProcess, NativeProcess, DaemonProcess
WEBCAM = os.getenv("USE_WEBCAM") is not None
UI_WATCHDOG_MAX_DT = int(os.getenv("UI_WATCHDOG_MAX_DT", "10"))
device_type = HARDWARE.get_device_type()
def _env_bool(name: str) -> bool | None:
value = os.getenv(name)
if value is None:
return None
return value.strip().lower() in ("1", "true", "yes", "on")
def python_ui_enabled(device_type: str) -> bool:
for env_name in ("USE_PYTHON_UI", "USE_RAYLIB_UI"):
enabled = _env_bool(env_name)
if enabled is not None:
return enabled
native_ui = _env_bool("USE_NATIVE_UI")
if native_ui is not None:
return not native_ui
return device_type not in ("tici", "tizi")
def python_ui_process_start_method(uses_python_ui: bool, is_pc: bool = PC) -> str:
return "fork" if is_pc or not uses_python_ui else "subprocess"
PYTHON_UI = python_ui_enabled(device_type)
PYTHON_UI_PROCESS_START_METHOD = python_ui_process_start_method(PYTHON_UI)
THE_GALAXY_PROCESS_START_METHOD = "fork" if PC else "subprocess"
UPDATED_PROCESS_START_METHOD = "fork" if PC else "subprocess"
from openpilot.system.manager.process import PythonProcess, NativeProcess, DaemonProcess
def driverview(started: bool, params: Params, CP: car.CarParams, starpilot_toggles: SimpleNamespace) -> bool:
return started or params.get_bool("IsDriverViewEnabled")
@@ -157,13 +125,7 @@ procs = [
PythonProcess("radard", "selfdrive.controls.radard", only_onroad),
PythonProcess("hardwared", "system.hardware.hardwared", always_run),
PythonProcess("tombstoned", "system.tombstoned", always_run, enabled=not PC),
PythonProcess(
"updated",
"system.updated.updated",
always_run,
enabled=not PC,
start_method=UPDATED_PROCESS_START_METHOD,
),
PythonProcess("updated", "system.updated.updated", always_run, enabled=not PC),
PythonProcess("uploader", "system.loggerd.uploader", allow_uploads),
PythonProcess("statsd", "system.statsd", always_run),
PythonProcess("feedbackd", "selfdrive.ui.feedback.feedbackd", only_onroad),
@@ -177,26 +139,16 @@ procs = [
# StarPilot variables
procs += [
PythonProcess(
"the_galaxy",
"starpilot.system.the_galaxy.the_galaxy",
always_run,
nice=19,
start_method=THE_GALAXY_PROCESS_START_METHOD,
),
PythonProcess("the_galaxy", "starpilot.system.the_galaxy.the_galaxy", always_run, nice=19),
PythonProcess("galaxy", "starpilot.system.galaxy.galaxy", always_run, nice=19),
]
if PYTHON_UI:
procs.append(PythonProcess(
"ui",
"selfdrive.ui.ui",
always_run,
watchdog_max_dt=UI_WATCHDOG_MAX_DT,
start_method=PYTHON_UI_PROCESS_START_METHOD,
))
else:
device_type = HARDWARE.get_device_type()
if device_type in ("tici", "tizi"):
procs.append(NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, watchdog_max_dt=UI_WATCHDOG_MAX_DT))
else:
# C4 (mici) runs the Python raylib UI path.
procs.append(PythonProcess("ui", "selfdrive.ui.ui", always_run, watchdog_max_dt=UI_WATCHDOG_MAX_DT))
procs += [
PythonProcess("device_syncd", "starpilot.system.device_syncd", always_run),
@@ -50,9 +50,7 @@ class FileBackedFakeParams:
def marker_path(tmp_path: Path, marker_name: str) -> Path:
path = tmp_path / MARKER_DIRNAME / "params" / marker_name
path.parent.mkdir(parents=True, exist_ok=True)
return path
return tmp_path / MARKER_DIRNAME / "params" / marker_name
def test_apply_launch_param_migrations_sets_branch_defaults_once(tmp_path):
+4 -102
View File
@@ -6,16 +6,10 @@ from pathlib import Path
import json
from cereal import car
from openpilot.common.params import Params, ParamKeyFlag
from openpilot.common.params import Params
import openpilot.system.manager.manager as manager
from openpilot.system.manager.process import ensure_running
from openpilot.system.manager.process import PythonProcess
from openpilot.system.manager.process_config import (
managed_processes,
procs,
python_ui_enabled,
python_ui_process_start_method,
)
from openpilot.system.manager.process_config import managed_processes, procs
from openpilot.system.hardware import HARDWARE
os.environ['FAKEUPLOAD'] = "1"
@@ -25,29 +19,15 @@ BLACKLIST_PROCS = ['manage_athenad', 'pandad', 'pigeond']
class FileBackedFakeParams:
def __init__(
self,
root: Path,
values: dict[str, object] | None = None,
keys: set[str] | None = None,
flags: dict[str, ParamKeyFlag] | None = None,
):
def __init__(self, root: Path, values: dict[str, object] | None = None):
self.root = root
self.keys = set(keys or [])
self.flags = dict(flags or {})
self.root.mkdir(parents=True, exist_ok=True)
for key, value in (values or {}).items():
self.put(key, value)
def get_param_path(self, key=""):
def get_param_path(self, key):
return str(self.root / (key.decode() if isinstance(key, bytes) else str(key)))
def all_keys(self):
return sorted(self.keys)
def get_key_flag(self, key):
return self.flags.get(key.decode() if isinstance(key, bytes) else str(key), ParamKeyFlag.PERSISTENT)
def get(self, key):
path = Path(self.get_param_path(key))
if not path.is_file():
@@ -68,7 +48,6 @@ class FileBackedFakeParams:
return str(value).strip().lower() in ("1", "true", "yes", "on")
def put(self, key, value):
self.keys.add(key.decode() if isinstance(key, bytes) else str(key))
path = Path(self.get_param_path(key))
path.parent.mkdir(parents=True, exist_ok=True)
@@ -117,83 +96,6 @@ class TestManager:
assert names.index("the_galaxy") < ui_idx
assert names.index("galaxy") < ui_idx
def test_python_ui_process_start_method_follows_ui_implementation(self):
assert python_ui_process_start_method(False, False) == "fork"
assert python_ui_process_start_method(True, False) == "subprocess"
assert python_ui_process_start_method(True, True) == "fork"
def test_python_ui_subprocess_is_scoped_to_ui(self):
ui_proc = managed_processes["ui"]
uses_python_ui = python_ui_enabled(HARDWARE.get_device_type())
subprocess_scoped_procs = {"the_galaxy", "updated"}
assert isinstance(ui_proc, PythonProcess) == uses_python_ui
if uses_python_ui:
assert ui_proc.start_method == python_ui_process_start_method(uses_python_ui)
for proc in procs:
if isinstance(proc, PythonProcess) and proc.name not in subprocess_scoped_procs | {"ui"}:
assert proc.start_method is None
def test_python_ui_env_override(self, monkeypatch):
monkeypatch.setenv("USE_RAYLIB_UI", "1")
assert python_ui_enabled("tici") is True
monkeypatch.setenv("USE_RAYLIB_UI", "0")
assert python_ui_enabled("mici") is False
def test_manager_startup_toggles_use_params_only(self, tmp_path, monkeypatch):
monkeypatch.setenv("GIT_BRANCH", "StarPilot")
params = FileBackedFakeParams(tmp_path / "params", {
"DeviceManagement": True,
"NoLogging": True,
"NoUploads": True,
"DisableOnroadUploads": True,
"SpeedLimitFiller": True,
"VisionSpeedLimitDetection": True,
"ForceOffroad": True,
"ForceOnroad": False,
})
toggles = manager._get_manager_startup_toggles(params)
assert toggles.no_logging is True
assert toggles.no_uploads is True
assert toggles.no_onroad_uploads is True
assert toggles.speed_limit_filler is True
assert toggles.vision_speed_limit_detection is True
assert toggles.force_offroad is True
assert toggles.force_onroad is False
def test_restore_missing_params_from_cache_preserves_live_values(self, tmp_path):
params = FileBackedFakeParams(
tmp_path / "params",
{"ExistingParam": "live"},
keys={"ExistingParam", "RestoredParam", "MissingParam", "TransientParam"},
flags={"TransientParam": ParamKeyFlag.CLEAR_ON_MANAGER_START},
)
params_cache = FileBackedFakeParams(
tmp_path / "cache",
{"ExistingParam": "cached", "RestoredParam": "restored", "TransientParam": "stale"},
)
restored_keys = manager._restore_missing_params_from_cache(params, params_cache)
assert restored_keys == ["RestoredParam"]
assert params.get("ExistingParam") == "live"
assert params.get("RestoredParam") == "restored"
assert params.get("MissingParam") is None
assert params.get("TransientParam") is None
def test_iter_param_store_keys_skips_lock_and_temp_files(self, tmp_path):
store_path = tmp_path / "params"
store_path.mkdir()
(store_path / "GoodParam").write_text("1")
(store_path / ".lock").write_text("")
(store_path / ".tmp_value_abc").write_text("stale")
(store_path / "nested").mkdir()
assert manager._iter_param_store_keys(store_path) == {"GoodParam"}
def test_blacklisted_procs(self):
# TODO: ensure there are blacklisted procs until we have a dedicated test
assert len(BLACKLIST_PROCS), "No blacklisted procs to test not_run"
-67
View File
@@ -1,67 +0,0 @@
import json
from openpilot.system import version
def _write_minimal_git_checkout(path, branch="StarPilot", commit="1" * 40, origin="https://example.com/openpilot"):
git_folder = path / ".git"
git_folder.mkdir()
(git_folder / "refs" / "heads").mkdir(parents=True)
(git_folder / "HEAD").write_text(f"ref: refs/heads/{branch}\n", encoding="utf-8")
(git_folder / "refs" / "heads" / branch).write_text(f"{commit}\n", encoding="utf-8")
(git_folder / "config").write_text(
f"""[remote "origin"]
url = {origin}
[branch "{branch}"]
remote = origin
merge = refs/heads/{branch}
""",
encoding="utf-8",
)
return {
"branch": branch,
"commit": commit,
"origin": origin,
}
def test_read_git_metadata_reads_head_ref_and_origin(tmp_path):
git_metadata = _write_minimal_git_checkout(tmp_path, branch="Dom", commit="a" * 40)
assert version._read_git_metadata(str(tmp_path)) == git_metadata
def test_get_build_metadata_uses_prebuilt_git_cache(tmp_path, monkeypatch):
git_metadata = _write_minimal_git_checkout(tmp_path, branch="Dom", commit="b" * 40)
cache_root = tmp_path / "cache"
monkeypatch.setenv("OPENPILOT_BUILD_METADATA_CACHE_ROOT", str(cache_root))
(tmp_path / "prebuilt").write_text("", encoding="utf-8")
(tmp_path / "common").mkdir()
(tmp_path / "common" / "version.h").write_text('#define COMMA_VERSION "1.2.3"\n', encoding="utf-8")
(tmp_path / "RELEASES.md").write_text("Release notes\n\nOlder notes\n", encoding="utf-8")
cached_payload = {
"path": str(tmp_path.resolve()),
"git_metadata": git_metadata,
"build_metadata": {
"channel": "Dom",
"openpilot": {
"version": "1.2.3",
"release_notes": "Release notes",
"git_commit": git_metadata["commit"],
"git_origin": git_metadata["origin"],
"git_commit_date": "123 1970-01-01 00:02:03 +0000",
"build_style": "unknown",
"is_dirty": False,
},
},
}
cache_root.mkdir()
(cache_root / version.BUILD_METADATA_CACHE_FILENAME).write_text(json.dumps(cached_payload), encoding="utf-8")
monkeypatch.setattr(version, "get_commit_date", lambda *args, **kwargs: (_ for _ in ()).throw(AssertionError("cache miss")))
build_metadata = version.get_build_metadata(str(tmp_path))
assert build_metadata.channel == "Dom"
assert build_metadata.openpilot.git_commit == git_metadata["commit"]
assert build_metadata.openpilot.git_commit_date == "123 1970-01-01 00:02:03 +0000"
+6 -61
View File
@@ -1,69 +1,14 @@
#!/usr/bin/env python3
from pathlib import Path
from openpilot.system.hardware import HARDWARE
BIG_UI_DEVICE_TYPES = ("tici", "tizi")
SMALL_UI_DEVICE_TYPE = "mici"
def _normalize_device_type(raw: str) -> str:
device_type = raw.replace("\x00", "").strip().lower()
if "comma " in device_type:
device_type = device_type.rsplit("comma ", 1)[-1].strip()
return device_type
def _device_tree_device_type() -> str | None:
model_path = Path("/sys/firmware/devicetree/base/model")
if not model_path.is_file():
return None
try:
return _normalize_device_type(model_path.read_text(encoding="utf-8", errors="ignore"))
except Exception:
return None
def _reported_device_type() -> str | None:
try:
return _normalize_device_type(HARDWARE.get_device_type())
except Exception:
return None
def _ui_device_type() -> str:
device_tree_type = _device_tree_device_type()
if device_tree_type in BIG_UI_DEVICE_TYPES:
return device_tree_type
if device_tree_type:
return SMALL_UI_DEVICE_TYPE
reported_type = _reported_device_type()
if reported_type in BIG_UI_DEVICE_TYPES:
return reported_type
return SMALL_UI_DEVICE_TYPE
def _patch_hardware_device_type(device_type: str) -> None:
HARDWARE.get_device_type = lambda: device_type
try:
import openpilot.system.hardware.tici.hardware as tici_hardware
tici_hardware.get_device_type = lambda: device_type
except Exception:
pass
from openpilot.system.ui.lib.application import gui_app
import openpilot.system.ui.tici_reset as tici_reset
import openpilot.system.ui.mici_reset as mici_reset
def main():
device_type = _ui_device_type()
_patch_hardware_device_type(device_type)
if device_type in BIG_UI_DEVICE_TYPES:
import openpilot.system.ui.tici_reset as reset_impl
if gui_app.big_ui():
tici_reset.main()
else:
import openpilot.system.ui.mici_reset as reset_impl
reset_impl.main()
mici_reset.main()
if __name__ == "__main__":
-42
View File
@@ -1,42 +0,0 @@
import importlib
import sys
import types
from unittest.mock import patch
import pytest
@pytest.fixture
def reset_module(monkeypatch):
hardware = types.ModuleType("openpilot.system.hardware")
hardware.HARDWARE = types.SimpleNamespace(get_device_type=lambda: "tici")
monkeypatch.setitem(sys.modules, "openpilot.system.hardware", hardware)
sys.modules.pop("openpilot.system.ui.reset", None)
module = importlib.import_module("openpilot.system.ui.reset")
yield module
sys.modules.pop("openpilot.system.ui.reset", None)
def test_device_tree_tici_uses_big_ui(reset_module):
with patch.object(reset_module, "_device_tree_device_type", return_value="tici"), \
patch.object(reset_module, "_reported_device_type", return_value="mici"):
assert reset_module._ui_device_type() == "tici"
def test_device_tree_tizi_uses_big_ui(reset_module):
with patch.object(reset_module, "_device_tree_device_type", return_value="tizi"), \
patch.object(reset_module, "_reported_device_type", return_value="mici"):
assert reset_module._ui_device_type() == "tizi"
@pytest.mark.parametrize("device_tree_type", ["mici", "comma 4", "comma four"])
def test_non_tici_device_tree_uses_small_ui(reset_module, device_tree_type):
with patch.object(reset_module, "_device_tree_device_type", return_value=device_tree_type), \
patch.object(reset_module, "_reported_device_type", return_value="tici"):
assert reset_module._ui_device_type() == "mici"
def test_reported_tici_used_when_no_device_tree(reset_module):
with patch.object(reset_module, "_device_tree_device_type", return_value=None), \
patch.object(reset_module, "_reported_device_type", return_value="tici"):
assert reset_module._ui_device_type() == "tici"
+6 -6
View File
@@ -26,7 +26,6 @@ from openpilot.starpilot.common.starpilot_variables import BACKUP_PATH, get_star
LOCK_FILE = os.getenv("UPDATER_LOCK_FILE", "/tmp/safe_staging_overlay.lock")
STAGING_ROOT = os.getenv("UPDATER_STAGING_ROOT", "/data/safe_staging")
GIT_CLEANUP_TIMEOUT = int(os.getenv("UPDATER_GIT_CLEANUP_TIMEOUT", "30"))
OVERLAY_UPPER = os.path.join(STAGING_ROOT, "upper")
OVERLAY_METADATA = os.path.join(STAGING_ROOT, "metadata")
@@ -234,12 +233,10 @@ def finalize_update() -> None:
cloudlog.info("Starting git cleanup in finalized update")
t = time.monotonic()
try:
subprocess.run(["git", "gc", "--auto"], cwd=FINALIZED, check=True, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, encoding="utf8", timeout=GIT_CLEANUP_TIMEOUT)
subprocess.run(["git", "lfs", "prune"], cwd=FINALIZED, check=True, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, encoding="utf8", timeout=GIT_CLEANUP_TIMEOUT)
run(["git", "gc"], FINALIZED)
run(["git", "lfs", "prune"], FINALIZED)
cloudlog.event("Done git cleanup", duration=time.monotonic() - t)
except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
except subprocess.CalledProcessError:
cloudlog.exception(f"Failed git cleanup, took {time.monotonic() - t:.3f} s")
set_consistent_flag(True)
@@ -495,6 +492,9 @@ def main() -> None:
update_failed_count = 0 # TODO: Load from param?
wait_helper = WaitTimeHelper()
# invalidate old finalized update
set_consistent_flag(False)
# set initial state
params.put("UpdaterState", "idle")
+9 -156
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
from dataclasses import asdict, dataclass
from dataclasses import dataclass
from functools import cache
import json
import os
@@ -14,7 +14,6 @@ RELEASE_BRANCHES = ['StarPilot', 'StarPilot-Vetting']
TESTED_BRANCHES = RELEASE_BRANCHES + ['StarPilot-Staging', 'StarPilot-Testing']
BUILD_METADATA_FILENAME = "build.json"
BUILD_METADATA_CACHE_FILENAME = "starpilot_build_metadata_cache.json"
training_version: str = "0.2.0"
terms_version: str = "2"
@@ -133,133 +132,6 @@ def build_metadata_from_dict(build_metadata: dict) -> BuildMetadata:
is_dirty=False))
def _read_text_file(path: pathlib.Path) -> str | None:
try:
return path.read_text(encoding="utf-8").strip()
except OSError:
return None
def _read_git_ref(git_folder: pathlib.Path, ref: str) -> str | None:
ref_value = _read_text_file(git_folder / ref)
if ref_value:
return ref_value.splitlines()[0].strip()
packed_refs = _read_text_file(git_folder / "packed-refs")
if packed_refs is None:
return None
for line in packed_refs.splitlines():
if not line or line.startswith(("#", "^")):
continue
parts = line.split()
if len(parts) == 2 and parts[1] == ref:
return parts[0]
return None
def _read_git_config_value(git_folder: pathlib.Path, section: str, key: str) -> str | None:
config = _read_text_file(git_folder / "config")
if config is None:
return None
current_section = None
for raw_line in config.splitlines():
line = raw_line.strip()
if not line or line.startswith(("#", ";")):
continue
if line.startswith("[") and line.endswith("]"):
current_section = line[1:-1]
continue
if current_section != section or "=" not in line:
continue
config_key, value = line.split("=", 1)
if config_key.strip() == key:
return value.strip()
return None
def _read_git_metadata(path: str) -> dict[str, str] | None:
git_folder = pathlib.Path(path) / ".git"
if not git_folder.is_dir():
return None
head = _read_text_file(git_folder / "HEAD")
if not head:
return None
branch = "HEAD"
commit = head.splitlines()[0].strip()
if head.startswith("ref:"):
ref = head.split(":", 1)[1].strip()
branch = ref.rsplit("/", 1)[-1]
commit = _read_git_ref(git_folder, ref)
if not commit:
return None
remote = _read_git_config_value(git_folder, f'branch "{branch}"', "remote") or "origin"
origin = _read_git_config_value(git_folder, f'remote "{remote}"', "url")
if origin is None and remote != "origin":
origin = _read_git_config_value(git_folder, 'remote "origin"', "url")
return {
"branch": branch,
"commit": commit,
"origin": origin or "",
}
def _build_metadata_cache_path(path: str) -> pathlib.Path | None:
if not (pathlib.Path(path) / ".git").is_dir():
return None
cache_root = pathlib.Path(os.environ.get("OPENPILOT_BUILD_METADATA_CACHE_ROOT", "/cache/starpilot"))
return cache_root / BUILD_METADATA_CACHE_FILENAME
def _read_cached_build_metadata(path: str, git_metadata: dict[str, str]) -> BuildMetadata | None:
cache_path = _build_metadata_cache_path(path)
if cache_path is None:
return None
try:
cache_payload = json.loads(cache_path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
return None
if cache_payload.get("path") != str(pathlib.Path(path).resolve()) or cache_payload.get("git_metadata") != git_metadata:
return None
build_metadata = cache_payload.get("build_metadata")
if not isinstance(build_metadata, dict):
return None
return build_metadata_from_dict(build_metadata)
def _write_cached_build_metadata(path: str, git_metadata: dict[str, str], build_metadata: BuildMetadata) -> None:
cache_path = _build_metadata_cache_path(path)
if cache_path is None:
return
try:
cache_path.parent.mkdir(parents=True, exist_ok=True)
cache_path.write_text(json.dumps({
"path": str(pathlib.Path(path).resolve()),
"git_metadata": git_metadata,
"build_metadata": asdict(build_metadata),
}, separators=(",", ":")), encoding="utf-8")
except OSError:
pass
def get_build_metadata(path: str = BASEDIR) -> BuildMetadata:
build_metadata_path = pathlib.Path(path) / BUILD_METADATA_FILENAME
@@ -270,34 +142,15 @@ def get_build_metadata(path: str = BASEDIR) -> BuildMetadata:
git_folder = pathlib.Path(path) / ".git"
if git_folder.exists():
prebuilt = is_prebuilt(path)
git_metadata = _read_git_metadata(path)
if prebuilt and git_metadata is not None:
cached_metadata = _read_cached_build_metadata(path, git_metadata)
if cached_metadata is not None:
return cached_metadata
build_metadata = BuildMetadata(git_metadata["branch"],
OpenpilotMetadata(
version=get_version(path),
release_notes=get_release_notes(path),
git_commit=git_metadata["commit"],
git_origin=git_metadata["origin"] or get_origin(path),
git_commit_date=get_commit_date(path, git_metadata["commit"]),
build_style="unknown",
is_dirty=False))
_write_cached_build_metadata(path, git_metadata, build_metadata)
return build_metadata
return BuildMetadata(get_short_branch(path),
OpenpilotMetadata(
version=get_version(path),
release_notes=get_release_notes(path),
git_commit=get_commit(path),
git_origin=get_origin(path),
git_commit_date=get_commit_date(path),
build_style="unknown",
is_dirty=is_dirty(path)))
OpenpilotMetadata(
version=get_version(path),
release_notes=get_release_notes(path),
git_commit=get_commit(path),
git_origin=get_origin(path),
git_commit_date=get_commit_date(path),
build_style="unknown",
is_dirty=is_dirty(path)))
cloudlog.exception("unable to get build metadata")
raise Exception("invalid build metadata")