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

Skip to content

Commit 9b6989e

Browse files
committed
WIN: 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 4ccfccb commit 9b6989e

File tree

5 files changed

+134
-94
lines changed

5 files changed

+134
-94
lines changed

.appveyor.yml

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -95,17 +95,6 @@ install:
9595
# pytest-cov>=2.3.1 due to https://github.com/pytest-dev/pytest-cov/issues/124
9696
- pip install -q pytest "pytest-cov>=2.3.1" pytest-rerunfailures pytest-timeout
9797

98-
# Let the install prefer the static builds of the libs
99-
- set LIBRARY_LIB=%CONDA_PREFIX%\Library\lib
100-
- mkdir lib || cmd /c "exit /b 0"
101-
- copy /y %LIBRARY_LIB%\zlibstatic.lib lib\z.lib
102-
- copy /y %LIBRARY_LIB%\libpng_static.lib lib\png.lib
103-
# These z.lib / png.lib are not static versions but files which end up as
104-
# dependencies to the dll file. This is fine for the conda build, but not here
105-
# and for the wheels
106-
- del %LIBRARY_LIB%\png.lib
107-
- del %LIBRARY_LIB%\z.lib
108-
- set MPLBASEDIRLIST=%CONDA_PREFIX%\Library\;.
10998
# enables the local freetype build
11099
- copy ci\travis\setup.cfg .
111100
# Show the installed packages + versions
@@ -146,9 +135,6 @@ after_test:
146135
# And now the conda build after a cleanup...
147136
# cleanup build files so that they don't pollute the conda build but keep the wheel in dist...
148137
- git clean -xdfq -e dist/
149-
# cleanup the environment so that the test-environment does not leak into the conda build...
150-
- set MPLBASEDIRLIST=
151-
- set LIBRARY_LIB=
152138
- deactivate
153139
- path
154140
- 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: 131 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

@@ -1088,19 +1099,24 @@ def version_from_header(self):
10881099
patch = value
10891100
return '.'.join([major, minor, patch])
10901101

1102+
def get_local_freetype_path(self, version=LOCAL_FREETYPE_VERSION):
1103+
return os.path.join('build', 'freetype-{0}'.format(version))
1104+
1105+
def get_local_freetype_lib_path(self, version=LOCAL_FREETYPE_VERSION):
1106+
if sys.platform == 'win32':
1107+
libfreetype = 'libfreetype.lib'
1108+
else:
1109+
libfreetype = 'libfreetype.a'
1110+
1111+
src_path = self.get_local_freetype_path(version)
1112+
return os.path.join(src_path, 'objs', '.libs', libfreetype)
1113+
10911114
def add_flags(self, ext):
10921115
if options.get('local_freetype'):
1093-
src_path = os.path.join(
1094-
'build', 'freetype-{0}'.format(LOCAL_FREETYPE_VERSION))
1095-
# Statically link to the locally-built freetype.
1096-
# This is certainly broken on Windows.
1116+
src_path = self.get_local_freetype_path()
1117+
# link to the locally-built freetype.
10971118
ext.include_dirs.insert(0, os.path.join(src_path, 'include'))
1098-
if sys.platform == 'win32':
1099-
libfreetype = 'libfreetype.lib'
1100-
else:
1101-
libfreetype = 'libfreetype.a'
1102-
ext.extra_objects.insert(
1103-
0, os.path.join(src_path, 'objs', '.libs', libfreetype))
1119+
ext.extra_objects.insert(0, self.get_local_freetype_lib_path())
11041120
ext.define_macros.append(('FREETYPE_BUILD_TYPE', 'local'))
11051121
else:
11061122
pkg_config.setup_extension(
@@ -1111,26 +1127,21 @@ def add_flags(self, ext):
11111127
'lib/freetype2/include/freetype2'],
11121128
default_library_dirs=[
11131129
'freetype2/lib'],
1114-
default_libraries=['freetype', 'z'])
1130+
default_libraries=['freetype'])
11151131
ext.define_macros.append(('FREETYPE_BUILD_TYPE', 'system'))
11161132

1117-
def do_custom_build(self):
1118-
# We're using a system freetype
1133+
def do_custom_build(self, cmd):
11191134
if not options.get('local_freetype'):
1135+
# We're using a system freetype
11201136
return
11211137

1122-
src_path = os.path.join(
1123-
'build', 'freetype-{0}'.format(LOCAL_FREETYPE_VERSION))
1124-
1125-
# We've already built freetype
1126-
if sys.platform == 'win32':
1127-
libfreetype = 'libfreetype.lib'
1128-
else:
1129-
libfreetype = 'libfreetype.a'
1130-
1131-
if os.path.isfile(os.path.join(src_path, 'objs', '.libs', libfreetype)):
1138+
libdir = os.path.dirname(self.get_local_freetype_lib_path())
1139+
if os.path.isfile(self.get_local_freetype_lib_path()):
1140+
# We've already built freetype
1141+
# TODO: This will prevent x86/x64 debug/release switch
11321142
return
11331143

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

1247-
cmdfile = os.path.join("build", 'build_freetype.cmd')
1248-
with open(cmdfile, 'w') as cmd:
1249-
cmd.write(prepare_build_cmd(FREETYPE_BUILD_CMD, vc20xx=vc, WinXX=WinXX,
1250-
config='Release' if VS2010 else 'LIB Release'))
1250+
if not cmd.compiler.initialized:
1251+
cmd.compiler.initialize()
12511252

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

12551298

12561299
class FT2Font(SetupPackage):
@@ -1267,6 +1310,23 @@ def get_extension(self):
12671310
Numpy().add_flags(ext)
12681311
return ext
12691312

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

12711331
class Png(SetupPackage):
12721332
name = "png"
@@ -1300,13 +1360,24 @@ def check(self):
13001360
raise
13011361

13021362
def get_extension(self):
1363+
# TODO: implement a dynamic/static linking switch
1364+
zlib = 'z' if sys.platform != 'win32' else 'zlibstatic'
1365+
if sys.platform != 'win32':
1366+
png = 'png'
1367+
zlib = 'z'
1368+
elif os.getenv('CONDA_DEFAULT_ENV'):
1369+
# libpng in conda copies versioned lib to unversioned
1370+
png = 'libpng_static'
1371+
else:
1372+
# TODO: there is no good solution with our build system
1373+
png = 'libpng16_static'
13031374
sources = [
13041375
'src/_png.cpp',
13051376
'src/mplutils.cpp'
13061377
]
13071378
ext = make_extension('matplotlib._png', sources)
13081379
pkg_config.setup_extension(
1309-
ext, 'libpng', default_libraries=['png', 'z'],
1380+
ext, 'libpng', default_libraries=[png, zlib],
13101381
alt_exec='libpng-config --ldflags')
13111382
Numpy().add_flags(ext)
13121383
return ext

0 commit comments

Comments
 (0)