diff --git a/src/octoprint/plugins/softwareupdate/scripts/update-octoprint.py b/src/octoprint/plugins/softwareupdate/scripts/update-octoprint.py index 9c6a600f..14574f45 100644 --- a/src/octoprint/plugins/softwareupdate/scripts/update-octoprint.py +++ b/src/octoprint/plugins/softwareupdate/scripts/update-octoprint.py @@ -8,6 +8,8 @@ __copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms import errno import sys +import traceback +import time def _log_call(*lines): _log(lines, prefix=">", stream="call") @@ -61,39 +63,54 @@ def _execute(command, **kwargs): try: p = sarge.run(command, **kwargs) - p.wait_events() - except Exception as e: - import traceback - exception_lines = traceback.format_exc() - error = "Error while trying to run command {}: {}\n{}".format(joined_command, str(e), exception_lines) - return None, "", error + while len(p.commands) == 0: + # somewhat ugly... we can't use wait_events because + # the events might not be all set if an exception + # by sarge is triggered within the async process + # thread + time.sleep(0.01) + + # by now we should have a command, let's wait for its + # process to have been prepared + p.commands[0].process_ready.wait() + + if not p.commands[0].process: + # the process might have been set to None in case of any exception + print("Error while trying to run command {}".format(joined_command), file=sys.stderr) + return None, [], [] + except: + print("Error while trying to run command {}".format(joined_command), file=sys.stderr) + traceback.print_exc(file=sys.stderr) + return None, [], [] all_stdout = [] all_stderr = [] try: - while p.returncode is None: + while p.commands[0].poll() is None: lines = p.stderr.readlines(timeout=0.5) if lines: + lines = map(lambda x: _to_unicode(x, errors="replace"), lines) _log_stderr(*lines) all_stderr += list(lines) lines = p.stdout.readlines(timeout=0.5) if lines: + lines = map(lambda x: _to_unicode(x, errors="replace"), lines) _log_stdout(*lines) all_stdout += list(lines) - p.commands[0].poll() - finally: p.close() lines = p.stderr.readlines() if lines: + lines = map(lambda x: _to_unicode(x, errors="replace"), lines) _log_stderr(*lines) all_stderr += lines lines = p.stdout.readlines() if lines: + lines = map(lambda x: _to_unicode(x, errors="replace"), lines) _log_stdout(*lines) all_stdout += lines @@ -107,27 +124,31 @@ def _get_git_executables(): return GITS -def _git(args, cwd, verbose=False, git_executable=None): +def _git(args, cwd, git_executable=None): if git_executable is not None: commands = [git_executable] else: commands = _get_git_executables() for c in commands: + command = [c] + args try: - return _execute([c] + args, cwd=cwd) + return _execute(command, cwd=cwd) except EnvironmentError: e = sys.exc_info()[1] if e.errno == errno.ENOENT: continue - if verbose: - print("unable to run %s" % args[0]) - print(e) - return None, None, None + + print("Error while trying to run command {}".format(" ".join(command)), file=sys.stderr) + traceback.print_exc(file=sys.stderr) + return None, [], [] + except: + print("Error while trying to run command {}".format(" ".join(command)), file=sys.stderr) + traceback.print_exc(file=sys.stderr) + return None, [], [] else: - if verbose: - print("unable to find command, tried %s" % (commands,)) - return None, None, None + print("Unable to find git command, tried {}".format(", ".join(commands)), file=sys.stderr) + return None, [], [] def _python(args, cwd, python_executable, sudo=False): @@ -137,31 +158,40 @@ def _python(args, cwd, python_executable, sudo=False): try: return _execute(command, cwd=cwd) except: - return None, None, None + import traceback + print("Error while trying to run command {}".format(" ".join(command)), file=sys.stderr) + traceback.print_exc(file=sys.stderr) + return None, [], [] def _to_error(*lines): - return u"".join(map(lambda x: _to_unicode(x, errors="replace"), lines)) + if len(lines) == 1: + if isinstance(lines[0], (list, tuple)): + lines = lines[0] + elif not isinstance(lines[0], (str, unicode)): + lines = [repr(lines[0]),] + return u"\n".join(map(lambda x: _to_unicode(x, errors="replace"), lines)) def _rescue_changes(git_executable, folder): print(">>> Running: git diff --shortstat") returncode, stdout, stderr = _git(["diff", "--shortstat"], folder, git_executable=git_executable) - if returncode != 0: - raise RuntimeError("Could not update, \"git diff\" failed with returncode %d: %s" % (returncode, _to_error(*stdout))) - if stdout and "".join(stdout).strip(): + if returncode is None or returncode != 0: + raise RuntimeError("Could not update, \"git diff\" failed with returncode {}".format(returncode)) + if stdout and u"".join(stdout).strip(): # we got changes in the working tree, maybe from the user, so we'll now rescue those into a patch import time import os timestamp = time.strftime("%Y%m%d%H%M") - patch = os.path.join(folder, "%s-preupdate.patch" % timestamp) + patch = os.path.join(folder, "{}-preupdate.patch".format(timestamp)) - print(">>> Running: git diff and saving output to %s" % timestamp) + print(">>> Running: git diff and saving output to {}".format(patch)) returncode, stdout, stderr = _git(["diff"], folder, git_executable=git_executable) - if returncode != 0: - raise RuntimeError("Could not update, installation directory was dirty and state could not be persisted as a patch to %s" % patch) + if returncode is None or returncode != 0: + raise RuntimeError("Could not update, installation directory was dirty and state could not be persisted as a patch to {}".format(patch)) - with open(patch, "wb") as f: + import codecs + with codecs.open(patch, "w", encoding="utf-8", errors="replace") as f: for line in stdout: f.write(line) @@ -174,46 +204,45 @@ def update_source(git_executable, folder, target, force=False, branch=None): if _rescue_changes(git_executable, folder): print(">>> Running: git reset --hard") returncode, stdout, stderr = _git(["reset", "--hard"], folder, git_executable=git_executable) - if returncode != 0: - raise RuntimeError("Could not update, \"git reset --hard\" failed with returncode %d: %s" % (returncode, _to_error(*stdout))) + if returncode is None or returncode != 0: + raise RuntimeError("Could not update, \"git reset --hard\" failed with returncode {}".format(returncode)) print(">>> Running: git clean -f -d -e *-preupdate.patch") returncode, stdout, stderr = _git(["clean", "-f", "-d", "-e", "*-preupdate.patch"], folder, git_executable=git_executable) - if returncode != 0: - raise RuntimeError("Could not update, \"git clean -f\" failed with returcode %d: %s" % (returncode, _to_error(*stdout))) + if returncode is None or returncode != 0: + raise RuntimeError("Could not update, \"git clean -f\" failed with returncode {}".format(returncode)) print(">>> Running: git fetch") returncode, stdout, stderr = _git(["fetch"], folder, git_executable=git_executable) - if returncode != 0: - raise RuntimeError("Could not update, \"git fetch\" failed with returncode %d: %s" % (returncode, _to_error(*stdout))) - print(stdout) + if returncode is None or returncode != 0: + raise RuntimeError("Could not update, \"git fetch\" failed with returncode {}".format(returncode)) if branch is not None and branch.strip() != "": print(">>> Running: git checkout {}".format(branch)) returncode, stdout, stderr = _git(["checkout", branch], folder, git_executable=git_executable) - if returncode != 0: - raise RuntimeError("Could not update, \"git checkout\" failed with returncode %d: %s" % (returncode, _to_error(*stdout))) + if returncode is None or returncode != 0: + raise RuntimeError("Could not update, \"git checkout\" failed with returncode {}".format(returncode)) print(">>> Running: git pull") returncode, stdout, stderr = _git(["pull"], folder, git_executable=git_executable) - if returncode != 0: - raise RuntimeError("Could not update, \"git pull\" failed with returncode %d: %s" % (returncode, _to_error(*stdout))) + if returncode is None or returncode != 0: + raise RuntimeError("Could not update, \"git pull\" failed with returncode {}".format(returncode)) if force: reset_command = ["reset", "--hard"] reset_command += [target] - print(">>> Running: git %s" % " ".join(reset_command)) + print(">>> Running: git {}".format(" ".join(reset_command))) returncode, stdout, stderr = _git(reset_command, folder, git_executable=git_executable) - if returncode != 0: - raise RuntimeError("Error while updating, \"git %s\" failed with returncode %d: %s" % (" ".join(reset_command), returncode, _to_error(*stdout))) + if returncode is None or returncode != 0: + raise RuntimeError("Error while updating, \"git {}\" failed with returncode {}".format(" ".join(reset_command), returncode)) def install_source(python_executable, folder, user=False, sudo=False): print(">>> Running: python setup.py clean") returncode, stdout, stderr = _python(["setup.py", "clean"], folder, python_executable) - if returncode != 0: - print("\"python setup.py clean\" failed with returncode %d: %s" % (returncode, stdout)) + if returncode is None or returncode != 0: + print("\"python setup.py clean\" failed with returncode {}".format(returncode)) print("Continuing anyways") print(">>> Running: python setup.py install") @@ -221,15 +250,14 @@ def install_source(python_executable, folder, user=False, sudo=False): if user: args.append("--user") returncode, stdout, stderr = _python(args, folder, python_executable, sudo=sudo) - if returncode != 0: - raise RuntimeError("Could not update, \"python setup.py install\" failed with returncode %d: %s" % (returncode, _to_error(*stdout))) + if returncode is None or returncode != 0: + raise RuntimeError("Could not update, \"python setup.py install\" failed with returncode {}".format(returncode)) def parse_arguments(): import argparse boolean_trues = ["true", "yes", "1"] - boolean_falses = ["false", "no", "0"] parser = argparse.ArgumentParser(prog="update-octoprint.py")