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
@@ -232,7 +232,7 @@ def report_hook(index, blksize, size):
232232 shprint (sh .git , 'clone' , '--recursive' , url , target )
233233 with current_directory (target ):
234234 if self .version :
235- shprint (sh .git , 'fetch' , '--depth ' , '1 ' , 'origin' , self . version )
235+ shprint (sh .git , 'fetch' , '--tags ' , '--depth ' , '1' )
236236 shprint (sh .git , 'checkout' , self .version )
237237 branch = sh .git ('branch' , '--show-current' )
238238 if branch :
@@ -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,38 @@ 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 , packages ):
1006+ _packages = []
1007+ for package in packages :
1008+ original_version = Recipe .get_recipe (package , self .ctx ).version
1009+ _packages .append (package + "==" + original_version )
1010+ self .install_hostpython_prerequisites (packages = _packages )
1011+
10021012
10031013class CompiledComponentsPythonRecipe (PythonRecipe ):
10041014 pre_build_ext = False
@@ -1157,7 +1167,159 @@ def get_recipe_env(self, arch, with_flags_in_cc=True):
11571167 return env
11581168
11591169
1160- class RustCompiledComponentsRecipe (PythonRecipe ):
1170+ class PyProjectRecipe (PythonRecipe ):
1171+ '''Recipe for projects which containes `pyproject.toml`'''
1172+
1173+ # Extra args to pass to `python -m build ...`
1174+ extra_build_args = []
1175+ call_hostpython_via_targetpython = False
1176+
1177+ def __init__ (self , * arg , ** kwargs ):
1178+ super ().__init__ (* arg , ** kwargs )
1179+
1180+ def get_recipe_env (self , arch , ** kwargs ):
1181+ # Custom hostpython
1182+ self .ctx .python_recipe .python_exe = join (
1183+ self .ctx .python_recipe .get_build_dir (arch ), "android-build" , "python3" )
1184+ env = super ().get_recipe_env (arch , ** kwargs )
1185+ build_dir = self .get_build_dir (arch )
1186+ ensure_dir (build_dir )
1187+ build_opts = join (build_dir , "build-opts.cfg" )
1188+
1189+ with open (build_opts , "w" ) as file :
1190+ file .write ("[bdist_wheel]\n plat-name=android_{}" .format (str (arch )))
1191+ file .close ()
1192+
1193+ env ["DIST_EXTRA_CONFIG" ] = build_opts
1194+ return env
1195+
1196+ def build_arch (self , arch ):
1197+ self .install_hostpython_prerequisites (
1198+ packages = ["build[virtualenv]" , "pip" ] + self .hostpython_prerequisites
1199+ )
1200+ build_dir = self .get_build_dir (arch .arch )
1201+ env = self .get_recipe_env (arch , with_flags_in_cc = True )
1202+ built_wheel = None
1203+ # make build dir separatly
1204+ sub_build_dir = join (build_dir , "p4a_android_build" )
1205+ ensure_dir (sub_build_dir )
1206+ # copy hostpython to built python to ensure correct libs and includes
1207+ shprint (sh .cp , self .real_hostpython_location , self .ctx .python_recipe .python_exe )
1208+
1209+ build_args = [
1210+ "-m" ,
1211+ "build" ,
1212+ # "--no-isolation",
1213+ # "--skip-dependency-check",
1214+ "--wheel" ,
1215+ "--config-setting" ,
1216+ "builddir={}" .format (sub_build_dir ),
1217+ ] + self .extra_build_args
1218+
1219+ with current_directory (build_dir ):
1220+ shprint (
1221+ sh .Command (self .ctx .python_recipe .python_exe ), * build_args , _env = env
1222+ )
1223+ built_wheel = realpath (glob .glob ("dist/*.whl" )[0 ])
1224+
1225+ info ("Unzipping built wheel '{}'" .format (basename (built_wheel )))
1226+
1227+ with zipfile .ZipFile (built_wheel , "r" ) as zip_ref :
1228+ zip_ref .extractall (self .ctx .get_python_install_dir (arch .arch ))
1229+ info ("Successfully installed '{}'" .format (basename (built_wheel )))
1230+
1231+
1232+ class MesonRecipe (PyProjectRecipe ):
1233+ '''Recipe for projects which uses meson as build system'''
1234+
1235+ meson_version = "1.4.0"
1236+ ninja_version = "1.11.1.1"
1237+
1238+ def sanitize_flags (self , * flag_strings ):
1239+ return " " .join (flag_strings ).strip ().split (" " )
1240+
1241+ def get_recipe_meson_options (self , arch ):
1242+ """Writes python dict to meson config file"""
1243+ env = self .get_recipe_env (arch , with_flags_in_cc = True )
1244+ return {
1245+ "binaries" : {
1246+ "c" : arch .get_clang_exe (with_target = True ),
1247+ "cpp" : arch .get_clang_exe (with_target = True , plus_plus = True ),
1248+ "ar" : self .ctx .ndk .llvm_ar ,
1249+ "strip" : self .ctx .ndk .llvm_strip ,
1250+ },
1251+ "built-in options" : {
1252+ "c_args" : self .sanitize_flags (env ["CFLAGS" ], env ["CPPFLAGS" ]),
1253+ "cpp_args" : self .sanitize_flags (env ["CXXFLAGS" ], env ["CPPFLAGS" ]),
1254+ "c_link_args" : self .sanitize_flags (env ["LDFLAGS" ]),
1255+ "cpp_link_args" : self .sanitize_flags (env ["LDFLAGS" ]),
1256+ },
1257+ "properties" : {
1258+ "needs_exe_wrapper" : True ,
1259+ "sys_root" : self .ctx .ndk .sysroot
1260+ },
1261+ "host_machine" : {
1262+ "cpu_family" : {
1263+ "arm64-v8a" : "aarch64" ,
1264+ "armeabi-v7a" : "arm" ,
1265+ "x86_64" : "x86_64" ,
1266+ "x86" : "x86"
1267+ }[arch .arch ],
1268+ "cpu" : {
1269+ "arm64-v8a" : "aarch64" ,
1270+ "armeabi-v7a" : "armv7" ,
1271+ "x86_64" : "x86_64" ,
1272+ "x86" : "i686"
1273+ }[arch .arch ],
1274+ "endian" : "little" ,
1275+ "system" : "android" ,
1276+ }
1277+ }
1278+
1279+ def write_build_options (self , arch ):
1280+ option_data = ""
1281+ build_options = self .get_recipe_meson_options (arch )
1282+ for key in build_options .keys ():
1283+ data_chunk = "[{}]" .format (key )
1284+ for subkey in build_options [key ].keys ():
1285+ value = build_options [key ][subkey ]
1286+ if isinstance (value , int ):
1287+ value = str (value )
1288+ elif isinstance (value , str ):
1289+ value = "'{}'" .format (value )
1290+ elif isinstance (value , bool ):
1291+ value = "true" if value else "false"
1292+ elif isinstance (value , list ):
1293+ value = "['" + "', '" .join (value ) + "']"
1294+ data_chunk += "\n " + subkey + " = " + value
1295+ option_data += data_chunk + "\n \n "
1296+ return option_data
1297+
1298+ def ensure_args (self , * args ):
1299+ for arg in args :
1300+ if arg not in self .extra_build_args :
1301+ self .extra_build_args .append (arg )
1302+
1303+ def build_arch (self , arch ):
1304+ cross_file = join ("/tmp" , "android.meson.cross" )
1305+ info ("Writing cross file at: {}" .format (cross_file ))
1306+ # write cross config file
1307+ with open (cross_file , "w" ) as file :
1308+ file .write (self .write_build_options (arch ))
1309+ file .close ()
1310+ # set cross file
1311+ self .ensure_args ('-Csetup-args=--cross-file' , '-Csetup-args={}' .format (cross_file ))
1312+ # ensure ninja and meson
1313+ for dep in [
1314+ "ninja=={}" .format (self .ninja_version ),
1315+ "meson=={}" .format (self .meson_version ),
1316+ ]:
1317+ if dep not in self .hostpython_prerequisites :
1318+ self .hostpython_prerequisites .append (dep )
1319+ super ().build_arch (arch )
1320+
1321+
1322+ class RustCompiledComponentsRecipe (PyProjectRecipe ):
11611323 # Rust toolchain codes
11621324 # https://doc.rust-lang.org/nightly/rustc/platform-support.html
11631325 RUST_ARCH_CODES = {
@@ -1167,41 +1329,10 @@ class RustCompiledComponentsRecipe(PythonRecipe):
11671329 "x86" : "i686-linux-android" ,
11681330 }
11691331
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-
11791332 call_hostpython_via_targetpython = False
11801333
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 )
1334+ def get_recipe_env (self , arch , ** kwargs ):
1335+ env = super ().get_recipe_env (arch , ** kwargs )
12051336
12061337 # Set rust build target
12071338 build_target = self .RUST_ARCH_CODES [arch .arch ]
@@ -1220,7 +1351,7 @@ def get_recipe_env(self, arch):
12201351 self .ctx .ndk_api ,
12211352 ),
12221353 )
1223- realpython_dir = Recipe . get_recipe ( "python3" , self .ctx ) .get_build_dir (arch .arch )
1354+ realpython_dir = self .ctx . python_recipe .get_build_dir (arch .arch )
12241355
12251356 env ["RUSTFLAGS" ] = "-Clink-args=-L{} -L{}" .format (
12261357 self .ctx .get_libs_dir (arch .arch ), join (realpython_dir , "android-build" )
@@ -1243,10 +1374,6 @@ def get_recipe_env(self, arch):
12431374 )
12441375 return env
12451376
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-
12501377 def check_host_deps (self ):
12511378 if not hasattr (sh , "rustup" ):
12521379 error (
@@ -1258,41 +1385,7 @@ def check_host_deps(self):
12581385
12591386 def build_arch (self , arch ):
12601387 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 )))
1388+ super ().build_arch (arch )
12961389
12971390
12981391class TargetPythonRecipe (Recipe ):
0 commit comments