Skip to content

Commit eba92ab

Browse files
committed
Merge pull request kivy#526 from kivy/split_toolchain
Split toolchain.py into separate modules
2 parents 3b631b1 + d0bfa9f commit eba92ab

File tree

18 files changed

+2600
-2562
lines changed

18 files changed

+2600
-2562
lines changed

pythonforandroid/archs.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
from os.path import (join)
2+
from os import environ, uname
3+
import sys
4+
from distutils.spawn import find_executable
5+
6+
from pythonforandroid.logger import warning
7+
from pythonforandroid.recipe import Recipe
8+
9+
10+
class Arch(object):
11+
12+
toolchain_prefix = None
13+
'''The prefix for the toolchain dir in the NDK.'''
14+
15+
command_prefix = None
16+
'''The prefix for NDK commands such as gcc.'''
17+
18+
def __init__(self, ctx):
19+
super(Arch, self).__init__()
20+
self.ctx = ctx
21+
22+
def __str__(self):
23+
return self.arch
24+
25+
@property
26+
def include_dirs(self):
27+
return [
28+
"{}/{}".format(
29+
self.ctx.include_dir,
30+
d.format(arch=self))
31+
for d in self.ctx.include_dirs]
32+
33+
def get_env(self):
34+
env = {}
35+
36+
env["CFLAGS"] = " ".join([
37+
"-DANDROID", "-mandroid", "-fomit-frame-pointer",
38+
"--sysroot", self.ctx.ndk_platform])
39+
40+
env["CXXFLAGS"] = env["CFLAGS"]
41+
42+
env["LDFLAGS"] = " ".join(['-lm'])
43+
44+
py_platform = sys.platform
45+
if py_platform in ['linux2', 'linux3']:
46+
py_platform = 'linux'
47+
48+
toolchain_prefix = self.ctx.toolchain_prefix
49+
toolchain_version = self.ctx.toolchain_version
50+
command_prefix = self.command_prefix
51+
52+
env['TOOLCHAIN_PREFIX'] = toolchain_prefix
53+
env['TOOLCHAIN_VERSION'] = toolchain_version
54+
55+
print('path is', environ['PATH'])
56+
cc = find_executable('{command_prefix}-gcc'.format(
57+
command_prefix=command_prefix), path=environ['PATH'])
58+
if cc is None:
59+
warning('Couldn\'t find executable for CC. This indicates a '
60+
'problem locating the {} executable in the Android '
61+
'NDK, not that you don\'t have a normal compiler '
62+
'installed. Exiting.')
63+
exit(1)
64+
65+
env['CC'] = '{command_prefix}-gcc {cflags}'.format(
66+
command_prefix=command_prefix,
67+
cflags=env['CFLAGS'])
68+
env['CXX'] = '{command_prefix}-g++ {cxxflags}'.format(
69+
command_prefix=command_prefix,
70+
cxxflags=env['CXXFLAGS'])
71+
72+
env['AR'] = '{}-ar'.format(command_prefix)
73+
env['RANLIB'] = '{}-ranlib'.format(command_prefix)
74+
env['LD'] = '{}-ld'.format(command_prefix)
75+
env['STRIP'] = '{}-strip --strip-unneeded'.format(command_prefix)
76+
env['MAKE'] = 'make -j5'
77+
env['READELF'] = '{}-readelf'.format(command_prefix)
78+
79+
hostpython_recipe = Recipe.get_recipe('hostpython2', self.ctx)
80+
81+
# AND: This hardcodes python version 2.7, needs fixing
82+
env['BUILDLIB_PATH'] = join(
83+
hostpython_recipe.get_build_dir(self.arch),
84+
'build', 'lib.linux-{}-2.7'.format(uname()[-1]))
85+
86+
env['PATH'] = environ['PATH']
87+
88+
env['ARCH'] = self.arch
89+
90+
return env
91+
92+
93+
class ArchARM(Arch):
94+
arch = "armeabi"
95+
toolchain_prefix = 'arm-linux-androideabi'
96+
command_prefix = 'arm-linux-androideabi'
97+
platform_dir = 'arch-arm'
98+
99+
100+
class ArchARMv7_a(ArchARM):
101+
arch = 'armeabi-v7a'
102+
103+
def get_env(self):
104+
env = super(ArchARMv7_a, self).get_env()
105+
env['CFLAGS'] = (env['CFLAGS'] +
106+
(' -march=armv7-a -mfloat-abi=softfp '
107+
'-mfpu=vfp -mthumb'))
108+
env['CXXFLAGS'] = env['CFLAGS']
109+
return env
110+
111+
112+
class Archx86(Arch):
113+
arch = 'x86'
114+
toolchain_prefix = 'x86'
115+
command_prefix = 'i686-linux-android'
116+
platform_dir = 'arch-x86'
117+
118+
def get_env(self):
119+
env = super(Archx86, self).get_env()
120+
env['CFLAGS'] = (env['CFLAGS'] +
121+
' -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32')
122+
env['CXXFLAGS'] = env['CFLAGS']
123+
return env
124+
125+
126+
class Archx86_64(Arch):
127+
arch = 'x86_64'
128+
toolchain_prefix = 'x86'
129+
command_prefix = 'x86_64-linux-android'
130+
platform_dir = 'arch-x86'
131+
132+
def get_env(self):
133+
env = super(Archx86_64, self).get_env()
134+
env['CFLAGS'] = (env['CFLAGS'] +
135+
' -march=x86-64 -msse4.2 -mpopcnt -m64 -mtune=intel')
136+
env['CXXFLAGS'] = env['CFLAGS']
137+
return env

pythonforandroid/bootstrap.py

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
from os.path import (join, dirname, isdir, splitext, basename)
2+
from os import listdir
3+
import sh
4+
import glob
5+
import json
6+
import importlib
7+
8+
from pythonforandroid.logger import (warning, shprint, info, logger,
9+
debug)
10+
from pythonforandroid.util import (current_directory, ensure_dir,
11+
temp_directory, which)
12+
from pythonforandroid.recipe import Recipe
13+
14+
15+
class Bootstrap(object):
16+
'''An Android project template, containing recipe stuff for
17+
compilation and templated fields for APK info.
18+
'''
19+
name = ''
20+
jni_subdir = '/jni'
21+
ctx = None
22+
23+
bootstrap_dir = None
24+
25+
build_dir = None
26+
dist_dir = None
27+
dist_name = None
28+
distribution = None
29+
30+
recipe_depends = []
31+
32+
can_be_chosen_automatically = True
33+
'''Determines whether the bootstrap can be chosen as one that
34+
satisfies user requirements. If False, it will not be returned
35+
from Bootstrap.get_bootstrap_from_recipes.
36+
'''
37+
38+
# Other things a Bootstrap might need to track (maybe separately):
39+
# ndk_main.c
40+
# whitelist.txt
41+
# blacklist.txt
42+
43+
@property
44+
def dist_dir(self):
45+
'''The dist dir at which to place the finished distribution.'''
46+
if self.distribution is None:
47+
warning('Tried to access {}.dist_dir, but {}.distribution '
48+
'is None'.format(self, self))
49+
exit(1)
50+
return self.distribution.dist_dir
51+
52+
@property
53+
def jni_dir(self):
54+
return self.name + self.jni_subdir
55+
56+
def get_build_dir(self):
57+
return join(self.ctx.build_dir, 'bootstrap_builds', self.name)
58+
59+
def get_dist_dir(self, name):
60+
return join(self.ctx.dist_dir, name)
61+
62+
@property
63+
def name(self):
64+
modname = self.__class__.__module__
65+
return modname.split(".", 2)[-1]
66+
67+
def prepare_build_dir(self):
68+
'''Ensure that a build dir exists for the recipe. This same single
69+
dir will be used for building all different archs.'''
70+
self.build_dir = self.get_build_dir()
71+
shprint(sh.cp, '-r',
72+
join(self.bootstrap_dir, 'build'),
73+
# join(self.ctx.root_dir,
74+
# 'bootstrap_templates',
75+
# self.name),
76+
self.build_dir)
77+
with current_directory(self.build_dir):
78+
with open('project.properties', 'w') as fileh:
79+
fileh.write('target=android-{}'.format(self.ctx.android_api))
80+
81+
def prepare_dist_dir(self, name):
82+
# self.dist_dir = self.get_dist_dir(name)
83+
ensure_dir(self.dist_dir)
84+
85+
def run_distribute(self):
86+
# print('Default bootstrap being used doesn\'t know how '
87+
# 'to distribute...failing.')
88+
# exit(1)
89+
with current_directory(self.dist_dir):
90+
info('Saving distribution info')
91+
with open('dist_info.json', 'w') as fileh:
92+
json.dump({'dist_name': self.ctx.dist_name,
93+
'bootstrap': self.ctx.bootstrap.name,
94+
'archs': [arch.arch for arch in self.ctx.archs],
95+
'recipes': self.ctx.recipe_build_order},
96+
fileh)
97+
98+
@classmethod
99+
def list_bootstraps(cls):
100+
'''Find all the available bootstraps and return them.'''
101+
forbidden_dirs = ('__pycache__', )
102+
bootstraps_dir = join(dirname(__file__), 'bootstraps')
103+
for name in listdir(bootstraps_dir):
104+
if name in forbidden_dirs:
105+
continue
106+
filen = join(bootstraps_dir, name)
107+
if isdir(filen):
108+
yield name
109+
110+
@classmethod
111+
def get_bootstrap_from_recipes(cls, recipes, ctx):
112+
'''Returns a bootstrap whose recipe requirements do not conflict with
113+
the given recipes.'''
114+
info('Trying to find a bootstrap that matches the given recipes.')
115+
bootstraps = [cls.get_bootstrap(name, ctx)
116+
for name in cls.list_bootstraps()]
117+
acceptable_bootstraps = []
118+
for bs in bootstraps:
119+
ok = True
120+
if not bs.can_be_chosen_automatically:
121+
ok = False
122+
for recipe in bs.recipe_depends:
123+
recipe = Recipe.get_recipe(recipe, ctx)
124+
if any([conflict in recipes for conflict in recipe.conflicts]):
125+
ok = False
126+
break
127+
for recipe in recipes:
128+
recipe = Recipe.get_recipe(recipe, ctx)
129+
if any([conflict in bs.recipe_depends
130+
for conflict in recipe.conflicts]):
131+
ok = False
132+
break
133+
if ok:
134+
acceptable_bootstraps.append(bs)
135+
info('Found {} acceptable bootstraps: {}'.format(
136+
len(acceptable_bootstraps),
137+
[bs.name for bs in acceptable_bootstraps]))
138+
if acceptable_bootstraps:
139+
info('Using the first of these: {}'
140+
.format(acceptable_bootstraps[0].name))
141+
return acceptable_bootstraps[0]
142+
return None
143+
144+
@classmethod
145+
def get_bootstrap(cls, name, ctx):
146+
'''Returns an instance of a bootstrap with the given name.
147+
148+
This is the only way you should access a bootstrap class, as
149+
it sets the bootstrap directory correctly.
150+
'''
151+
# AND: This method will need to check user dirs, and access
152+
# bootstraps in a slightly different way
153+
if name is None:
154+
return None
155+
if not hasattr(cls, 'bootstraps'):
156+
cls.bootstraps = {}
157+
if name in cls.bootstraps:
158+
return cls.bootstraps[name]
159+
mod = importlib.import_module('pythonforandroid.bootstraps.{}'
160+
.format(name))
161+
if len(logger.handlers) > 1:
162+
logger.removeHandler(logger.handlers[1])
163+
bootstrap = mod.bootstrap
164+
bootstrap.bootstrap_dir = join(ctx.root_dir, 'bootstraps', name)
165+
bootstrap.ctx = ctx
166+
return bootstrap
167+
168+
def distribute_libs(self, arch, src_dirs, wildcard='*'):
169+
'''Copy existing arch libs from build dirs to current dist dir.'''
170+
info('Copying libs')
171+
tgt_dir = join('libs', arch.arch)
172+
ensure_dir(tgt_dir)
173+
for src_dir in src_dirs:
174+
for lib in glob.glob(join(src_dir, wildcard)):
175+
shprint(sh.cp, '-a', lib, tgt_dir)
176+
177+
def distribute_javaclasses(self, javaclass_dir):
178+
'''Copy existing javaclasses from build dir to current dist dir.'''
179+
info('Copying java files')
180+
for filename in glob.glob(javaclass_dir):
181+
shprint(sh.cp, '-a', filename, 'src')
182+
183+
def distribute_aars(self, arch):
184+
'''Process existing .aar bundles and copy to current dist dir.'''
185+
info('Unpacking aars')
186+
for aar in glob.glob(join(self.ctx.aars_dir, '*.aar')):
187+
self._unpack_aar(aar, arch)
188+
189+
def _unpack_aar(self, aar, arch):
190+
'''Unpack content of .aar bundle and copy to current dist dir.'''
191+
with temp_directory() as temp_dir:
192+
name = splitext(basename(aar))[0]
193+
jar_name = name + '.jar'
194+
info("unpack {} aar".format(name))
195+
debug(" from {}".format(aar))
196+
debug(" to {}".format(temp_dir))
197+
shprint(sh.unzip, '-o', aar, '-d', temp_dir)
198+
199+
jar_src = join(temp_dir, 'classes.jar')
200+
jar_tgt = join('libs', jar_name)
201+
debug("copy {} jar".format(name))
202+
debug(" from {}".format(jar_src))
203+
debug(" to {}".format(jar_tgt))
204+
ensure_dir('libs')
205+
shprint(sh.cp, '-a', jar_src, jar_tgt)
206+
207+
so_src_dir = join(temp_dir, 'jni', arch.arch)
208+
so_tgt_dir = join('libs', arch.arch)
209+
debug("copy {} .so".format(name))
210+
debug(" from {}".format(so_src_dir))
211+
debug(" to {}".format(so_tgt_dir))
212+
ensure_dir(so_tgt_dir)
213+
so_files = glob.glob(join(so_src_dir, '*.so'))
214+
for f in so_files:
215+
shprint(sh.cp, '-a', f, so_tgt_dir)
216+
217+
def strip_libraries(self, arch):
218+
info('Stripping libraries')
219+
env = arch.get_env()
220+
strip = which('arm-linux-androideabi-strip', env['PATH'])
221+
if strip is None:
222+
warning('Can\'t find strip in PATH...')
223+
return
224+
strip = sh.Command(strip)
225+
filens = shprint(sh.find, join(self.dist_dir, 'private'),
226+
join(self.dist_dir, 'libs'),
227+
'-iname', '*.so', _env=env).stdout.decode('utf-8')
228+
logger.info('Stripping libraries in private dir')
229+
for filen in filens.split('\n'):
230+
try:
231+
strip(filen, _env=env)
232+
except sh.ErrorReturnCode_1:
233+
logger.debug('Failed to strip ' + filen)

pythonforandroid/bootstraps/empty/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
from pythonforandroid.toolchain import Bootstrap, shprint, current_directory, info, warning, ArchARM, logger, info_main, which
1+
from pythonforandroid.toolchain import Bootstrap
22
from os.path import join, exists
33
from os import walk
44
import glob
55
import sh
66

7+
78
class EmptyBootstrap(Bootstrap):
89
name = 'empty'
910

0 commit comments

Comments
 (0)