from __future__ import annotations import argparse import os import shutil import subprocess from pathlib import Path from cobot import process, ui from cobot import privilege from cobot.ui import done, header from cobot.commands.docker_setup import run as _docker_setup # Root directory of the project, used as the working directory for colcon builds. # Корневая директория проекта, используется как рабочая директория для сборки colcon. _PROJECT_DIR = Path(__file__).parent.parent.parent # ROS2 distribution name targeted by this installer. # Название дистрибутива ROS2, который устанавливает этот скрипт. _DISTRO = "jazzy" # Webots simulator version targeted by this installer. # Версия симулятора Webots, устанавливаемая этим скриптом. _WEBOTS_VERSION = "2025a" # Directory that contains the shell scripts used by this command. # Директория с shell-скриптами, используемыми этой командой. _SCRIPTS_DIR = _PROJECT_DIR / "scripts" # apt packages that must exist before rosdep can install the pip-based keys # (python3-pip / dev / venv). Their absence is what produced the "pip is not # installed" failure in the screenshots. # apt-пакеты, необходимые до того как rosdep сможет установить pip-зависимости. # Именно их отсутствие давало ошибку "pip is not installed" на скриншотах. _APT_PREREQS = ["python3-pip", "python3-dev", "python3-venv"] # OS and tool detection helpers # Вспомогательные функции для определения ОС и наличия инструментов def _detect_ubuntu_2404() -> bool: """Return True if the current OS is Ubuntu 24.04 (Noble). Возвращает True, если текущая ОС - Ubuntu 24.04 (Noble). """ path = Path("/etc/os-release") if not path.exists(): return False info: dict[str, str] = {} for line in path.read_text().splitlines(): if "=" in line: k, _, v = line.partition("=") info[k.strip()] = v.strip().strip('"') return info.get("ID") == "ubuntu" and info.get("VERSION_ID") == "24.04" def _detect_ros2() -> bool: """Return True if ROS2 Jazzy is already installed under /opt/ros/jazzy. Возвращает True, если ROS2 Jazzy уже установлен в /opt/ros/jazzy. """ return Path(f"/opt/ros/{_DISTRO}").is_dir() def webots_installed() -> bool: """Return True if the Webots binary is available on PATH. Возвращает True, если бинарный файл Webots доступен в PATH. """ return shutil.which("webots") is not None def _ros2_env() -> dict: """Build an environment dict with ROS2 variables sourced from setup.bash. Sources /opt/ros/jazzy/setup.bash in a subprocess, captures all exported variables and merges them into a copy of os.environ. Falls back to plain os.environ if the setup file does not exist yet. Формирует словарь окружения с переменными ROS2, полученными из setup.bash. Возвращает чистый os.environ если файл setup.bash ещё не существует. """ setup = Path(f"/opt/ros/{_DISTRO}/setup.bash") if not setup.exists(): return os.environ.copy() result = subprocess.run( ["bash", "-c", f"source {setup} && env"], capture_output=True, text=True, ) env = os.environ.copy() for line in result.stdout.splitlines(): if "=" in line: k, _, v = line.partition("=") env[k] = v # CMake's find_package(Python3) ignores PATH and uses its own search logic, # so we must pin it explicitly to the system Python where catkin_pkg is installed. # CMake игнорирует PATH при поиске Python через find_package(Python3), # поэтому явно указываем системный Python, где установлен catkin_pkg. env["Python3_EXECUTABLE"] = "/usr/bin/python3" env["PYTHON_EXECUTABLE"] = "/usr/bin/python3" # Keep PATH clean so other tools (rosdep, colcon itself) use system Python. # Чистим PATH чтобы другие инструменты тоже использовали системный Python. _SYSTEM_PATHS = ["/usr/bin", "/usr/local/bin"] existing = env.get("PATH", "").split(":") env["PATH"] = ":".join( _SYSTEM_PATHS + [p for p in existing if p not in _SYSTEM_PATHS] ) return env # Bash-script runner that understands PROGRESS::