Skip to content

Commit b82586b

Browse files
committed
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)
1 parent a18dd50 commit b82586b

File tree

2 files changed

+83
-4
lines changed

2 files changed

+83
-4
lines changed

setup.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,10 +201,14 @@ def run(self):
201201
# Abort if any of the required packages can not be built.
202202
if required_failed:
203203
print_line()
204-
print_message(
205-
"The following required packages can not "
206-
"be built: %s" %
207-
', '.join(x.name for x in required_failed))
204+
message = ("The following required packages can not "
205+
"be built: %s" %
206+
", ".join(x.name for x in required_failed))
207+
for pkg in required_failed:
208+
pkg_help = pkg.install_help_msg()
209+
if pkg_help:
210+
message += "\n* " + pkg_help
211+
print_message(message)
208212
sys.exit(1)
209213

210214
# Now collect all of the information we need to build all of the

setupext.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import glob
77
import multiprocessing
88
import os
9+
import platform
910
import re
1011
import subprocess
1112
from subprocess import check_output
@@ -412,6 +413,14 @@ class CheckFailed(Exception):
412413

413414
class SetupPackage(object):
414415
optional = False
416+
pkg_names = {
417+
"apt-get": None,
418+
"yum": None,
419+
"dnf": None,
420+
"brew": None,
421+
"port": None,
422+
"windows_url": None
423+
}
415424

416425
def check(self):
417426
"""
@@ -531,6 +540,56 @@ def do_custom_build(self):
531540
"""
532541
pass
533542

543+
def install_help_msg(self):
544+
"""
545+
Do not override this method !
546+
547+
Generate the help message to show if the package is not installed.
548+
To use this in subclasses, simply add the dictionary `pkg_names` as
549+
a class variable:
550+
551+
pkg_names = {
552+
"apt-get": <Name of the apt-get package>,
553+
"yum": <Name of the yum package>,
554+
"dnf": <Name of the dnf package>,
555+
"brew": <Name of the brew package>,
556+
"port": <Name of the port package>,
557+
"windows_url": <The url which has installation instructions>
558+
}
559+
560+
All the dictionary keys are optional. If a key is not present or has
561+
the value `None` no message is provided for that platform.
562+
"""
563+
def _try_managers(*managers):
564+
for manager in managers:
565+
pkg_name = self.pkg_names.get(manager, None)
566+
if pkg_name:
567+
try:
568+
# `shutil.which()` can be used when Python 2.7 support
569+
# is dropped. It is available in Python 3.3+
570+
_ = check_output(["which", manager],
571+
stderr=subprocess.STDOUT)
572+
return ('Try installing {0} with `{1} install {2}`'
573+
.format(self.name, manager, pkg_name))
574+
except subprocess.CalledProcessError:
575+
pass
576+
577+
message = None
578+
if sys.platform == "win32":
579+
url = self.pkg_names.get("windows_url", None)
580+
if url:
581+
message = ('Please check {0} for instructions to install {1}'
582+
.format(url, self.name))
583+
elif sys.platform == "darwin":
584+
message = _try_managers("brew", "port")
585+
elif sys.platform.startswith("linux"):
586+
release = platform.linux_distribution()[0].lower()
587+
if release in ('debian', 'ubuntu'):
588+
message = _try_managers('apt-get')
589+
elif release in ('centos', 'redhat', 'fedora'):
590+
message = _try_managers('dnf', 'yum')
591+
return message
592+
534593

535594
class OptionalPackage(SetupPackage):
536595
optional = True
@@ -953,6 +1012,14 @@ def add_flags(self, ext, add_sources=True):
9531012

9541013
class FreeType(SetupPackage):
9551014
name = "freetype"
1015+
pkg_names = {
1016+
"apt-get": "libfreetype6-dev",
1017+
"yum": "freetype-devel",
1018+
"dnf": "freetype-devel",
1019+
"brew": "freetype",
1020+
"port": "freetype",
1021+
"windows_url": "http://gnuwin32.sourceforge.net/packages/freetype.htm"
1022+
}
9561023

9571024
def check(self):
9581025
if options.get('local_freetype'):
@@ -1167,6 +1234,14 @@ def get_extension(self):
11671234

11681235
class Png(SetupPackage):
11691236
name = "png"
1237+
pkg_names = {
1238+
"apt-get": "libpng12-dev",
1239+
"yum": "libpng-devel",
1240+
"dnf": "libpng-devel",
1241+
"brew": "libpng",
1242+
"port": "libpng",
1243+
"windows_url": "http://gnuwin32.sourceforge.net/packages/libpng.htm"
1244+
}
11701245

11711246
def check(self):
11721247
if sys.platform == 'win32':

0 commit comments

Comments
 (0)