Beispiel #1
0
    def run(self, options, robot_class, **static_options):

        try:
            from robotpy_installer import installer
        except ImportError:
            raise ImportError(
                "You must have the robotpy-installer package installed to deploy code!"
            )

        from .. import config

        config.mode = "upload"

        # run the test suite before uploading
        if not options.skip_tests:
            from .cli_test import PyFrcTest

            tester = PyFrcTest()

            retval = tester.run_test(
                [], robot_class, options.builtin, ignore_missing_test=True
            )
            if retval != 0:
                print_err("ERROR: Your robot tests failed, aborting upload.")
                if not sys.stdin.isatty():
                    print_err("- Use --skip-tests if you want to upload anyways")
                    return retval

                print()
                if not yesno("- Upload anyways?"):
                    return retval

                if not yesno("- Are you sure? Your robot code may crash!"):
                    return retval

                print()
                print("WARNING: Uploading code against my better judgement...")

        # upload all files in the robot.py source directory
        robot_file = abspath(inspect.getfile(robot_class))
        robot_path = dirname(robot_file)
        robot_filename = basename(robot_file)
        cfg_filename = join(robot_path, ".deploy_cfg")

        if not options.nonstandard and robot_filename != "robot.py":
            print_err(
                "ERROR: Your robot code must be in a file called robot.py (launched from %s)!"
                % robot_filename
            )
            print_err()
            print_err(
                "If you really want to do this, then specify the --nonstandard argument"
            )
            return 1

        # This probably should be configurable... oh well

        deploy_dir = PurePosixPath("/home/lvuser")
        py_deploy_subdir = "py"
        py_new_deploy_subdir = "py_new"
        py_deploy_dir = deploy_dir / py_deploy_subdir

        # note below: deployed_cmd appears that it only can be a single line

        # In 2015, there were stdout/stderr issues. In 2016, they seem to
        # have been fixed, but need to use -u for it to really work properly

        if options.debug:
            compileall_flags = ""
            deployed_cmd = (
                "env LD_LIBRARY_PATH=/usr/local/frc/lib/ /usr/local/bin/python3 -u %s/%s -v run"
                % (py_deploy_dir, robot_filename)
            )
            deployed_cmd_fname = "robotDebugCommand"
            extra_cmd = "touch /tmp/frcdebug; chown lvuser:ni /tmp/frcdebug"
            bash_cmd = "/bin/bash -cex"
        else:
            compileall_flags = "-O"
            deployed_cmd = (
                "env LD_LIBRARY_PATH=/usr/local/frc/lib/ /usr/local/bin/python3 -u -O %s/%s run"
                % (py_deploy_dir, robot_filename)
            )
            deployed_cmd_fname = "robotCommand"
            extra_cmd = ""
            bash_cmd = "/bin/bash -ce"

        if options.in_place:
            replace_cmd = "true"
            py_new_deploy_subdir = py_deploy_subdir
        else:
            replace_cmd = (
                "rm -rf %(py_deploy_dir)s; mv %(py_new_deploy_dir)s %(py_deploy_dir)s"
            )

        py_new_deploy_dir = deploy_dir / py_new_deploy_subdir
        replace_cmd %= {
            "py_deploy_dir": py_deploy_dir,
            "py_new_deploy_dir": py_new_deploy_dir,
        }

        check_version = (
            '/usr/local/bin/python3 -c "exec(open(\\"$SITEPACKAGES/wpilib/version.py\\", \\"r\\").read(), globals()); print(\\"WPILib version on robot is \\" + __version__);exit(0) if __version__ == \\"%s\\" else exit(89)"'
            % wpilib.__version__
        )
        if options.no_version_check:
            check_version = ""

        check_startup_dlls = '(if [ "$(grep ^StartupDLLs /etc/natinst/share/ni-rt.ini)" != "" ]; then exit 91; fi)'

        # This is a nasty bit of code now...
        sshcmd = inspect.cleandoc(
            """
            %(bash_cmd)s '[ -x /usr/local/bin/python3 ] || exit 87
            SITEPACKAGES=$(/usr/local/bin/python3 -c "import site; print(site.getsitepackages()[0])")
            [ -f $SITEPACKAGES/wpilib/version.py ] || exit 88
            %(check_version)s
            echo "%(deployed_cmd)s" > %(deploy_dir)s/%(deployed_cmd_fname)s
            %(extra_cmd)s
            %(check_startup_dlls)s
            rm -rf %(py_new_deploy_dir)s
            '
        """
        )

        sshcmd %= locals()

        sshcmd = re.sub("\n+", ";", sshcmd)

        nc_thread = None

        hostname_or_team = options.robot
        if not hostname_or_team and options.team:
            hostname_or_team = options.team

        try:
            controller = installer.ssh_from_cfg(
                cfg_filename,
                username="******",
                password="",
                hostname=hostname_or_team,
                allow_mitm=True,
                no_resolve=options.no_resolve,
            )

            try:
                # Housekeeping first
                logger.debug("SSH: %s", sshcmd)
                controller.ssh(sshcmd)
            except installer.SshExecError as e:
                doret = True
                if e.retval == 87:
                    print_err(
                        "ERROR: python3 was not found on the roboRIO: have you installed robotpy?"
                    )
                elif e.retval == 88:
                    print_err(
                        "ERROR: WPILib was not found on the roboRIO: have you installed robotpy?"
                    )
                elif e.retval == 89:
                    print_err("ERROR: expected WPILib version %s" % wpilib.__version__)
                    print_err()
                    print_err("You should either:")
                    print_err(
                        "- If the robot version is older, upgrade the RobotPy on your robot"
                    )
                    print_err("- Otherwise, upgrade pyfrc on your computer")
                    print_err()
                    print_err(
                        "Alternatively, you can specify --no-version-check to skip this check"
                    )
                elif e.retval == 90:
                    print_err("ERROR: error running compileall")
                elif e.retval == 91:
                    # Not an error; ssh in as admin and fix the startup dlls (Saves 24M of RAM)
                    # -> https://github.com/wpilibsuite/EclipsePlugins/pull/154
                    logger.info("Fixing StartupDLLs to save RAM...")
                    controller.username = "******"
                    controller.ssh(
                        'sed -i -e "s/^StartupDLLs/;StartupDLLs/" /etc/natinst/share/ni-rt.ini'
                    )

                    controller.username = "******"
                    doret = False
                else:
                    print_err("ERROR: %s" % e)

                if doret:
                    return 1

            # Copy the files over, copy to a temporary directory first
            # -> this is inefficient, but it's easier in sftp
            tmp_dir = tempfile.mkdtemp()
            try:
                py_tmp_dir = join(tmp_dir, py_new_deploy_subdir)
                self._copy_to_tmpdir(py_tmp_dir, robot_path)
                controller.sftp(py_tmp_dir, deploy_dir, mkdir=not options.in_place)
            finally:
                shutil.rmtree(tmp_dir)

            # start the netconsole listener now if requested, *before* we
            # actually start the robot code, so we can see all messages
            if options.nc or options.nc_ds:
                from netconsole import run

                nc_event = threading.Event()
                nc_thread = threading.Thread(
                    target=run,
                    args=(controller.hostname,),
                    kwargs=dict(connect_event=nc_event, fakeds=options.nc_ds),
                    daemon=True,
                )
                nc_thread.start()
                nc_event.wait(5)
                logger.info("Netconsole is listening...")

            if not options.in_place:
                # Restart the robot code and we're done!
                sshcmd = (
                    "%(bash_cmd)s '"
                    + "%(replace_cmd)s;"
                    + "/usr/local/bin/python3 %(compileall_flags)s -m compileall -q -r 5 /home/lvuser/py;"
                    + ". /etc/profile.d/natinst-path.sh; "
                    + "chown -R lvuser:ni %(py_deploy_dir)s; "
                    + "sync; "
                    + "/usr/local/frc/bin/frcKillRobot.sh -t -r || true"
                    + "'"
                )

                sshcmd %= {
                    "bash_cmd": bash_cmd,
                    "compileall_flags": compileall_flags,
                    "py_deploy_dir": py_deploy_dir,
                    "replace_cmd": replace_cmd,
                }

                logger.debug("SSH: %s", sshcmd)
                controller.ssh(sshcmd)

        except installer.Error as e:
            print_err("ERROR: %s" % e)
            return 1
        else:
            print("\nSUCCESS: Deploy was successful!")

        if nc_thread is not None:
            nc_thread.join()

        return 0
Beispiel #2
0
    def run(self, options, robot_class, **static_options):

        try:
            from robotpy_installer import installer
        except ImportError:
            raise ImportError(
                "You must have the robotpy-installer package installed to deploy code!"
            )

        from .. import config
        config.mode = 'upload'

        # run the test suite before uploading
        if not options.skip_tests:
            from .cli_test import PyFrcTest

            tester = PyFrcTest()

            retval = tester.run_test([],
                                     robot_class,
                                     options.builtin,
                                     ignore_missing_test=True)
            if retval != 0:
                print_err("ERROR: Your robot tests failed, aborting upload.")
                if not sys.stdin.isatty():
                    print_err(
                        "- Use --skip-tests if you want to upload anyways")
                    return retval

                print()
                if not yesno('- Upload anyways?'):
                    return retval

                if not yesno('- Are you sure? Your robot code may crash!'):
                    return retval

                print()
                print("WARNING: Uploading code against my better judgement...")

        # upload all files in the robot.py source directory
        robot_file = abspath(inspect.getfile(robot_class))
        robot_path = dirname(robot_file)
        robot_filename = basename(robot_file)
        cfg_filename = join(robot_path, '.deploy_cfg')

        if not options.nonstandard and robot_filename != 'robot.py':
            print_err(
                "ERROR: Your robot code must be in a file called robot.py (launched from %s)!"
                % robot_filename)
            print_err()
            print_err(
                "If you really want to do this, then specify the --nonstandard argument"
            )
            return 1

        # This probably should be configurable... oh well

        deploy_dir = '/home/lvuser'
        py_deploy_dir = '%s/py' % deploy_dir

        # note below: deployed_cmd appears that it only can be a single line

        # In 2015, there were stdout/stderr issues. In 2016, they seem to
        # have been fixed, but need to use -u for it to really work properly

        if options.debug:
            deployed_cmd = 'env LD_LIBRARY_PATH=/usr/local/frc/rpath-lib/ /usr/local/frc/bin/netconsole-host /usr/local/bin/python3 -u %s/%s -v run' % (
                py_deploy_dir, robot_filename)
            deployed_cmd_fname = 'robotDebugCommand'
            extra_cmd = 'touch /tmp/frcdebug; chown lvuser:ni /tmp/frcdebug'
            bash_cmd = '/bin/bash -cex'
        else:
            deployed_cmd = 'env LD_LIBRARY_PATH=/usr/local/frc/rpath-lib/ /usr/local/frc/bin/netconsole-host /usr/local/bin/python3 -u -O %s/%s run' % (
                py_deploy_dir, robot_filename)
            deployed_cmd_fname = 'robotCommand'
            extra_cmd = ''
            bash_cmd = '/bin/bash -ce'

        if options.in_place:
            del_cmd = ''
        else:
            del_cmd = "[ -d %(py_deploy_dir)s ] && rm -rf %(py_deploy_dir)s"

        del_cmd %= {"py_deploy_dir": py_deploy_dir}

        check_version = '/usr/local/bin/python3 -c "exec(open(\\"$SITEPACKAGES/wpilib/version.py\\", \\"r\\").read(), globals()); print(\\"WPILib version on robot is \\" + __version__);exit(0) if __version__ == \\"%s\\" else exit(89)"' % wpilib.__version__
        if options.no_version_check:
            check_version = ''

        # This is a nasty bit of code now...
        sshcmd = inspect.cleandoc("""
            %(bash_cmd)s '[ -x /usr/local/bin/python3 ] || exit 87
            SITEPACKAGES=$(/usr/local/bin/python3 -c "import site; print(site.getsitepackages()[0])")
            [ -f $SITEPACKAGES/wpilib/version.py ] || exit 88
            %(check_version)s
            %(del_cmd)s
            echo "%(cmd)s" > %(deploy_dir)s/%(cmd_fname)s
            %(extra_cmd)s'
        """)

        sshcmd %= {
            'bash_cmd': bash_cmd,
            'del_cmd': del_cmd,
            'deploy_dir': deploy_dir,
            'cmd': deployed_cmd,
            'cmd_fname': deployed_cmd_fname,
            'extra_cmd': extra_cmd,
            'check_version': check_version
        }

        sshcmd = re.sub("\n+", ";", sshcmd)

        nc_thread = None

        try:
            controller = installer.ssh_from_cfg(cfg_filename,
                                                username='******',
                                                password='',
                                                hostname=options.robot,
                                                allow_mitm=True,
                                                no_resolve=options.no_resolve)

            # Housekeeping first
            logger.debug('SSH: %s', sshcmd)
            controller.ssh(sshcmd)

            # Copy the files over, copy to a temporary directory first
            # -> this is inefficient, but it's easier in sftp
            tmp_dir = tempfile.mkdtemp()
            py_tmp_dir = join(tmp_dir, 'py')

            try:
                self._copy_to_tmpdir(py_tmp_dir, robot_path)
                controller.sftp(py_tmp_dir,
                                deploy_dir,
                                mkdir=not options.in_place)
            finally:
                shutil.rmtree(tmp_dir)

            # start the netconsole listener now if requested, *before* we
            # actually start the robot code, so we can see all messages
            if options.nc:
                from netconsole import run
                nc_event = threading.Event()
                nc_thread = threading.Thread(target=run,
                                             kwargs={'init_event': nc_event},
                                             daemon=True)
                nc_thread.start()
                nc_event.wait(5)
                logger.info("Netconsole is listening...")

            if not options.in_place:
                # Restart the robot code and we're done!
                # TODO: add the following to the beginning of the next command:
                # '/usr/local/bin/python3 -m compileall -q -r 5 /home/lvuser/py;' + \
                # -> breaks because of http://bugs.python.org/issue29877
                sshcmd = "%(bash_cmd)s '" + \
                         '. /etc/profile.d/natinst-path.sh; ' + \
                         'chown -R lvuser:ni %(py_deploy_dir)s; ' + \
                         'sync; ' + \
                         '/usr/local/frc/bin/frcKillRobot.sh -t -r' + \
                         "'"

                sshcmd %= {
                    'bash_cmd': bash_cmd,
                    'py_deploy_dir': py_deploy_dir,
                }

                logger.debug('SSH: %s', sshcmd)
                controller.ssh(sshcmd)

        except installer.SshExecError as e:
            if e.retval == 87:
                print_err(
                    "ERROR: python3 was not found on the roboRIO: have you installed robotpy?"
                )
            elif e.retval == 88:
                print_err(
                    "ERROR: WPILib was not found on the roboRIO: have you installed robotpy?"
                )
            elif e.retval == 89:
                print_err("ERROR: expected WPILib version %s" %
                          wpilib.__version__)
                print_err()
                print_err("You should either:")
                print_err(
                    "- If the robot version is older, upgrade the RobotPy on your robot"
                )
                print_err("- Otherwise, upgrade pyfrc on your computer")
                print_err()
                print_err(
                    "Alternatively, you can specify --no-version-check to skip this check"
                )
            elif e.retval == 90:
                print_err("ERROR: error running compileall")
            else:
                print_err("ERROR: %s" % e)
            return 1
        except installer.Error as e:
            print_err("ERROR: %s" % e)
            return 1
        else:
            print("\nSUCCESS: Deploy was successful!")

        if nc_thread is not None:
            nc_thread.join()

        return 0