11
2+ from pythonforandroid .logger import info_notify
23from pythonforandroid .recipe import CppCompiledComponentsPythonRecipe
4+ from pythonforandroid .util import ensure_dir
35
46from os .path import join
57
68
79class 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
33164recipe = MatplotlibRecipe ()
0 commit comments