Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5a6058e

Browse files
committed
BLD: Rewrite local freetype build script
These changes should fix some build problems on windows: - Mismatch Python MSVC runtime version and local freetype - `zlib` and `libpng` linking
1 parent e6448ba commit 5a6058e

File tree

5 files changed

+133
-94
lines changed

5 files changed

+133
-94
lines changed

.appveyor.yml

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -99,17 +99,6 @@ install:
9999
curl -sL https://github.com/python/cpython/pull/1224.patch |
100100
patch -fsup 1 -d %CONDA_PREFIX% ) || ( set errorlevel= )
101101

102-
# Let the install prefer the static builds of the libs
103-
- set LIBRARY_LIB=%CONDA_PREFIX%\Library\lib
104-
- mkdir lib || cmd /c "exit /b 0"
105-
- copy /y %LIBRARY_LIB%\zlibstatic.lib lib\z.lib
106-
- copy /y %LIBRARY_LIB%\libpng_static.lib lib\png.lib
107-
# These z.lib / png.lib are not static versions but files which end up as
108-
# dependencies to the dll file. This is fine for the conda build, but not here
109-
# and for the wheels
110-
- del %LIBRARY_LIB%\png.lib
111-
- del %LIBRARY_LIB%\z.lib
112-
- set MPLBASEDIRLIST=%CONDA_PREFIX%\Library\;.
113102
# enables the local freetype build
114103
- copy ci\travis\setup.cfg .
115104
# Show the installed packages + versions
@@ -150,9 +139,6 @@ after_test:
150139
# And now the conda build after a cleanup...
151140
# cleanup build files so that they don't pollute the conda build but keep the wheel in dist...
152141
- git clean -xdfq -e dist/
153-
# cleanup the environment so that the test-environment does not leak into the conda build...
154-
- set MPLBASEDIRLIST=
155-
- set LIBRARY_LIB=
156142
- deactivate
157143
- path
158144
- where python

INSTALL.rst

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -332,16 +332,6 @@ without fiddling with environment variables::
332332
# for Python 2.7
333333
conda install -c conda-forge backports.functools_lru_cache
334334

335-
# copy the libs which have "wrong" names
336-
set LIBRARY_LIB=%CONDA_DEFAULT_ENV%\Library\lib
337-
mkdir lib || cmd /c "exit /b 0"
338-
copy %LIBRARY_LIB%\zlibstatic.lib lib\z.lib
339-
copy %LIBRARY_LIB%\libpng_static.lib lib\png.lib
340-
341-
# Make the header files and the rest of the static libs available during the build
342-
# CONDA_DEFAULT_ENV is a env variable which is set to the currently active environment path
343-
set MPLBASEDIRLIST=%CONDA_DEFAULT_ENV%\Library\;.
344-
345335
# build the wheel
346336
python setup.py bdist_wheel
347337

ci/conda_recipe/bld.bat

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
1-
set LIBPATH=%LIBRARY_LIB%;
2-
set INCLUDE=%INCLUDE%;%PREFIX%\Library\include\freetype2
3-
41
ECHO [directories] > setup.cfg
52
ECHO basedirlist = %LIBRARY_PREFIX% >> setup.cfg
63
ECHO [packages] >> setup.cfg
74
ECHO tests = False >> setup.cfg
85
ECHO sample_data = False >> setup.cfg
96
ECHO toolkits_tests = False >> setup.cfg
107

11-
@rem workaround for https://github.com/matplotlib/matplotlib/issues/6460
12-
@rem see also https://github.com/conda-forge/libpng-feedstock/pull/4
13-
copy /y %LIBRARY_LIB%\libpng16.lib %LIBRARY_LIB%\png.lib
14-
158
%PYTHON% setup.py install --single-version-externally-managed --record=record.txt
169
if errorlevel 1 exit 1

setup.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,11 @@ def run(self):
136136

137137

138138
class BuildExtraLibraries(BuildExtCommand):
139-
def run(self):
139+
def build_extensions(self):
140140
for package in good_packages:
141-
package.do_custom_build()
141+
package.do_custom_build(self)
142142

143-
return BuildExtCommand.run(self)
143+
return BuildExtCommand.build_extensions(self)
144144

145145

146146
cmdclass = versioneer.get_cmdclass()

setupext.py

Lines changed: 130 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ def get_win32_compiler():
124124
"""
125125
Determine the compiler being used on win32.
126126
"""
127+
# TODO: rewrite to use distutils
127128
# Used to determine mingw32 or msvc
128129
# This is pretty bad logic, someone know a better way?
129130
for v in sys.argv:
@@ -151,7 +152,8 @@ def has_include_file(include_dirs, filename):
151152
directories in `include_dirs`.
152153
"""
153154
if sys.platform == 'win32':
154-
include_dirs += os.environ.get('INCLUDE', '.').split(';')
155+
include_dirs = list(include_dirs)
156+
include_dirs += os.environ.get('INCLUDE', '.').split(os.pathsep)
155157
for dir in include_dirs:
156158
if os.path.exists(os.path.join(dir, filename)):
157159
return True
@@ -180,10 +182,14 @@ def get_base_dirs():
180182
return os.environ.get('MPLBASEDIRLIST').split(os.pathsep)
181183

182184
win_bases = ['win32_static', ]
183-
# on conda windows, we also add the <installdir>\Library of the local interpreter,
185+
# on conda windows, we also add the <conda_env_dir>\Library,
184186
# as conda installs libs/includes there
185-
if os.getenv('CONDA_DEFAULT_ENV'):
186-
win_bases.append(os.path.join(os.getenv('CONDA_DEFAULT_ENV'), "Library"))
187+
# env var names mess: https://github.com/conda/conda/issues/2312
188+
conda_env_path = os.getenv('CONDA_PREFIX') # conda >= 4.1
189+
if not conda_env_path:
190+
conda_env_path = os.getenv('CONDA_DEFAULT_ENV') # conda < 4.1
191+
if conda_env_path and os.path.isdir(conda_env_path):
192+
win_bases.append(os.path.join(conda_env_path, "Library"))
187193

188194
basedir_map = {
189195
'win32': win_bases,
@@ -541,11 +547,16 @@ def _check_for_pkg_config(self, package, include_file, min_version=None,
541547

542548
return 'version %s' % version
543549

544-
def do_custom_build(self):
550+
def do_custom_build(self, cmd):
545551
"""
546552
If a package needs to do extra custom things, such as building a
547553
third-party library, before building an extension, it should
548554
override this method.
555+
556+
Parameters
557+
----------
558+
cmd : distutils.command.build_ext
559+
The 'build_ext' instance
549560
"""
550561
pass
551562

@@ -1094,19 +1105,24 @@ def version_from_header(self):
10941105
patch = value
10951106
return '.'.join([major, minor, patch])
10961107

1108+
def get_local_freetype_path(self, version=LOCAL_FREETYPE_VERSION):
1109+
return os.path.join('build', 'freetype-{0}'.format(version))
1110+
1111+
def get_local_freetype_lib_path(self, version=LOCAL_FREETYPE_VERSION):
1112+
if sys.platform == 'win32':
1113+
libfreetype = 'libfreetype.lib'
1114+
else:
1115+
libfreetype = 'libfreetype.a'
1116+
1117+
src_path = self.get_local_freetype_path(version)
1118+
return os.path.join(src_path, 'objs', '.libs', libfreetype)
1119+
10971120
def add_flags(self, ext):
10981121
if options.get('local_freetype'):
1099-
src_path = os.path.join(
1100-
'build', 'freetype-{0}'.format(LOCAL_FREETYPE_VERSION))
1101-
# Statically link to the locally-built freetype.
1102-
# This is certainly broken on Windows.
1122+
src_path = self.get_local_freetype_path()
1123+
# link to the locally-built freetype.
11031124
ext.include_dirs.insert(0, os.path.join(src_path, 'include'))
1104-
if sys.platform == 'win32':
1105-
libfreetype = 'libfreetype.lib'
1106-
else:
1107-
libfreetype = 'libfreetype.a'
1108-
ext.extra_objects.insert(
1109-
0, os.path.join(src_path, 'objs', '.libs', libfreetype))
1125+
ext.extra_objects.insert(0, self.get_local_freetype_lib_path())
11101126
ext.define_macros.append(('FREETYPE_BUILD_TYPE', 'local'))
11111127
else:
11121128
pkg_config.setup_extension(
@@ -1117,26 +1133,21 @@ def add_flags(self, ext):
11171133
'lib/freetype2/include/freetype2'],
11181134
default_library_dirs=[
11191135
'freetype2/lib'],
1120-
default_libraries=['freetype', 'z'])
1136+
default_libraries=['freetype'])
11211137
ext.define_macros.append(('FREETYPE_BUILD_TYPE', 'system'))
11221138

1123-
def do_custom_build(self):
1124-
# We're using a system freetype
1139+
def do_custom_build(self, cmd):
11251140
if not options.get('local_freetype'):
1141+
# We're using a system freetype
11261142
return
11271143

1128-
src_path = os.path.join(
1129-
'build', 'freetype-{0}'.format(LOCAL_FREETYPE_VERSION))
1130-
1131-
# We've already built freetype
1132-
if sys.platform == 'win32':
1133-
libfreetype = 'libfreetype.lib'
1134-
else:
1135-
libfreetype = 'libfreetype.a'
1136-
1137-
if os.path.isfile(os.path.join(src_path, 'objs', '.libs', libfreetype)):
1144+
libdir = os.path.dirname(self.get_local_freetype_lib_path())
1145+
if os.path.isfile(self.get_local_freetype_lib_path()):
1146+
# We've already built freetype
1147+
# TODO: This will prevent x86/x64 debug/release switch
11381148
return
11391149

1150+
src_path = self.get_local_freetype_path()
11401151
tarball = 'freetype-{0}.tar.gz'.format(LOCAL_FREETYPE_VERSION)
11411152
tarball_path = os.path.join('build', tarball)
11421153
try:
@@ -1223,40 +1234,71 @@ def do_custom_build(self):
12231234
subprocess.check_call(
12241235
[cflags + 'make'], shell=True, cwd=src_path)
12251236
else:
1226-
# compilation on windows
1227-
FREETYPE_BUILD_CMD = """\
1228-
call "%ProgramFiles%\\Microsoft SDKs\\Windows\\v7.0\\Bin\\SetEnv.Cmd" /Release /{xXX} /xp
1229-
call "{vcvarsall}" {xXX}
1230-
set MSBUILD=C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319\\MSBuild.exe
1231-
rd /S /Q %FREETYPE%\\objs
1232-
%MSBUILD% %FREETYPE%\\builds\\windows\\{vc20xx}\\freetype.sln /t:Clean;Build /p:Configuration="{config}";Platform={WinXX}
1233-
echo Build completed, moving result"
1234-
:: move to the "normal" path for the unix builds...
1235-
mkdir %FREETYPE%\\objs\\.libs
1236-
:: REMINDER: fix when changing the version
1237-
copy %FREETYPE%\\objs\\{vc20xx}\\{xXX}\\freetype261.lib %FREETYPE%\\objs\\.libs\\libfreetype.lib
1238-
if errorlevel 1 (
1239-
rem This is a py27 version, which has a different location for the lib file :-/
1240-
copy %FREETYPE%\\objs\\win32\\{vc20xx}\\freetype261.lib %FREETYPE%\\objs\\.libs\\libfreetype.lib
1241-
)
1242-
"""
1243-
from setup_external_compile import fixproj, prepare_build_cmd, VS2010, X64, tar_extract
1244-
# Note: freetype has no build profile for 2014, so we don't bother...
1245-
vc = 'vc2010' if VS2010 else 'vc2008'
1246-
WinXX = 'x64' if X64 else 'Win32'
1237+
assert cmd.compiler.compiler_type == 'msvc', (
1238+
'Support for other compilers is not implemented')
1239+
1240+
from distutils.msvccompiler import get_build_version
1241+
from setup_external_compile import fixproj, X64, tar_extract
1242+
1243+
if not cmd.compiler.initialized:
1244+
cmd.compiler.initialize()
1245+
1246+
# NOTE: need change on upgrade of local freetype to newer versions
1247+
vc = {9: 'vc2005', 11: 'vc2010'}[min(int(get_build_version()), 11)]
1248+
ftproj = os.path.join(src_path, 'builds', 'windows', vc, 'freetype')
1249+
1250+
vc_config = 'Debug' if cmd.debug else 'Release'
1251+
vc_platform = 'x64' if X64 else 'x86'
1252+
12471253
tar_extract(tarball_path, "build")
1248-
# This is only false for py2.7, even on py3.5...
1249-
if not VS2010:
1250-
fixproj(os.path.join(src_path, 'builds', 'windows', vc, 'freetype.sln'), WinXX)
1251-
fixproj(os.path.join(src_path, 'builds', 'windows', vc, 'freetype.vcproj'), WinXX)
12521254

1253-
cmdfile = os.path.join("build", 'build_freetype.cmd')
1254-
with open(cmdfile, 'w') as cmd:
1255-
cmd.write(prepare_build_cmd(FREETYPE_BUILD_CMD, vc20xx=vc, WinXX=WinXX,
1256-
config='Release' if VS2010 else 'LIB Release'))
1255+
if not cmd.compiler.initialized:
1256+
cmd.compiler.initialize()
12571257

1258-
os.environ['FREETYPE'] = src_path
1259-
subprocess.check_call([cmdfile], shell=True)
1258+
def spawn(command):
1259+
command = list(command)
1260+
if hasattr(cmd.compiler, 'find_exe'):
1261+
executable = cmd.compiler.find_exe(command[0])
1262+
if not executable:
1263+
raise ValueError("Couldn't find %s" % command[0])
1264+
return cmd.compiler.spawn([executable] + command[1:])
1265+
else:
1266+
return cmd.compiler.spawn(command)
1267+
1268+
if get_build_version() < 11.0:
1269+
vc_config = 'LIB ' + vc_config
1270+
# monkey-patch project files, because
1271+
# vc2005 and vc2008 have only Win32 targets
1272+
# TODO: fix `win32` output dir to `x64`?
1273+
fixproj(ftproj + '.sln', vc_platform)
1274+
fixproj(ftproj + '.vcproj', vc_platform)
1275+
spawn(['vcbuild', ftproj + '.sln', '/M', '/time',
1276+
'/platform:{}'.format(vc_platform),
1277+
'{config}|{platform}'.format(config=vc_config,
1278+
platform='x64' if X64 else 'Win32')
1279+
])
1280+
postfix = '_D' if cmd.debug else ''
1281+
builddir = os.path.join(src_path, 'objs', 'win32', vc)
1282+
else:
1283+
spawn(['msbuild', ftproj + '.sln', '/m', '/t:Clean;Build',
1284+
'/toolsversion:{}'.format(get_build_version()),
1285+
('/p:Configuration="{config}"'
1286+
';Platform={platform}'
1287+
';VisualStudioVersion={version}'
1288+
';ToolsVersion={version}'
1289+
';PlatformToolset=v{toolset:d}'
1290+
).format(config=vc_config, platform=vc_platform,
1291+
version=get_build_version(),
1292+
toolset=int(get_build_version() * 10)),
1293+
])
1294+
postfix = 'd' if cmd.debug else ''
1295+
builddir = os.path.join(src_path, 'objs', vc, vc_platform)
1296+
1297+
verstr = LOCAL_FREETYPE_VERSION.replace('.', '')
1298+
buildname = 'freetype{0}{1}.lib'.format(verstr, postfix)
1299+
buildpath = os.path.join(builddir, buildname)
1300+
assert os.path.isfile(buildpath)
1301+
os.renames(buildpath, self.get_local_freetype_lib_path())
12601302

12611303

12621304
class FT2Font(SetupPackage):
@@ -1273,6 +1315,23 @@ def get_extension(self):
12731315
Numpy().add_flags(ext)
12741316
return ext
12751317

1318+
def do_custom_build(self, cmd):
1319+
if cmd.compiler.compiler_type != 'msvc':
1320+
return
1321+
1322+
if not cmd.compiler.initialized:
1323+
cmd.compiler.initialize()
1324+
1325+
# TODO: What about compiler pathes?
1326+
if not has_include_file(cmd.compiler.include_dirs, 'stdint.h'):
1327+
if os.getenv('CONDA_DEFAULT_ENV'):
1328+
# TODO: spawn conda install msinttypes?
1329+
pass
1330+
else:
1331+
# TODO: download from https://code.google.com/p/msinttypes/
1332+
#cmd.compiler.add_include_dir(...)
1333+
pass
1334+
12761335

12771336
class Png(SetupPackage):
12781337
name = "png"
@@ -1306,13 +1365,24 @@ def check(self):
13061365
raise
13071366

13081367
def get_extension(self):
1368+
# TODO: implement a dynamic/static linking switch
1369+
zlib = 'z' if sys.platform != 'win32' else 'zlibstatic'
1370+
if sys.platform != 'win32':
1371+
png = 'png'
1372+
zlib = 'z'
1373+
elif os.getenv('CONDA_DEFAULT_ENV'):
1374+
# libpng in conda copies versioned lib to unversioned
1375+
png = 'libpng_static'
1376+
else:
1377+
# TODO: there is no good solution with our build system
1378+
png = 'libpng16_static'
13091379
sources = [
13101380
'src/_png.cpp',
13111381
'src/mplutils.cpp'
13121382
]
13131383
ext = make_extension('matplotlib._png', sources)
13141384
pkg_config.setup_extension(
1315-
ext, 'libpng', default_libraries=['png', 'z'],
1385+
ext, 'libpng', default_libraries=[png, zlib],
13161386
alt_exec='libpng-config --ldflags')
13171387
Numpy().add_flags(ext)
13181388
return ext

0 commit comments

Comments
 (0)