diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 000000000000..372d4812dab6 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,185 @@ +# With infos from +# http://tjelvarolsson.com/blog/how-to-continuously-test-your-python-code-on-windows-using-appveyor/ +# https://packaging.python.org/en/latest/appveyor/ +# https://github.com/rmcgibbo/python-appveyor-conda-example + +# Backslashes in quotes need to be escaped: \ -> "\\" + +environment: + + global: + # SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the + # /E:ON and /V:ON options are not enabled in the batch script intepreter + # See: http://stackoverflow.com/a/13751649/163740 + CMD_IN_ENV: "cmd /E:ON /V:ON /C obvci_appveyor_python_build_env.cmd" + # Workaround for https://github.com/conda/conda-build/issues/636 + PYTHONIOENCODING: "UTF-8" + TEST_ARGS: --no-pep8 + PYTEST_ARGS: -ra --timeout=300 --durations=25 #--cov-report= --cov=lib #-n %NUMBER_OF_PROCESSORS% + USE_PYTEST: no + #PYTHONHASHSEED: 0 # Workaround for pytest-xdist flaky colletion order + # # https://github.com/pytest-dev/pytest/issues/920 + # # https://github.com/pytest-dev/pytest/issues/1075 + + matrix: + # for testing purpose: numpy 1.8 on py2.7, for the rest use 1.10/latest + # theoretically the CONDA_INSTALL_LOCN could be only two: one for 32bit, + # one for 64bit because we construct envs anyway. But using one for the + # right python version is hopefully making it fast due to package caching. + - TARGET_ARCH: "x64" + CONDA_PY: "27" + CONDA_NPY: "18" + PYTHON_VERSION: "2.7" + TEST_ALL: "no" + CONDA_INSTALL_LOCN: "C:\\Miniconda-x64" + - TARGET_ARCH: "x64" + CONDA_PY: "34" + CONDA_NPY: "110" + PYTHON_VERSION: "3.4" + TEST_ALL: "no" + CONDA_INSTALL_LOCN: "C:\\Miniconda3-x64" + - TARGET_ARCH: "x64" + CONDA_PY: "35" + CONDA_NPY: "110" + PYTHON_VERSION: "3.5" + TEST_ALL: "no" + CONDA_INSTALL_LOCN: "C:\\Miniconda35-x64" + - TARGET_ARCH: "x86" + CONDA_PY: "27" + CONDA_NPY: "18" + PYTHON_VERSION: "2.7" + # this variable influence pdf/svg and most importantly the latex related tests + # which triples the runtime of the tests (7-8min vs 30min). + # pick the one which seems to make the most problems and run it last, so that + # the rest of the tests can give feedback earlier + TEST_ALL: "yes" + CONDA_INSTALL_LOCN: "C:\\Miniconda" + +# We always use a 64-bit machine, but can build x86 distributions +# with the PYTHON_ARCH variable (which is used by CMD_IN_ENV). +platform: + - x64 + +# all our python builds have to happen in tests_script... +build: false + +init: + - cmd: "ECHO %PYTHON_VERSION% PYTEST=%USE_PYTEST% %CONDA_INSTALL_LOCN%" + +install: + - cmd: set PATH=%CONDA_INSTALL_LOCN%;%CONDA_INSTALL_LOCN%\scripts;%PATH%; + - cmd: set PYTHONUNBUFFERED=1 + # for obvci_appveyor_python_build_env.cmd + - cmd: conda install -c pelson/channel/development --yes --quiet obvious-ci + # for msinttypes and newer stuff + - cmd: conda config --add channels conda-forge + - cmd: conda config --set show_channel_urls yes + - cmd: conda config --set always_yes true + # For building conda packages + - cmd: conda install --yes conda-build jinja2 anaconda-client + # this is now the downloaded conda... + - conda info -a + + # Fix the appveyor build environment to work with conda build + # workaround for missing vcvars64.bat in py34 64bit + - cmd: copy ci\appveyor\vcvars64.bat "C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin\amd64" + + # For building, use a new environment which only includes the requirements for mpl + # same things as the requirements in ci/conda_recipe/meta.yaml + # if conda-forge gets a new pyqt, it might be nice to install it as well to have more backends + # https://github.com/conda-forge/conda-forge.github.io/issues/157#issuecomment-223536381 + - conda create -q -n test-environment python=%PYTHON_VERSION% + pip setuptools numpy python-dateutil freetype=2.6 msinttypes "tk=8.5" + pyparsing pytz tornado "libpng>=1.6.21,<1.7" "zlib=1.2" "cycler>=0.10" + nose mock sphinx + - activate test-environment + - cmd: echo %PYTHON_VERSION% %TARGET_ARCH% + - cmd: IF %PYTHON_VERSION% == 2.7 conda install -q functools32 + # pytest-cov>=2.3.1 due to https://github.com/pytest-dev/pytest-cov/issues/124 + - if x%USE_PYTEST% == xyes conda install -q pytest "pytest-cov>=2.3.1" pytest-timeout #pytest-xdist + + # Let the install prefer the static builds of the libs + - set LIBRARY_LIB=%CONDA_PREFIX%\Library\lib + - cmd: 'mkdir lib || cmd /c "exit /b 0"' + - copy /Y %LIBRARY_LIB%\zlibstatic.lib lib\z.lib + - copy /Y %LIBRARY_LIB%\libpng_static.lib lib\png.lib + # These z.lib / png.lib are not static versions but files which end up as + # dependencies to the dll file. This is fine for the conda build, but not here + # and for the wheels + - del %LIBRARY_LIB%\png.lib + - del %LIBRARY_LIB%\z.lib + - set MPLBASEDIRLIST=%CONDA_PREFIX%\Library\;. + # enables the local freetype build + - copy ci\travis\setup.cfg . + # Show the installed packages + versions + - conda list + +test_script: + # Now build the thing.. + - '%CMD_IN_ENV% python setup.py develop' + # these should show no z, png, or freetype dll... + - set "DUMPBIN=%VS140COMNTOOLS%\..\..\VC\bin\dumpbin.exe" + - cmd: '"%DUMPBIN%" /DEPENDENTS lib\matplotlib\_png*.pyd' + - cmd: '"%DUMPBIN%" /DEPENDENTS lib\matplotlib\ft2font*.pyd' + - cmd: '"%DUMPBIN%" /DEPENDENTS lib\matplotlib\ft2font*.pyd | findstr freetype.*.dll && exit /b 1 || exit /b 0' + - cmd: '"%DUMPBIN%" /DEPENDENTS lib\\matplotlib\\_png*.pyd | findstr z.*.dll && exit /b 1 || exit /b 0' + - cmd: '"%DUMPBIN%" /DEPENDENTS lib\\matplotlib\\_png*.pyd | findstr png.*.dll && exit /b 1 || exit /b 0' + + # this are optional dependencies so that we don't skip so many tests... + - cmd: if x%TEST_ALL% == xyes; conda install -q pillow miktex inkscape + # missing packages on conda-forge for ffmpeg avconv mencoder imagemagick + - cmd: if x%TEST_ALL% == xyes; conda install -q -c menpo ffmpeg # a repackaged version + # This install sometimes failed randomly :-( + #- cmd: choco install imagemagick + + # Test import of tkagg backend + - python -c "import matplotlib as m; m.use('tkagg'); import matplotlib.pyplot as plt; print(plt.get_backend())" + # tests + - if x%USE_PYTEST% == xyes echo The following args are passed to pytest %PYTEST_ARGS% + - if x%USE_PYTEST% == xyes py.test %PYTEST_ARGS% %TEST_ARGS% + - if x%USE_PYTEST% == xno python tests.py %TEST_ARGS% + # Generate a html for visual tests + - python visual_tests.py + +after_test: + # After the tests were a success, build packages (wheels and conda) + + # Build the wheel with the static libs + # Hide the output, the copied files really clutter the build log... + - cmd: '%CMD_IN_ENV% python setup.py bdist_wheel > NUL:' + # Delete the result images from the wheel + - pip install delocate + - cmd: for %%w in ("dist\*.whl") do python tools\rm_test_images.py %%w + + # And now the conda build after a cleanup... + # cleanup build files so that they don't pollute the conda build but keep the wheel in dist... + - cmd: git clean -d -x -f -e dist/ + # cleanup the environment so that the test-environment does not leak into the conda build... + - cmd: set MPLBASEDIRLIST= + - cmd: set LIBRARY_LIB= + - cmd: deactivate + - cmd: path + - cmd: where python + - cmd: '%CMD_IN_ENV% conda config --get channels' + - cmd: '%CMD_IN_ENV% conda build -q .\ci\conda_recipe' + + # Move the conda package into the dist directory, to register it + # as an "artifact" for Appveyor. + - cmd: 'copy /Y %CONDA_INSTALL_LOCN%\conda-bld\win-32\*.bz2 dist || cmd /c "exit /b 0"' + - cmd: 'copy /Y %CONDA_INSTALL_LOCN%\conda-bld\win-64\*.bz2 dist || cmd /c "exit /b 0"' + - cmd: dir dist\ + - cmd: echo finished... + +artifacts: + - path: dist\* + name: packages + + - path: result_images\* + name: result_images + type: zip + +on_failure: + - python visual_tests.py + - echo zipping images after a failure... + - 7z a result_images.zip result_images\ |grep -v "Compressing" + - appveyor PushArtifact result_images.zip diff --git a/build_alllocal.cmd b/build_alllocal.cmd new file mode 100644 index 000000000000..37560d1830de --- /dev/null +++ b/build_alllocal.cmd @@ -0,0 +1,34 @@ +:: This assumes you have installed all the dependencies via conda packages: +:: # create a new environment with the required packages +:: conda create -n "matplotlib_build" python=3.4 numpy python-dateutil pyparsing pytz tornado pyqt cycler tk libpng zlib freetype +:: activate matplotlib_build +:: # this package is only available in the conda-forge channel +:: conda install -c conda-forge msinttypes +:: if you build on py2.7: +:: conda install -c conda-forge functools32 + +set TARGET=bdist_wheel +IF [%1]==[] ( + echo Using default target: %TARGET% +) else ( + set TARGET=%1 + echo Using user supplied target: %TARGET% +) + +IF NOT DEFINED CONDA_DEFAULT_ENV ( + echo No Conda env activated: you need to create a conda env with the right packages and activate it! + GOTO:eof +) + +:: copy the libs which have "wrong" names +set LIBRARY_LIB=%CONDA_DEFAULT_ENV%\Library\lib +mkdir lib || cmd /c "exit /b 0" +copy %LIBRARY_LIB%\zlibstatic.lib lib\z.lib +copy %LIBRARY_LIB%\libpng_static.lib lib\png.lib + +:: Make the header files and the rest of the static libs available during the build +:: CONDA_DEFAULT_ENV is a env variable which is set to the currently active environment path +set MPLBASEDIRLIST=%CONDA_DEFAULT_ENV%\Library\;. + +:: build the target +python setup.py %TARGET% diff --git a/ci/appveyor/vcvars64.bat b/ci/appveyor/vcvars64.bat new file mode 100644 index 000000000000..c4659becc3ae --- /dev/null +++ b/ci/appveyor/vcvars64.bat @@ -0,0 +1 @@ +CALL "C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd" /x64 \ No newline at end of file diff --git a/ci/conda_recipe/README.md b/ci/conda_recipe/README.md new file mode 100644 index 000000000000..7819c9f0c766 --- /dev/null +++ b/ci/conda_recipe/README.md @@ -0,0 +1,3 @@ +# conda package + +Up to now, this is mainly used to build a test conda package on windows on appveyor. \ No newline at end of file diff --git a/ci/conda_recipe/bld.bat b/ci/conda_recipe/bld.bat new file mode 100644 index 000000000000..a7810d418d2f --- /dev/null +++ b/ci/conda_recipe/bld.bat @@ -0,0 +1,16 @@ +set LIBPATH=%LIBRARY_LIB%; +set INCLUDE=%INCLUDE%;%PREFIX%\Library\include\freetype2 + +ECHO [directories] > setup.cfg +ECHO basedirlist = %LIBRARY_PREFIX% >> setup.cfg +ECHO [packages] >> setup.cfg +ECHO tests = False >> setup.cfg +ECHO sample_data = False >> setup.cfg +ECHO toolkits_tests = False >> setup.cfg + +@rem workaround for https://github.com/matplotlib/matplotlib/issues/6460 +@rem see also https://github.com/conda-forge/libpng-feedstock/pull/4 +copy /y %LIBRARY_LIB%\libpng16.lib %LIBRARY_LIB%\png.lib + +%PYTHON% setup.py install --single-version-externally-managed --record=record.txt +if errorlevel 1 exit 1 diff --git a/ci/conda_recipe/build.sh b/ci/conda_recipe/build.sh new file mode 100644 index 000000000000..c2967acb98cf --- /dev/null +++ b/ci/conda_recipe/build.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +if [ `uname` == Linux ]; then + pushd $PREFIX/lib + ln -s libtcl8.5.so libtcl.so + ln -s libtk8.5.so libtk.so + popd +fi + +cat < setup.cfg +[directories] +basedirlist = $PREFIX + +[packages] +tests = False +toolkit_tests = False +sample_data = False + +EOF + +# The macosx backend isn't building with conda at this stage. +if [ `uname` == Darwin ]; then +cat << EOF >> setup.cfg + +[gui_support] +tkagg = true +macosx = false + +EOF +fi + +cat setup.cfg +sed -i.bak "s|/usr/local|$PREFIX|" setupext.py + + +$PYTHON setup.py install --single-version-externally-managed --record=record.txt + diff --git a/ci/conda_recipe/cfg_qt4agg.patch b/ci/conda_recipe/cfg_qt4agg.patch new file mode 100644 index 000000000000..16e6fc6c3934 --- /dev/null +++ b/ci/conda_recipe/cfg_qt4agg.patch @@ -0,0 +1,13 @@ +diff --git setup.cfg.template setup.cfg.template +index cae6f67..fd11c79 100644 +--- setup.cfg.template ++++ setup.cfg.template +@@ -88,7 +88,7 @@ + # if you have disabled the relevent extension modules. Agg will be used + # by default. + # +-#backend = Agg ++backend = Qt4Agg + # + + [package_data] diff --git a/ci/conda_recipe/condaversion.patch b/ci/conda_recipe/condaversion.patch new file mode 100644 index 000000000000..915fda3bcc23 --- /dev/null +++ b/ci/conda_recipe/condaversion.patch @@ -0,0 +1,15 @@ +diff --git setup.py setup.py +index 8af8b6d..4e4f9d2 100644 +--- setup.py ++++ setup.py +@@ -57,6 +57,9 @@ + import versioneer + __version__ = versioneer.get_version() + ++# For conda builds... ++with open("__conda_version__.txt", "w") as f: ++ f.write(__version__) + + # These are the packages in the order we want to display them. This + # list may contain strings to create section headers for the display. + \ No newline at end of file diff --git a/ci/conda_recipe/meta.yaml b/ci/conda_recipe/meta.yaml new file mode 100644 index 000000000000..88cf1cfd6fd1 --- /dev/null +++ b/ci/conda_recipe/meta.yaml @@ -0,0 +1,73 @@ +# Full credit goes to https://github.com/conda/conda-recipes for providing this recipe. +# It has been subsequently adapted for automated building with conda-forge and matplotlib. + +package: + name: matplotlib + version: 1.9.9 + +source: + path: ../../ + + patches: + # A patch to make Qt4Agg the default backend. + - cfg_qt4agg.patch # [linux] + # Patches the matplotlibrc template to default to Qt4. + - rctmp_pyside.patch # [not osx] + # dynamic version from git + # we can't use condas usual dynamic versions as setup.py uses + # multiprocessing during the configure stage and this seems to confuse conda-build. + # https://github.com/conda/conda-build/issues/1061 + - condaversion.patch + +requirements: + build: + - python + - setuptools + - pkg-config # [not win] + - numpy x.x + - python-dateutil + - freetype 2.6* + - msinttypes # [win] + - cycler >=0.10 + - nose + - pyparsing + - pytz +# - py2cairo # [linux and py2k] + - tornado + - libpng >=1.6.21,<1.7 + - zlib 1.2* # [win] + - pyqt # [not osx] + - tk 8.5* # [linux] + - functools32 # [py2k] + + run: + - python + - numpy x.x + - cycler >=0.10 + - python-dateutil + - freetype 2.6* + - pytz + - pyparsing +# - py2cairo # [linux and py2k] + - libpng >=1.6.21,<1.7 + - pyqt # [not osx] + - tk 8.5* # [linux and win] + - functools32 # [py2k] + +test: + imports: + - matplotlib + - matplotlib.pyplot + +about: + home: http://matplotlib.org/ + license: PSF-based (http://matplotlib.org/users/license.html) + summary: Publication quality figures in Python + +extra: + recipe-maintainers: + - janschulz # only in the mpl repository + - mdboom # rest form conda-forge + - ocefpaf + - pelson + - tacaswell diff --git a/ci/conda_recipe/osx-tk.patch b/ci/conda_recipe/osx-tk.patch new file mode 100644 index 000000000000..1411225550e9 --- /dev/null +++ b/ci/conda_recipe/osx-tk.patch @@ -0,0 +1,60 @@ +diff --git setupext.py setupext.py +index ddf2ca1..b9e0729 100755 +--- setupext.py ++++ setupext.py +@@ -1659,52 +1659,11 @@ class BackendTkAgg(OptionalBackendPackage): + ext.library_dirs.extend([os.path.join(sys.prefix, 'dlls')]) + + elif sys.platform == 'darwin': +- # this config section lifted directly from Imaging - thanks to +- # the effbot! +- +- # First test for a MacOSX/darwin framework install + from os.path import join, exists +- framework_dirs = [ +- join(os.getenv('HOME'), '/Library/Frameworks'), +- '/Library/Frameworks', +- '/System/Library/Frameworks/', +- ] + +- # Find the directory that contains the Tcl.framework and +- # Tk.framework bundles. +- tk_framework_found = 0 +- for F in framework_dirs: +- # both Tcl.framework and Tk.framework should be present +- for fw in 'Tcl', 'Tk': +- if not exists(join(F, fw + '.framework')): +- break +- else: +- # ok, F is now directory with both frameworks. Continure +- # building +- tk_framework_found = 1 +- break +- if tk_framework_found: +- # For 8.4a2, we must add -I options that point inside +- # the Tcl and Tk frameworks. In later release we +- # should hopefully be able to pass the -F option to +- # gcc, which specifies a framework lookup path. +- +- tk_include_dirs = [ +- join(F, fw + '.framework', H) +- for fw in ('Tcl', 'Tk') +- for H in ('Headers', 'Versions/Current/PrivateHeaders') +- ] +- +- # For 8.4a2, the X11 headers are not included. Rather +- # than include a complicated search, this is a +- # hard-coded path. It could bail out if X11 libs are +- # not found... +- +- # tk_include_dirs.append('/usr/X11R6/include') +- frameworks = ['-framework', 'Tcl', '-framework', 'Tk'] +- ext.include_dirs.extend(tk_include_dirs) +- ext.extra_link_args.extend(frameworks) +- ext.extra_compile_args.extend(frameworks) ++ ext.include_dirs.append(join(sys.prefix, 'include')) ++ ext.libraries.extend(['tk8.5', 'tcl8.5']) ++ ext.library_dirs.append(join(sys.prefix, 'lib')) + + # you're still here? ok we'll try it this way... + else: diff --git a/ci/conda_recipe/rctmp_pyside.patch b/ci/conda_recipe/rctmp_pyside.patch new file mode 100644 index 000000000000..906803575a90 --- /dev/null +++ b/ci/conda_recipe/rctmp_pyside.patch @@ -0,0 +1,19 @@ +diff --git matplotlibrc.template matplotlibrc.template +index fdbbf26..6902fe9 100644 +--- matplotlibrc.template ++++ matplotlibrc.template +@@ -35,12 +35,12 @@ + # You can also deploy your own backend outside of matplotlib by + # referring to the module name (which must be in the PYTHONPATH) as + # 'module://my_backend'. +-backend : $TEMPLATE_BACKEND ++backend : Qt4Agg + + # If you are using the Qt4Agg backend, you can choose here + # to use the PyQt4 bindings or the newer PySide bindings to + # the underlying Qt4 toolkit. +-#backend.qt4 : PyQt4 # PyQt4 | PySide ++backend.qt4 : PyQt4 # PyQt4 | PySide + + # Note that this can be overridden by the environment variable + # QT_API used by Enthought Tool Suite (ETS); valid values are diff --git a/ci/conda_recipe/run_test.py b/ci/conda_recipe/run_test.py new file mode 100644 index 000000000000..fba57d981e5d --- /dev/null +++ b/ci/conda_recipe/run_test.py @@ -0,0 +1,29 @@ +import os +import platform +import sys + +import matplotlib +import matplotlib.pyplot +import matplotlib._cntr +import matplotlib._delaunay +import matplotlib._image +import matplotlib._path +import matplotlib._png +import matplotlib._tri +import matplotlib.backends._backend_agg +import matplotlib.ft2font +import matplotlib.ttconv +if platform.system() not in ['Windows']: + import matplotlib.backends._tkagg + +import pylab +import mpl_toolkits + +if int(os.getenv('GUI_TEST', 0)): + assert matplotlib.rcParams['backend.qt4'] == 'PyQt4' + + import matplotlib.pyplot as plt + plt.ioff() + plt.title('If this window displays, success: CLOSE TO CONTINUE TESTS') + plt.plot([1,2,5,9]) + plt.show() diff --git a/ci/travis/setup.cfg b/ci/travis/setup.cfg new file mode 100644 index 000000000000..61cdc102a0f8 --- /dev/null +++ b/ci/travis/setup.cfg @@ -0,0 +1,2 @@ +[test] +local_freetype=True \ No newline at end of file diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index 7a0b983acaa6..b5d284cfffbd 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -1503,8 +1503,13 @@ def gs_distill(tmpfile, eps=False, ptype='letter', bbox=None, rotated=False): with io.open(outfile, 'rb') as fh: if exit_status: - raise RuntimeError('ghostscript was not able to process \ - your image.\nHere is the full report generated by ghostscript:\n\n' + fh.read()) + output = fh.read() + m = "\n".join(["ghostscript was not able to process your image.", + "Here is the full report generated by ghostscript:", + "", + "%s"]) + # use % to prevent problems with bytes + raise RuntimeError(m % output) else: verbose.report(fh.read(), 'debug') os.remove(outfile) diff --git a/lib/matplotlib/testing/compare.py b/lib/matplotlib/testing/compare.py index 1d476d40e975..d5a41a611f7c 100644 --- a/lib/matplotlib/testing/compare.py +++ b/lib/matplotlib/testing/compare.py @@ -174,8 +174,8 @@ def convert(filename, cache): """ base, extension = filename.rsplit('.', 1) if extension not in converter: - raise ImageComparisonFailure( - "Don't know how to convert %s files to png" % extension) + from nose import SkipTest + raise SkipTest("Don't know how to convert %s files to png" % extension) newname = base + '_' + extension + '.png' if not os.path.exists(filename): raise IOError("'%s' does not exist" % filename) diff --git a/lib/matplotlib/tests/test_backend_ps.py b/lib/matplotlib/tests/test_backend_ps.py index ae12c501ba14..f017164ff7bb 100644 --- a/lib/matplotlib/tests/test_backend_ps.py +++ b/lib/matplotlib/tests/test_backend_ps.py @@ -71,6 +71,7 @@ def test_savefig_to_stringio_with_distiller(): @cleanup @needs_tex +@needs_ghostscript def test_savefig_to_stringio_with_usetex(): matplotlib.rcParams['text.latex.unicode'] = True matplotlib.rcParams['text.usetex'] = True @@ -90,6 +91,7 @@ def test_savefig_to_stringio_eps_afm(): @cleanup @needs_tex +@needs_ghostscript def test_savefig_to_stringio_with_usetex_eps(): matplotlib.rcParams['text.latex.unicode'] = True matplotlib.rcParams['text.usetex'] = True diff --git a/lib/matplotlib/tests/test_patches.py b/lib/matplotlib/tests/test_patches.py index 44856642b282..3cd550bd5ea8 100644 --- a/lib/matplotlib/tests/test_patches.py +++ b/lib/matplotlib/tests/test_patches.py @@ -20,6 +20,9 @@ from matplotlib import path as mpath from matplotlib import transforms as mtrans +import sys +on_win = (sys.platform == 'win32') + def test_Polygon_close(): #: Github issue #1018 identified a bug in the Polygon handling @@ -249,8 +252,9 @@ def test_wedge_movement(): assert_equal(getattr(w, attr), new_v) +# png needs tol>=0.06, pdf tol>=1.617 @image_comparison(baseline_images=['wedge_range'], - remove_text=True) + remove_text=True, tol=1.65 if on_win else 0) def test_wedge_range(): ax = plt.axes() diff --git a/lib/matplotlib/tests/test_triangulation.py b/lib/matplotlib/tests/test_triangulation.py index 06e56a3c6230..49a7d40baffb 100644 --- a/lib/matplotlib/tests/test_triangulation.py +++ b/lib/matplotlib/tests/test_triangulation.py @@ -14,6 +14,8 @@ import matplotlib.cm as cm from matplotlib.path import Path +import sys +on_win = (sys.platform == 'win32') def test_delaunay(): # No duplicate points, regular grid. @@ -770,7 +772,8 @@ def z(x, y): @image_comparison(baseline_images=['tri_smooth_gradient'], - extensions=['png'], remove_text=True) + extensions=['png'], remove_text=True, + tol=0.015 if on_win else 0) def test_tri_smooth_gradient(): # Image comparison based on example trigradient_demo. diff --git a/setup_external_compile.py b/setup_external_compile.py new file mode 100644 index 000000000000..25b7dcfb5325 --- /dev/null +++ b/setup_external_compile.py @@ -0,0 +1,228 @@ +""" +This file extracts and builds library dependencies libpng, zlib, & freetype2 on +MS Windows. It also extract tcl/tk for the header files. + +There are four possible build targets -- one for each permutation of VS 2008, +2010 and 32/64 bit. This builds the configuration that matches the Python +install that is executing. + +For Python 2.6, 2.7, 3.2: + +- VS 2008, 32 bit -- Windows SDK v7.0 +- VS 2008, 64 bit -- Windows SDK v7.0 + +For Python 3.3, 3.4: + +- VS 2010, 32 bit -- Windows SDK v7.1 +- VS 2010, 64 bit -- Windows SDK v7.1 +""" + +from __future__ import print_function, absolute_import +import sys +import platform +import os +import glob +import shutil +import zipfile +import tarfile +import distutils.msvc9compiler as msvc + +def fixproj(project_file, bit_target): + """ + :param bit_target: one of 'Win32' or 'x64' + """ + with open(project_file, 'r') as fd: + content = '\n'.join(line.strip() for line in fd if line.strip()) + content = content.replace('Win32', bit_target).replace('x64', bit_target) + with open(project_file, 'w') as fd: + fd.write(content) + +def tar_extract(tar_file, target): + with tarfile.open(tar_file, 'r:gz') as tgz: + tgz.extractall(target) + +def zip_extract(zip_file, target): + with zipfile.ZipFile(zip_file) as zf: + zf.extractall(target) + +# Configuration selection & declaration: +DEPSSRC = os.path.join(os.path.dirname(os.path.normpath(__file__)), 'deps_source') +DEPSBUILD = os.path.join(os.path.dirname(os.path.normpath(__file__)), 'build') +X64 = platform.architecture()[0] == '64bit' +PYVER = sys.version_info[:2] +VS2010 = PYVER >= (3, 3) +# If not VS2010, then use VS2008 + +VCVARSALL = None + +def prepare_build_cmd(build_cmd, **kwargs): + global VCVARSALL + if VCVARSALL == None: + candidate = msvc.find_vcvarsall(10.0 if VS2010 else 9.0) + if candidate == None: + raise RuntimeError('Microsoft VS {} required'.format('2010' if VS2010 else '2008')) + else: + VCVARSALL = candidate + + return build_cmd.format(vcvarsall=VCVARSALL, xXX='x64' if X64 else 'x86', **kwargs) + +def config_dir(): + segment = 'msvcr{}-x{}'.format('100' if VS2010 else '90', '64' if X64 else '32') + return os.path.join(DEPSBUILD, segment) + +def tcl_config_dir(): + return os.path.join(config_dir(), 'tcl85', 'include') + +def build_tcl(): + inclib = config_dir() + tcl_inclib = tcl_config_dir() + if not os.path.exists(tcl_inclib): + os.makedirs(tcl_inclib) + tcl_inclib_x11 = os.path.join(tcl_inclib, 'X11') + if not os.path.exists(tcl_inclib_x11): + os.makedirs(tcl_inclib_x11) + + distfile = os.path.join(DEPSSRC, 'tcl8513-src.zip') + compfile = os.path.join(tcl_inclib, 'tcl.h') + if not os.path.exists(compfile) or os.path.getmtime(distfile) > os.path.getmtime(compfile): + zip_extract(distfile, DEPSBUILD) + targetdir = os.path.join(DEPSBUILD, 'tcl8.5.13') + headers = glob.glob(os.path.join(targetdir, 'generic', '*.h')) + for filename in headers: + shutil.copy(filename, tcl_inclib) + + distfile = os.path.join(DEPSSRC, 'tk8513-src.zip') + compfile = os.path.join(tcl_inclib, 'tk.h') + if not os.path.exists(compfile) or os.path.getmtime(distfile) > os.path.getmtime(compfile): + zip_extract(distfile, DEPSBUILD) + targetdir = os.path.join(DEPSBUILD, 'tk8.5.13') + headers = glob.glob(os.path.join(targetdir, 'generic', '*.h')) + for filename in headers: + shutil.copy(filename, tcl_inclib) + headers = glob.glob(os.path.join(targetdir, 'xlib', 'X11', '*.*')) + for filename in headers: + shutil.copy(filename, tcl_inclib_x11) + +ZLIB_BUILD_CMD = """\ +@ECHO OFF +REM call "%ProgramFiles%\\Microsoft SDKs\\Windows\\v7.0\\Bin\\SetEnv.Cmd" /Release /{xXX} /xp +call "{vcvarsall}" {xXX} + +cd /D %ZLIB% +nmake -f win32\\Makefile.msc clean +nmake -f win32\\Makefile.msc +copy /Y /B *.dll %INCLIB% +copy /Y /B *.lib %INCLIB% +copy /Y /B zlib.lib %INCLIB%\\z.lib +copy /Y /B zlib.h %INCLIB% +copy /Y /B zconf.h %INCLIB% +""" + +def build_zlib(): + inclib = config_dir() + if not os.path.exists(inclib): + os.makedirs(inclib) + + distfile = os.path.join(DEPSSRC, 'zlib128.zip') + compfile = os.path.join(inclib, 'z.lib') + if os.path.exists(compfile) and os.path.getmtime(distfile) < os.path.getmtime(compfile): + # already built + return + + zip_extract(distfile, DEPSBUILD) + + cmdfile = os.path.join(DEPSBUILD, 'build_zlib.cmd') + with open(cmdfile, 'w') as cmd: + cmd.write(prepare_build_cmd(ZLIB_BUILD_CMD)) + + os.environ['INCLIB'] = inclib + os.environ['ZLIB'] = os.path.join(DEPSBUILD, 'zlib-1.2.8') + os.system(cmdfile) + +LIBPNG_BUILD_CMD = """\ +@ECHO OFF +REM call "%ProgramFiles%\\Microsoft SDKs\\Windows\\v7.0\\Bin\\SetEnv.Cmd" /Release /{xXX} /xp +call "{vcvarsall}" {xXX} +set CMAKE="cmake.exe" + +set BUILDDIR=%LIBPNG%-build +rd /S /Q %BUILDDIR% +%CMAKE% -G"NMake Makefiles" -H%LIBPNG% -B%BUILDDIR% ^ + -DCMAKE_BUILD_TYPE=Release ^ + -DZLIB_INCLUDE_DIR=%INCLIB% ^ + -DZLIB_LIBRARY:FILEPATH=%INCLIB%\\zlib.lib ^ + -DPNG_STATIC=ON ^ + -DPNG_SHARED=OFF +copy /Y /B %BUILDDIR%\\pnglibconf.h %INCLIB% +copy /Y /B %LIBPNG%\\png.h %INCLIB% +copy /Y /B %LIBPNG%\\pngconf.h %INCLIB% +cd %BUILDDIR% +nmake -f Makefile +REM It's a static lib -- no *.dll in sight! +REM copy /Y /B *.dll %INCLIB% +copy /Y /B *.lib %INCLIB% +copy /Y /B libpng16_static.lib %INCLIB%\\png.lib +""" + +def build_libpng(): + inclib = config_dir() + if not os.path.exists(inclib): + os.mkdir(inclib) + + distfile = os.path.join(DEPSSRC, 'libpng-1.6.7.tar.gz') + compfile = os.path.join(inclib, 'png.lib') + if os.path.exists(compfile) and os.path.getmtime(distfile) < os.path.getmtime(compfile): + # already built + return + + tar_extract(distfile, DEPSBUILD) + + cmdfile = os.path.join(DEPSBUILD, 'build_libpng.cmd') + with open(cmdfile, 'w') as cmd: + cmd.write(prepare_build_cmd(LIBPNG_BUILD_CMD)) + + os.environ['INCLIB'] = inclib + os.environ['LIBPNG'] = os.path.join(DEPSBUILD, 'libpng-1.6.7') + os.system(cmdfile) + +FREETYPE_VERSION = '2.4.11' + +FREETYPE_BUILD_CMD = """\ +@ECHO OFF +REM call "%ProgramFiles%\\Microsoft SDKs\\Windows\\v7.0\\Bin\\SetEnv.Cmd" /Release /{xXX} /xp +call "{vcvarsall}" {xXX} +set MSBUILD=C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319\\MSBuild.exe + +rd /S /Q %FREETYPE%\\objs +%MSBUILD% %FREETYPE%\\builds\\win32\\{vc20xx}\\freetype.sln /t:Clean;Build /p:Configuration="{config}";Platform={WinXX} +xcopy /Y /E /Q %FREETYPE%\\include %INCLIB% +xcopy /Y /E /Q %FREETYPE%\\objs\\win32\\{vc20xx} %INCLIB% +copy /Y /B %FREETYPE%\\objs\\win32\\{vc20xx}\\*.lib %INCLIB%\\freetype.lib +""" + +def build_freetype(): + inclib = config_dir() + if not os.path.exists(inclib): + os.mkdir(inclib) + + distfile = os.path.join(DEPSSRC, 'ft2411.zip') + compfile = os.path.join(inclib, 'freetype.lib') + if os.path.exists(compfile) and os.path.getmtime(distfile) < os.path.getmtime(compfile): + # already built + return + + vc = 'vc2010' if VS2010 else 'vc2008' + WinXX = 'x64' if X64 else 'Win32' + + zip_extract(distfile, DEPSBUILD) + ft_dir = os.path.join(DEPSBUILD, 'freetype-2.4.11') + fixproj(os.path.join(ft_dir, 'builds', 'win32', vc, 'freetype.sln'), WinXX) + fixproj(os.path.join(ft_dir, 'builds', 'win32', vc, 'freetype.{}'.format('vcxproj' if VS2010 else 'vcproj')), WinXX) + + cmdfile = os.path.join(DEPSBUILD, 'build_freetype.cmd') + with open(cmdfile, 'w') as cmd: + cmd.write(prepare_build_cmd(FREETYPE_BUILD_CMD, vc20xx=vc, WinXX=WinXX, config='Release' if VS2010 else 'LIB Release')) + + os.environ['INCLIB'] = inclib + os.environ['FREETYPE'] = ft_dir + os.system(cmdfile) \ No newline at end of file diff --git a/setupext.py b/setupext.py index 5b84c0a4f121..dbc398bd518e 100755 --- a/setupext.py +++ b/setupext.py @@ -61,7 +61,8 @@ def _get_xdg_cache_dir(): # This is the version of FreeType to use when building a local # version. It must match the value in -# lib/matplotlib.__init__.py +# lib/matplotlib.__init__.py and also needs to be changed below in the +# embedded windows build script (grep for "REMINDER" in this file) LOCAL_FREETYPE_VERSION = '2.6.1' # md5 hash of the freetype tarball LOCAL_FREETYPE_HASH = '348e667d728c597360e4a87c16556597' @@ -172,8 +173,17 @@ def get_base_dirs(): if options['basedirlist']: return options['basedirlist'] + if os.environ.get('MPLBASEDIRLIST'): + return os.environ.get('MPLBASEDIRLIST').split(os.pathsep) + + win_bases = ['win32_static', ] + # on conda windows, we also add the \Library of the local interperter, + # as conda installs libs/includes there + if os.getenv('CONDA_DEFAULT_ENV'): + win_bases.append(os.path.join(os.getenv('CONDA_DEFAULT_ENV'), "Library")) + basedir_map = { - 'win32': ['win32_static', ], + 'win32': win_bases, 'darwin': ['/usr/local/', '/usr', '/usr/X11', '/opt/X11', '/opt/local'], 'sunos5': [os.getenv('MPLIB_BASE') or '/usr/local', ], @@ -188,8 +198,11 @@ def get_include_dirs(): Returns a list of standard include directories on this platform. """ include_dirs = [os.path.join(d, 'include') for d in get_base_dirs()] - include_dirs.extend( - os.environ.get('CPLUS_INCLUDE_PATH', '').split(os.pathsep)) + if sys.platform != 'win32': + # gcc includes this dir automatically, so also look for headers in + # these dirs + include_dirs.extend( + os.environ.get('CPLUS_INCLUDE_PATH', '').split(os.pathsep)) return include_dirs @@ -945,7 +958,10 @@ def check(self): return "Using local version for testing" if sys.platform == 'win32': - check_include_file(get_include_dirs(), 'ft2build.h', 'freetype') + try: + check_include_file(get_include_dirs(), 'ft2build.h', 'freetype') + except CheckFailed: + check_include_file(get_include_dirs(), 'freetype2\\ft2build.h', 'freetype') return 'Using unknown version found on system.' status, output = getstatusoutput("freetype-config --ftversion") @@ -995,8 +1011,12 @@ def add_flags(self, ext): # Statically link to the locally-built freetype. # This is certainly broken on Windows. ext.include_dirs.insert(0, os.path.join(src_path, 'include')) + if sys.platform == 'win32': + libfreetype = 'libfreetype.lib' + else: + libfreetype = 'libfreetype.a' ext.extra_objects.insert( - 0, os.path.join(src_path, 'objs', '.libs', 'libfreetype.a')) + 0, os.path.join(src_path, 'objs', '.libs', libfreetype)) ext.define_macros.append(('FREETYPE_BUILD_TYPE', 'local')) else: pkg_config.setup_extension( @@ -1019,8 +1039,12 @@ def do_custom_build(self): 'build', 'freetype-{0}'.format(LOCAL_FREETYPE_VERSION)) # We've already built freetype - if os.path.isfile( - os.path.join(src_path, 'objs', '.libs', 'libfreetype.a')): + if sys.platform == 'win32': + libfreetype = 'libfreetype.lib' + else: + libfreetype = 'libfreetype.a' + + if os.path.isfile(os.path.join(src_path, 'objs', '.libs', libfreetype)): return tarball = 'freetype-{0}.tar.gz'.format(LOCAL_FREETYPE_VERSION) @@ -1092,15 +1116,52 @@ def do_custom_build(self): "{0} does not match expected hash.".format(tarball)) print("Building {0}".format(tarball)) - cflags = 'CFLAGS="{0} -fPIC" '.format(os.environ.get('CFLAGS', '')) - - subprocess.check_call( - ['tar', 'zxf', tarball], cwd='build') - subprocess.check_call( - [cflags + './configure --with-zlib=no --with-bzip2=no ' - '--with-png=no --with-harfbuzz=no'], shell=True, cwd=src_path) - subprocess.check_call( - [cflags + 'make'], shell=True, cwd=src_path) + if sys.platform != 'win32': + # compilation on all other platforms than windows + cflags = 'CFLAGS="{0} -fPIC" '.format(os.environ.get('CFLAGS', '')) + + subprocess.check_call( + ['tar', 'zxf', tarball], cwd='build') + subprocess.check_call( + [cflags + './configure --with-zlib=no --with-bzip2=no ' + '--with-png=no --with-harfbuzz=no'], shell=True, cwd=src_path) + subprocess.check_call( + [cflags + 'make'], shell=True, cwd=src_path) + else: + # compilation on windows + FREETYPE_BUILD_CMD = """\ +call "%ProgramFiles%\\Microsoft SDKs\\Windows\\v7.0\\Bin\\SetEnv.Cmd" /Release /{xXX} /xp +call "{vcvarsall}" {xXX} +set MSBUILD=C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319\\MSBuild.exe +rd /S /Q %FREETYPE%\\objs +%MSBUILD% %FREETYPE%\\builds\\windows\\{vc20xx}\\freetype.sln /t:Clean;Build /p:Configuration="{config}";Platform={WinXX} +echo Build completed, moving result" +:: move to the "normal" path for the unix builds... +mkdir %FREETYPE%\\objs\\.libs +:: REMINDER: fix when changing the version +copy %FREETYPE%\\objs\\{vc20xx}\\{xXX}\\freetype261.lib %FREETYPE%\\objs\\.libs\\libfreetype.lib +if errorlevel 1 ( + rem This is a py27 version, which has a different location for the lib file :-/ + copy %FREETYPE%\\objs\\win32\\{vc20xx}\\freetype261.lib %FREETYPE%\\objs\\.libs\\libfreetype.lib +) +""" + from setup_external_compile import fixproj, prepare_build_cmd, VS2010, X64, tar_extract + # Note: freetype has no build profile for 2014, so we don't bother... + vc = 'vc2010' if VS2010 else 'vc2008' + WinXX = 'x64' if X64 else 'Win32' + tar_extract(tarball_path, "build") + # This is only false for py2.7, even on py3.5... + if not VS2010: + fixproj(os.path.join(src_path, 'builds', 'windows', vc, 'freetype.sln'), WinXX) + fixproj(os.path.join(src_path, 'builds', 'windows', vc, 'freetype.vcproj'), WinXX) + + cmdfile = os.path.join("build", 'build_freetype.cmd') + with open(cmdfile, 'w') as cmd: + cmd.write(prepare_build_cmd(FREETYPE_BUILD_CMD, vc20xx=vc, WinXX=WinXX, + config='Release' if VS2010 else 'LIB Release')) + + os.environ['FREETYPE'] = src_path + subprocess.check_call([cmdfile], shell=True) class FT2Font(SetupPackage): diff --git a/tools/rm_test_images.py b/tools/rm_test_images.py new file mode 100644 index 000000000000..f53c1b12ff95 --- /dev/null +++ b/tools/rm_test_images.py @@ -0,0 +1,47 @@ +#!/usr/bin/env +""" Remove test images from matplotlib wheel(s) +""" +from __future__ import print_function + +from os.path import join as pjoin, basename, abspath, isdir +from shutil import rmtree +from argparse import ArgumentParser + +IMAGE_PATH = pjoin('matplotlib', 'tests', 'baseline_images') + +from delocate.wheeltools import InWheelCtx + + +def rm_images(whl_fname, out_fname, verbose=False): + whl_fname = abspath(whl_fname) + out_fname = abspath(out_fname) + with InWheelCtx(whl_fname) as ctx: + if not isdir(IMAGE_PATH): + if verbose: + print('No {} in {}'.format(IMAGE_PATH, whl_fname)) + return + rmtree(IMAGE_PATH) + # Write the wheel + ctx.out_wheel = out_fname + + +def get_parser(): + parser = ArgumentParser() + parser.add_argument('whl_fnames', nargs='+') + parser.add_argument('--verbose', action='store_true') + parser.add_argument('--out-path') + return parser + + +def main(): + args = get_parser().parse_args() + for whl_fname in args.whl_fnames: + out_fname = (pjoin(args.out_path, basename(whl_fname)) if args.out_path + else whl_fname) + if args.verbose: + print('Removing test images from {}'.format(whl_fname)) + rm_images(whl_fname, out_fname, verbose=args.verbose) + + +if __name__ == "__main__": + main() diff --git a/visual_tests.py b/visual_tests.py new file mode 100644 index 000000000000..f395b9d21d0b --- /dev/null +++ b/visual_tests.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python +# +# This builds a html page of all images from the image comparison tests +# and opens that page in the browser. +# +# $ python visual_tests.py +# + +import os +import time +import six + +from collections import defaultdict + +def run(): + # Build a website for visual comparison + image_dir = "result_images" + # build the website + _html = "" + _html += """ + \n""" + _subdirs = [name for name in os.listdir(image_dir) if os.path.isdir(os.path.join(image_dir, name))] + # loop over all pictures + _row = '{0} {1}{2}{4}\n' + _failed = "" + _failed += "

Only Failed

" + _failed += "\n\n" + _has_failure = False + _body = "" + for subdir in _subdirs: + if subdir == "test_compare_images": + # these are the image which test the image comparison functions... + continue + pictures = defaultdict(dict) + for file in os.listdir(os.path.join(image_dir, subdir)): + if os.path.isdir(os.path.join(image_dir, subdir, file)): + continue + fn, fext = os.path.splitext(file) + if fext != ".png": + continue + # Always use / for URLs. + if "-failed-diff" in fn: + pictures[fn[:-12]]["f"] = "/".join((subdir, file)) + elif "-expected" in fn: + pictures[fn[:-9]]["e"] = "/".join((subdir, file)) + else: + pictures[fn]["c"] = "/".join((subdir, file)) + + _body += "

{0}

".format(subdir) + _body += "
nameactualexpecteddiff
\n\n" + for name, test in six.iteritems(pictures): + if test.get("f", None): + # a real failure in the image generation, resulting in different images + _has_failure = True + s = "(failed)" + failed = 'diff'.format(test.get("f", "")) + current = ''.format(test.get("c", "")) + _failed += _row.format(name, "", current, test.get("e", ""), failed) + elif test.get("c", None) is None: + # A failure in the test, resulting in no current image + _has_failure = True + s = "(failed)" + failed = '--' + current = '(Failure in test, no image produced)' + _failed += _row.format(name, "", current, test.get("e", ""), failed) + else: + s = "(passed)" + failed = '--' + current = ''.format(test.get("c", "")) + _body += _row.format(name, "", current, test.get("e", ""), failed) + _body += "
nameactualexpecteddiff
\n" + _failed += "\n" + if _has_failure: + _html += _failed + _html += _body + _html += "\n" + index = os.path.join(image_dir, "index.html") + with open(index, "w") as f: + f.write(_html) + try: + import webbrowser + webbrowser.open(index) + except: + print("Open {0} in a browser for a visual comparison.".format(str(index))) + +if __name__ == '__main__': + run()