Skip to content

Commit 0d5770a

Browse files
committed
Some code cleanup
1 parent 40816f7 commit 0d5770a

File tree

2 files changed

+162
-160
lines changed

2 files changed

+162
-160
lines changed

pythonforandroid/build.py

Lines changed: 159 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,52 @@ def get_toolchain_versions(ndk_dir, arch):
5656
return toolchain_versions, toolchain_path_exists
5757

5858

59+
def select_and_check_toolchain_version(sdk_dir, ndk_dir, arch, ndk_sysroot_exists, py_platform):
60+
toolchain_versions, toolchain_path_exists = get_toolchain_versions(ndk_dir, arch)
61+
ok = ndk_sysroot_exists and toolchain_path_exists
62+
toolchain_versions.sort()
63+
64+
toolchain_versions_gcc = []
65+
for toolchain_version in toolchain_versions:
66+
if toolchain_version[0].isdigit():
67+
# GCC toolchains begin with a number
68+
toolchain_versions_gcc.append(toolchain_version)
69+
70+
if toolchain_versions:
71+
info('Found the following toolchain versions: {}'.format(
72+
toolchain_versions))
73+
info('Picking the latest gcc toolchain, here {}'.format(
74+
toolchain_versions_gcc[-1]))
75+
toolchain_version = toolchain_versions_gcc[-1]
76+
else:
77+
warning('Could not find any toolchain for {}!'.format(
78+
arch.toolchain_prefix))
79+
ok = False
80+
81+
# Modify the path so that sh finds modules appropriately
82+
environ['PATH'] = (
83+
'{ndk_dir}/toolchains/{toolchain_prefix}-{toolchain_version}/'
84+
'prebuilt/{py_platform}-x86/bin/:{ndk_dir}/toolchains/'
85+
'{toolchain_prefix}-{toolchain_version}/prebuilt/'
86+
'{py_platform}-x86_64/bin/:{ndk_dir}:{sdk_dir}/'
87+
'tools:{path}').format(
88+
sdk_dir=sdk_dir, ndk_dir=ndk_dir,
89+
toolchain_prefix=arch.toolchain_prefix,
90+
toolchain_version=toolchain_version,
91+
py_platform=py_platform, path=environ.get('PATH'))
92+
93+
for executable in ("pkg-config", "autoconf", "automake", "libtoolize",
94+
"tar", "bzip2", "unzip", "make", "gcc", "g++"):
95+
if not sh.which(executable):
96+
warning(f"Missing executable: {executable} is not installed")
97+
98+
if not ok:
99+
raise BuildInterruptingException(
100+
'python-for-android cannot continue due to the missing executables above')
101+
102+
return toolchain_version
103+
104+
59105
def get_targets(sdk_dir):
60106
if exists(join(sdk_dir, 'tools', 'bin', 'avdmanager')):
61107
avdmanager = sh.Command(join(sdk_dir, 'tools', 'bin', 'avdmanager'))
@@ -253,8 +299,6 @@ def prepare_build_environment(self,
253299
if self._build_env_prepared:
254300
return
255301

256-
ok = True
257-
258302
# Work out where the Android SDK is
259303
sdk_dir = None
260304
if user_sdk_dir:
@@ -385,55 +429,13 @@ def prepare_build_environment(self,
385429

386430
self.ndk_standalone = get_ndk_standalone(self.ndk_dir)
387431
self.ndk_sysroot, ndk_sysroot_exists = get_ndk_sysroot(self.ndk_dir)
388-
ok = ok and ndk_sysroot_exists
389432
self.ndk_include_dir = join(self.ndk_sysroot, 'usr', 'include')
390433

391434
for arch in self.archs:
392-
393-
toolchain_versions, toolchain_path_exists = get_toolchain_versions(
394-
self.ndk_dir, arch)
395-
ok = ok and toolchain_path_exists
396-
toolchain_versions.sort()
397-
398-
toolchain_versions_gcc = []
399-
for toolchain_version in toolchain_versions:
400-
if toolchain_version[0].isdigit():
401-
# GCC toolchains begin with a number
402-
toolchain_versions_gcc.append(toolchain_version)
403-
404-
if toolchain_versions:
405-
info('Found the following toolchain versions: {}'.format(
406-
toolchain_versions))
407-
info('Picking the latest gcc toolchain, here {}'.format(
408-
toolchain_versions_gcc[-1]))
409-
toolchain_version = toolchain_versions_gcc[-1]
410-
else:
411-
warning('Could not find any toolchain for {}!'.format(
412-
arch.toolchain_prefix))
413-
ok = False
414-
415-
# Modify the path so that sh finds modules appropriately
416-
environ['PATH'] = (
417-
'{ndk_dir}/toolchains/{toolchain_prefix}-{toolchain_version}/'
418-
'prebuilt/{py_platform}-x86/bin/:{ndk_dir}/toolchains/'
419-
'{toolchain_prefix}-{toolchain_version}/prebuilt/'
420-
'{py_platform}-x86_64/bin/:{ndk_dir}:{sdk_dir}/'
421-
'tools:{path}').format(
422-
sdk_dir=self.sdk_dir, ndk_dir=self.ndk_dir,
423-
toolchain_prefix=arch.toolchain_prefix,
424-
toolchain_version=toolchain_version,
425-
py_platform=py_platform, path=environ.get('PATH'))
426-
427-
for executable in ("pkg-config", "autoconf", "automake", "libtoolize",
428-
"tar", "bzip2", "unzip", "make", "gcc", "g++"):
429-
if not sh.which(executable):
430-
warning(f"Missing executable: {executable} is not installed")
431-
432-
if not ok:
433-
raise BuildInterruptingException(
434-
'python-for-android cannot continue due to the missing executables above')
435-
436-
self.toolchain_version = toolchain_version # We assume that the toolchain version is the same for all the archs
435+
# We assume that the toolchain version is the same for all the archs.
436+
self.toolchain_version = select_and_check_toolchain_version(
437+
self.sdk_dir, self.ndk_dir, arch, ndk_sysroot_exists, py_platform
438+
)
437439

438440
def __init__(self):
439441
self.include_dirs = []
@@ -604,10 +606,11 @@ def build_recipes(build_order, python_modules, ctx, project_dir,
604606
recipe.postbuild_arch(arch)
605607

606608
info_main('# Installing pure Python modules')
607-
run_pymodules_install(
608-
ctx, python_modules, project_dir,
609-
ignore_setup_py=ignore_project_setup_py
610-
)
609+
for arch in ctx.archs:
610+
run_pymodules_install(
611+
ctx, arch, python_modules, project_dir,
612+
ignore_setup_py=ignore_project_setup_py
613+
)
611614

612615

613616
def project_has_setup_py(project_dir):
@@ -728,7 +731,7 @@ def run_setuppy_install(ctx, project_dir, env=None, arch=None):
728731
os.remove("._tmp_p4a_recipe_constraints.txt")
729732

730733

731-
def run_pymodules_install(ctx, modules, project_dir=None,
734+
def run_pymodules_install(ctx, arch, modules, project_dir=None,
732735
ignore_setup_py=False):
733736
""" This function will take care of all non-recipe things, by:
734737
@@ -740,119 +743,118 @@ def run_pymodules_install(ctx, modules, project_dir=None,
740743
741744
"""
742745

743-
info('*** PYTHON PACKAGE / PROJECT INSTALL STAGE ***')
746+
info('*** PYTHON PACKAGE / PROJECT INSTALL STAGE FOR ARCH: {} ***'.format(arch))
744747

745-
for arch in ctx.archs:
746-
modules = [m for m in modules if ctx.not_has_package(m, arch)]
748+
modules = [m for m in modules if ctx.not_has_package(m, arch)]
747749

748-
# We change current working directory later, so this has to be an absolute
749-
# path or `None` in case that we didn't supply the `project_dir` via kwargs
750-
project_dir = abspath(project_dir) if project_dir else None
750+
# We change current working directory later, so this has to be an absolute
751+
# path or `None` in case that we didn't supply the `project_dir` via kwargs
752+
project_dir = abspath(project_dir) if project_dir else None
751753

752-
# Bail out if no python deps and no setup.py to process:
753-
if not modules and (
754-
ignore_setup_py or
755-
project_dir is None or
756-
not project_has_setup_py(project_dir)
757-
):
758-
info('No Python modules and no setup.py to process, skipping')
759-
return
754+
# Bail out if no python deps and no setup.py to process:
755+
if not modules and (
756+
ignore_setup_py or
757+
project_dir is None or
758+
not project_has_setup_py(project_dir)
759+
):
760+
info('No Python modules and no setup.py to process, skipping')
761+
return
760762

761-
# Output messages about what we're going to do:
762-
if modules:
763-
info(
764-
"The requirements ({}) don\'t have recipes, attempting to "
765-
"install them with pip".format(', '.join(modules))
766-
)
767-
info(
768-
"If this fails, it may mean that the module has compiled "
769-
"components and needs a recipe."
770-
)
771-
if project_dir is not None and \
772-
project_has_setup_py(project_dir) and not ignore_setup_py:
763+
# Output messages about what we're going to do:
764+
if modules:
765+
info(
766+
"The requirements ({}) don\'t have recipes, attempting to "
767+
"install them with pip".format(', '.join(modules))
768+
)
769+
info(
770+
"If this fails, it may mean that the module has compiled "
771+
"components and needs a recipe."
772+
)
773+
if project_dir is not None and \
774+
project_has_setup_py(project_dir) and not ignore_setup_py:
775+
info(
776+
"Will process project install, if it fails then the "
777+
"project may not be compatible for Android install."
778+
)
779+
780+
# Use our hostpython to create the virtualenv
781+
host_python = sh.Command(ctx.hostpython)
782+
with current_directory(join(ctx.build_dir)):
783+
shprint(host_python, '-m', 'venv', 'venv')
784+
785+
# Prepare base environment and upgrade pip:
786+
base_env = dict(copy.copy(os.environ))
787+
base_env["PYTHONPATH"] = ctx.get_site_packages_dir(arch)
788+
info('Upgrade pip to latest version')
789+
shprint(sh.bash, '-c', (
790+
"source venv/bin/activate && pip install -U pip"
791+
), _env=copy.copy(base_env))
792+
793+
# Install Cython in case modules need it to build:
794+
info('Install Cython in case one of the modules needs it to build')
795+
shprint(sh.bash, '-c', (
796+
"venv/bin/pip install Cython"
797+
), _env=copy.copy(base_env))
798+
799+
# Get environment variables for build (with CC/compiler set):
800+
standard_recipe = CythonRecipe()
801+
standard_recipe.ctx = ctx
802+
# (note: following line enables explicit -lpython... linker options)
803+
standard_recipe.call_hostpython_via_targetpython = False
804+
recipe_env = standard_recipe.get_recipe_env(ctx.archs[0])
805+
env = copy.copy(base_env)
806+
env.update(recipe_env)
807+
808+
# Make sure our build package dir is available, and the virtualenv
809+
# site packages come FIRST (so the proper pip version is used):
810+
env["PYTHONPATH"] += ":" + ctx.get_site_packages_dir(arch)
811+
env["PYTHONPATH"] = os.path.abspath(join(
812+
ctx.build_dir, "venv", "lib",
813+
"python" + ctx.python_recipe.major_minor_version_string,
814+
"site-packages")) + ":" + env["PYTHONPATH"]
815+
816+
# Install the manually specified requirements first:
817+
if not modules:
818+
info('There are no Python modules to install, skipping')
819+
else:
820+
info('Creating a requirements.txt file for the Python modules')
821+
with open('requirements.txt', 'w') as fileh:
822+
for module in modules:
823+
key = 'VERSION_' + module
824+
if key in environ:
825+
line = '{}=={}\n'.format(module, environ[key])
826+
else:
827+
line = '{}\n'.format(module)
828+
fileh.write(line)
829+
830+
info('Installing Python modules with pip')
773831
info(
774-
"Will process project install, if it fails then the "
775-
"project may not be compatible for Android install."
832+
"IF THIS FAILS, THE MODULES MAY NEED A RECIPE. "
833+
"A reason for this is often modules compiling "
834+
"native code that is unaware of Android cross-compilation "
835+
"and does not work without additional "
836+
"changes / workarounds."
776837
)
777838

778-
# Use our hostpython to create the virtualenv
779-
host_python = sh.Command(ctx.hostpython)
780-
with current_directory(join(ctx.build_dir)):
781-
shprint(host_python, '-m', 'venv', 'venv')
782-
783-
# Prepare base environment and upgrade pip:
784-
base_env = dict(copy.copy(os.environ))
785-
base_env["PYTHONPATH"] = ctx.get_site_packages_dir(arch)
786-
info('Upgrade pip to latest version')
787839
shprint(sh.bash, '-c', (
788-
"source venv/bin/activate && pip install -U pip"
789-
), _env=copy.copy(base_env))
840+
"venv/bin/pip " +
841+
"install -v --target '{0}' --no-deps -r requirements.txt"
842+
).format(ctx.get_site_packages_dir(arch).replace("'", "'\"'\"'")),
843+
_env=copy.copy(env))
790844

791-
# Install Cython in case modules need it to build:
792-
info('Install Cython in case one of the modules needs it to build')
793-
shprint(sh.bash, '-c', (
794-
"venv/bin/pip install Cython"
795-
), _env=copy.copy(base_env))
796-
797-
# Get environment variables for build (with CC/compiler set):
798-
standard_recipe = CythonRecipe()
799-
standard_recipe.ctx = ctx
800-
# (note: following line enables explicit -lpython... linker options)
801-
standard_recipe.call_hostpython_via_targetpython = False
802-
recipe_env = standard_recipe.get_recipe_env(ctx.archs[0])
803-
env = copy.copy(base_env)
804-
env.update(recipe_env)
805-
806-
# Make sure our build package dir is available, and the virtualenv
807-
# site packages come FIRST (so the proper pip version is used):
808-
env["PYTHONPATH"] += ":" + ctx.get_site_packages_dir(arch)
809-
env["PYTHONPATH"] = os.path.abspath(join(
810-
ctx.build_dir, "venv", "lib",
811-
"python" + ctx.python_recipe.major_minor_version_string,
812-
"site-packages")) + ":" + env["PYTHONPATH"]
813-
814-
# Install the manually specified requirements first:
815-
if not modules:
816-
info('There are no Python modules to install, skipping')
817-
else:
818-
info('Creating a requirements.txt file for the Python modules')
819-
with open('requirements.txt', 'w') as fileh:
820-
for module in modules:
821-
key = 'VERSION_' + module
822-
if key in environ:
823-
line = '{}=={}\n'.format(module, environ[key])
824-
else:
825-
line = '{}\n'.format(module)
826-
fileh.write(line)
827-
828-
info('Installing Python modules with pip')
829-
info(
830-
"IF THIS FAILS, THE MODULES MAY NEED A RECIPE. "
831-
"A reason for this is often modules compiling "
832-
"native code that is unaware of Android cross-compilation "
833-
"and does not work without additional "
834-
"changes / workarounds."
835-
)
836-
837-
shprint(sh.bash, '-c', (
838-
"venv/bin/pip " +
839-
"install -v --target '{0}' --no-deps -r requirements.txt"
840-
).format(ctx.get_site_packages_dir(arch).replace("'", "'\"'\"'")),
841-
_env=copy.copy(env))
842-
843-
# Afterwards, run setup.py if present:
844-
if project_dir is not None and (
845-
project_has_setup_py(project_dir) and not ignore_setup_py
846-
):
847-
run_setuppy_install(ctx, project_dir, env, arch.arch)
848-
elif not ignore_setup_py:
849-
info("No setup.py found in project directory: " + str(project_dir))
850-
851-
# Strip object files after potential Cython or native code builds:
852-
if not ctx.with_debug_symbols:
853-
standard_recipe.strip_object_files(
854-
arch, env, build_dir=ctx.build_dir
855-
)
845+
# Afterwards, run setup.py if present:
846+
if project_dir is not None and (
847+
project_has_setup_py(project_dir) and not ignore_setup_py
848+
):
849+
run_setuppy_install(ctx, project_dir, env, arch.arch)
850+
elif not ignore_setup_py:
851+
info("No setup.py found in project directory: " + str(project_dir))
852+
853+
# Strip object files after potential Cython or native code builds:
854+
if not ctx.with_debug_symbols:
855+
standard_recipe.strip_object_files(
856+
arch, env, build_dir=ctx.build_dir
857+
)
856858

857859

858860
def biglink(ctx, arch):

tests/test_build.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def test_run_pymodules_install_optional_project_dir(self):
1919
modules = []
2020
project_dir = None
2121
with mock.patch('pythonforandroid.build.info') as m_info:
22-
assert run_pymodules_install(ctx, modules, project_dir) is None
22+
assert run_pymodules_install(ctx, ctx.archs[0], modules, project_dir) is None
2323
assert m_info.call_args_list[-1] == mock.call(
2424
'No Python modules and no setup.py to process, skipping')
2525

@@ -44,13 +44,13 @@ def test_strip_if_with_debug_symbols(self):
4444

4545
# Make sure it is NOT called when `with_debug_symbols` is true:
4646
ctx.with_debug_symbols = True
47-
assert run_pymodules_install(ctx, modules, project_dir) is None
47+
assert run_pymodules_install(ctx, ctx.archs[0], modules, project_dir) is None
4848
assert m_CythonRecipe().strip_object_files.called is False
4949

5050
# Make sure strip object files IS called when
5151
# `with_debug_symbols` is fasle:
5252
ctx.with_debug_symbols = False
53-
assert run_pymodules_install(ctx, modules, project_dir) is None
53+
assert run_pymodules_install(ctx, ctx.archs[0], modules, project_dir) is None
5454
assert m_CythonRecipe().strip_object_files.called is True
5555

5656

0 commit comments

Comments
 (0)