diff --git a/src/octoprint/plugins/pluginmanager/__init__.py b/src/octoprint/plugins/pluginmanager/__init__.py index e59de75e..cee575b5 100644 --- a/src/octoprint/plugins/pluginmanager/__init__.py +++ b/src/octoprint/plugins/pluginmanager/__init__.py @@ -12,6 +12,7 @@ import octoprint.plugin.core from octoprint.settings import valid_boolean_trues from octoprint.server.util.flask import restricted_access from octoprint.server import admin_permission +from octoprint.util.pip import PipCaller, UnknownPip from flask import jsonify, make_response from flask.ext.babel import gettext @@ -22,6 +23,7 @@ import sys import requests import re import os +import pkg_resources class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin, octoprint.plugin.TemplatePlugin, @@ -36,6 +38,9 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin, self._pending_install = set() self._pending_uninstall = set() + self._pip_caller = None + self._pip_version_dependency_links = pkg_resources.parse_version("1.5") + self._repository_available = False self._repository_plugins = [] self._repository_cache_path = None @@ -46,6 +51,11 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin, self._repository_cache_path = os.path.join(self._settings.get_plugin_data_folder(), "plugins.json") self._repository_cache_ttl = self._settings.get_int(["repository_ttl"]) * 60 + self._pip_caller = PipCaller(configured=self._settings.get(["pip"])) + self._pip_caller.on_log_call = self._log_call + self._pip_caller.on_log_stdout = self._log_stdout + self._pip_caller.on_log_stderr = self._log_stderr + ##~~ StartupPlugin def on_startup(self, host, port): @@ -72,6 +82,7 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin, def on_settings_save(self, data): octoprint.plugin.SettingsPlugin.on_settings_save(self, data) self._repository_cache_ttl = self._settings.get_int(["repository_ttl"]) * 60 + self._pip_caller.refresh = True ##~~ AssetPlugin @@ -396,74 +407,16 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin, self._plugin_manager.send_plugin_message(self._identifier, notification) def _call_pip(self, args): - pip_command = self._settings.get(["pip"]) - if pip_command is None: - import os - python_command = sys.executable - binary_dir = os.path.dirname(python_command) + if self._pip_caller is None or not self._pip_caller.available: + raise RuntimeError(u"No pip available, can't operate".format(**locals())) - pip_command = os.path.join(binary_dir, "pip") - if sys.platform == "win32": - # Windows is a bit special... first of all the file will be called pip.exe, not just pip, and secondly - # for a non-virtualenv install (e.g. global install) the pip binary will not be located in the - # same folder as python.exe, but in a subfolder Scripts, e.g. - # - # C:\Python2.7\ - # |- python.exe - # `- Scripts - # `- pip.exe + if "--process-dependency-links" in args and self._pip_caller < self._pip_version_dependency_links: + args.remove("--process-dependency-links") - # virtual env? - pip_command = os.path.join(binary_dir, "pip.exe") + return self._pip_caller.execute(*args) - if not os.path.isfile(pip_command): - # nope, let's try the Scripts folder then - scripts_dir = os.path.join(binary_dir, "Scripts") - if os.path.isdir(scripts_dir): - pip_command = os.path.join(scripts_dir, "pip.exe") - - if not os.path.isfile(pip_command) or not os.access(pip_command, os.X_OK): - raise RuntimeError(u"No pip path configured and {pip_command} does not exist or is not executable, can't install".format(**locals())) - - command = [pip_command] + args - - self._logger.debug(u"Calling: {}".format(" ".join(command))) - - p = sarge.run(" ".join(command), shell=True, async=True, stdout=sarge.Capture(), stderr=sarge.Capture()) - p.wait_events() - - all_stdout = [] - all_stderr = [] - try: - while p.returncode is None: - line = p.stderr.readline(timeout=0.5) - if line: - self._log_stderr(line) - all_stderr.append(line) - - line = p.stdout.readline(timeout=0.5) - if line: - self._log_stdout(line) - all_stdout.append(line) - - p.commands[0].poll() - - finally: - p.close() - - stderr = p.stderr.text - if stderr: - split_lines = stderr.split("\n") - self._log_stderr(*split_lines) - all_stderr += split_lines - - stdout = p.stdout.text - if stdout: - split_lines = stdout.split("\n") - self._log_stdout(*split_lines) - all_stdout += split_lines - - return p.returncode, all_stdout, all_stderr + def _log_call(self, *lines): + self._log(lines, prefix=" ", stream="call") def _log_stdout(self, *lines): self._log(lines, prefix=">", stream="stdout")