Better error and subprocess handling
Removed a potential deadlock, added logging for all raised exceptions, made _to_error more solid and removed another potential encoding issue when creating diffs
This commit is contained in:
parent
8beedce98f
commit
528e99898f
1 changed files with 75 additions and 47 deletions
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue