1- from os .path import basename , dirname , exists , isdir , isfile , join , realpath , split , sep
1+ from os .path import basename , dirname , exists , isdir , isfile , join , realpath , split
22import glob
33
44import hashlib
@@ -478,10 +478,10 @@ def unpack(self, arch):
478478 elif isdir (extraction_filename ):
479479 ensure_dir (directory_name )
480480 for entry in listdir (extraction_filename ):
481- if entry not in ('.git' ,):
482- shprint (sh .cp , '-Rv' ,
483- join (extraction_filename , entry ),
484- directory_name )
481+ # if entry not in ('.git',): .git folder is required by `setuptools_scm`
482+ shprint (sh .cp , '-Rv' ,
483+ join (extraction_filename , entry ),
484+ directory_name )
485485 else :
486486 raise Exception (
487487 'Given path is neither a file nor a directory: {}'
@@ -843,7 +843,6 @@ class PythonRecipe(Recipe):
843843
844844 def __init__ (self , * args , ** kwargs ):
845845 super ().__init__ (* args , ** kwargs )
846-
847846 if 'python3' not in self .depends :
848847 # We ensure here that the recipe depends on python even it overrode
849848 # `depends`. We only do this if it doesn't already depend on any
@@ -893,12 +892,12 @@ def folder_name(self):
893892
894893 def get_recipe_env (self , arch = None , with_flags_in_cc = True ):
895894 env = super ().get_recipe_env (arch , with_flags_in_cc )
896-
897895 env ['PYTHONNOUSERSITE' ] = '1'
898-
899896 # Set the LANG, this isn't usually important but is a better default
900897 # as it occasionally matters how Python e.g. reads files
901898 env ['LANG' ] = "en_GB.UTF-8"
899+ # Binaries made by packages installed by pip
900+ env ["PATH" ] = join (self .hostpython_site_dir , "bin" ) + ":" + env ["PATH" ]
902901
903902 if not self .call_hostpython_via_targetpython :
904903 env ['CFLAGS' ] += ' -I{}' .format (
@@ -978,27 +977,35 @@ def install_hostpython_package(self, arch):
978977 '--install-lib=Lib/site-packages' ,
979978 _env = env , * self .setup_extra_args )
980979
981- @property
982- def python_version (self ):
983- return Recipe .get_recipe ("python3" , self .ctx ).version
980+ def get_python_formatted_version (self ):
981+ parsed_version = packaging .version .parse (self .ctx .python_recipe .version )
982+ return f"{ parsed_version .major } .{ parsed_version .minor } "
983+
984+ def install_hostpython_prerequisites (self , packages = None , force_upgrade = True ):
985+ if not packages :
986+ packages = self .hostpython_prerequisites
984987
985- def install_hostpython_prerequisites (self , force_upgrade = True ):
986- if len (self .hostpython_prerequisites ) == 0 :
988+ if len (packages ) == 0 :
987989 return
990+
988991 pip_options = [
989992 "install" ,
990- * self . hostpython_prerequisites ,
993+ * packages ,
991994 "--target" , self .hostpython_site_dir , "--python-version" ,
992- self .python_version ,
995+ self .ctx . python_recipe . version ,
993996 # Don't use sources, instead wheels
994997 "--only-binary=:all:" ,
995- "--no-deps"
998+ # "--no-deps"
996999 ]
9971000 if force_upgrade :
9981001 pip_options .append ("--upgrade" )
9991002 # Use system's pip
10001003 shprint (sh .pip , * pip_options )
10011004
1005+ def restore_hostpython_prerequisites (self , package ):
1006+ original_version = Recipe .get_recipe (package , self .ctx ).version
1007+ self .install_hostpython_prerequisites (packages = [package + "==" + original_version ])
1008+
10021009
10031010class CompiledComponentsPythonRecipe (PythonRecipe ):
10041011 pre_build_ext = False
@@ -1157,7 +1164,158 @@ def get_recipe_env(self, arch, with_flags_in_cc=True):
11571164 return env
11581165
11591166
1160- class RustCompiledComponentsRecipe (PythonRecipe ):
1167+ class PyProjectRecipe (PythonRecipe ):
1168+ '''Recipe for projects which containes `pyproject.toml`'''
1169+
1170+ # Extra args to pass to `python -m build ...`
1171+ extra_build_args = []
1172+ call_hostpython_via_targetpython = False
1173+
1174+ def __init__ (self , * arg , ** kwargs ):
1175+ super ().__init__ (* arg , ** kwargs )
1176+
1177+ def get_recipe_env (self , arch , ** kwargs ):
1178+ self .ctx .python_recipe .python_exe = join (
1179+ self .ctx .python_recipe .get_build_dir (arch ), "android-build" , "python3" )
1180+ env = super ().get_recipe_env (arch , ** kwargs )
1181+ build_dir = self .get_build_dir (arch )
1182+ ensure_dir (build_dir )
1183+ build_opts = join (build_dir , "build-opts.cfg" )
1184+
1185+ with open (build_opts , "w" ) as file :
1186+ file .write ("[bdist_wheel]\n plat-name=android_{}" .format (str (arch )))
1187+ file .close ()
1188+
1189+ env ["DIST_EXTRA_CONFIG" ] = build_opts
1190+ return env
1191+
1192+ def build_arch (self , arch ):
1193+ self .install_hostpython_prerequisites (
1194+ packages = ["build[virtualenv]" , "pip" ] + self .hostpython_prerequisites
1195+ )
1196+ build_dir = self .get_build_dir (arch .arch )
1197+ env = self .get_recipe_env (arch , with_flags_in_cc = True )
1198+ built_wheel = None
1199+ # make build dir separatly
1200+ sub_build_dir = join (build_dir , "p4a_android_build" )
1201+ ensure_dir (sub_build_dir )
1202+ # copy hostpython to built python to ensure correct libs and includes
1203+ shprint (sh .cp , self .real_hostpython_location , self .ctx .python_recipe .python_exe )
1204+
1205+ build_args = [
1206+ "-m" ,
1207+ "build" ,
1208+ # "--no-isolation",
1209+ # "--skip-dependency-check",
1210+ "--wheel" ,
1211+ "--config-setting" ,
1212+ "builddir={}" .format (sub_build_dir ),
1213+ ] + self .extra_build_args
1214+
1215+ with current_directory (build_dir ):
1216+ shprint (
1217+ sh .Command (self .ctx .python_recipe .python_exe ), * build_args , _env = env
1218+ )
1219+ built_wheel = realpath (glob .glob ("dist/*.whl" )[0 ])
1220+
1221+ info ("Unzipping built wheel '{}'" .format (basename (built_wheel )))
1222+
1223+ with zipfile .ZipFile (built_wheel , "r" ) as zip_ref :
1224+ zip_ref .extractall (self .ctx .get_python_install_dir (arch .arch ))
1225+ info ("Successfully installed '{}'" .format (basename (built_wheel )))
1226+
1227+
1228+ class MesonRecipe (PyProjectRecipe ):
1229+ '''Recipe for projects which uses meson as build system'''
1230+
1231+ meson_version = "1.4.0"
1232+ ninja_version = "1.11.1.1"
1233+
1234+ def sanitize_flags (self , * flag_strings ):
1235+ return " " .join (flag_strings ).strip ().split (" " )
1236+
1237+ def get_recipe_meson_options (self , arch ):
1238+ """Writes python dict to meson config file"""
1239+ env = self .get_recipe_env (arch , with_flags_in_cc = True )
1240+ return {
1241+ "binaries" : {
1242+ "c" : arch .get_clang_exe (with_target = True ),
1243+ "cpp" : arch .get_clang_exe (with_target = True , plus_plus = True ),
1244+ "ar" : self .ctx .ndk .llvm_ar ,
1245+ "strip" : self .ctx .ndk .llvm_strip ,
1246+ },
1247+ "built-in options" : {
1248+ "c_args" : self .sanitize_flags (env ["CFLAGS" ], env ["CPPFLAGS" ]),
1249+ "cpp_args" : self .sanitize_flags (env ["CXXFLAGS" ], env ["CPPFLAGS" ]),
1250+ "c_link_args" : self .sanitize_flags (env ["LDFLAGS" ]),
1251+ "cpp_link_args" : self .sanitize_flags (env ["LDFLAGS" ]),
1252+ },
1253+ "properties" : {
1254+ "needs_exe_wrapper" : True ,
1255+ "sys_root" : self .ctx .ndk .sysroot
1256+ },
1257+ "host_machine" : {
1258+ "cpu_family" : {
1259+ "arm64-v8a" : "aarch64" ,
1260+ "armeabi-v7a" : "arm" ,
1261+ "x86_64" : "x86_64" ,
1262+ "x86" : "x86"
1263+ }[arch .arch ],
1264+ "cpu" : {
1265+ "arm64-v8a" : "aarch64" ,
1266+ "armeabi-v7a" : "armv7" ,
1267+ "x86_64" : "x86_64" ,
1268+ "x86" : "i686"
1269+ }[arch .arch ],
1270+ "endian" : "little" ,
1271+ "system" : "android" ,
1272+ }
1273+ }
1274+
1275+ def write_build_options (self , arch ):
1276+ option_data = ""
1277+ build_options = self .get_recipe_meson_options (arch )
1278+ for key in build_options .keys ():
1279+ data_chunk = "[{}]" .format (key )
1280+ for subkey in build_options [key ].keys ():
1281+ value = build_options [key ][subkey ]
1282+ if isinstance (value , int ):
1283+ value = str (value )
1284+ elif isinstance (value , str ):
1285+ value = "'{}'" .format (value )
1286+ elif isinstance (value , bool ):
1287+ value = "true" if value else "false"
1288+ elif isinstance (value , list ):
1289+ value = "['" + "', '" .join (value ) + "']"
1290+ data_chunk += "\n " + subkey + " = " + value
1291+ option_data += data_chunk + "\n \n "
1292+ return option_data
1293+
1294+ def ensure_args (self , * args ):
1295+ for arg in args :
1296+ if arg not in self .extra_build_args :
1297+ self .extra_build_args .append (arg )
1298+
1299+ def build_arch (self , arch ):
1300+ cross_file = join (self .get_build_dir (arch ), "android.meson.cross" )
1301+ info ("Writing cross file at: {}" .format (cross_file ))
1302+ # write cross config file
1303+ with open (cross_file , "w" ) as file :
1304+ file .write (self .write_build_options (arch ))
1305+ file .close ()
1306+ # set cross file
1307+ self .ensure_args ('-Csetup-args=--cross-file' , '-Csetup-args={}' .format (cross_file ))
1308+ # ensure ninja and meson
1309+ for dep in [
1310+ "ninja=={}" .format (self .ninja_version ),
1311+ "meson=={}" .format (self .meson_version ),
1312+ ]:
1313+ if dep not in self .hostpython_prerequisites :
1314+ self .hostpython_prerequisites .append (dep )
1315+ super ().build_arch (arch )
1316+
1317+
1318+ class RustCompiledComponentsRecipe (PyProjectRecipe ):
11611319 # Rust toolchain codes
11621320 # https://doc.rust-lang.org/nightly/rustc/platform-support.html
11631321 RUST_ARCH_CODES = {
@@ -1167,41 +1325,10 @@ class RustCompiledComponentsRecipe(PythonRecipe):
11671325 "x86" : "i686-linux-android" ,
11681326 }
11691327
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-
11791328 call_hostpython_via_targetpython = False
11801329
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 )
1330+ def get_recipe_env (self , arch , ** kwargs ):
1331+ env = super ().get_recipe_env (arch , ** kwargs )
12051332
12061333 # Set rust build target
12071334 build_target = self .RUST_ARCH_CODES [arch .arch ]
@@ -1220,7 +1347,7 @@ def get_recipe_env(self, arch):
12201347 self .ctx .ndk_api ,
12211348 ),
12221349 )
1223- realpython_dir = Recipe . get_recipe ( "python3" , self .ctx ) .get_build_dir (arch .arch )
1350+ realpython_dir = self .ctx . python_recipe .get_build_dir (arch .arch )
12241351
12251352 env ["RUSTFLAGS" ] = "-Clink-args=-L{} -L{}" .format (
12261353 self .ctx .get_libs_dir (arch .arch ), join (realpython_dir , "android-build" )
@@ -1243,10 +1370,6 @@ def get_recipe_env(self, arch):
12431370 )
12441371 return env
12451372
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-
12501373 def check_host_deps (self ):
12511374 if not hasattr (sh , "rustup" ):
12521375 error (
@@ -1258,41 +1381,7 @@ def check_host_deps(self):
12581381
12591382 def build_arch (self , arch ):
12601383 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 )))
1384+ super ().build_arch (arch )
12961385
12971386
12981387class TargetPythonRecipe (Recipe ):
0 commit comments