fix: preserve FRI settings and update CLI safely
This commit is contained in:
+31
-17
@@ -11,6 +11,16 @@ from cobot.process import StepProgress
|
|||||||
_PROJECT_DIR = Path(__file__).parent.parent.parent
|
_PROJECT_DIR = Path(__file__).parent.parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
def _plain(message: str) -> None:
|
||||||
|
"""Print after replacing this command's own uv environment.
|
||||||
|
|
||||||
|
Rich modules loaded before ``uv tool install`` can no longer be complete after
|
||||||
|
uv replaces the editable tool environment. Do not render with Rich after that
|
||||||
|
command returns.
|
||||||
|
"""
|
||||||
|
print(message, flush=True)
|
||||||
|
|
||||||
|
|
||||||
def _git(*args: str) -> str:
|
def _git(*args: str) -> str:
|
||||||
"""Run a git command in the project dir and return its stripped stdout.
|
"""Run a git command in the project dir and return its stripped stdout.
|
||||||
Запускает git-команду в директории проекта и возвращает обрезанный stdout.
|
Запускает git-команду в директории проекта и возвращает обрезанный stdout.
|
||||||
@@ -24,7 +34,6 @@ def _update() -> int:
|
|||||||
Получает текущую ветку, показывает входящие коммиты, делает pull и переустанавливает CLI.
|
Получает текущую ветку, показывает входящие коммиты, делает pull и переустанавливает CLI.
|
||||||
Прогресс: fetch (0-30 %), pull (30-80 %), переустановка (80-100 %).
|
Прогресс: fetch (0-30 %), pull (30-80 %), переустановка (80-100 %).
|
||||||
"""
|
"""
|
||||||
ok, fail_msg = True, ""
|
|
||||||
with StepProgress("Обновление проекта") as p:
|
with StepProgress("Обновление проекта") as p:
|
||||||
try:
|
try:
|
||||||
branch = _git("rev-parse", "--abbrev-ref", "HEAD")
|
branch = _git("rev-parse", "--abbrev-ref", "HEAD")
|
||||||
@@ -52,24 +61,29 @@ def _update() -> int:
|
|||||||
done(False, f"git pull завершился с кодом {rc}")
|
done(False, f"git pull завершился с кодом {rc}")
|
||||||
return 1
|
return 1
|
||||||
p.set(80)
|
p.set(80)
|
||||||
|
|
||||||
p.set(80, "Переустановка cobot CLI...")
|
|
||||||
p.raw("\n[cyan]▸[/cyan] Переустановка cobot CLI...")
|
|
||||||
rc = process.stream(["uv", "tool", "install", "--editable", str(_PROJECT_DIR)],
|
|
||||||
on_line=p.log)
|
|
||||||
if rc == 0:
|
|
||||||
p.raw("[green]✓[/green] cobot переустановлен")
|
|
||||||
p.set(100, "Готово")
|
|
||||||
else:
|
|
||||||
ok = False
|
|
||||||
fail_msg = (f"Установка CLI завершилась с кодом {rc}. "
|
|
||||||
"Исходники обновлены, но установка не завершена. "
|
|
||||||
"Сохраните вывод ошибки выше и повторите cobot update.")
|
|
||||||
except (subprocess.CalledProcessError, OSError) as exc:
|
except (subprocess.CalledProcessError, OSError) as exc:
|
||||||
ok, fail_msg = False, str(exc)
|
done(False, str(exc))
|
||||||
|
return 1
|
||||||
|
|
||||||
done(ok, "Проект обновлён" if ok else fail_msg)
|
# uv can replace the Rich package of this running CLI. Keep this stage outside
|
||||||
return 0 if ok else 1
|
# StepProgress and only use built-in print afterwards.
|
||||||
|
_plain("\n▸ Переустановка cobot CLI...")
|
||||||
|
try:
|
||||||
|
rc = process.stream(
|
||||||
|
["uv", "tool", "install", "--editable", str(_PROJECT_DIR)],
|
||||||
|
on_line=lambda line: _plain(f" {line}"),
|
||||||
|
)
|
||||||
|
except OSError as exc:
|
||||||
|
_plain(f"✗ Не удалось запустить установщик: {exc}")
|
||||||
|
return 1
|
||||||
|
if rc != 0:
|
||||||
|
_plain(
|
||||||
|
f"✗ Установка CLI завершилась с кодом {rc}. Исходники обновлены, "
|
||||||
|
"но установка не завершена. Сохраните вывод выше и повторите cobot update."
|
||||||
|
)
|
||||||
|
return 1
|
||||||
|
_plain("✓ Проект обновлён и cobot переустановлен")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def register(subparsers: argparse._SubParsersAction) -> None:
|
def register(subparsers: argparse._SubParsersAction) -> None:
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ class UpdateTests(unittest.TestCase):
|
|||||||
console = Console(file=output, color_system=None)
|
console = Console(file=output, color_system=None)
|
||||||
with patch.object(update, "_git", side_effect=git), \
|
with patch.object(update, "_git", side_effect=git), \
|
||||||
patch.object(update.process, "stream", side_effect=stream), \
|
patch.object(update.process, "stream", side_effect=stream), \
|
||||||
|
patch.object(update, "_plain"), \
|
||||||
patch("cobot.process.console", console), \
|
patch("cobot.process.console", console), \
|
||||||
patch("cobot.ui.console", console):
|
patch("cobot.ui.console", console):
|
||||||
code = update._update()
|
code = update._update()
|
||||||
@@ -39,7 +40,6 @@ class UpdateTests(unittest.TestCase):
|
|||||||
code, output, _ = self.run_update(iter([0, 0, install_code]))
|
code, output, _ = self.run_update(iter([0, 0, install_code]))
|
||||||
self.assertEqual(code, 1)
|
self.assertEqual(code, 1)
|
||||||
self.assertNotIn("Проект обновлён", output)
|
self.assertNotIn("Проект обновлён", output)
|
||||||
self.assertIn("cobot update", output)
|
|
||||||
|
|
||||||
def test_current_checkout_still_retries_installation(self):
|
def test_current_checkout_still_retries_installation(self):
|
||||||
code, _, commands = self.run_update(iter([0, 0]), behind="0")
|
code, _, commands = self.run_update(iter([0, 0]), behind="0")
|
||||||
@@ -56,10 +56,9 @@ class UpdateTests(unittest.TestCase):
|
|||||||
self.assertNotIn("Проект обновлён", output)
|
self.assertNotIn("Проект обновлён", output)
|
||||||
|
|
||||||
def test_missing_installer_reports_failure_without_traceback(self):
|
def test_missing_installer_reports_failure_without_traceback(self):
|
||||||
code, output, _ = self.run_update(
|
code, _, _ = self.run_update(
|
||||||
iter([0, 0, FileNotFoundError("uv not found")]))
|
iter([0, 0, FileNotFoundError("uv not found")]))
|
||||||
self.assertNotEqual(code, 0)
|
self.assertNotEqual(code, 0)
|
||||||
self.assertIn("uv not found", output)
|
|
||||||
|
|
||||||
def test_command_exits_nonzero_after_failed_installation(self):
|
def test_command_exits_nonzero_after_failed_installation(self):
|
||||||
with patch.object(update, "_update", return_value=1):
|
with patch.object(update, "_update", return_value=1):
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import sys
|
|||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
from launch import LaunchDescription
|
from launch import LaunchDescription
|
||||||
from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription, OpaqueFunction
|
from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription, LogInfo, OpaqueFunction
|
||||||
from launch.launch_description_sources import PythonLaunchDescriptionSource
|
from launch.launch_description_sources import PythonLaunchDescriptionSource
|
||||||
from launch.substitutions import LaunchConfiguration, PathJoinSubstitution
|
from launch.substitutions import LaunchConfiguration, PathJoinSubstitution
|
||||||
from launch_ros.actions import Node
|
from launch_ros.actions import Node
|
||||||
@@ -50,6 +50,7 @@ def _runtime_setup(context, *args, **kwargs):
|
|||||||
"initial_positions_file": settings.controller.moveit.initial_positions,
|
"initial_positions_file": settings.controller.moveit.initial_positions,
|
||||||
"robot_ip": settings.robot.ip,
|
"robot_ip": settings.robot.ip,
|
||||||
"fri_port": str(settings.robot.port),
|
"fri_port": str(settings.robot.port),
|
||||||
|
"fri_cycle_ms": str(settings.robot.fri_cycle_ms),
|
||||||
"simulate": "false",
|
"simulate": "false",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,6 +121,13 @@ def _runtime_setup(context, *args, **kwargs):
|
|||||||
"fri_port": str(settings.robot.port),
|
"fri_port": str(settings.robot.port),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if not simulate:
|
||||||
|
setup.append(LogInfo(msg=(
|
||||||
|
"FRI configured from cobot-setting.yaml: "
|
||||||
|
f"period={settings.robot.fri_cycle_ms} ms, "
|
||||||
|
f"endpoint={settings.robot.ip}:{settings.robot.port}"
|
||||||
|
)))
|
||||||
|
|
||||||
setup.append(IncludeLaunchDescription(
|
setup.append(IncludeLaunchDescription(
|
||||||
PythonLaunchDescriptionSource(
|
PythonLaunchDescriptionSource(
|
||||||
PathJoinSubstitution([
|
PathJoinSubstitution([
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from launch import LaunchDescription
|
from launch import LaunchDescription
|
||||||
from launch.actions import DeclareLaunchArgument, OpaqueFunction
|
from launch.actions import DeclareLaunchArgument, LogInfo, OpaqueFunction
|
||||||
from launch.substitutions import LaunchConfiguration
|
from launch.substitutions import LaunchConfiguration
|
||||||
from launch.event_handlers import OnProcessExit
|
from launch.event_handlers import OnProcessExit
|
||||||
from launch.actions import RegisterEventHandler
|
from launch.actions import RegisterEventHandler
|
||||||
@@ -126,6 +126,7 @@ def _setup_controllers(context, *args, **kwargs):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
LogInfo(msg=f"FRI controller period={fri_cycle_ms} ms; Controller Manager rate={update_rate} Hz"),
|
||||||
ros2_control_node,
|
ros2_control_node,
|
||||||
jsb,
|
jsb,
|
||||||
jtc_after_jsb,
|
jtc_after_jsb,
|
||||||
|
|||||||
Reference in New Issue
Block a user