Skip to content

Commit 03053e9

Browse files
committed
Add support for building a local copy of freetype
1 parent 66beb52 commit 03053e9

File tree

9 files changed

+178
-6
lines changed

9 files changed

+178
-6
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ dist
3434
# tox testing tool
3535
.tox
3636
MANIFEST
37+
setup.cfg
3738

3839
# OS generated files #
3940
######################

buildext/build_local_freetype.py

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# Copyright (c) 2015, Michael Droettboom All rights reserved.
4+
5+
# Redistribution and use in source and binary forms, with or without
6+
# modification, are permitted provided that the following conditions
7+
# are met:
8+
9+
# 1. Redistributions of source code must retain the above copyright
10+
# notice, this list of conditions and the following disclaimer.
11+
# 2. Redistributions in binary form must reproduce the above copyright
12+
# notice, this list of conditions and the following disclaimer in
13+
# the documentation and/or other materials provided with the
14+
# distribution.
15+
16+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
19+
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
20+
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
21+
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
22+
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25+
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26+
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27+
# POSSIBILITY OF SUCH DAMAGE.
28+
29+
# The views and conclusions contained in the software and
30+
# documentation are those of the authors and should not be interpreted
31+
# as representing official policies, either expressed or implied, of
32+
# the FreeBSD Project.
33+
34+
from __future__ import print_function, unicode_literals, absolute_import
35+
36+
37+
import hashlib
38+
import os
39+
import subprocess
40+
import sys
41+
42+
43+
# This is the version of FreeType to use when building a local
44+
# version. It must match the value in
45+
# lib/matplotlib.__init__.py
46+
LOCAL_FREETYPE_VERSION = '2.6.1'
47+
# md5 hash of the freetype tarball
48+
LOCAL_FREETYPE_HASH = '348e667d728c597360e4a87c16556597'
49+
50+
51+
def get_file_hash(filename):
52+
"""
53+
Get the MD5 hash of a given filename.
54+
"""
55+
BLOCKSIZE = 1 << 16
56+
hasher = hashlib.md5()
57+
with open(filename, 'rb') as fd:
58+
buf = fd.read(BLOCKSIZE)
59+
while len(buf) > 0:
60+
hasher.update(buf)
61+
buf = fd.read(BLOCKSIZE)
62+
return hasher.hexdigest()
63+
64+
65+
def build_local_freetype():
66+
src_path = os.path.join(
67+
'build', 'freetype-{0}'.format(LOCAL_FREETYPE_VERSION))
68+
69+
# We've already built freetype
70+
if os.path.isfile(os.path.join(src_path, 'objs', '.libs', 'libfreetype.a')):
71+
return
72+
73+
tarball = 'freetype-{0}.tar.gz'.format(LOCAL_FREETYPE_VERSION)
74+
tarball_path = os.path.join('build', tarball)
75+
if not os.path.isfile(tarball_path):
76+
tarball_url = 'http://download.savannah.gnu.org/releases/freetype/{0}'.format(tarball)
77+
78+
print("Downloading {0}".format(tarball_url))
79+
if sys.version_info[0] == 2:
80+
from urllib import urlretrieve
81+
else:
82+
from urllib.request import urlretrieve
83+
84+
if not os.path.exists('build'):
85+
os.makedirs('build')
86+
urlretrieve(tarball_url, tarball_path)
87+
88+
if get_file_hash(tarball_path) != LOCAL_FREETYPE_HASH:
89+
raise IOError("{0} does not match expected hash.".format(tarball))
90+
91+
print("Building {0}".format(tarball))
92+
cflags = 'CFLAGS="{0} -fPIC" '.format(os.environ.get('CFLAGS', ''))
93+
94+
subprocess.check_call(
95+
['tar', 'zxf', tarball], cwd='build')
96+
subprocess.check_call(
97+
[cflags + './configure --with-zlib=no --with-bzip2=no '
98+
'--with-png=no --with-harfbuzz=no'], shell=True, cwd=src_path)
99+
subprocess.check_call(
100+
[cflags + 'make'], shell=True, cwd=src_path)
101+
102+
103+
def set_flags(ext):
104+
src_path = os.path.join(
105+
'build', 'freetype-{0}'.format(LOCAL_FREETYPE_VERSION))
106+
107+
# Statically link to the locally-built freetype.
108+
# This is certainly broken on Windows.
109+
ext.include_dirs.insert(0, os.path.join(src_path, 'include'))
110+
ext.extra_objects.insert(
111+
0, os.path.join(src_path, 'objs', '.libs', 'libfreetype.a'))
112+
ext.define_macros.append(('FREETYPE_BUILD_TYPE', 'local'))

buildext/convert_docstrings.py

-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import glob
3737
import imp
3838
import os
39-
import re
4039
import sys
4140

4241

lib/freetypy/__init__.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737

3838
from ._freetypy import *
3939

40-
from ._freetypy import __freetype_version__
40+
from ._freetypy import (__freetype_version__,
41+
__version__,
42+
__freetype_build_type__)
4143

4244
from . import util

lib/freetypy/tests/__init__.py

+13
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,16 @@
3232
# the FreeBSD Project.
3333

3434
from __future__ import print_function, unicode_literals, absolute_import
35+
36+
import warnings
37+
38+
39+
def setup():
40+
import freetypy as ft
41+
LOCAL_FREETYPE_VERSION = "2.6.1"
42+
43+
if (ft.__freetype_version__ != LOCAL_FREETYPE_VERSION or
44+
ft.__freetype_build_type__ != 'local'):
45+
warnings.warn(
46+
"freetypy is not built with the correct FreeType version to run "
47+
"tests. Set local_freetype=True in setup.cfg and rebuild. ")

setup.cfg.template

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[test]
2+
# If you want to make sure the font renderings from freetypy are
3+
# absolutely repeatable, you should build with a specific version of
4+
# FreeType. Setting this option to `True` will download and build a
5+
# specific version of FreeType, and then use that to build the freetypy
6+
# extension.
7+
8+
#local_freetype = False

setup.py

+34-3
Original file line numberDiff line numberDiff line change
@@ -34,26 +34,56 @@
3434
from __future__ import print_function, absolute_import
3535

3636
from distutils.core import setup, Extension
37+
from distutils.command.build_ext import build_ext as BuildExtCommand
38+
3739
import glob
40+
import os
3841
import sys
3942

43+
PY3 = (sys.version_info[0] >= 3)
44+
45+
if PY3:
46+
import configparser
47+
else:
48+
import ConfigParser as configparser
49+
4050
sys.path.insert(0, 'buildext')
4151
sys.path.insert(0, 'doc')
4252

53+
import build_local_freetype
4354
import convert_docstrings
4455
import pkgconfig
4556

4657

4758
if __name__ == '__main__':
4859
convert_docstrings.convert_all_docstrings('docstrings', 'src/doc')
4960

61+
local_freetype = False
62+
setup_cfg = 'setup.cfg'
63+
if os.path.exists(setup_cfg):
64+
config = configparser.SafeConfigParser()
65+
config.read(setup_cfg)
66+
67+
if config.has_option('test', 'local_freetype'):
68+
local_freetype = True
69+
70+
class BuildLocalFreetype(BuildExtCommand):
71+
def run(self):
72+
if local_freetype:
73+
build_local_freetype.build_local_freetype()
74+
return BuildExtCommand.run(self)
75+
5076
extension = Extension(
5177
'freetypy._freetypy',
5278
glob.glob('src/*.c') +
5379
glob.glob('src/doc/*.c'))
5480

55-
pkgconfig.pkgconfig.setup_extension(
56-
extension, 'freetype2', alt_exec='freetype-config')
81+
if local_freetype:
82+
build_local_freetype.set_flags(extension)
83+
else:
84+
pkgconfig.pkgconfig.setup_extension(
85+
extension, 'freetype2', alt_exec='freetype-config')
86+
extension.define_macros.append(('FREETYPE_BUILD_TYPE', 'system'))
5787

5888
setup(name="freetypy",
5989
version="0.1",
@@ -63,4 +93,5 @@
6393
packages=['freetypy', 'freetypy.tests', 'freetypy.codecs'],
6494
package_dir={'': 'lib'},
6595
package_data={'freetypy': ['data/*.ttf']},
66-
ext_modules=[extension])
96+
ext_modules=[extension],
97+
cmdclass={'build_ext': BuildLocalFreetype})

src/pyutil.h

+4
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ either expressed or implied, of the FreeBSD Project.
3333
#include "freetypy.h"
3434

3535

36+
#define STRINGIFY(s) XSTRINGIFY(s)
37+
#define XSTRINGIFY(s) #s
38+
39+
3640
typedef struct {
3741
PyObject_HEAD
3842
PyObject *owner;

src/version.c

+3-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ int setup_version(PyObject *m)
4141
return (
4242
PyModule_AddStringConstant(
4343
freetypy_module, "__freetype_version__", version_string) ||
44-
PyModule_AddStringConstant(m, "__version__", "0.1")
44+
PyModule_AddStringConstant(m, "__version__", "0.1") ||
45+
PyModule_AddStringConstant(
46+
m, "__freetype_build_type__", STRINGIFY(FREETYPE_BUILD_TYPE))
4547
);
4648
}

0 commit comments

Comments
 (0)