import sh import os from multiprocessing import cpu_count from pathlib import Path from os.path import join from pythonforandroid.logger import shprint from pythonforandroid.recipe import Recipe from pythonforandroid.util import ( BuildInterruptingException, current_directory, ensure_dir, ) from pythonforandroid.prerequisites import OpenSSLPrerequisite HOSTPYTHON_VERSION_UNSET_MESSAGE = ( 'The hostpython recipe must have set version' ) SETUP_DIST_NOT_FIND_MESSAGE = ( 'Could not find Setup.dist or Setup in Python build' ) class HostPython3Recipe(Recipe): ''' The hostpython3's recipe. .. versionchanged:: 2019.10.06.post0 Refactored from deleted class ``python.HostPythonRecipe`` into here. .. versionchanged:: 0.6.0 Refactored into the new class :class:`~pythonforandroid.python.HostPythonRecipe` ''' version = '3.9.9' name = 'hostpython3' build_subdir = 'native-build' '''Specify the sub build directory for the hostpython3 recipe. Defaults to ``native-build``.''' url = 'https://www.python.org/ftp/python/{version}/Python-{version}.tgz' '''The default url to download our host python recipe. This url will change depending on the python version set in attribute :attr:`version`.''' patches = ['patches/pyconfig_detection.patch'] @property def _exe_name(self): ''' Returns the name of the python executable depending on the version. ''' if not self.version: raise BuildInterruptingException(HOSTPYTHON_VERSION_UNSET_MESSAGE) return f'python{self.version.split(".")[0]}' @property def python_exe(self): '''Returns the full path of the hostpython executable.''' return join(self.get_path_to_python(), self._exe_name) def get_recipe_env(self, arch=None): env = os.environ.copy() openssl_prereq = OpenSSLPrerequisite() if env.get("PKG_CONFIG_PATH", ""): env["PKG_CONFIG_PATH"] = os.pathsep.join( [openssl_prereq.pkg_config_location, env["PKG_CONFIG_PATH"]] ) else: env["PKG_CONFIG_PATH"] = openssl_prereq.pkg_config_location return env def should_build(self, arch): if Path(self.python_exe).exists(): # no need to build, but we must set hostpython for our Context self.ctx.hostpython = self.python_exe return False return True def get_build_container_dir(self, arch=None): choices = self.check_recipe_choices() dir_name = '-'.join([self.name] + choices) return join(self.ctx.build_dir, 'other_builds', dir_name, 'desktop') def get_build_dir(self, arch=None): ''' .. note:: Unlike other recipes, the hostpython build dir doesn't depend on the target arch ''' return join(self.get_build_container_dir(), self.name) def get_path_to_python(self): return join(self.get_build_dir(), self.build_subdir) def build_arch(self, arch): env = self.get_recipe_env(arch) recipe_build_dir = self.get_build_dir(arch.arch) # Create a subdirectory to actually perform the build build_dir = join(recipe_build_dir, self.build_subdir) ensure_dir(build_dir) # Configure the build with current_directory(build_dir): if not Path('config.status').exists(): shprint(sh.Command(join(recipe_build_dir, 'configure')), _env=env) with current_directory(recipe_build_dir): # Create the Setup file. This copying from Setup.dist is # the normal and expected procedure before Python 3.8, but # after this the file with default options is already named "Setup" setup_dist_location = join('Modules', 'Setup.dist') if Path(setup_dist_location).exists(): shprint(sh.cp, setup_dist_location, join(build_dir, 'Modules', 'Setup')) else: # Check the expected file does exist setup_location = join('Modules', 'Setup') if not Path(setup_location).exists(): raise BuildInterruptingException( SETUP_DIST_NOT_FIND_MESSAGE ) shprint(sh.make, '-j', str(cpu_count()), '-C', build_dir, _env=env) # make a copy of the python executable giving it the name we want, # because we got different python's executable names depending on # the fs being case-insensitive (Mac OS X, Cygwin...) or # case-sensitive (linux)...so this way we will have an unique name # for our hostpython, regarding the used fs for exe_name in ['python.exe', 'python']: exe = join(self.get_path_to_python(), exe_name) if Path(exe).is_file(): shprint(sh.cp, exe, self.python_exe) break self.ctx.hostpython = self.python_exe recipe = HostPython3Recipe()