diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..f7b3d594 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "ci/multibuild"] + path = ci/multibuild + url = https://github.com/matthew-brett/multibuild diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..85212720 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,81 @@ +env: + global: + - REPO_DIR=. + # Commit from your-project that you want to build + #- BUILD_COMMIT=v0.1.0 + # pip dependencies to _build_ your project + #- BUILD_DEPENDS="Cython numpy" + # pip dependencies to _test_ your project. Include any dependencies + # that you need, that are also specified in BUILD_DEPENDS, this will be + # a separate install. + - TEST_DEPENDS="pytest" + - PLAT=x86_64 + - CONFIG_PATH="ci/multibuild_config.sh" + +language: python +# The travis Python version is unrelated to the version we build and test +# with. This is set with the MB_PYTHON_VERSION variable. +python: 3.5 +sudo: required +dist: trusty +services: docker + +cache: + directories: + - $HOME/.ccache + +matrix: + exclude: + # Exclude the default Python 3.5 build + - python: 3.5 + include: + - os: linux + env: + - MB_PYTHON_VERSION=2.7 + - PLAT=i686 + - USE_CCACHE=1 + - FREETYPEPY_BUNDLE_FT=1 + - os: linux + env: + - MB_PYTHON_VERSION=3.6 + - USE_CCACHE=1 + - FREETYPEPY_BUNDLE_FT=1 + - os: linux # No bundling. + env: + - MB_PYTHON_VERSION=3.6 + - USE_CCACHE=1 + - os: osx + language: generic + env: + - MB_PYTHON_VERSION=3.6 + - FREETYPEPY_BUNDLE_FT=1 + +before_install: + - source ci/multibuild/common_utils.sh + - source ci/multibuild/travis_steps.sh + - before_install + +install: + - build_wheel $REPO_DIR $PLAT + +script: + - install_run $PLAT + +after_success: + # copy compiled wheels to dist/ where Travis `dpl` tool can find them and + # upload to PyPI + - if [ -n "$TRAVIS_TAG" ]; then mkdir -p dist; cp wheelhouse/*.whl dist; fi + +deploy: + # Deploy to PyPI on tags. Since the hard work of building wheels is already + # done, we need to defeat some of Travis' automation. + provider: pypi + on: + repo: rougier/freetype-py # Prevent triggering from forks. + tags: true + all_branches: true + user: xxx + password: + secure: xxx + skip_cleanup: true # Prevent deletion of dist/*. + distributions: check # Dummy argument so dists aren't rebuilt by dpl. diff --git a/MANIFEST.in b/MANIFEST.in index 0c0387e3..0673f1c5 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -8,3 +8,4 @@ recursive-include examples * include MANIFEST.in include LICENSE.txt include README.rst +include setup-build-freetype.py diff --git a/README.rst b/README.rst index 619db85c..851bd102 100644 --- a/README.rst +++ b/README.rst @@ -1,47 +1,71 @@ -FreeType high-level python API -============================== +FreeType (high-level Python API) +================================ -Freetype python provides bindings for the FreeType library. Only the high-level API is bound. +Freetype Python provides bindings for the FreeType library. Only the +high-level API is bound. Documentation available at: http://freetype-py.readthedocs.org/en/latest/ Installation ============ -To be able to use freetype python, you need the freetype library version 2 -installed on your system. +**From PyPI, recommended**: `pip install freetype-py`. This will install the +library with a bundled FreeType binary, so you're ready to go on Windows, +macOS and Linux (all with 32 and 64 bit x86 architecture support). + +Do note: if you specify the `--no-binary` paramater to pip, or use a different +architecture for which we don't pre-compile binaries, the package will default +to using an external FreeType library. Specify the environment variable +`FREETYPEPY_BUNDLE_FT=1` before calling pip to compile a binary yourself. + +Installation with compiling FreeType from source +------------------------------------------------ + +If you don't want to or can't use the pre-built binaries, build FreeType +yourself: `export FREETYPEPY_BUNDLE_FT=yesplease && pip install .`. +This will download and compile FreeType with Harfbuzz support as specified in +`setup-build-freetype.py`. Set the environment variable `PYTHON_ARCH` to 32 or +64 to explicitly set an architecture, default is whatever your host machine +uses. On macOS, we will always build a universal 32 and 64 bit Intel binary. + +- Windows: You need CMake and a C and C++ compiler, e.g. the Visual Code + Community 2017 distribution with the desktop C++ workload. +- macOS: You need CMake and the XCode tools (full IDE not necessary) +- Linux: You need CMake, gcc and g++. For building a 32 bit library on a + 64 bit machine, you need gcc-multilib and g++-multilib (Debian) or + glibc-devel.i686 and libstdc++-devel.i686 (Fedora). + +Installation with an external FreeType library (the default) +------------------------------------------------------------ + +Install just the pure Python library and let it find a system-wide installed +FreeType at runtime. Mac users ---------- +~~~~~~~~~ Freetype should be already installed on your system. If not, either install it using `homebrew `_ or compile it and place the library binary file in '/usr/local/lib'. Linux users ------------ +~~~~~~~~~~~ Freetype should be already installed on your system. If not, either install relevant package from your package manager or compile from sources and place the library binary file in '/usr/local/lib'. Window users ------------- +~~~~~~~~~~~~ There are no official Freetype binary releases available, but they offer some links to precompiled Windows DLLs. Please see the `FreeType Downloads `_ page for links. -You can also compile the FreeType library from source. +You can also compile the FreeType library from source yourself. -32-Bit vs 64-Bit on Windows -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you are using freetype-py on Windows with a 32-Bit version of python, you +If you are using freetype-py on Windows with a 32-Bit version of Python, you need the 32-Bit version of the Freetype binary. The same applies for a 64-Bit -version of python. - -Installation on Windows -~~~~~~~~~~~~~~~~~~~~~~~ +version of Python. Because of the way Windows searches for dll files, make sure the resulting file is named 'freetype.dll' (and not something like Freetype245.dll). @@ -123,3 +147,4 @@ Contributors * Tao Gong (bug report) * Matthew Sitton (Remove raw interfaces from the __init__.py file) * Daniel McCloy (Adde glyph_name function) +* Nikolaus Waxweiler (Setup of CI services and bundling of FreeType) diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..4fd14aa5 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,80 @@ +environment: + global: + PACKAGE_NAME: freetype-py + FREETYPEPY_BUNDLE_FT: 1 + # PyPI username and encrypted password + TWINE_USERNAME: xxx + TWINE_PASSWORD: + secure: xxx + matrix: + - PYTHON: C:\Python27 + PYTHON_VERSION: 2.7 + PYTHON_ARCH: '32' + + - PYTHON: C:\Python36-x64 + PYTHON_VERSION: 3.6 + PYTHON_ARCH: '64' + +init: + - ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH% + +install: + # If there is a newer build queued for the same PR, cancel this one. + # The AppVeyor 'rollout builds' option is supposed to serve the same + # purpose but it is problematic because it tends to cancel builds pushed + # directly to master instead of just PR builds (or the converse). + # credits: JuliaLang developers. + - ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod ` + https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | ` + Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { ` + throw "There are newer queued builds for this pull request, failing early." } + + # checkout git sub-modules + - git submodule update --init --recursive + + # prepend Python to the PATH + - SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH% + + # check that we have the expected version and architecture for Python + - python --version + - python -c "import struct; print(struct.calcsize('P') * 8)" + + # install/upgrade python setup requirements + - python -m pip install --disable-pip-version-check --upgrade pip + - pip --version + - pip install --upgrade setuptools wheel virtualenv + +build_script: + # build the wheel in the default 'dist/' folder + - python setup.py bdist_wheel + +test_script: + # create test env + - python -m virtualenv test_env + - test_env\Scripts\activate + - where python + - pip install pytest + + # install from wheel + - pip install --no-index --find-links dist %PACKAGE_NAME% + + # run tests from installed wheel + - cd tests + - pytest + +artifacts: + # archive the generated packages in the ci.appveyor.com build report + - path: dist\*.whl + +on_success: + # deploy wheels on tags to PyPI + - ps: >- + if($env:APPVEYOR_REPO_TAG -eq 'true') { + Write-Output ("Deploying " + $env:APPVEYOR_REPO_TAG_NAME + " to PyPI...") + pip install --upgrade twine + # If powershell ever sees anything on stderr it thinks it's a fail. + # So we use cmd to redirect stderr to stdout before PS can see it. + cmd /c 'twine upload dist\*.whl 2>&1' + } else { + Write-Output "Not deploying as this is not a tagged commit" + } diff --git a/ci/multibuild b/ci/multibuild new file mode 160000 index 00000000..cba6a422 --- /dev/null +++ b/ci/multibuild @@ -0,0 +1 @@ +Subproject commit cba6a4228b28922799fde42e8a19833b81016816 diff --git a/ci/multibuild_config.sh b/ci/multibuild_config.sh new file mode 100644 index 00000000..2dee9997 --- /dev/null +++ b/ci/multibuild_config.sh @@ -0,0 +1,15 @@ +# Define custom utilities +# Test for OSX with [ -n "$IS_OSX" ] + +# function pre_build { +# # Any stuff that you need to do before you start building the wheels +# # Runs in the root directory of this repository. +# +# } + +function run_tests { + # The function is called from an empty temporary directory. + cd ../tests + python -c "import freetype; print('Using FreeType version ', freetype.version())" + pytest +} diff --git a/doc/index.rst b/doc/index.rst index 5e5d358a..27a2af18 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -4,13 +4,12 @@ Freetype python documentation Freetype python provides bindings for the FreeType library. Only the high-level API is bound. -Freetype-py lives at https://github.com/rougier/freetype-py/ +Freetype-py lives at https://github.com/rougier/freetype-py/, see the installation instructions there. .. toctree:: :maxdepth: 1 - introduction.rst usage.rst screenshots.rst api.rst diff --git a/doc/introduction.rst b/doc/introduction.rst deleted file mode 100644 index fa64696e..00000000 --- a/doc/introduction.rst +++ /dev/null @@ -1,53 +0,0 @@ -============ -Introduction -============ - -To be able to use freetype python, you need the freetype library version 2 -installed on your system. - - -Pre-requisites -============== - -You need to have the Freetype library installed on your system to be able to use -the freetype python bindings. - -.. warning:: - - If you don't compile the Freetype library yourself, chances are subpixel - anti-aliasing will be disabled due to patent problems. Have a look at - `Freetype FAQ `_ - to know how to enable it. - -Mac users ---------- -Freetype should be already installed on your system. If not, either install it -using `homebrew `_ or compile it and place the library binary -file in '/usr/local/lib'. - -Linux users ------------ -Freetype should be already installed on your system. If not, either install -relevant package from your package manager or compile from sources and place -the library binary file in '/usr/local/lib'. - -Window users ------------- -You can try to install a window binaries available from the Freetype site or -you can compile it from sources. In such a case, make sure the resulting -library binaries is named 'Freetype.dll' (and not something like -Freetype245.dll) and make sure to place a copy in Windows/System32 directory. - - -Installation -============ - -The easiest way to install freetype-py is to use pip:: - - pip install freetype-py - -Or you can get the latest version from git and install yourself:: - - git clone https://github.com/rougier/freetype-py.git - cd freetype-py - python setup.py install diff --git a/freetype/raw.py b/freetype/raw.py index 70da63dc..9cbe7cdb 100644 --- a/freetype/raw.py +++ b/freetype/raw.py @@ -13,22 +13,37 @@ from ctypes import * import ctypes.util +import freetype from freetype.ft_types import * from freetype.ft_enums import * from freetype.ft_errors import * from freetype.ft_structs import * -# on windows all ctypes does when checking for the library -# is to append .dll to the end and look for an exact match -# within any entry in PATH. -filename = ctypes.util.find_library('freetype') +# First, look for a bundled FreeType shared object on the top-level of the +# installed freetype-py module. +system = platform.system() +if system == 'Windows': + library_name = 'libfreetype.dll' +elif system == 'Darwin': + library_name = 'libfreetype.dylib' +else: + library_name = 'libfreetype.so' -if filename is None: - if platform.system() == 'Windows': - # Check current working directory for dll as ctypes fails to do so - filename = os.path.join(os.path.realpath('.'), 'freetype.dll') - else: - filename = 'libfreetype.so.6' +filename = os.path.join(os.path.dirname(freetype.__file__), library_name) + +# If no bundled shared object is found, look for a system-wide installed one. +if not os.path.exists(filename): + # on windows all ctypes does when checking for the library + # is to append .dll to the end and look for an exact match + # within any entry in PATH. + filename = ctypes.util.find_library('freetype') + + if filename is None: + if platform.system() == 'Windows': + # Check current working directory for dll as ctypes fails to do so + filename = os.path.join(os.path.realpath('.'), "freetype.dll") + else: + filename = library_name try: _lib = ctypes.CDLL(filename) diff --git a/setup-build-freetype.py b/setup-build-freetype.py new file mode 100644 index 00000000..8fb54071 --- /dev/null +++ b/setup-build-freetype.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# This will build a FreeType binary for bundling with freetype-py on Windows, +# Linux and macOS. The environment determines if it's a 32 or 64 bit build (on +# Windows, set $env:PYTHON_ARCH="64" for a x64 build; on Linux, set it to "32" +# to explicitly make a 32 bit build). + +# It can be used stand-alone, in conjunction with setup.py and on CI services +# like Travis and Appveyor. You need CMake and some C/C++ compiler suite (e.g. +# GCC, Visual Studio Community 2017, ...) + +import distutils.dir_util +import distutils.spawn +import glob +import os +import subprocess +import sys +from os import path + +FREETYPE_HOST = "https://download.savannah.gnu.org/releases/freetype/" +FREETYPE_TARBALL = "freetype-2.9.tar.bz2" +FREETYPE_URL = FREETYPE_HOST + FREETYPE_TARBALL +FREETYPE_SHA256 = ( + "e6ffba3c8cef93f557d1f767d7bc3dee860ac7a3aaff588a521e081bc36f4c8a") +HARFBUZZ_HOST = "https://www.freedesktop.org/software/harfbuzz/release/" +HARFBUZZ_TARBALL = "harfbuzz-1.7.6.tar.bz2" +HARFBUZZ_URL = HARFBUZZ_HOST + HARFBUZZ_TARBALL +HARFBUZZ_SHA256 = ( + "da7bed39134826cd51e57c29f1dfbe342ccedb4f4773b1c951ff05ff383e2e9b") + +root_dir = "." +build_dir = path.join(root_dir, "build") +# CMake requires an absolute path to a prefix. +prefix_dir = path.abspath(path.join(build_dir, "local")) +build_dir_ft = path.join(build_dir, FREETYPE_TARBALL.split(".tar")[0], "build") +build_dir_hb = path.join(build_dir, HARFBUZZ_TARBALL.split(".tar")[0], "build") + +CMAKE_GLOBAL_SWITCHES = ('-DCMAKE_COLOR_MAKEFILE:BOOL=false ' + '-DCMAKE_PREFIX_PATH:PATH="{}" ' + '-DCMAKE_INSTALL_PREFIX:PATH="{}" ').format( + prefix_dir, prefix_dir) + +# Try to use Ninja to build things if it's available. Much faster. +# On Windows, I first need to figure out how to make it aware of VC, bitness, +# etc. +if sys.platform != "win32" and distutils.spawn.find_executable("ninja"): + CMAKE_GLOBAL_SWITCHES += "-G Ninja " + +bitness = None + +if sys.platform == "win32": + if os.environ.get("PYTHON_ARCH", "") == "64": + print("# Making a 64 bit build.") + bitness = 64 + CMAKE_GLOBAL_SWITCHES += ('-DCMAKE_GENERATOR_PLATFORM=x64 ' + '-DCMAKE_GENERATOR_TOOLSET="host=x64" ') + else: + print("# Making a 32 bit build.") + bitness = 32 + +if sys.platform == "darwin": + print("# Making a 96 bit build.") + CMAKE_GLOBAL_SWITCHES += ('-DCMAKE_OSX_ARCHITECTURES="x86_64;i386" ' + '-DCMAKE_OSX_DEPLOYMENT_TARGET="10.6" ' + '-DCMAKE_C_FLAGS="-O2" ' + '-DCMAKE_CXX_FLAGS="-O2" ') + bitness = 96 + +if "linux" in sys.platform: + if os.environ.get("PYTHON_ARCH", "") == "32": + print("# Making a 32 bit build.") + # On a 64 bit Debian/Ubuntu, this needs gcc-multilib and g++-multilib. + # On a 64 bit Fedora, install glibc-devel and libstdc++-devel. + CMAKE_GLOBAL_SWITCHES += ('-DCMAKE_C_FLAGS="-m32 -O2" ' + '-DCMAKE_CXX_FLAGS="-m32 -O2" ' + '-DCMAKE_LD_FLAGS="-m32" ') + bitness = 32 + else: + print("# Making a 64 bit build.") + CMAKE_GLOBAL_SWITCHES += ('-DCMAKE_C_FLAGS="-m64 -O2" ' + '-DCMAKE_CXX_FLAGS="-m64 -O2" ' + '-DCMAKE_LD_FLAGS="-m64" ') + bitness = 64 + + +def shell(cmd, cwd=None): + """Run a shell command specified by cmd string.""" + try: # Python2 compatibility wrapper. + subprocess.run(cmd, shell=True, check=True, cwd=cwd) + except AttributeError: + rv = subprocess.Popen(cmd, cwd=cwd, shell=True).wait() + if rv != 0: + sys.exit(rv) + + +def download(url, target_path): + """Download url to target_path.""" + try: # Python2 compatibility wrapper. + from urllib.request import urlretrieve + except ImportError: + from urllib import urlretrieve + + print("Downloading {}...".format(url)) + urlretrieve(url, target_path) + + +def ensure_downloaded(url, sha256_sum): + """Download .tar.bz2 tarball at url unless already present, extract.""" + filename = path.basename(url) + tarball = path.join(build_dir, filename) + tarball_dir = filename.split(".tar")[0] + + if not path.exists(tarball): + download(url, tarball) + + if not path.exists(path.join(build_dir, tarball_dir, "CMakeLists.txt")): + import hashlib + hasher = hashlib.sha256() + with open(tarball, 'rb') as tb: + hasher.update(tb.read()) + assert hasher.hexdigest() == sha256_sum + + import tarfile + with tarfile.open(tarball, 'r:bz2') as tb: + tb.extractall(build_dir) + + +distutils.dir_util.mkpath(build_dir) +distutils.dir_util.mkpath(prefix_dir) +distutils.dir_util.mkpath(build_dir_ft) +distutils.dir_util.mkpath(build_dir_hb) + +ensure_downloaded(FREETYPE_URL, FREETYPE_SHA256) +ensure_downloaded(HARFBUZZ_URL, HARFBUZZ_SHA256) + +print("# First, build FreeType without Harfbuzz support") +shell( + "cmake -DBUILD_SHARED_LIBS:BOOL=false " + "-DWITH_HarfBuzz=OFF -DWITH_PNG=OFF -DWITH_BZip2=OFF -DWITH_ZLIB=OFF " + "{} ..".format(CMAKE_GLOBAL_SWITCHES), + cwd=build_dir_ft) +shell("cmake --build . --config Release --target install", cwd=build_dir_ft) + +print("\n# Next, build Harfbuzz and point it to the FreeType we just build.") +shell( + "cmake -DBUILD_SHARED_LIBS:BOOL=false " + + # https://stackoverflow.com/questions/3961446 + ("-DCMAKE_POSITION_INDEPENDENT_CODE=ON " if bitness > 32 else "") + + "-DHB_HAVE_FREETYPE=ON -DHB_HAVE_GLIB=OFF -DHB_HAVE_CORETEXT=OFF " + "{} ..".format(CMAKE_GLOBAL_SWITCHES), + cwd=build_dir_hb) +shell("cmake --build . --config Release --target install", cwd=build_dir_hb) + +print("\n# Lastly, rebuild FreeType, this time with Harfbuzz support.") +harfbuzz_includes = path.join(prefix_dir, "include", "harfbuzz") +shell( + "cmake -DBUILD_SHARED_LIBS:BOOL=true -DMINGW=ON " + "-DWITH_HarfBuzz=ON -DWITH_PNG=OFF -DWITH_BZip2=OFF -DWITH_ZLIB=OFF " + "-DPKG_CONFIG_EXECUTABLE=\"\" " # Prevent finding system libraries + "-DHARFBUZZ_INCLUDE_DIRS=\"{}\" " + "{} ..".format(harfbuzz_includes, CMAKE_GLOBAL_SWITCHES), + cwd=build_dir_ft) +shell("cmake --build . --config Release --target install", cwd=build_dir_ft) + +# Move libraries from PREFIX/bin to PREFIX/lib if need be. This keeps setup.py +# simple. +bin_so = glob.glob(path.join(prefix_dir, "bin", "*freetype*")) +for so in bin_so: + so_target_name = path.basename(so) + if not so_target_name.startswith("lib"): + so_target_name = "lib" + so_target_name + lib_path = path.join(prefix_dir, "lib", so_target_name) + print("# Moving '{}' to '{}'".format(so, lib_path)) + os.rename(so, lib_path) diff --git a/setup.py b/setup.py old mode 100755 new mode 100644 index 3228f17b..4f56ff28 --- a/setup.py +++ b/setup.py @@ -4,34 +4,133 @@ # FreeType high-level python API - copyright 2011 Nicolas P. Rougier # Distributed under the terms of the new BSD license. # ----------------------------------------------------------------------------- +from __future__ import absolute_import, print_function + +import distutils +import distutils.dir_util +import distutils.file_util +import os +import subprocess +import sys +from io import open from os import path -from codecs import open -from setuptools import setup, Extension - -description = open(path.join(path.abspath(path.dirname(__file__)), - 'README.rst'), encoding='utf-8').read() - -setup( name = 'freetype-py', - version = '1.2', - description = 'Freetype python bindings', - long_description = description, - author = 'Nicolas P. Rougier', - author_email= 'Nicolas.Rougier@inria.fr', - url = 'https://github.com/rougier/freetype-py', - packages = ['freetype', 'freetype.ft_enums'], - classifiers = [ - 'Development Status :: 5 - Production/Stable', - 'Environment :: X11 Applications', - 'Environment :: MacOS X', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: GNU General Public License (GPL)', - 'Operating System :: MacOS', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: POSIX', - 'Operating System :: Unix', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 3', - 'Topic :: Multimedia :: Graphics', - ], - keywords = ['freetype', 'font'], - ) + +from setuptools import setup + +if os.environ.get("FREETYPEPY_BUNDLE_FT"): + print("# Will build and bundle FreeType.") + + from setuptools import Extension + from setuptools.command.build_ext import build_ext + from wheel.bdist_wheel import bdist_wheel + + class UniversalBdistWheel(bdist_wheel): + def get_tag(self): + return ( + 'py2.py3', + 'none', + ) + bdist_wheel.get_tag(self)[2:] + + class SharedLibrary(Extension): + """Object that describes the library (filename) and how to make it.""" + if sys.platform == "darwin": + suffix = ".dylib" + elif sys.platform == "win32": + suffix = ".dll" + else: + suffix = ".so" + + def __init__(self, name, cmd, cwd=".", output_dir=".", env=None): + Extension.__init__(self, name, sources=[]) + self.cmd = cmd + self.cwd = path.normpath(cwd) + self.output_dir = path.normpath(output_dir) + self.env = env or dict(os.environ) + + class SharedLibBuildExt(build_ext): + """Object representing command to produce and install a shared + library.""" + + # Needed to make setuptools and wheel believe they're looking at an + # extension instead of a shared library. + def get_ext_filename(self, ext_name): + for ext in self.extensions: + if isinstance(ext, SharedLibrary): + return os.path.join(*ext_name.split('.')) + ext.suffix + return build_ext.get_ext_filename(self, ext_name) + + def build_extension(self, ext): + if not isinstance(ext, SharedLibrary): + build_ext.build_extension(self, ext) + return + + distutils.log.info("running '{}'".format(ext.cmd)) + if not self.dry_run: + rv = subprocess.Popen( + ext.cmd, cwd=ext.cwd, env=ext.env, shell=True).wait() + if rv != 0: + sys.exit(rv) + + lib_name = ext.name.split(".")[-1] + ext.suffix + lib_fullpath = path.join(ext.output_dir, lib_name) + dest_path = self.get_ext_fullpath(ext.name) + + distutils.dir_util.mkpath( + path.dirname(dest_path), + verbose=self.verbose, + dry_run=self.dry_run) + + distutils.file_util.copy_file( + lib_fullpath, + dest_path, + verbose=self.verbose, + dry_run=self.dry_run) + + ext_modules = [ + SharedLibrary( + "freetype.libfreetype", # package.shared_lib_name + cmd='"{}" ./setup-build-freetype.py'.format(sys.executable), + output_dir="build/local/lib") + ] + cmdclass = { + 'bdist_wheel': UniversalBdistWheel, + 'build_ext': SharedLibBuildExt + } + +else: + print("# Will use the system-provided FreeType.") + ext_modules = [] + cmdclass = {} + +description = open( + path.join(path.abspath(path.dirname(__file__)), 'README.rst'), + encoding='utf-8').read() + +setup( + name='freetype-py', + version='1.2', + description='Freetype python bindings', + long_description=description, + author='Nicolas P. Rougier', + author_email='Nicolas.Rougier@inria.fr', + url='https://github.com/rougier/freetype-py', + packages=['freetype', 'freetype.ft_enums'], + ext_modules=ext_modules, + zip_safe=False if ext_modules else True, + cmdclass=cmdclass, + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Environment :: X11 Applications', + 'Environment :: MacOS X', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: GNU General Public License (GPL)', + 'Operating System :: MacOS', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: POSIX', + 'Operating System :: Unix', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 3', + 'Topic :: Multimedia :: Graphics', + ], + keywords=['freetype', 'font'], +) diff --git a/tests/smoke_test.py b/tests/smoke_test.py new file mode 100644 index 00000000..32fe6a52 --- /dev/null +++ b/tests/smoke_test.py @@ -0,0 +1,27 @@ +import glob +import os + +import freetype +import pytest + + +def test_load_ft_face(): + """A smoke test.""" + assert freetype.Face('../examples/Vera.ttf') + + +def test_bundle_version(): + module_dir = os.path.dirname(freetype.__file__) + shared_object = glob.glob(os.path.join(module_dir, "libfreetype*")) + if shared_object: + import re + with open("../setup-build-freetype.py") as f: + m = re.findall(r"freetype-(\d+)\.(\d+)\.?(\d+)?\.tar", f.read()) + version = m[0] + if not version[2]: + version = (int(version[0]), int(version[1]), 0) + else: + version = (int(version[0]), int(version[1]), int(version[2])) + assert freetype.version() == version + else: + pytest.skip("Not using a bundled FreeType library.") diff --git a/tests/test_ft_face.py b/tests/test_ft_face.py deleted file mode 100644 index dccc0878..00000000 --- a/tests/test_ft_face.py +++ /dev/null @@ -1,6 +0,0 @@ -import freetype - - -def test_ft_face(): - """A smoke test.""" - assert freetype.Face('examples/Vera.ttf') \ No newline at end of file diff --git a/tox.ini b/tox.ini index 0000eeff..28e1c806 100644 --- a/tox.ini +++ b/tox.ini @@ -2,5 +2,8 @@ envlist = py27,py36 [testenv] -deps=pytest -commands=pytest \ No newline at end of file +deps= + pytest + wheel +commands=pytest {posargs} +changedir=tests