diff --git a/pythonforandroid/build.py b/pythonforandroid/build.py index e504ecfe65..b0b350fd32 100644 --- a/pythonforandroid/build.py +++ b/pythonforandroid/build.py @@ -3,6 +3,7 @@ from os.path import (join, realpath, dirname, expanduser, exists, split, isdir) from os import environ +import copy import os import glob import sys @@ -13,7 +14,7 @@ from pythonforandroid.util import (ensure_dir, current_directory, BuildInterruptingException) from pythonforandroid.logger import (info, warning, info_notify, info_main, shprint) from pythonforandroid.archs import ArchARM, ArchARMv7_a, ArchAarch_64, Archx86, Archx86_64 -from pythonforandroid.recipe import Recipe +from pythonforandroid.recipe import CythonRecipe, Recipe DEFAULT_ANDROID_API = 15 @@ -682,17 +683,51 @@ def run_pymodules_install(ctx, modules): line = '{}\n'.format(module) fileh.write(line) - info('Installing Python modules with pip') - info('If this fails with a message about /bin/false, this ' - 'probably means the package cannot be installed with ' - 'pip as it needs a compilation recipe.') + base_env = copy.copy(os.environ) + base_env["PYTHONPATH"] = ctx.get_site_packages_dir() + + info('Upgrade pip to latest version') + shprint(sh.bash, '-c', ( + "source venv/bin/activate && pip install -U pip" + ), _env=copy.copy(base_env)) - # This bash method is what old-p4a used - # It works but should be replaced with something better + info('Install Cython in case one of the modules needs it to build') + shprint(sh.bash, '-c', ( + "venv/bin/pip install Cython" + ), _env=copy.copy(base_env)) + + # Get environment variables for build (with CC/compiler set): + standard_recipe = CythonRecipe() + standard_recipe.ctx = ctx + # (note: following line enables explicit -lpython... linker options) + standard_recipe.call_hostpython_via_targetpython = False + recipe_env = standard_recipe.get_recipe_env(ctx.archs[0]) + env = copy.copy(base_env) + env.update(recipe_env) + + info('Installing Python modules with pip') + info('IF THIS FAILS, THE MODULES MAY NEED A RECIPE. ' + 'A reason for this is often modules compiling ' + 'native code that is unaware of Android cross-compilation ' + 'and does not work without additional ' + 'changes / workarounds.') + + # Make sure our build package dir is available, and the virtualenv + # site packages come FIRST (for the proper pip version): + env["PYTHONPATH"] += ":" + ctx.get_site_packages_dir() + env["PYTHONPATH"] = os.path.abspath(join( + ctx.build_dir, "venv", "lib", + "python" + ctx.python_recipe.major_minor_version_string, + "site-packages")) + ":" + env["PYTHONPATH"] shprint(sh.bash, '-c', ( - "env CC=/bin/false CXX=/bin/false " - "PYTHONPATH={0} venv/bin/pip install --target '{0}' --no-deps -r requirements.txt" - ).format(ctx.get_site_packages_dir())) + "source venv/bin/activate && " + + "pip install -v --target '{0}' --no-deps -r requirements.txt" + ).format(ctx.get_site_packages_dir().replace("'", "'\"'\"'")), + _env=copy.copy(env)) + + # Strip object files after potential Cython or native code builds: + standard_recipe.strip_object_files(ctx.archs[0], env, + build_dir=ctx.build_dir) def biglink(ctx, arch): diff --git a/pythonforandroid/recipe.py b/pythonforandroid/recipe.py index c476eaa31d..c616f1539f 100644 --- a/pythonforandroid/recipe.py +++ b/pythonforandroid/recipe.py @@ -779,12 +779,14 @@ def get_recipe_env(self, arch=None, with_flags_in_cc=True): hppath.append(join(dirname(self.hostpython_location), 'Lib')) hppath.append(join(hppath[0], 'site-packages')) builddir = join(dirname(self.hostpython_location), 'build') - hppath += [join(builddir, d) for d in listdir(builddir) - if isdir(join(builddir, d))] - if 'PYTHONPATH' in env: - env['PYTHONPATH'] = ':'.join(hppath + [env['PYTHONPATH']]) - else: - env['PYTHONPATH'] = ':'.join(hppath) + if exists(builddir): + hppath += [join(builddir, d) for d in listdir(builddir) + if isdir(join(builddir, d))] + if len(hppath) > 0: + if 'PYTHONPATH' in env: + env['PYTHONPATH'] = ':'.join(hppath + [env['PYTHONPATH']]) + else: + env['PYTHONPATH'] = ':'.join(hppath) return env def should_build(self, arch): @@ -955,16 +957,6 @@ def build_cython_components(self, arch): env = self.get_recipe_env(arch) - if self.ctx.python_recipe.from_crystax: - command = sh.Command('python{}'.format(self.ctx.python_recipe.version)) - site_packages_dirs = command( - '-c', 'import site; print("\\n".join(site.getsitepackages()))') - site_packages_dirs = site_packages_dirs.stdout.decode('utf-8').split('\n') - if 'PYTHONPATH' in env: - env['PYTHONPATH'] = env['PYTHONPATH'] + ':{}'.format(':'.join(site_packages_dirs)) - else: - env['PYTHONPATH'] = ':'.join(site_packages_dirs) - with current_directory(self.get_build_dir(arch.arch)): hostpython = sh.Command(self.ctx.hostpython) shprint(hostpython, '-c', 'import sys; print(sys.path)', _env=env) @@ -989,8 +981,15 @@ def build_cython_components(self, arch): info('First build appeared to complete correctly, skipping manual' 'cythonising.') + self.strip_object_files(arch, env) + + def strip_object_files(self, arch, env, build_dir=None): + if build_dir is None: + build_dir = self.get_build_dir(arch.arch) + with current_directory(build_dir): info('Stripping object files') if self.ctx.python_recipe.name == 'python2legacy': + info('Stripping object files') build_lib = glob.glob('./build/lib*') shprint(sh.find, build_lib[0], '-name', '*.o', '-exec', env['STRIP'], '{}', ';', _env=env) @@ -1055,6 +1054,24 @@ def get_recipe_env(self, arch, with_flags_in_cc=True): env['LIBLINK_PATH'] = liblink_path ensure_dir(liblink_path) + # Add crystax-specific site packages: + if self.ctx.python_recipe.from_crystax: + command = sh.Command('python{}'.format(self.ctx.python_recipe.version)) + site_packages_dirs = command( + '-c', 'import site; print("\\n".join(site.getsitepackages()))') + site_packages_dirs = site_packages_dirs.stdout.decode('utf-8').split('\n') + if 'PYTHONPATH' in env: + env['PYTHONPATH'] = env['PYTHONPATH'] +\ + ':{}'.format(':'.join(site_packages_dirs)) + else: + env['PYTHONPATH'] = ':'.join(site_packages_dirs) + while env['PYTHONPATH'].find("::") > 0: + env['PYTHONPATH'] = env['PYTHONPATH'].replace("::", ":") + if env['PYTHONPATH'].endswith(":"): + env['PYTHONPATH'] = env['PYTHONPATH'][:-1] + if env['PYTHONPATH'].startswith(":"): + env['PYTHONPATH'] = env['PYTHONPATH'][1:] + return env