Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lots of improvements to deploy #91

Merged
merged 7 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions robotpy_installer/cli_deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

from os.path import join, splitext

from . import pyproject, sshcontroller
from . import pyproject, roborio_utils, sshcontroller
from .installer import PipInstallError, PythonMissingError, RobotpyInstaller
from .errors import Error
from .utils import handle_cli_error, print_err, yesno
Expand Down Expand Up @@ -327,6 +327,10 @@ def _ensure_requirements(
python_exists = False
requirements_installed = False

# does c++/java exist
with wrap_ssh_error("removing c++/java user programs"):
cpp_java_exists = roborio_utils.uninstall_cpp_java_lvuser(ssh)

# does python exist
with wrap_ssh_error("checking if python exists"):
python_exists = (
Expand Down Expand Up @@ -370,21 +374,31 @@ def _ensure_requirements(
if force_install:
requirements_installed = False

if not python_exists or not requirements_installed:
if cpp_java_exists or not python_exists or not requirements_installed:
if no_install and not python_exists:
raise Error(
"python3 was not found on the roboRIO\n"
"- could not install it because no-install was specified\n"
"- Use 'python -m robotpy installer install-python' to install python separately"
)

# This also will give more memory
ssh.exec_bash(
". /etc/profile.d/frc-path.sh",
". /etc/profile.d/natinst-path.sh",
roborio_utils.kill_robot_cmd,
)

installer = RobotpyInstaller()
with installer.connect_to_robot(
project_path=project_path,
main_file=main_file,
ignore_image_version=ignore_image_version,
ssh=ssh,
):
if cpp_java_exists:
roborio_utils.uninstall_cpp_java_admin(installer.ssh)

if not python_exists:
try:
installer.install_python()
Expand All @@ -395,6 +409,9 @@ def _ensure_requirements(
) from e

if not requirements_installed:
# pip is greedy
installer.ensure_more_memory()

logger.info("Installing project requirements on RoboRIO:")
assert project is not None
packages = project.get_install_list()
Expand Down
31 changes: 30 additions & 1 deletion robotpy_installer/cli_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import shutil
import typing

from . import roborio_utils
from .utils import handle_cli_error

from .installer import (
Expand Down Expand Up @@ -151,6 +152,33 @@ def run(
installer.uninstall_python()


class InstallerUninstallJavaCpp:
"""
Uninstall FRC Java/C++ programs from a RoboRIO
"""

def __init__(self, parser: argparse.ArgumentParser) -> None:
_add_ssh_options(parser)

@handle_cli_error
def run(
self,
project_path: pathlib.Path,
main_file: pathlib.Path,
ignore_image_version: bool,
robot: typing.Optional[str],
):
installer = RobotpyInstaller()
with installer.connect_to_robot(
project_path=project_path,
main_file=main_file,
robot_or_team=robot,
ignore_image_version=ignore_image_version,
):
if not roborio_utils.uninstall_cpp_java_lvuser(installer.ssh):
roborio_utils.uninstall_cpp_java_admin(installer.ssh)


#
# Installer pip things
#
Expand Down Expand Up @@ -290,7 +318,7 @@ def run(
main_file=main_file,
robot_or_team=robot,
ignore_image_version=ignore_image_version,
log_disk_usage=False,
log_usage=False,
):
installer.pip_list()

Expand Down Expand Up @@ -347,4 +375,5 @@ class Installer:
("list", InstallerList),
("uninstall", InstallerUninstall),
("uninstall-python", InstallerUninstallPython),
("uninstall-frc-java-cpp", InstallerUninstallJavaCpp),
]
50 changes: 48 additions & 2 deletions robotpy_installer/cli_sync.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import argparse
import inspect
import logging
import os
import pathlib
import subprocess
import sys
import tempfile

from .utils import handle_cli_error
from packaging.version import Version

from .utils import handle_cli_error, yesno


from .installer import RobotpyInstaller
Expand Down Expand Up @@ -83,6 +88,27 @@ def run(
# parse pyproject.toml to determine the requirements
project = pyproject.load(project_path, write_if_missing=True)

# Get the local version and don't accidentally downgrade them
try:
local_robotpy_version = Version(pyproject.robotpy_installed_version())
if project.robotpy_version < local_robotpy_version:
logger.warning(
"pyproject.toml robotpy version is older than currently installed version"
)
print()
msg = (
f"Version currently installed: {local_robotpy_version}\n"
f"Version in `pyproject.toml`: {project.robotpy_version}\n"
"- Should we downgrade robotpy?"
)
if not yesno(msg):
print(
"Please update your pyproject.toml with the desired version of robotpy"
)
return False
except pyproject.NoRobotpyError:
pass

packages = project.get_install_list()

logger.info("Robot project requirements:")
Expand Down Expand Up @@ -123,4 +149,24 @@ def run(
pip_args.append("--user")
pip_args.extend(packages)

os.execv(sys.executable, pip_args)
# POSIX systems are easy, just execv and we're done
if sys.platform != "win32":
os.execv(sys.executable, pip_args)

with tempfile.NamedTemporaryFile("w", delete=False, suffix=".py") as fp:
fp.write(
inspect.cleandoc(
f"""
import os, subprocess
subprocess.run({pip_args!r})
print()
input("Install complete, press enter to continue")
os.unlink(__file__)
"""
)
)

print("pip is launching in a new window to complete the installation")
subprocess.Popen(
[sys.executable, fp.name], creationflags=subprocess.CREATE_NEW_CONSOLE
)
58 changes: 55 additions & 3 deletions robotpy_installer/installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ def __init__(self, *, log_startup: bool = True):
self._image_version_ok = False
self._robot_pip_ok = False

self._webserver_stopped = False
self._webserver_needs_start = False

if log_startup:
logger.info("RobotPy Installer %s", __version__)
logger.info("-> caching files at %s", self.cache_root)
Expand All @@ -85,7 +88,7 @@ def connect_to_robot(
main_file: pathlib.Path,
robot_or_team: typing.Union[None, str, int] = None,
ignore_image_version: bool = False,
log_disk_usage: bool = True,
log_usage: bool = True,
no_resolve: bool = False,
ssh: typing.Optional[SshController] = None,
):
Expand All @@ -106,13 +109,20 @@ def connect_to_robot(

self.ensure_image_version(ignore_image_version)

if log_disk_usage:
if log_usage:
self.show_disk_space()
self.show_mem_usage()

yield

if log_disk_usage:
if self._webserver_needs_start:
self.ssh.exec_cmd("/etc/init.d/systemWebServer start")
self._webserver_needs_start = False
self._webserver_stopped = False

if log_usage:
self.show_disk_space()
self.show_mem_usage()

self._ssh = None

Expand Down Expand Up @@ -243,6 +253,48 @@ def show_disk_space(

return size, used, pct

def show_mem_usage(self):
with catch_ssh_error("checking memory info"):
result = self.ssh.check_output("cat /proc/meminfo")

total_kb = 0
available_kb = 0
found = 0

for line in result.strip().splitlines():
if line.startswith("MemTotal:"):
total_kb = int(line.split()[1])
found += 1
elif line.startswith("MemAvailable"):
available_kb = int(line.split()[1])
found += 1

if found == 2:
break

used_kb = total_kb - available_kb
pct_free = (available_kb / float(total_kb)) * 100.0

logger.info(
"-> RoboRIO memory %.1fM/%.1fM (%.0f%% full)",
used_kb / 1000.0,
total_kb / 1000.0,
pct_free,
)

def ensure_more_memory(self):
if self._webserver_stopped:
return

# This takes up a ton of memory and we need the memory...
with catch_ssh_error("Stopping NI webserver"):
result = self.ssh.exec_bash('[ -z "$(ps | grep NIWebServiceContainer)" ]')
if result.returncode != 0:
self.ssh.exec_cmd("/etc/init.d/systemWebServer stop")
self._webserver_needs_start = True

self._webserver_stopped = True

def ensure_image_version(self, ignore_image_version: bool):
if self._image_version_ok:
return
Expand Down
28 changes: 18 additions & 10 deletions robotpy_installer/pyproject.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,20 @@ class RobotPyProjectToml:

"""

#: Version of robotpy that is depended on
robotpy_version: Version

robotpy_extras: typing.List[str] = dataclasses.field(default_factory=list)

#: Requirement for the robotpy meta package -- all RobotPy projects must
#: depend on it
robotpy_requires: Requirement
@property
def robotpy_requires(self) -> Requirement:
if self.robotpy_extras:
extras = f"[{','.join(self.robotpy_extras)}]"
else:
extras = ""
return Requirement(f"robotpy{extras}=={self.robotpy_version}")

#: Requirements for
requires: typing.List[Requirement] = dataclasses.field(default_factory=list)
Expand Down Expand Up @@ -139,7 +150,7 @@ def load(
if not pyproject_path.exists():
if default_if_missing:
return RobotPyProjectToml(
robotpy_requires=Requirement(f"robotpy=={robotpy_installed_version()}")
robotpy_version=Version(robotpy_installed_version())
)
if write_if_missing:
write_default_pyproject(project_path)
Expand Down Expand Up @@ -175,13 +186,6 @@ def load(
else:
robotpy_extras = [str(robotpy_extras_any)]

# Construct the full requirement
robotpy_pkg = "robotpy"
if robotpy_extras:
extras_s = ",".join(robotpy_extras)
robotpy_pkg = f"robotpy[{extras_s}]"
robotpy_requires = Requirement(f"{robotpy_pkg}=={robotpy_version}")

requires_any = robotpy_data.get("requires")
if isinstance(requires_any, list):
requires = []
Expand All @@ -192,7 +196,11 @@ def load(
else:
requires = []

return RobotPyProjectToml(robotpy_requires=robotpy_requires, requires=requires)
return RobotPyProjectToml(
robotpy_version=robotpy_version,
robotpy_extras=robotpy_extras,
requires=requires,
)


def are_requirements_met(
Expand Down
Loading