Skip to content

Commit b80e0f1

Browse files
committed
Merge pull request #6442 from matthew-brett/dynamic-tkagg
MRG: loading TCL / Tk symbols dynamically cherry-pick of commit f3e5576 Conflicts resolved: .travis.yml appveyor.yml setupext.py
1 parent cab8cf7 commit b80e0f1

File tree

4 files changed

+390
-319
lines changed

4 files changed

+390
-319
lines changed

.travis.yml

+4
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ script:
119119
# multiple processes
120120
- python -c "from matplotlib import font_manager"
121121
- |
122+
echo Testing import of tkagg backend
123+
MPLBACKEND="tkagg" python -c 'import matplotlib.pyplot as plt; print(plt.get_backend())'
124+
echo Testing using $NPROC processes
125+
echo The following args are passed to nose $NOSE_ARGS
122126
if [[ $BUILD_DOCS == false ]]; then
123127
export MPL_REPO_DIR=$PWD # needed for pep8-conformance test of the examples
124128
gdb -return-child-result -batch -ex r -ex bt --args python tests.py -s --processes=$NPROC --process-timeout=300 $TEST_ARGS

setupext.py

+8-282
Original file line numberDiff line numberDiff line change
@@ -1283,6 +1283,7 @@ def get_install_requires(self):
12831283

12841284
class BackendAgg(OptionalBackendPackage):
12851285
name = "agg"
1286+
force = True
12861287

12871288
def get_extension(self):
12881289
sources = [
@@ -1300,36 +1301,10 @@ def get_extension(self):
13001301

13011302
class BackendTkAgg(OptionalBackendPackage):
13021303
name = "tkagg"
1304+
force = True
13031305

1304-
def __init__(self):
1305-
self.tcl_tk_cache = None
1306-
1307-
def check_requirements(self):
1308-
try:
1309-
if PY3min:
1310-
import tkinter as Tkinter
1311-
else:
1312-
import Tkinter
1313-
except ImportError:
1314-
raise CheckFailed('TKAgg requires Tkinter.')
1315-
except RuntimeError:
1316-
raise CheckFailed('Tkinter present but import failed.')
1317-
else:
1318-
if Tkinter.TkVersion < 8.3:
1319-
raise CheckFailed("Tcl/Tk v8.3 or later required.")
1320-
1321-
ext = self.get_extension()
1322-
check_include_file(ext.include_dirs, "tk.h", "Tk")
1323-
1324-
try:
1325-
tk_v = Tkinter.__version__.split()[-2]
1326-
except (AttributeError, IndexError):
1327-
# Tkinter.__version__ has been removed in python 3
1328-
tk_v = 'not identified'
1329-
1330-
BackendAgg.force = True
1331-
1332-
return "version %s" % tk_v
1306+
def check(self):
1307+
return "installing; run-time loading from Python Tcl / Tk"
13331308

13341309
def get_extension(self):
13351310
sources = [
@@ -1343,251 +1318,11 @@ def get_extension(self):
13431318
LibAgg().add_flags(ext, add_sources=False)
13441319
return ext
13451320

1346-
def query_tcltk(self):
1347-
"""
1348-
Tries to open a Tk window in order to query the Tk object
1349-
about its library paths. This should never be called more
1350-
than once by the same process, as Tk intricacies may cause the
1351-
Python interpreter to hang. The function also has a workaround
1352-
if no X server is running (useful for autobuild systems).
1353-
"""
1354-
# Use cached values if they exist, which ensures this function
1355-
# only executes once
1356-
if self.tcl_tk_cache is not None:
1357-
return self.tcl_tk_cache
1358-
1359-
# By this point, we already know that Tkinter imports correctly
1360-
if PY3min:
1361-
import tkinter as Tkinter
1362-
else:
1363-
import Tkinter
1364-
tcl_lib_dir = ''
1365-
tk_lib_dir = ''
1366-
# First try to open a Tk window (requires a running X server)
1367-
try:
1368-
tk = Tkinter.Tk()
1369-
except Tkinter.TclError:
1370-
# Next, start Tcl interpreter without opening a Tk window
1371-
# (no need for X server) This feature is available in
1372-
# python version 2.4 and up
1373-
try:
1374-
tcl = Tkinter.Tcl()
1375-
except AttributeError: # Python version not high enough
1376-
pass
1377-
except Tkinter.TclError: # Something went wrong while opening Tcl
1378-
pass
1379-
else:
1380-
tcl_lib_dir = str(tcl.getvar('tcl_library'))
1381-
# Guess Tk location based on Tcl location
1382-
(head, tail) = os.path.split(tcl_lib_dir)
1383-
tail = tail.replace('Tcl', 'Tk').replace('tcl', 'tk')
1384-
tk_lib_dir = os.path.join(head, tail)
1385-
if not os.path.exists(tk_lib_dir):
1386-
tk_lib_dir = tcl_lib_dir.replace(
1387-
'Tcl', 'Tk').replace('tcl', 'tk')
1388-
else:
1389-
# Obtain Tcl and Tk locations from Tk widget
1390-
tk.withdraw()
1391-
tcl_lib_dir = str(tk.getvar('tcl_library'))
1392-
tk_lib_dir = str(tk.getvar('tk_library'))
1393-
tk.destroy()
1394-
1395-
# Save directories and version string to cache
1396-
self.tcl_tk_cache = tcl_lib_dir, tk_lib_dir, str(Tkinter.TkVersion)[:3]
1397-
return self.tcl_tk_cache
1398-
1399-
def parse_tcl_config(self, tcl_lib_dir, tk_lib_dir):
1400-
try:
1401-
if PY3min:
1402-
import tkinter as Tkinter
1403-
else:
1404-
import Tkinter
1405-
except ImportError:
1406-
return None
1407-
1408-
tcl_poss = [tcl_lib_dir,
1409-
os.path.normpath(os.path.join(tcl_lib_dir, '..')),
1410-
"/usr/lib/tcl" + str(Tkinter.TclVersion),
1411-
"/usr/lib"]
1412-
tk_poss = [tk_lib_dir,
1413-
os.path.normpath(os.path.join(tk_lib_dir, '..')),
1414-
"/usr/lib/tk" + str(Tkinter.TkVersion),
1415-
"/usr/lib"]
1416-
for ptcl, ptk in zip(tcl_poss, tk_poss):
1417-
tcl_config = os.path.join(ptcl, "tclConfig.sh")
1418-
tk_config = os.path.join(ptk, "tkConfig.sh")
1419-
if (os.path.exists(tcl_config) and os.path.exists(tk_config)):
1420-
break
1421-
if not (os.path.exists(tcl_config) and os.path.exists(tk_config)):
1422-
return None
1423-
1424-
def get_var(file, varname):
1425-
p = subprocess.Popen(
1426-
'. %s ; eval echo ${%s}' % (file, varname),
1427-
shell=True,
1428-
executable="/bin/sh",
1429-
stdout=subprocess.PIPE)
1430-
result = p.communicate()[0]
1431-
return result.decode('ascii')
1432-
1433-
tcl_lib_dir = get_var(
1434-
tcl_config, 'TCL_LIB_SPEC').split()[0][2:].strip()
1435-
tcl_inc_dir = get_var(
1436-
tcl_config, 'TCL_INCLUDE_SPEC')[2:].strip()
1437-
tcl_lib = get_var(tcl_config, 'TCL_LIB_FLAG')[2:].strip()
1438-
1439-
tk_lib_dir = get_var(tk_config, 'TK_LIB_SPEC').split()[0][2:].strip()
1440-
tk_inc_dir = get_var(tk_config, 'TK_INCLUDE_SPEC').strip()
1441-
if tk_inc_dir == '':
1442-
tk_inc_dir = tcl_inc_dir
1443-
else:
1444-
tk_inc_dir = tk_inc_dir[2:]
1445-
tk_lib = get_var(tk_config, 'TK_LIB_FLAG')[2:].strip()
1446-
1447-
if not os.path.exists(os.path.join(tk_inc_dir, 'tk.h')):
1448-
return None
1449-
1450-
return (tcl_lib_dir, tcl_inc_dir, tcl_lib,
1451-
tk_lib_dir, tk_inc_dir, tk_lib)
1452-
1453-
def guess_tcl_config(self, tcl_lib_dir, tk_lib_dir, tk_ver):
1454-
if not (os.path.exists(tcl_lib_dir) and os.path.exists(tk_lib_dir)):
1455-
return None
1456-
1457-
tcl_lib = os.path.normpath(os.path.join(tcl_lib_dir, '../'))
1458-
tk_lib = os.path.normpath(os.path.join(tk_lib_dir, '../'))
1459-
1460-
tcl_inc = os.path.normpath(
1461-
os.path.join(tcl_lib_dir,
1462-
'../../include/tcl' + tk_ver))
1463-
if not os.path.exists(tcl_inc):
1464-
tcl_inc = os.path.normpath(
1465-
os.path.join(tcl_lib_dir,
1466-
'../../include'))
1467-
1468-
tk_inc = os.path.normpath(os.path.join(
1469-
tk_lib_dir,
1470-
'../../include/tk' + tk_ver))
1471-
if not os.path.exists(tk_inc):
1472-
tk_inc = os.path.normpath(os.path.join(
1473-
tk_lib_dir,
1474-
'../../include'))
1475-
1476-
if not os.path.exists(os.path.join(tk_inc, 'tk.h')):
1477-
tk_inc = tcl_inc
1478-
1479-
if not os.path.exists(tcl_inc):
1480-
# this is a hack for suse linux, which is broken
1481-
if (sys.platform.startswith('linux') and
1482-
os.path.exists('/usr/include/tcl.h') and
1483-
os.path.exists('/usr/include/tk.h')):
1484-
tcl_inc = '/usr/include'
1485-
tk_inc = '/usr/include'
1486-
1487-
if not os.path.exists(os.path.join(tk_inc, 'tk.h')):
1488-
return None
1489-
1490-
return tcl_lib, tcl_inc, 'tcl' + tk_ver, tk_lib, tk_inc, 'tk' + tk_ver
1491-
1492-
def hardcoded_tcl_config(self):
1493-
tcl_inc = "/usr/local/include"
1494-
tk_inc = "/usr/local/include"
1495-
tcl_lib = "/usr/local/lib"
1496-
tk_lib = "/usr/local/lib"
1497-
return tcl_lib, tcl_inc, 'tcl', tk_lib, tk_inc, 'tk'
1498-
14991321
def add_flags(self, ext):
1322+
ext.include_dirs.extend(['src'])
15001323
if sys.platform == 'win32':
1501-
major, minor1, minor2, s, tmp = sys.version_info
1502-
if sys.version_info[0:2] < (3, 4):
1503-
ext.include_dirs.extend(['win32_static/include/tcl85'])
1504-
ext.libraries.extend(['tk85', 'tcl85'])
1505-
else:
1506-
ext.include_dirs.extend(['win32_static/include/tcl86'])
1507-
ext.libraries.extend(['tk86t', 'tcl86t'])
1508-
ext.library_dirs.extend([os.path.join(sys.prefix, 'dlls')])
1509-
1510-
elif sys.platform == 'darwin':
1511-
# this config section lifted directly from Imaging - thanks to
1512-
# the effbot!
1513-
1514-
# First test for a MacOSX/darwin framework install
1515-
from os.path import join, exists
1516-
framework_dirs = [
1517-
join(os.getenv('HOME'), '/Library/Frameworks'),
1518-
'/Library/Frameworks',
1519-
'/System/Library/Frameworks/',
1520-
]
1521-
1522-
# Find the directory that contains the Tcl.framework and
1523-
# Tk.framework bundles.
1524-
tk_framework_found = 0
1525-
for F in framework_dirs:
1526-
# both Tcl.framework and Tk.framework should be present
1527-
for fw in 'Tcl', 'Tk':
1528-
if not exists(join(F, fw + '.framework')):
1529-
break
1530-
else:
1531-
# ok, F is now directory with both frameworks. Continure
1532-
# building
1533-
tk_framework_found = 1
1534-
break
1535-
if tk_framework_found:
1536-
# For 8.4a2, we must add -I options that point inside
1537-
# the Tcl and Tk frameworks. In later release we
1538-
# should hopefully be able to pass the -F option to
1539-
# gcc, which specifies a framework lookup path.
1540-
1541-
tk_include_dirs = [
1542-
join(F, fw + '.framework', H)
1543-
for fw in ('Tcl', 'Tk')
1544-
for H in ('Headers', 'Versions/Current/PrivateHeaders')
1545-
]
1546-
1547-
# For 8.4a2, the X11 headers are not included. Rather
1548-
# than include a complicated search, this is a
1549-
# hard-coded path. It could bail out if X11 libs are
1550-
# not found...
1551-
1552-
# tk_include_dirs.append('/usr/X11R6/include')
1553-
frameworks = ['-framework', 'Tcl', '-framework', 'Tk']
1554-
ext.include_dirs.extend(tk_include_dirs)
1555-
ext.extra_link_args.extend(frameworks)
1556-
ext.extra_compile_args.extend(frameworks)
1557-
1558-
# you're still here? ok we'll try it this way...
1559-
else:
1560-
# There are 3 methods to try, in decreasing order of "smartness"
1561-
#
1562-
# 1. Parse the tclConfig.sh and tkConfig.sh files that have
1563-
# all the information we need
1564-
#
1565-
# 2. Guess the include and lib dirs based on the location of
1566-
# Tkinter's 'tcl_library' and 'tk_library' variables.
1567-
#
1568-
# 3. Use some hardcoded locations that seem to work on a lot
1569-
# of distros.
1570-
1571-
# Query Tcl/Tk system for library paths and version string
1572-
try:
1573-
tcl_lib_dir, tk_lib_dir, tk_ver = self.query_tcltk()
1574-
except:
1575-
tk_ver = ''
1576-
result = self.hardcoded_tcl_config()
1577-
else:
1578-
result = self.parse_tcl_config(tcl_lib_dir, tk_lib_dir)
1579-
if result is None:
1580-
result = self.guess_tcl_config(
1581-
tcl_lib_dir, tk_lib_dir, tk_ver)
1582-
if result is None:
1583-
result = self.hardcoded_tcl_config()
1584-
1585-
# Add final versions of directories and libraries to ext lists
1586-
(tcl_lib_dir, tcl_inc_dir, tcl_lib,
1587-
tk_lib_dir, tk_inc_dir, tk_lib) = result
1588-
ext.include_dirs.extend([tcl_inc_dir, tk_inc_dir])
1589-
ext.library_dirs.extend([tcl_lib_dir, tk_lib_dir])
1590-
ext.libraries.extend([tcl_lib, tk_lib])
1324+
# PSAPI library needed for finding Tcl / Tk at run time
1325+
ext.libraries.extend(['psapi'])
15911326

15921327

15931328
class BackendGtk(OptionalBackendPackage):
@@ -1701,8 +1436,6 @@ def check(self):
17011436
return super(BackendGtkAgg, self).check()
17021437
except:
17031438
raise
1704-
else:
1705-
BackendAgg.force = True
17061439

17071440
def get_package_data(self):
17081441
return {'matplotlib': ['mpl-data/*.glade']}
@@ -1778,7 +1511,6 @@ def check_requirements(self):
17781511
p.join()
17791512

17801513
if success:
1781-
BackendAgg.force = True
17821514
return msg
17831515
else:
17841516
raise CheckFailed(msg)
@@ -1851,7 +1583,6 @@ def check_requirements(self):
18511583
p.join()
18521584

18531585
if success:
1854-
BackendAgg.force = True
18551586
return msg
18561587
else:
18571588
raise CheckFailed(msg)
@@ -1895,8 +1626,6 @@ def check_requirements(self):
18951626
raise CheckFailed(
18961627
"Requires wxPython 2.8, found %s" % backend_version)
18971628

1898-
BackendAgg.force = True
1899-
19001629
return "version %s" % backend_version
19011630

19021631

@@ -1935,7 +1664,7 @@ def check_requirements(self):
19351664
config = self.get_config()
19361665
if config is False:
19371666
raise CheckFailed("skipping due to configuration")
1938-
return "installing"
1667+
return ""
19391668

19401669
def get_extension(self):
19411670
sources = [
@@ -2010,7 +1739,6 @@ def backend_pyside_internal_check(self):
20101739
except ImportError:
20111740
raise CheckFailed("PySide not found")
20121741
else:
2013-
BackendAgg.force = True
20141742
return ("Qt: %s, PySide: %s" %
20151743
(QtCore.__version__, __version__))
20161744

@@ -2027,7 +1755,6 @@ def backend_pyqt4_internal_check(self):
20271755
except AttributeError:
20281756
raise CheckFailed('PyQt4 not correctly imported')
20291757
else:
2030-
BackendAgg.force = True
20311758
return ("Qt: %s, PyQt: %s" % (self.convert_qt_version(qt_version), pyqt_version_str))
20321759

20331760

@@ -2069,7 +1796,6 @@ def backend_qt5_internal_check(self):
20691796
except AttributeError:
20701797
raise CheckFailed('PyQt5 not correctly imported')
20711798
else:
2072-
BackendAgg.force = True
20731799
return ("Qt: %s, PyQt: %s" % (self.convert_qt_version(qt_version), pyqt_version_str))
20741800

20751801

0 commit comments

Comments
 (0)