1
- from os .path import basename , dirname , exists , isdir , isfile , join , realpath , split
1
+ from os .path import basename , dirname , exists , isdir , isfile , join , realpath , split , sep
2
2
import glob
3
3
4
4
import hashlib
7
7
import sh
8
8
import shutil
9
9
import fnmatch
10
+ import zipfile
10
11
import urllib .request
11
12
from urllib .request import urlretrieve
12
13
from os import listdir , unlink , environ , curdir , walk
20
21
import packaging .version
21
22
22
23
from pythonforandroid .logger import (
23
- logger , info , warning , debug , shprint , info_main )
24
+ logger , info , warning , debug , shprint , info_main , error )
24
25
from pythonforandroid .util import (
25
26
current_directory , ensure_dir , BuildInterruptingException , rmdir , move ,
26
27
touch )
@@ -175,6 +176,7 @@ def download_file(self, url, target, cwd=None):
175
176
"""
176
177
if not url :
177
178
return
179
+
178
180
info ('Downloading {} from {}' .format (self .name , url ))
179
181
180
182
if cwd :
@@ -458,7 +460,6 @@ def unpack(self, arch):
458
460
# apparently happens sometimes with
459
461
# github zips
460
462
pass
461
- import zipfile
462
463
fileh = zipfile .ZipFile (extraction_filename , 'r' )
463
464
root_directory = fileh .filelist [0 ].filename .split ('/' )[0 ]
464
465
if root_directory != basename (directory_name ):
@@ -837,6 +838,9 @@ class PythonRecipe(Recipe):
837
838
on python2 or python3 which can break the dependency graph
838
839
'''
839
840
841
+ hostpython_prerequisites = []
842
+ '''List of hostpython packages required to build a recipe'''
843
+
840
844
def __init__ (self , * args , ** kwargs ):
841
845
super ().__init__ (* args , ** kwargs )
842
846
@@ -930,6 +934,7 @@ def should_build(self, arch):
930
934
def build_arch (self , arch ):
931
935
'''Install the Python module by calling setup.py install with
932
936
the target Python dir.'''
937
+ self .install_hostpython_prerequisites ()
933
938
super ().build_arch (arch )
934
939
self .install_python_package (arch )
935
940
@@ -958,9 +963,13 @@ def install_python_package(self, arch, name=None, env=None, is_dir=True):
958
963
959
964
def get_hostrecipe_env (self , arch ):
960
965
env = environ .copy ()
961
- env ['PYTHONPATH' ] = join ( dirname ( self .real_hostpython_location ), 'Lib' , 'site-packages' )
966
+ env ['PYTHONPATH' ] = self .hostpython_site_dir
962
967
return env
963
968
969
+ @property
970
+ def hostpython_site_dir (self ):
971
+ return join (dirname (self .real_hostpython_location ), 'Lib' , 'site-packages' )
972
+
964
973
def install_hostpython_package (self , arch ):
965
974
env = self .get_hostrecipe_env (arch )
966
975
real_hostpython = sh .Command (self .real_hostpython_location )
@@ -969,6 +978,27 @@ def install_hostpython_package(self, arch):
969
978
'--install-lib=Lib/site-packages' ,
970
979
_env = env , * self .setup_extra_args )
971
980
981
+ @property
982
+ def python_version (self ):
983
+ return Recipe .get_recipe ("python3" , self .ctx ).version
984
+
985
+ def install_hostpython_prerequisites (self , force_upgrade = True ):
986
+ if len (self .hostpython_prerequisites ) == 0 :
987
+ return
988
+ pip_options = [
989
+ "install" ,
990
+ * self .hostpython_prerequisites ,
991
+ "--target" , self .hostpython_site_dir , "--python-version" ,
992
+ self .python_version ,
993
+ # Don't use sources, instead wheels
994
+ "--only-binary=:all:" ,
995
+ "--no-deps"
996
+ ]
997
+ if force_upgrade :
998
+ pip_options .append ("--upgrade" )
999
+ # Use system's pip
1000
+ shprint (sh .pip , * pip_options )
1001
+
972
1002
973
1003
class CompiledComponentsPythonRecipe (PythonRecipe ):
974
1004
pre_build_ext = False
@@ -1127,6 +1157,144 @@ def get_recipe_env(self, arch, with_flags_in_cc=True):
1127
1157
return env
1128
1158
1129
1159
1160
+ class RustCompiledComponentsRecipe (PythonRecipe ):
1161
+ # Rust toolchain codes
1162
+ # https://doc.rust-lang.org/nightly/rustc/platform-support.html
1163
+ RUST_ARCH_CODES = {
1164
+ "arm64-v8a" : "aarch64-linux-android" ,
1165
+ "armeabi-v7a" : "armv7-linux-androideabi" ,
1166
+ "x86_64" : "x86_64-linux-android" ,
1167
+ "x86" : "i686-linux-android" ,
1168
+ }
1169
+
1170
+ # Build python wheel using `maturin` instead
1171
+ # of default `python -m build [...]`
1172
+ use_maturin = False
1173
+
1174
+ # Directory where to find built wheel
1175
+ # For normal build: "dist/*.whl"
1176
+ # For maturin: "target/wheels/*-linux_*.whl"
1177
+ built_wheel_pattern = None
1178
+
1179
+ call_hostpython_via_targetpython = False
1180
+
1181
+ def __init__ (self , * arg , ** kwargs ):
1182
+ super ().__init__ (* arg , ** kwargs )
1183
+ self .append_deps_if_absent (["python3" ])
1184
+ self .set_default_hostpython_deps ()
1185
+ if not self .built_wheel_pattern :
1186
+ self .built_wheel_pattern = (
1187
+ "target/wheels/*-linux_*.whl"
1188
+ if self .use_maturin
1189
+ else "dist/*.whl"
1190
+ )
1191
+
1192
+ def set_default_hostpython_deps (self ):
1193
+ if not self .use_maturin :
1194
+ self .hostpython_prerequisites += ["build" , "setuptools_rust" , "wheel" , "pyproject_hooks" ]
1195
+ else :
1196
+ self .hostpython_prerequisites += ["maturin" ]
1197
+
1198
+ def append_deps_if_absent (self , deps ):
1199
+ for dep in deps :
1200
+ if dep not in self .depends :
1201
+ self .depends .append (dep )
1202
+
1203
+ def get_recipe_env (self , arch ):
1204
+ env = super ().get_recipe_env (arch )
1205
+
1206
+ # Set rust build target
1207
+ build_target = self .RUST_ARCH_CODES [arch .arch ]
1208
+ cargo_linker_name = "CARGO_TARGET_{}_LINKER" .format (
1209
+ build_target .upper ().replace ("-" , "_" )
1210
+ )
1211
+ env ["CARGO_BUILD_TARGET" ] = build_target
1212
+ env [cargo_linker_name ] = join (
1213
+ self .ctx .ndk .llvm_prebuilt_dir ,
1214
+ "bin" ,
1215
+ "{}{}-clang" .format (
1216
+ # NDK's Clang format
1217
+ build_target .replace ("7" , "7a" )
1218
+ if build_target .startswith ("armv7" )
1219
+ else build_target ,
1220
+ self .ctx .ndk_api ,
1221
+ ),
1222
+ )
1223
+ realpython_dir = Recipe .get_recipe ("python3" , self .ctx ).get_build_dir (arch .arch )
1224
+
1225
+ env ["RUSTFLAGS" ] = "-Clink-args=-L{} -L{}" .format (
1226
+ self .ctx .get_libs_dir (arch .arch ), join (realpython_dir , "android-build" )
1227
+ )
1228
+
1229
+ env ["PYO3_CROSS_LIB_DIR" ] = realpath (glob .glob (join (
1230
+ realpython_dir , "android-build" , "build" ,
1231
+ "lib.linux-*-{}/" .format (self .get_python_formatted_version ()),
1232
+ ))[0 ])
1233
+
1234
+ info_main ("Ensuring rust build toolchain" )
1235
+ shprint (sh .rustup , "target" , "add" , build_target )
1236
+
1237
+ # Add host python to PATH
1238
+ env ["PATH" ] = ("{hostpython_dir}:{old_path}" ).format (
1239
+ hostpython_dir = Recipe .get_recipe (
1240
+ "hostpython3" , self .ctx
1241
+ ).get_path_to_python (),
1242
+ old_path = env ["PATH" ],
1243
+ )
1244
+ return env
1245
+
1246
+ def get_python_formatted_version (self ):
1247
+ parsed_version = packaging .version .parse (self .python_version )
1248
+ return f"{ parsed_version .major } .{ parsed_version .minor } "
1249
+
1250
+ def check_host_deps (self ):
1251
+ if not hasattr (sh , "rustup" ):
1252
+ error (
1253
+ "`rustup` was not found on host system."
1254
+ "Please install it using :"
1255
+ "\n `curl https://sh.rustup.rs -sSf | sh`\n "
1256
+ )
1257
+ exit (1 )
1258
+
1259
+ def build_arch (self , arch ):
1260
+ self .check_host_deps ()
1261
+ self .install_hostpython_prerequisites ()
1262
+ build_dir = self .get_build_dir (arch .arch )
1263
+ env = self .get_recipe_env (arch )
1264
+ built_wheel = None
1265
+
1266
+ # Copy the exec with version info
1267
+ hostpython_exec = join (
1268
+ sep ,
1269
+ * self .hostpython_location .split (sep )[:- 1 ],
1270
+ "python{}" .format (self .get_python_formatted_version ()),
1271
+ )
1272
+ shprint (sh .cp , self .hostpython_location , hostpython_exec )
1273
+
1274
+ with current_directory (build_dir ):
1275
+ if self .use_maturin :
1276
+ shprint (
1277
+ sh .Command (join (self .hostpython_site_dir , "bin" , "maturin" )),
1278
+ "build" , "--interpreter" , hostpython_exec , "--skip-auditwheel" ,
1279
+ _env = env ,
1280
+ )
1281
+ else :
1282
+ shprint (
1283
+ sh .Command (hostpython_exec ),
1284
+ "-m" , "build" , "--no-isolation" , "--skip-dependency-check" , "--wheel" ,
1285
+ _env = env ,
1286
+ )
1287
+ # Find the built wheel
1288
+ built_wheel = realpath (glob .glob (self .built_wheel_pattern )[0 ])
1289
+
1290
+ info ("Unzipping built wheel '{}'" .format (basename (built_wheel )))
1291
+
1292
+ # Unzip .whl file into site-packages
1293
+ with zipfile .ZipFile (built_wheel , "r" ) as zip_ref :
1294
+ zip_ref .extractall (self .ctx .get_python_install_dir (arch .arch ))
1295
+ info ("Successfully installed '{}'" .format (basename (built_wheel )))
1296
+
1297
+
1130
1298
class TargetPythonRecipe (Recipe ):
1131
1299
'''Class for target python recipes. Sets ctx.python_recipe to point to
1132
1300
itself, so as to know later what kind of Python was built or used.'''
0 commit comments