From b82586b42ee0f4d1dcfa3a55f38f5c03e1cc8c97 Mon Sep 17 00:00:00 2001 From: AbdealiJK Date: Sat, 11 Jun 2016 19:05:59 +0530 Subject: [PATCH] setup.py: Recommend installation command for pkgs If a package is not found, provide a better error message that also explains how to install the package or gives the user a link explaining what to do to install the package. The supported methods are: - Provide a link for windows - Provide a command using a package manager for the OS (For example: apt-get for Ubuntu/Debian, dnf and yum for Fedora/CentOS/RHEL, brew and port for OSX) --- setup.py | 12 ++++++---- setupext.py | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 54849dd78a4..22014a5847c 100644 --- a/setup.py +++ b/setup.py @@ -201,10 +201,14 @@ def run(self): # Abort if any of the required packages can not be built. if required_failed: print_line() - print_message( - "The following required packages can not " - "be built: %s" % - ', '.join(x.name for x in required_failed)) + message = ("The following required packages can not " + "be built: %s" % + ", ".join(x.name for x in required_failed)) + for pkg in required_failed: + pkg_help = pkg.install_help_msg() + if pkg_help: + message += "\n* " + pkg_help + print_message(message) sys.exit(1) # Now collect all of the information we need to build all of the diff --git a/setupext.py b/setupext.py index 14e7ae835b2..0fa8d839fb7 100755 --- a/setupext.py +++ b/setupext.py @@ -6,6 +6,7 @@ import glob import multiprocessing import os +import platform import re import subprocess from subprocess import check_output @@ -412,6 +413,14 @@ class CheckFailed(Exception): class SetupPackage(object): optional = False + pkg_names = { + "apt-get": None, + "yum": None, + "dnf": None, + "brew": None, + "port": None, + "windows_url": None + } def check(self): """ @@ -531,6 +540,56 @@ def do_custom_build(self): """ pass + def install_help_msg(self): + """ + Do not override this method ! + + Generate the help message to show if the package is not installed. + To use this in subclasses, simply add the dictionary `pkg_names` as + a class variable: + + pkg_names = { + "apt-get": , + "yum": , + "dnf": , + "brew": , + "port": , + "windows_url": + } + + All the dictionary keys are optional. If a key is not present or has + the value `None` no message is provided for that platform. + """ + def _try_managers(*managers): + for manager in managers: + pkg_name = self.pkg_names.get(manager, None) + if pkg_name: + try: + # `shutil.which()` can be used when Python 2.7 support + # is dropped. It is available in Python 3.3+ + _ = check_output(["which", manager], + stderr=subprocess.STDOUT) + return ('Try installing {0} with `{1} install {2}`' + .format(self.name, manager, pkg_name)) + except subprocess.CalledProcessError: + pass + + message = None + if sys.platform == "win32": + url = self.pkg_names.get("windows_url", None) + if url: + message = ('Please check {0} for instructions to install {1}' + .format(url, self.name)) + elif sys.platform == "darwin": + message = _try_managers("brew", "port") + elif sys.platform.startswith("linux"): + release = platform.linux_distribution()[0].lower() + if release in ('debian', 'ubuntu'): + message = _try_managers('apt-get') + elif release in ('centos', 'redhat', 'fedora'): + message = _try_managers('dnf', 'yum') + return message + class OptionalPackage(SetupPackage): optional = True @@ -953,6 +1012,14 @@ def add_flags(self, ext, add_sources=True): class FreeType(SetupPackage): name = "freetype" + pkg_names = { + "apt-get": "libfreetype6-dev", + "yum": "freetype-devel", + "dnf": "freetype-devel", + "brew": "freetype", + "port": "freetype", + "windows_url": "http://gnuwin32.sourceforge.net/packages/freetype.htm" + } def check(self): if options.get('local_freetype'): @@ -1167,6 +1234,14 @@ def get_extension(self): class Png(SetupPackage): name = "png" + pkg_names = { + "apt-get": "libpng12-dev", + "yum": "libpng-devel", + "dnf": "libpng-devel", + "brew": "libpng", + "port": "libpng", + "windows_url": "http://gnuwin32.sourceforge.net/packages/libpng.htm" + } def check(self): if sys.platform == 'win32':