Skip to content

Commit 2b5b402

Browse files
authored
Fix matplotlib and update to v3.1.3 (#2076)
* [recipes] Fix `matplotlib` and update to `v3.1.3` * [recipes] Fix misspelled word * [recipes] Remove unneeded changes from the patch * [recipes] Fix typo * [CI] Remove `matplotlib` from broken recipes Because the `matplotlib` recipe should work now in our `CI` tests * [recipes] Set library version dynamically for `*.pc.template`
1 parent c1b94fd commit 2b5b402

File tree

6 files changed

+208
-28
lines changed

6 files changed

+208
-28
lines changed

ci/constants.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ class TargetPython(Enum):
5454
'websocket-client',
5555
'zeroconf',
5656
'zope',
57-
'matplotlib', # https://github.com/kivy/python-for-android/issues/1900
5857
])
5958
BROKEN_RECIPES_PYTHON3 = set([
6059
'brokenrecipe',
@@ -74,7 +73,6 @@ class TargetPython(Enum):
7473
# mpmath package with a version >= 0.19 required
7574
'sympy',
7675
'vlc',
77-
'matplotlib', # https://github.com/kivy/python-for-android/issues/1900
7876
])
7977

8078
BROKEN_RECIPES = {
Lines changed: 135 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,98 @@
11

2+
from pythonforandroid.logger import info_notify
23
from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe
4+
from pythonforandroid.util import ensure_dir
35

46
from os.path import join
57

68

79
class MatplotlibRecipe(CppCompiledComponentsPythonRecipe):
810

9-
version = '3.0.3'
11+
version = '3.1.3'
1012
url = 'https://github.com/matplotlib/matplotlib/archive/v{version}.zip'
1113

1214
depends = ['numpy', 'png', 'setuptools', 'freetype', 'kiwisolver']
15+
conflicts = ['python2']
1316

1417
python_depends = ['pyparsing', 'cycler', 'python-dateutil']
1518

1619
# We need to patch to:
17-
# - make mpl build against the same numpy version as the numpy recipe
18-
# (this could be done better by setting the target version dynamically)
19-
# - prevent mpl trying to build TkAgg, which wouldn't work on Android anyway but has build issues
20+
# - make mpl install work without importing numpy
21+
# - make mpl use shared libraries for freetype and png
22+
# - make mpl link to png16, to match p4a library name for png
23+
# - prevent mpl trying to build TkAgg, which wouldn't work
24+
# on Android anyway but has build issues
2025
patches = ['mpl_android_fixes.patch']
2126

2227
call_hostpython_via_targetpython = False
2328

29+
def generate_libraries_pc_files(self, arch):
30+
"""
31+
Create *.pc files for libraries that `matplotib` depends on.
32+
33+
Because, for unix platforms, the mpl install script uses `pkg-config`
34+
to detect libraries installed in non standard locations (our case...
35+
well...we don't even install the libraries...so we must trick a little
36+
the mlp install).
37+
"""
38+
pkg_config_path = self.get_recipe_env(arch)['PKG_CONFIG_PATH']
39+
ensure_dir(pkg_config_path)
40+
41+
lib_to_pc_file = {
42+
# `pkg-config` search for version freetype2.pc, our current
43+
# version for freetype, but we have our recipe named without
44+
# the version...so we add it in here for our pc file
45+
'freetype': 'freetype2.pc',
46+
'png': 'png.pc',
47+
}
48+
49+
for lib_name in {'freetype', 'png'}:
50+
pc_template_file = join(
51+
self.get_recipe_dir(),
52+
f'lib{lib_name}.pc.template'
53+
)
54+
# read template file into buffer
55+
with open(pc_template_file) as template_file:
56+
text_buffer = template_file.read()
57+
# set the library absolute path and library version
58+
lib_recipe = self.get_recipe(lib_name, self.ctx)
59+
text_buffer = text_buffer.replace(
60+
'path_to_built', lib_recipe.get_build_dir(arch.arch),
61+
)
62+
text_buffer = text_buffer.replace(
63+
'library_version', lib_recipe.version,
64+
)
65+
66+
# write the library pc file into our defined dir `PKG_CONFIG_PATH`
67+
pc_dest_file = join(pkg_config_path, lib_to_pc_file[lib_name])
68+
with open(pc_dest_file, 'w') as pc_file:
69+
pc_file.write(text_buffer)
70+
71+
def download_web_backend_dependencies(self, arch):
72+
"""
73+
During building, host needs to download the jquery-ui package (in order
74+
to make it work the mpl web backend). This operation seems to fail
75+
in our CI tests, so we download this package at the expected location
76+
by the mpl install script which is defined by the environ variable
77+
`XDG_CACHE_HOME` (we modify that one in our `get_recipe_env` so it will
78+
be the same regardless of the host platform).
79+
"""
80+
81+
env = self.get_recipe_env(arch)
82+
83+
info_notify('Downloading jquery-ui for matplatlib web backend')
84+
# We use the same jquery-ui version than mpl's setup.py script,
85+
# inside function `_download_jquery_to`
86+
jquery_sha = (
87+
'f8233674366ab36b2c34c577ec77a3d70cac75d2e387d8587f3836345c0f624d'
88+
)
89+
url = f'https://jqueryui.com/resources/download/jquery-ui-1.12.1.zip'
90+
target_file = join(env['XDG_CACHE_HOME'], 'matplotlib', jquery_sha)
91+
92+
info_notify(f'Will download into {env["XDG_CACHE_HOME"]}')
93+
ensure_dir(join(env['XDG_CACHE_HOME'], 'matplotlib'))
94+
self.download_file(url, target_file)
95+
2496
def prebuild_arch(self, arch):
2597
with open(join(self.get_recipe_dir(), 'setup.cfg.template')) as fileh:
2698
setup_cfg = fileh.read()
@@ -29,5 +101,64 @@ def prebuild_arch(self, arch):
29101
fileh.write(setup_cfg.format(
30102
ndk_sysroot_usr=join(self.ctx.ndk_dir, 'sysroot', 'usr')))
31103

104+
self.generate_libraries_pc_files(arch)
105+
self.download_web_backend_dependencies(arch)
106+
107+
def get_recipe_env(self, arch=None, with_flags_in_cc=True):
108+
env = super().get_recipe_env(arch, with_flags_in_cc)
109+
if self.need_stl_shared:
110+
# matplotlib compile flags does not honor the standard flags:
111+
# `CPPFLAGS` and `LDLIBS`, so we put in `CFLAGS` and `LDFLAGS` to
112+
# correctly link with the `c++_shared` library
113+
env['CFLAGS'] += ' -I{}'.format(self.stl_include_dir)
114+
env['CFLAGS'] += ' -frtti -fexceptions'
115+
116+
env['LDFLAGS'] += ' -L{}'.format(self.get_stl_lib_dir(arch))
117+
env['LDFLAGS'] += ' -l{}'.format(self.stl_lib_name)
118+
119+
# we modify `XDG_CACHE_HOME` to download `jquery-ui` into that folder,
120+
# or mpl install will fail when trying to download/install it, but if
121+
# we have the proper package already downloaded, it will use the cached
122+
# package to successfully finish the installation.
123+
# Note: this may not be necessary for some local systems, but it is
124+
# for our CI providers: `gh-actions` and travis, which will
125+
# fail trying to download the `jquery-ui` package
126+
env['XDG_CACHE_HOME'] = join(self.get_build_dir(arch), 'p4a_files')
127+
# we make use of the same directory than `XDG_CACHE_HOME`, for our
128+
# custom library pc files, so we have all the install files that we
129+
# generate at the same place
130+
env['PKG_CONFIG_PATH'] = env['XDG_CACHE_HOME']
131+
132+
# We set a new environ variable `NUMPY_INCLUDES` to be able to tell
133+
# the matplotlib script where to find our numpy without importing it
134+
# (which will fail, because numpy isn't installed in our hostpython)
135+
env['NUMPY_INCLUDES'] = join(
136+
self.ctx.get_site_packages_dir(),
137+
'numpy', 'core', 'include',
138+
)
139+
140+
# creating proper *.pc files for our libraries does not seem enough to
141+
# success with our build (without depending on system development
142+
# libraries), but if we tell the compiler where to find our libraries
143+
# and includes, then the install success :)
144+
png = self.get_recipe('png', self.ctx)
145+
env['CFLAGS'] += f' -I{png.get_build_dir(arch)}'
146+
env['LDFLAGS'] += f' -L{join(png.get_build_dir(arch.arch), ".libs")}'
147+
148+
freetype = self.get_recipe('freetype', self.ctx)
149+
free_lib_dir = join(freetype.get_build_dir(arch.arch), 'objs', '.libs')
150+
free_inc_dir = join(freetype.get_build_dir(arch.arch), 'include')
151+
env['CFLAGS'] += f' -I{free_inc_dir}'
152+
env['LDFLAGS'] += f' -L{free_lib_dir}'
153+
154+
# `freetype` could be built with `harfbuzz` support,
155+
# so we also include the necessary flags...just to be sure
156+
if 'harfbuzz' in self.ctx.recipe_build_order:
157+
harfbuzz = self.get_recipe('harfbuzz', self.ctx)
158+
harf_build = harfbuzz.get_build_dir(arch.arch)
159+
env['CFLAGS'] += f' -I{harf_build} -I{join(harf_build, "src")}'
160+
env['LDFLAGS'] += f' -L{join(harf_build, "src", ".libs")}'
161+
return env
162+
32163

33164
recipe = MatplotlibRecipe()
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
prefix=path_to_built
2+
exec_prefix=${prefix}
3+
includedir=${prefix}/include
4+
libdir=${exec_prefix}/objs/.libs
5+
6+
Name: freetype2
7+
Description: The freetype2 library
8+
Version: library_version
9+
Cflags: -I${includedir}
10+
Libs: -L${libdir} -lfreetype
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
prefix=path_to_built
2+
exec_prefix=${prefix}
3+
includedir=${prefix}
4+
libdir=${exec_prefix}/.libs
5+
6+
Name: libpng
7+
Description: The png library
8+
Version: library_version
9+
Cflags: -I${includedir}
10+
Libs: -L${libdir} -lpng16
Lines changed: 51 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,60 @@
1-
diff --git a/setupext.py b/setupext.py
2-
index fc82d5d..2067db0 100644
3-
--- a/setupext.py
4-
+++ b/setupext.py
5-
@@ -1004,10 +1004,10 @@ class Numpy(SetupPackage):
6-
ext.define_macros.append(('__STDC_FORMAT_MACROS', 1))
7-
1+
--- matplotlib-3.1.1/setupext.py.orig 2019-12-31 01:25:00.000000000 +0100
2+
+++ matplotlib-3.1.1/setupext.py 2020-03-01 21:12:41.493350250 +0100
3+
@@ -604,8 +604,7 @@ class Numpy(SetupPackage):
4+
name = "numpy"
5+
6+
def add_flags(self, ext):
7+
- import numpy as np
8+
- ext.include_dirs.append(np.get_include())
9+
+ ext.include_dirs.append(os.environ['NUMPY_INCLUDES'])
10+
ext.define_macros.extend([
11+
# Ensure that PY_ARRAY_UNIQUE_SYMBOL is uniquely defined for each
12+
# extension.
13+
@@ -617,7 +616,7 @@ class Numpy(SetupPackage):
14+
])
15+
816
def get_setup_requires(self):
9-
- return ['numpy>=1.10.0']
10-
+ return ['numpy==1.16.4'] # to match p4a's target version
11-
17+
- return ['numpy>=1.11']
18+
+ return [] # don't need it for p4a, due to above changes
19+
1220
def get_install_requires(self):
13-
- return ['numpy>=1.10.0']
14-
+ return ['numpy==1.16.4'] # to match p4a's target version
15-
16-
17-
class LibAgg(SetupPackage):
18-
@@ -1443,9 +1443,10 @@ class BackendAgg(OptionalBackendPackage):
19-
21+
return ['numpy>=1.11']
22+
@@ -674,7 +673,7 @@ class FreeType(SetupPackage):
23+
if sys.platform == 'win32':
24+
libfreetype = 'libfreetype.lib'
25+
else:
26+
- libfreetype = 'libfreetype.a'
27+
+ libfreetype = 'libfreetype.so'
28+
ext.extra_objects.insert(
29+
0, os.path.join(src_path, 'objs', '.libs', libfreetype))
30+
ext.define_macros.append(('FREETYPE_BUILD_TYPE', 'local'))
31+
@@ -701,7 +700,7 @@ class FreeType(SetupPackage):
32+
if sys.platform == 'win32':
33+
libfreetype = 'libfreetype.lib'
34+
else:
35+
- libfreetype = 'libfreetype.a'
36+
+ libfreetype = 'libfreetype.so'
37+
38+
# bailing because it is already built
39+
if os.path.isfile(os.path.join(
40+
@@ -830,7 +829,7 @@ class Png(SetupPackage):
41+
ext, 'libpng',
42+
atleast_version='1.2',
43+
alt_exec=['libpng-config', '--ldflags'],
44+
- default_libraries=['png', 'z'])
45+
+ default_libraries=['png16', 'z']) # adapted to p4a's png library
46+
Numpy().add_flags(ext)
47+
return ext
48+
49+
@@ -957,9 +956,10 @@ class BackendAgg(OptionalBackendPackage)
50+
2051
class BackendTkAgg(OptionalBackendPackage):
2152
name = "tkagg"
2253
- force = True
2354
+ force = False
24-
55+
2556
def check(self):
2657
+ raise CheckFailed("Disabled by patching during Android build") # tk doesn't work on Android but causes build problems
27-
return "installing; run-time loading from Python Tcl / Tk"
28-
58+
return "installing; run-time loading from Python Tcl/Tk"
59+
2960
def get_extension(self):

pythonforandroid/recipes/png/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77

88
class PngRecipe(Recipe):
99
name = 'png'
10-
version = 'v1.6.37'
11-
url = 'https://github.com/glennrp/libpng/archive/{version}.zip'
10+
version = '1.6.37'
11+
url = 'https://github.com/glennrp/libpng/archive/v{version}.zip'
1212
built_libraries = {'libpng16.so': '.libs'}
1313

1414
def build_arch(self, arch):

0 commit comments

Comments
 (0)