@@ -899,6 +899,8 @@ def get_recipe_env(self, arch=None, with_flags_in_cc=True):
899899 # Set the LANG, this isn't usually important but is a better default
900900 # as it occasionally matters how Python e.g. reads files
901901 env ['LANG' ] = "en_GB.UTF-8"
902+ # Binaries made by packages installed by pip
903+ env ["PATH" ] = join (self .hostpython_site_dir , "bin" ) + ":" + env ["PATH" ]
902904
903905 if not self .call_hostpython_via_targetpython :
904906 env ['CFLAGS' ] += ' -I{}' .format (
@@ -982,9 +984,17 @@ def install_hostpython_package(self, arch):
982984 def python_version (self ):
983985 return Recipe .get_recipe ("python3" , self .ctx ).version
984986
985- def install_hostpython_prerequisites (self , force_upgrade = True ):
986- if len (self .hostpython_prerequisites ) == 0 :
987+ def get_python_formatted_version (self ):
988+ parsed_version = packaging .version .parse (self .python_version )
989+ return f"{ parsed_version .major } .{ parsed_version .minor } "
990+
991+ def install_hostpython_prerequisites (self , packages = None , force_upgrade = True ):
992+ if not packages :
993+ packages = self .hostpython_prerequisites
994+
995+ if len (packages ) == 0 :
987996 return
997+
988998 pip_options = [
989999 "install" ,
9901000 * self .hostpython_prerequisites ,
@@ -999,6 +1009,10 @@ def install_hostpython_prerequisites(self, force_upgrade=True):
9991009 # Use system's pip
10001010 shprint (sh .pip , * pip_options )
10011011
1012+ def restore_hostpython_prerequisites (self , package ):
1013+ original_version = Recipe .get_recipe (package , self .ctx ).version
1014+ self .install_hostpython_prerequisites (packages = [package + "==" + original_version ])
1015+
10021016
10031017class CompiledComponentsPythonRecipe (PythonRecipe ):
10041018 pre_build_ext = False
@@ -1243,10 +1257,6 @@ def get_recipe_env(self, arch):
12431257 )
12441258 return env
12451259
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-
12501260 def check_host_deps (self ):
12511261 if not hasattr (sh , "rustup" ):
12521262 error (
@@ -1295,6 +1305,145 @@ def build_arch(self, arch):
12951305 info ("Successfully installed '{}'" .format (basename (built_wheel )))
12961306
12971307
1308+ class PyProjectRecipe (PythonRecipe ):
1309+ '''Recipe for projects which containes `pyproject.toml`'''
1310+
1311+ default_deps = ["build" , "wheel" , "pyproject_hooks" ,
1312+ "setuptools" , "packaging" , "pyproject_metadata" ]
1313+ # Extra args to pass to `python -m build ...`
1314+ extra_build_args = []
1315+ hostpython_prerequisites = default_deps
1316+ call_hostpython_via_targetpython = False
1317+
1318+ def ensure_default_deps (self ):
1319+ for dep in self .default_deps :
1320+ if dep not in self .hostpython_prerequisites :
1321+ self .hostpython_prerequisites .append (dep )
1322+
1323+ def build_arch (self , arch ):
1324+ self .ensure_default_deps ()
1325+ self .install_hostpython_prerequisites ()
1326+
1327+ build_dir = self .get_build_dir (arch .arch )
1328+ env = self .get_recipe_env (arch , with_flags_in_cc = True )
1329+ built_wheel = None
1330+ info (str (env ))
1331+
1332+ # make build dir separatly
1333+ sub_build_dir = join (build_dir , "p4a_android_build" )
1334+ ensure_dir (sub_build_dir )
1335+
1336+ # Copying hostpython to the Python build ensures that it correctly identifies libraries and include files.
1337+ python_recipe = Recipe .get_recipe ("python3" , self .ctx )
1338+ custom_python = join (python_recipe .get_build_dir (arch ), "android-build" , "python3" )
1339+ shprint (sh .cp , self .real_hostpython_location , custom_python )
1340+
1341+ build_args = [
1342+ "-m" ,
1343+ "build" ,
1344+ "--no-isolation" ,
1345+ "--skip-dependency-check" ,
1346+ "--wheel" ,
1347+ "--config-setting" ,
1348+ "builddir={}" .format (sub_build_dir ),
1349+ ] + self .extra_build_args
1350+
1351+ with current_directory (build_dir ):
1352+ shprint (
1353+ sh .Command (custom_python ), * build_args , _env = env
1354+ )
1355+ built_wheel = realpath (glob .glob ("dist/*.whl" )[0 ])
1356+
1357+ info ("Unzipping built wheel '{}'" .format (basename (built_wheel )))
1358+
1359+ with zipfile .ZipFile (built_wheel , "r" ) as zip_ref :
1360+ zip_ref .extractall (self .ctx .get_python_install_dir (arch .arch ))
1361+ info ("Successfully installed '{}'" .format (basename (built_wheel )))
1362+
1363+
1364+ class MesonRecipe (PyProjectRecipe ):
1365+ '''Recipe for projects which uses meson as build system'''
1366+
1367+ def sanitize_flags (self , * flag_strings ):
1368+ return " " .join (flag_strings ).strip ().split (" " )
1369+
1370+ def get_recipe_meson_options (self , arch ):
1371+ """Writes python dict to meson config file"""
1372+ env = self .get_recipe_env (arch , with_flags_in_cc = True )
1373+ return {
1374+ "binaries" : {
1375+ "c" : arch .get_clang_exe (with_target = True ),
1376+ "cpp" : arch .get_clang_exe (with_target = True , plus_plus = True ),
1377+ "ar" : self .ctx .ndk .llvm_ar ,
1378+ "strip" : self .ctx .ndk .llvm_strip ,
1379+ },
1380+ "built-in options" : {
1381+ "c_args" : self .sanitize_flags (env ["CFLAGS" ], env ["CPPFLAGS" ]),
1382+ "cpp_args" : self .sanitize_flags (env ["CXXFLAGS" ], env ["CPPFLAGS" ]),
1383+ "c_link_args" : self .sanitize_flags (env ["LDFLAGS" ]),
1384+ "cpp_link_args" : self .sanitize_flags (env ["LDFLAGS" ]),
1385+ },
1386+ "properties" : {
1387+ "needs_exe_wrapper" : True ,
1388+ "sys_root" : self .ctx .ndk .sysroot
1389+ },
1390+ "host_machine" : {
1391+ "cpu_family" : {
1392+ "arm64-v8a" : "aarch64" ,
1393+ "armeabi-v7a" : "arm" ,
1394+ "x86_64" : "x86_64" ,
1395+ "x86" : "x86"
1396+ }[arch .arch ],
1397+ "cpu" : {
1398+ "arm64-v8a" : "aarch64" ,
1399+ "armeabi-v7a" : "armv7" ,
1400+ "x86_64" : "x86_64" ,
1401+ "x86" : "i686"
1402+ }[arch .arch ],
1403+ "endian" : "little" ,
1404+ "system" : "android" ,
1405+ }
1406+ }
1407+
1408+ def write_build_options (self , arch ):
1409+ option_data = ""
1410+ build_options = self .get_recipe_meson_options (arch )
1411+ for key in build_options .keys ():
1412+ data_chunk = "[{}]" .format (key )
1413+ for subkey in build_options [key ].keys ():
1414+ value = build_options [key ][subkey ]
1415+ if isinstance (value , int ):
1416+ value = str (value )
1417+ elif isinstance (value , str ):
1418+ value = "'{}'" .format (value )
1419+ elif isinstance (value , bool ):
1420+ value = "true" if value else "false"
1421+ elif isinstance (value , list ):
1422+ value = "['" + "', '" .join (value ) + "']"
1423+ data_chunk += "\n " + subkey + " = " + value
1424+ option_data += data_chunk + "\n \n "
1425+ return option_data
1426+
1427+ def ensure_args (self , * args ):
1428+ for arg in args :
1429+ if arg not in self .extra_build_args :
1430+ self .extra_build_args .append (arg )
1431+
1432+ def build_arch (self , arch ):
1433+ cross_file = join (self .get_build_dir (arch ), "android.meson.cross" )
1434+ info ("Writing cross file at: {}" .format (cross_file ))
1435+ # write cross config file
1436+ with open (cross_file , "w" ) as file :
1437+ file .write (self .write_build_options (arch ))
1438+ file .close ()
1439+ # set cross file
1440+ self .ensure_args ('-Csetup-args=--cross-file' , '-Csetup-args={}' .format (cross_file ))
1441+ # ensure ninja
1442+ if "ninja" not in self .hostpython_prerequisites :
1443+ self .hostpython_prerequisites .append ("ninja" )
1444+ super ().build_arch (arch )
1445+
1446+
12981447class TargetPythonRecipe (Recipe ):
12991448 '''Class for target python recipes. Sets ctx.python_recipe to point to
13001449 itself, so as to know later what kind of Python was built or used.'''
0 commit comments