diff --git a/MANIFEST.in b/MANIFEST.in index dad2e7d..ffa679b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,3 +4,4 @@ recursive-include octoprint_netconnectd/translations * include LICENSE include requirements.txt include README.md +graft octoprint_netconnectd/scripts \ No newline at end of file diff --git a/octoprint_netconnectd/scripts/update_script.py b/octoprint_netconnectd/scripts/update_script.py index bb10f66..d98cb9e 100644 --- a/octoprint_netconnectd/scripts/update_script.py +++ b/octoprint_netconnectd/scripts/update_script.py @@ -16,8 +16,8 @@ from octoprint.plugins.softwareupdate import exceptions from octoprint.settings import _default_basedir -from octoprint_mrbeam.util.pip_util import get_version_of_pip_module, \ - get_pip_caller # TODO check how to be independent of mrbeam plugin +from octoprint_netconnectd.util.pip_util import get_version_of_pip_module, \ + get_pip_caller from requests.adapters import HTTPAdapter from urllib3 import Retry @@ -25,11 +25,12 @@ _logger = logging.getLogger("octoprint.plugins.netconnectd.softwareupdate.updatescript") -UPDATE_CONFIG_NAME = "netconnectd_plugin" +UPDATE_CONFIG_NAME = "netconnectd" REPO_NAME = "OctoPrint-Netconnectd" MAIN_SRC_FOLDER_NAME = "octoprint_netconnectd" PLUGIN_NAME = "OctoPrint-Netconnectd" DEFAULT_OPRINT_VENV = "/home/pi/oprint/bin/pip" +PIP_WHEEL_TEMP_FOLDER = "/tmp/wheelhouse" """ copy pasta of mrbeam plugin update script @@ -96,14 +97,6 @@ def _parse_arguments(): default=None, help="Path of target zip file on local system", ) - parser.add_argument( - "--target_version", - action="store", - type=str, - dest="target_version", - default=None, - help="Version number of the target", - ) parser.add_argument( "folder", type=str, @@ -175,13 +168,25 @@ def build_wheels(build_queue): None """ + try: + if not os.path.isdir(PIP_WHEEL_TEMP_FOLDER): + os.mkdir(PIP_WHEEL_TEMP_FOLDER) + except OSError as e: + raise RuntimeError("can't create wheel tmp folder {} - {}".format(PIP_WHEEL_TEMP_FOLDER, e)) + for venv, packages in build_queue.items(): + tmp_folder = os.path.join(PIP_WHEEL_TEMP_FOLDER, re.search(r"\w+((?=\/venv)|(?=\/bin))", venv).group(0)) + if os.path.isdir(tmp_folder): + try: + os.system("sudo rm -r {}".format(tmp_folder)) + except Exception as e: + raise RuntimeError("can't delete pip wheel temp folder {} - {}".format(tmp_folder, e)) pip_args = [ "wheel", "--no-python-version-warning", "--disable-pip-version-check", - "--wheel-dir=/tmp/wheelhouse", # Build wheels into , where the default is the current working directory. + "--wheel-dir={}".format(tmp_folder), # Build wheels into , where the default is the current working directory. "--no-dependencies", # Don't install package dependencies. ] for package in packages: @@ -213,20 +218,20 @@ def install_wheels(install_queue): raise RuntimeError("install queue is not a dict") for venv, packages in install_queue.items(): - + tmp_folder = os.path.join(PIP_WHEEL_TEMP_FOLDER, re.search(r"\w+((?=\/venv)|(?=\/bin))", venv).group(0)) pip_args = [ "install", "--no-python-version-warning", "--disable-pip-version-check", "--upgrade", # Upgrade all specified packages to the newest available version. The handling of dependencies depends on the upgrade-strategy used. "--no-index", # Ignore package index (only looking at --find-links URLs instead). - "--find-links=/tmp/wheelhouse", # If a URL or path to an html file, then parse for links to archives such as sdist (.tar.gz) or wheel (.whl) files. If a local path or file:// URL that's a directory, then look for archives in the directory listing. Links to VCS project URLs are not supported. + "--find-links={}".format(tmp_folder), # If a URL or path to an html file, then parse for links to archives such as sdist (.tar.gz) or wheel (.whl) files. If a local path or file:// URL that's a directory, then look for archives in the directory listing. Links to VCS project URLs are not supported. "--no-dependencies", # Don't install package dependencies. ] for package in packages: pip_args.append( - "{package}=={package_version}".format( - package=package["name"], package_version=package["target"] + "{package}".format( + package=package["name"] ) ) @@ -239,14 +244,14 @@ def install_wheels(install_queue): ) -def build_queue(update_info, dependencies, target, plugin_archive): +def build_queue(update_info, dependencies, plugin_archive): """ build the queue of packages to install Args: update_info: a dict of informations how to update the packages dependencies: a list dicts of dependencies [{"name", "version"}] - target: target of the Mr Beam Plugin to update to + plugin_archive: path to archive of the plugin Returns: install_queue: dict of venvs with a list of package dicts {"": [{"name", "archive", "target"}] @@ -259,7 +264,7 @@ def build_queue(update_info, dependencies, target, plugin_archive): { "name": PLUGIN_NAME, "archive": plugin_archive, - "target": target, + "target": '', } ) print("dependencies - {}".format(dependencies)) @@ -322,7 +327,7 @@ def run_update(): update_info = get_update_info() install_queue = build_queue( - update_info, dependencies, args.target_version, args.archive + update_info, dependencies, args.archive ) print("install_queue", install_queue) @@ -367,7 +372,7 @@ def loadPluginTarget(archive, folder): folder: working directory Returns: - (zip_file_path, target_version) - path of the downloaded zip file and target version string + zip_file_path - path of the downloaded zip file """ # download target repo zip @@ -421,17 +426,7 @@ def loadPluginTarget(archive, folder): except IOError: raise RuntimeError("Could not copy update_script to working directory") - # get target version - exec( - open( - os.path.join( - plugin_extracted_path_folder, MAIN_SRC_FOLDER_NAME, "__version.py" - ) - ).read() - ) - target_version = __version__ - - return zip_file_path, target_version + return zip_file_path def main(): @@ -445,9 +440,9 @@ def main(): args = _parse_arguments() if args.call: - if args.archive is None or args.target_version is None: + if args.archive is None: raise RuntimeError( - "Could not run update archive or target_version is missing" + "Could not run update archive is missing" ) run_update() else: @@ -460,7 +455,7 @@ def main(): raise RuntimeError("Could not update, base folder is not writable") update_info = get_update_info() - archive, target_version = loadPluginTarget( + archive = loadPluginTarget( update_info.get(UPDATE_CONFIG_NAME) .get("pip") .format(target_version=args.target), @@ -470,8 +465,7 @@ def main(): # call new update script with args sys.argv = [ "--call=true", - "--archive={}".format(archive), - "--target_version={}".format(target_version), + "--archive={}".format(archive) ] + sys.argv[1:] try: result = subprocess.call( diff --git a/octoprint_netconnectd/util/__init__.py b/octoprint_netconnectd/util/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/octoprint_netconnectd/util/cmd_exec.py b/octoprint_netconnectd/util/cmd_exec.py new file mode 100644 index 0000000..57c544f --- /dev/null +++ b/octoprint_netconnectd/util/cmd_exec.py @@ -0,0 +1,78 @@ +""" +copy pasta of octoprint_mrbeam/util/cmd_exec.py +only changed the logging to be mrbeam plugin independend +""" +import logging +import subprocess +from logging import DEBUG + + +def exec_cmd(cmd, log=True, shell=True, loglvl=DEBUG): + """ + Executes a system command + :param cmd: + :return: True if system returncode was 0, + False if the command returned with an error, + None if there was an exception. + """ + _logger = logging.getLogger(__name__ + ".exec_cmd") + code = None + if log: + _logger.log(loglvl, "cmd=%s", cmd) + try: + code = subprocess.call(cmd, shell=shell) + except Exception as e: + _logger.debug( + "Failed to execute command '%s', return code: %s, Exception: %s", + cmd, + code, + e, + ) + return None + if code != 0 and log: + _logger.info("cmd= '%s', return code: '%s'", code) + return code == 0 + + +def exec_cmd_output(cmd, log=True, shell=False, loglvl=DEBUG): + """ + Executes a system command and returns its output. + :param cmd: + :return: Tuple(String:output , int return_code) + """ + _logger = logging.getLogger(__name__ + "exec_cmd_output") + output = None + code = 0 + if log: + _logger.log(loglvl, "cmd='%s'", cmd) + try: + output = subprocess.check_output(cmd, shell=shell, stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as e: + code = e.returncode + + if not log: + cmd = cmd[:50] + "..." if len(cmd) > 30 else cmd + if e.output is not None: + output = e.output[:30] + "..." if len(e.output) > 30 else e.output + else: + output = e.output + _logger.log( + loglvl, + "Failed to execute command '%s', return code: %s, output: '%s'", + cmd, + e.returncode, + output, + ) + + except Exception as e: + code = 99 + output = "{e}: {o}".format(e=e, o=output) + _logger.log( + loglvl, + "Failed to execute command '%s', return code: %s, output: '%s'", + cmd, + None, + output, + ) + + return output, code diff --git a/octoprint_netconnectd/util/pip_util.py b/octoprint_netconnectd/util/pip_util.py new file mode 100644 index 0000000..8991491 --- /dev/null +++ b/octoprint_netconnectd/util/pip_util.py @@ -0,0 +1,109 @@ +""" +copy pasta of octoprint_mrbeam/util/pip_util.py +only changed the logging to be mrbeam plugin independend +""" +import logging + +from octoprint.plugins.softwareupdate.updaters.pip import _get_pip_caller +from octoprint.util.pip import PipCaller +from cmd_exec import exec_cmd_output + +DISABLE_PIP_CHECK = "--disable-pip-version-check" +DISABLE_PY_WARNING = "--no-python-version-warning" + +# Dictionary of package versions available at different locations +# { +# /home/pi/oprint/bin/pip : { +# "OctoPrint x.x.x", +# ... +# }, +# /usr/share/iobeam/venv/bin/pip : { +# "iobeam y.y.y", +# ... +# } +# } +_pip_package_version_lists = {} + + +def get_version_of_pip_module(pip_name, pip_command=None, disable_pip_ver_check=True): + _logger = logging.getLogger(__name__ + ".get_version_of_pip_module") + global _pip_package_version_lists + version = None + returncode = -1 + if pip_command is None: + pip_command = "pip" + elif isinstance(pip_command, list): + pip_command = " ".join(pip_command) + # Checking for pip version outdate takes extra time and text output. + # NOTE: Older versions of pip do not have the --no-python-version-warning flag + for disabled in [ + DISABLE_PIP_CHECK, + ]: # DISABLE_PY_WARNING]: + if disable_pip_ver_check and not disabled in pip_command: + pip_command += " " + disabled + venv_packages = _pip_package_version_lists.get(pip_command, None) + + if venv_packages is None: + # perform a pip discovery and remember it for next time + command = "{pip_command} list".format(pip_command=pip_command) + _logger.debug("refreshing list of installed packages (%s list)", pip_command) + output, returncode = exec_cmd_output(command, shell=True, log=False) + if returncode == 0: + venv_packages = output.splitlines() + _pip_package_version_lists[pip_command] = venv_packages + elif returncode == 127: + _logger.error( + "`%s` was not found in local $PATH (returncode %s)", + pip_command, + returncode, + ) + return None + else: + _logger.warning("`%s list` returned code %s", pip_command, returncode) + return None + # Go through the package list available in our venv + for line in venv_packages: + token = line.split() + if len(token) >= 2 and token[0] == pip_name: + version = token[1] + break + _logger.debug("%s==%s", pip_name, version) + return version + + +def get_pip_caller(venv, _logger=None): + """ + gets the pip caller of the givenv venv + + Args: + venv: path to venv + _logger: logger to log call, stdout and stderr of the pip caller + + Returns: + PipCaller of the venv + """ + pip_caller = _get_pip_caller(command=venv) + if not isinstance(pip_caller, PipCaller): + raise RuntimeError("Can't run pip", None) + + def _log_call(*lines): + _log(lines, prefix=" ", stream="call") + + def _log_stdout(*lines): + _log(lines, prefix=">", stream="stdout") + + def _log_stderr(*lines): + _log(lines, prefix="!", stream="stderr") + + def _log(lines, prefix=None, stream=None, strip=True): + if strip: + lines = map(lambda x: x.strip(), lines) + for line in lines: + print(u"{} {}".format(prefix, line)) + + if _logger is not None: + pip_caller.on_log_call = _log_call + pip_caller.on_log_stdout = _log_stdout + pip_caller.on_log_stderr = _log_stderr + + return pip_caller