Skip to content

Commit cf7f958

Browse files
committed
[python] Fix build for Mac OSX 🍎
It turns out that the generated binary for Mac OSX and Cygwin is not `python`...it's `python.exe` because the FS used in Mac OS (HFS+) is case insensitive per default and that would create conflicts with the generated folder `Python` by the python's build system. So we:   - add cls attribute `HostPythonRecipe.python_bin` (which will be different depending on the build platform)   - implement `HostPythonRecipe.should_build` to refactor a little - check the library instead of the executable to decide if we must build python's recipe (to avoid conflicts with case insensitive FS) - add message which prints the files/folders of the hostpython's build directory - fix `PythonRecipe.real_hostpython_location` because the hostpython's executable name will change depending on the build platform - fix python's build when host OS is an Mac OSX (via patch: `fix-interpreter-for-darwin.patch`) See also: https://github.com/python/cpython/blob/3.7/README.rst#build-instructions Note: @TheSin- , thanks for your debugging sessions and lines of code
1 parent 61ee55f commit cf7f958

File tree

6 files changed

+115
-28
lines changed

6 files changed

+115
-28
lines changed

pythonforandroid/python.py

Lines changed: 61 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
build our python3 and python2 recipes and his corresponding hostpython recipes.
44
'''
55

6-
from os.path import dirname, exists, join
6+
from os.path import dirname, exists, join, isfile
77
from multiprocessing import cpu_count
88
from shutil import copy2
99
from os import environ
@@ -16,6 +16,7 @@
1616
from pythonforandroid.util import (
1717
current_directory, ensure_dir, walk_valid_filens,
1818
BuildInterruptingException, build_platform)
19+
from pythonforandroid.patching import is_darwin
1920

2021

2122
class GuestPythonRecipe(TargetPythonRecipe):
@@ -243,13 +244,20 @@ def build_arch(self, arch):
243244
exec_prefix=sys_exec_prefix)).split(' '),
244245
_env=env)
245246

246-
if not exists('python'):
247-
py_version = self.major_minor_version_string
248-
if self.major_minor_version_string[0] == '3':
249-
py_version += 'm'
250-
shprint(sh.make, 'all', '-j', str(cpu_count()),
251-
'INSTSONAME=libpython{version}.so'.format(
252-
version=py_version), _env=env)
247+
# Some platforms use case insensitive FS (like MacOs with HFS+), so
248+
# we check if we have the python's library instead of the
249+
# executable, which shouldn't conflict with any existing folder in
250+
# python's build dir
251+
py_version = self.major_minor_version_string
252+
if self.major_minor_version_string[0] == '3':
253+
py_version += 'm'
254+
lib_name = 'libpython{version}.so'.format(version=py_version)
255+
if not isfile(join(build_dir, lib_name)):
256+
shprint(
257+
sh.make, 'all', '-j', str(cpu_count()),
258+
'INSTSONAME={lib_name}'.format(lib_name=lib_name),
259+
_env=env
260+
)
253261

254262
# TODO: Look into passing the path to pyconfig.h in a
255263
# better way, although this is probably acceptable
@@ -382,6 +390,32 @@ class HostPythonRecipe(Recipe):
382390
'''The default url to download our host python recipe. This url will
383391
change depending on the python version set in attribute :attr:`version`.'''
384392

393+
@property
394+
def python_bin(self):
395+
'''
396+
Returns the name of the python executable depending on the build
397+
platform since, as explained in python docs, some platforms generates a
398+
different executable name to avoid conflicts with case insensitive File
399+
systems (like Mac Os X).
400+
401+
.. seealso::
402+
https://github.com/python/cpython/blob/3.7/README.rst#build-instructions
403+
404+
'''
405+
if is_darwin():
406+
return 'python.exe'
407+
return 'python'
408+
409+
def should_build(self, arch):
410+
python_bin = join(
411+
self.get_build_dir(arch.arch), self.build_subdir, self.python_bin
412+
)
413+
if exists(python_bin):
414+
# no need to build, but we must set:
415+
self.ctx.hostpython = python_bin
416+
return False
417+
return True
418+
385419
def get_build_container_dir(self, arch=None):
386420
choices = self.check_recipe_choices()
387421
dir_name = '-'.join([self.name] + choices)
@@ -404,22 +438,25 @@ def build_arch(self, arch):
404438
build_dir = join(recipe_build_dir, self.build_subdir)
405439
ensure_dir(build_dir)
406440

407-
if not exists(join(build_dir, 'python')):
408-
with current_directory(recipe_build_dir):
409-
# Configure the build
410-
with current_directory(build_dir):
411-
if not exists('config.status'):
412-
shprint(
413-
sh.Command(join(recipe_build_dir, 'configure')))
441+
with current_directory(recipe_build_dir):
442+
# Configure the build
443+
with current_directory(build_dir):
444+
if not exists('config.status'):
445+
shprint(
446+
sh.Command(join(recipe_build_dir, 'configure')))
414447

415-
# Create the Setup file. This copying from Setup.dist
416-
# seems to be the normal and expected procedure.
417-
shprint(sh.cp, join('Modules', 'Setup.dist'),
418-
join(build_dir, 'Modules', 'Setup'))
448+
# Create the Setup file. This copying from Setup.dist
449+
# seems to be the normal and expected procedure.
450+
shprint(sh.cp, join('Modules', 'Setup.dist'),
451+
join(build_dir, 'Modules', 'Setup'))
419452

420-
shprint(sh.make, '-j', str(cpu_count()), '-C', build_dir)
421-
else:
422-
info('Skipping {name} ({version}) build, as it has already '
423-
'been completed'.format(name=self.name, version=self.version))
453+
shprint(sh.make, '-j', str(cpu_count()), '-C', build_dir)
454+
455+
# Since we got different python's executable names depending on the
456+
# build platform, we add a a debug message which prints the generated
457+
# files/folders, in case that we need that...
458+
python_dir = join(self.get_build_dir(arch.arch), self.build_subdir)
459+
info("Files/folders in python's build directory are:")
460+
shprint(sh.ls, python_dir)
424461

425-
self.ctx.hostpython = join(build_dir, 'python')
462+
self.ctx.hostpython = join(build_dir, self.python_bin)

pythonforandroid/recipe.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -773,9 +773,13 @@ def clean_build(self, arch=None):
773773
@property
774774
def real_hostpython_location(self):
775775
host_name = 'host{}'.format(self.ctx.python_recipe.name)
776-
host_build = Recipe.get_recipe(host_name, self.ctx).get_build_dir()
777776
if host_name in ['hostpython2', 'hostpython3']:
778-
return join(host_build, 'native-build', 'python')
777+
python_recipe = Recipe.get_recipe(host_name, self.ctx)
778+
return join(
779+
python_recipe.get_build_dir(),
780+
'native-build',
781+
python_recipe.python_bin
782+
)
779783
else:
780784
python_recipe = self.ctx.python_recipe
781785
return 'python{}'.format(python_recipe.version)

pythonforandroid/recipes/python2/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from pythonforandroid.recipe import Recipe
33
from pythonforandroid.python import GuestPythonRecipe
44
from pythonforandroid.logger import shprint, warning
5+
from pythonforandroid.patching import is_darwin
56
import sh
67

78

@@ -33,6 +34,8 @@ class Python2Recipe(GuestPythonRecipe):
3334
'patches/fix-posix-declarations.patch',
3435
'patches/fix-pwd-gecos.patch',
3536
'patches/fix-ctypes-util-find-library.patch']
37+
if is_darwin():
38+
patches += ["patches/fix-interpreter-for-darwin.patch"]
3639

3740
configure_args = ('--host={android_host}',
3841
'--build={android_build}',
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
--- Python-2.7.15/configure.orig 2018-04-30 00:47:33.000000000 +0200
2+
+++ Python-2.7.15/configure 2019-08-16 14:09:48.696030207 +0200
3+
@@ -2903,7 +2903,7 @@ case $host_os in *\ *) host_os=`echo "$h
4+
# pybuilddir.txt will be created by --generate-posix-vars in the Makefile
5+
rm -f pybuilddir.txt
6+
7+
-for ac_prog in python$PACKAGE_VERSION python3 python
8+
+for ac_prog in python$PACKAGE_VERSION python3 python.exe python
9+
do
10+
# Extract the first word of "$ac_prog", so it can be a program name with args.
11+
set dummy $ac_prog; ac_word=$2
12+
@@ -2952,7 +2952,7 @@ if test "$cross_compiling" = yes; then
13+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for python interpreter for cross build" >&5
14+
$as_echo_n "checking for python interpreter for cross build... " >&6; }
15+
if test -z "$PYTHON_FOR_BUILD"; then
16+
- for interp in python$PACKAGE_VERSION python2 python; do
17+
+ for interp in python$PACKAGE_VERSION python2 python.exe python; do
18+
which $interp >/dev/null 2>&1 || continue
19+
if $interp -c 'import sys;sys.exit(not (sys.version_info[:2] >= (2,7) and sys.version_info[0] < 3))'; then
20+
break

pythonforandroid/recipes/python3/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import sh
22
from pythonforandroid.python import GuestPythonRecipe
33
from pythonforandroid.recipe import Recipe
4+
from pythonforandroid.patching import is_darwin
45

56

67
class Python3Recipe(GuestPythonRecipe):
@@ -23,9 +24,10 @@ class Python3Recipe(GuestPythonRecipe):
2324
name = 'python3'
2425

2526
patches = ["patches/fix-ctypes-util-find-library.patch"]
26-
2727
if sh.which('lld') is not None:
28-
patches = patches + ["patches/remove-fix-cortex-a8.patch"]
28+
patches += ["patches/remove-fix-cortex-a8.patch"]
29+
if is_darwin():
30+
patches += ["patches/fix-interpreter-for-darwin.patch"]
2931

3032
depends = ['hostpython3', 'sqlite3', 'openssl', 'libffi']
3133
conflicts = ['python2']
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
diff --git a/configure b/configure
2+
--- a/configure 2019-08-06 02:35:17.000000000 +0000
3+
+++ b/configure 2019-08-06 02:36:22.000000000 +0000
4+
@@ -2868,7 +2868,7 @@
5+
# pybuilddir.txt will be created by --generate-posix-vars in the Makefile
6+
rm -f pybuilddir.txt
7+
8+
-for ac_prog in python$PACKAGE_VERSION python3 python
9+
+for ac_prog in python$PACKAGE_VERSION python3 python.exe python
10+
do
11+
# Extract the first word of "$ac_prog", so it can be a program name with args.
12+
set dummy $ac_prog; ac_word=$2
13+
@@ -2917,7 +2917,7 @@
14+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for python interpreter for cross build" >&5
15+
$as_echo_n "checking for python interpreter for cross build... " >&6; }
16+
if test -z "$PYTHON_FOR_BUILD"; then
17+
- for interp in python$PACKAGE_VERSION python3 python; do
18+
+ for interp in python$PACKAGE_VERSION python3 python.exe python; do
19+
which $interp >/dev/null 2>&1 || continue
20+
if $interp -c "import sys;sys.exit(not '.'.join(str(n) for n in sys.version_info[:2]) == '$PACKAGE_VERSION')"; then
21+
break

0 commit comments

Comments
 (0)