From 34c4bb585a5d5e8611b75f1399811a649c98c10b Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sat, 5 Sep 2015 04:50:35 -0400 Subject: [PATCH 1/2] Fix pip installation issues. This commit is to fix issue #325. There were three issues with the PIP installations. 1) If multiple instances of the same platform were found, pip could attempt to install the same dependency multiple times at once by being run simultaneously in different processes. This would cause pip failures due to race conditions. This has been fixed by using a thread lock to allow only one instance of PIP to run at a time. 2) PIP would not check the target if the dependency was already met. This would lead to PIP attempting to reinstall every dependency on every boot. This would eventually fail because the package was already installed, but it significantly increased boot time, especially on Raspberry Pis. 3) PIP would not upgrade packages that were already installed. Usually, when a version is specified to PIP, it will install the specified version if it is not already installed, even without the \-\-upgrade flag. This behavior did not work when using the \-\-target flag. When using the target flag, a new install is always attempted, but nothing will be overwritten unless the \-\-upgrade flag is also given. This caused new packages to not be installed when their dependencies were increased. This is fixed by defaulting towards using the \-\-upgrade flag. --- homeassistant/util/package.py | 43 +++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/homeassistant/util/package.py b/homeassistant/util/package.py index 802e3834b90..1ebaeed5a15 100644 --- a/homeassistant/util/package.py +++ b/homeassistant/util/package.py @@ -1,19 +1,54 @@ """Helpers to install PyPi packages.""" import os +import logging +import pkg_resources import subprocess import sys +import threading + +_LOGGER = logging.getLogger(__name__) +INSTALL_LOCK = threading.Lock() -def install_package(package, upgrade=False, target=None): +def install_package(package, upgrade=True, target=None): """Install a package on PyPi. Accepts pip compatible package strings. Return boolean if install successfull.""" # Not using 'import pip; pip.main([])' because it breaks the logger args = [sys.executable, '-m', 'pip', 'install', '--quiet', package] + if upgrade: args.append('--upgrade') if target: - args += ['--target', os.path.abspath(target)] + target = os.path.abspath(target) + args += ['--target', target] + + with INSTALL_LOCK: + if check_package_exists(package, target): + return True + + _LOGGER.info('Attempting install of %s', package) + try: + return 0 == subprocess.call(args) + except subprocess.SubprocessError: + return False + + +def check_package_exists(package, target=None): + """Check if a package exists. + Returns True when the requirement is met. + Returns False when the package is not installed or doesn't meet req.""" + req = pkg_resources.Requirement.parse(package) + + if target: + work_set = pkg_resources.WorkingSet([target]) + search_fun = work_set.find + + else: + search_fun = pkg_resources.get_distribution + try: - return 0 == subprocess.call(args) - except subprocess.SubprocessError: + result = search_fun(req) + except (pkg_resources.DistributionNotFound, pkg_resources.VersionConflict): return False + + return bool(result) From a097e9caf2ea4994bc03f05f74b24ec375a06de7 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sat, 5 Sep 2015 04:53:44 -0400 Subject: [PATCH 2/2] Reverted a line in package.py to its previous state. --- homeassistant/util/package.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/util/package.py b/homeassistant/util/package.py index 1ebaeed5a15..3eccc06e59a 100644 --- a/homeassistant/util/package.py +++ b/homeassistant/util/package.py @@ -19,8 +19,7 @@ def install_package(package, upgrade=True, target=None): if upgrade: args.append('--upgrade') if target: - target = os.path.abspath(target) - args += ['--target', target] + args += ['--target', os.path.abspath(target)] with INSTALL_LOCK: if check_package_exists(package, target):