def _check_pip_setup(self, pip_command): pip_command_str = pip_command if isinstance(pip_command_str, list): pip_command_str = " ".join(pip_command_str) if not self.ignore_cache and pip_command_str in _cache["setup"]: self._logger.debug( "Using cached pip setup information for {}".format( pip_command_str)) return _cache["setup"][pip_command_str] # This is horribly ugly and I'm sorry... # # While we can figure out the install directory, if that's writable and if a virtual environment # is active for pip that belongs to our sys.executable python instance by just checking some # variables, we can't for stuff like third party software we allow to update via the software # update plugin. # # What we do instead for these situations is try to install (and of course uninstall) the # testballoon dummy package, which collects that information for us. For pip <= 7 we could # have the testballoon provide us with the info needed through stdout, if pip was called # with --verbose anything printed to stdout within setup.py would be output. Pip 8 managed # to break this mechanism. Any (!) output within setup.py appears to be suppressed now, and # no combination of --log and multiple --verbose or -v arguments could get it to bring the # output back. # # So here's what we do now instead. Our sarge call sets an environment variable # "TESTBALLOON_OUTPUT" that points to a temporary file. If the testballoon sees that # variable set, it opens the file and writes to it the output it so far printed on stdout. # We then open the file and read in the data that way. # # Yeah, I'm not happy with that either. But as long as there's no way to otherwise figure # out for a generic pip command whether OctoPrint can even install anything with that # and if so how, well, that's how we'll have to do things. import os testballoon = os.path.join(os.path.realpath(os.path.dirname(__file__)), "piptestballoon") from octoprint.util import temppath with temppath() as testballoon_output_file: sarge_command = self.to_sarge_command(pip_command, "install", ".") try: # our testballoon is no real package, so this command will fail - that's ok though, # we only need the output produced within the pip environment sarge.run(sarge_command, stdout=sarge.Capture(), stderr=sarge.Capture(), cwd=testballoon, env=dict(TESTBALLOON_OUTPUT=testballoon_output_file)) except: self._logger.exception( "Error while trying to install testballoon to figure out pip setup" ) return False, False, False, None data = dict() with open(testballoon_output_file) as f: for line in f: key, value = line.split("=", 2) data[key] = value install_dir_str = data.get("PIP_INSTALL_DIR", None) virtual_env_str = data.get("PIP_VIRTUAL_ENV", None) writable_str = data.get("PIP_WRITABLE", None) if install_dir_str is not None and virtual_env_str is not None and writable_str is not None: install_dir = install_dir_str.strip() virtual_env = virtual_env_str.strip() == "True" writable = writable_str.strip() == "True" can_use_user_flag = not virtual_env and site.ENABLE_USER_SITE # ok, enable user flag, virtual env yes/no, installation dir result = writable or can_use_user_flag, \ not writable and can_use_user_flag, \ virtual_env, \ install_dir _cache["setup"][pip_command_str] = result return result else: self._logger.debug( "Could not detect desired output from testballoon install, got this instead: {!r}" .format(data)) return False, False, False, None
def _check_pip_setup(self, pip_command): pip_command_str = pip_command if isinstance(pip_command_str, list): pip_command_str = " ".join(pip_command_str) with _cache_mutex: if not self.ignore_cache and pip_command_str in _cache["setup"]: self._logger.debug("Using cached pip setup information for {}".format(pip_command_str)) return _cache["setup"][pip_command_str] # This is horribly ugly and I'm sorry... # # While we can figure out the install directory, if that's writable and if a virtual environment # is active for pip that belongs to our sys.executable python instance by just checking some # variables, we can't for stuff like third party software we allow to update via the software # update plugin. # # What we do instead for these situations is try to install (and of course uninstall) the # testballoon dummy package, which collects that information for us. For pip <= 7 we could # have the testballoon provide us with the info needed through stdout, if pip was called # with --verbose anything printed to stdout within setup.py would be output. Pip 8 managed # to break this mechanism. Any (!) output within setup.py appears to be suppressed now, and # no combination of --log and multiple --verbose or -v arguments could get it to bring the # output back. # # So here's what we do now instead. Our sarge call sets an environment variable # "TESTBALLOON_OUTPUT" that points to a temporary file. If the testballoon sees that # variable set, it opens the file and writes to it the output it so far printed on stdout. # We then open the file and read in the data that way. # # Yeah, I'm not happy with that either. But as long as there's no way to otherwise figure # out for a generic pip command whether OctoPrint can even install anything with that # and if so how, well, that's how we'll have to do things. import os testballoon = os.path.join(os.path.realpath(os.path.dirname(__file__)), "piptestballoon") from octoprint.util import temppath with temppath() as testballoon_output_file: sarge_command = self.to_sarge_command(pip_command, "install", ".") try: # our testballoon is no real package, so this command will fail - that's ok though, # we only need the output produced within the pip environment sarge.run(sarge_command, stdout=sarge.Capture(), stderr=sarge.Capture(), cwd=testballoon, env=dict(TESTBALLOON_OUTPUT=testballoon_output_file)) except: self._logger.exception("Error while trying to install testballoon to figure out pip setup") return False, False, False, None data = dict() with open(testballoon_output_file) as f: for line in f: key, value = line.split("=", 2) data[key] = value install_dir_str = data.get("PIP_INSTALL_DIR", None) virtual_env_str = data.get("PIP_VIRTUAL_ENV", None) writable_str = data.get("PIP_WRITABLE", None) if install_dir_str is not None and virtual_env_str is not None and writable_str is not None: install_dir = install_dir_str.strip() virtual_env = virtual_env_str.strip() == "True" writable = writable_str.strip() == "True" can_use_user_flag = not virtual_env and site.ENABLE_USER_SITE ok = writable or can_use_user_flag user_flag = not writable and can_use_user_flag self._logger.info("pip installs to {}, --user flag needed => {}, " "virtual env => {}".format(install_dir, "yes" if user_flag else "no", "yes" if virtual_env else "no")) # ok, enable user flag, virtual env yes/no, installation dir result = ok, user_flag, virtual_env, install_dir _cache["setup"][pip_command_str] = result return result else: self._logger.debug("Could not detect desired output from testballoon install, got this instead: {!r}".format(data)) return False, False, False, None