Skip to content

Split toolchain.py into separate modules #526

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Dec 11, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 137 additions & 0 deletions pythonforandroid/archs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
from os.path import (join)
from os import environ, uname
import sys
from distutils.spawn import find_executable

from pythonforandroid.logger import warning
from pythonforandroid.recipe import Recipe


class Arch(object):

toolchain_prefix = None
'''The prefix for the toolchain dir in the NDK.'''

command_prefix = None
'''The prefix for NDK commands such as gcc.'''

def __init__(self, ctx):
super(Arch, self).__init__()
self.ctx = ctx

def __str__(self):
return self.arch

@property
def include_dirs(self):
return [
"{}/{}".format(
self.ctx.include_dir,
d.format(arch=self))
for d in self.ctx.include_dirs]

def get_env(self):
env = {}

env["CFLAGS"] = " ".join([
"-DANDROID", "-mandroid", "-fomit-frame-pointer",
"--sysroot", self.ctx.ndk_platform])

env["CXXFLAGS"] = env["CFLAGS"]

env["LDFLAGS"] = " ".join(['-lm'])

py_platform = sys.platform
if py_platform in ['linux2', 'linux3']:
py_platform = 'linux'

toolchain_prefix = self.ctx.toolchain_prefix
toolchain_version = self.ctx.toolchain_version
command_prefix = self.command_prefix

env['TOOLCHAIN_PREFIX'] = toolchain_prefix
env['TOOLCHAIN_VERSION'] = toolchain_version

print('path is', environ['PATH'])
cc = find_executable('{command_prefix}-gcc'.format(
command_prefix=command_prefix), path=environ['PATH'])
if cc is None:
warning('Couldn\'t find executable for CC. This indicates a '
'problem locating the {} executable in the Android '
'NDK, not that you don\'t have a normal compiler '
'installed. Exiting.')
exit(1)

env['CC'] = '{command_prefix}-gcc {cflags}'.format(
command_prefix=command_prefix,
cflags=env['CFLAGS'])
env['CXX'] = '{command_prefix}-g++ {cxxflags}'.format(
command_prefix=command_prefix,
cxxflags=env['CXXFLAGS'])

env['AR'] = '{}-ar'.format(command_prefix)
env['RANLIB'] = '{}-ranlib'.format(command_prefix)
env['LD'] = '{}-ld'.format(command_prefix)
env['STRIP'] = '{}-strip --strip-unneeded'.format(command_prefix)
env['MAKE'] = 'make -j5'
env['READELF'] = '{}-readelf'.format(command_prefix)

hostpython_recipe = Recipe.get_recipe('hostpython2', self.ctx)

# AND: This hardcodes python version 2.7, needs fixing
env['BUILDLIB_PATH'] = join(
hostpython_recipe.get_build_dir(self.arch),
'build', 'lib.linux-{}-2.7'.format(uname()[-1]))

env['PATH'] = environ['PATH']

env['ARCH'] = self.arch

return env


class ArchARM(Arch):
arch = "armeabi"
toolchain_prefix = 'arm-linux-androideabi'
command_prefix = 'arm-linux-androideabi'
platform_dir = 'arch-arm'


class ArchARMv7_a(ArchARM):
arch = 'armeabi-v7a'

def get_env(self):
env = super(ArchARMv7_a, self).get_env()
env['CFLAGS'] = (env['CFLAGS'] +
(' -march=armv7-a -mfloat-abi=softfp '
'-mfpu=vfp -mthumb'))
env['CXXFLAGS'] = env['CFLAGS']
return env


class Archx86(Arch):
arch = 'x86'
toolchain_prefix = 'x86'
command_prefix = 'i686-linux-android'
platform_dir = 'arch-x86'

def get_env(self):
env = super(Archx86, self).get_env()
env['CFLAGS'] = (env['CFLAGS'] +
' -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32')
env['CXXFLAGS'] = env['CFLAGS']
return env


class Archx86_64(Arch):
arch = 'x86_64'
toolchain_prefix = 'x86'
command_prefix = 'x86_64-linux-android'
platform_dir = 'arch-x86'

def get_env(self):
env = super(Archx86_64, self).get_env()
env['CFLAGS'] = (env['CFLAGS'] +
' -march=x86-64 -msse4.2 -mpopcnt -m64 -mtune=intel')
env['CXXFLAGS'] = env['CFLAGS']
return env
233 changes: 233 additions & 0 deletions pythonforandroid/bootstrap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
from os.path import (join, dirname, isdir, splitext, basename)
from os import listdir
import sh
import glob
import json
import importlib

from pythonforandroid.logger import (warning, shprint, info, logger,
debug)
from pythonforandroid.util import (current_directory, ensure_dir,
temp_directory, which)
from pythonforandroid.recipe import Recipe


class Bootstrap(object):
'''An Android project template, containing recipe stuff for
compilation and templated fields for APK info.
'''
name = ''
jni_subdir = '/jni'
ctx = None

bootstrap_dir = None

build_dir = None
dist_dir = None
dist_name = None
distribution = None

recipe_depends = []

can_be_chosen_automatically = True
'''Determines whether the bootstrap can be chosen as one that
satisfies user requirements. If False, it will not be returned
from Bootstrap.get_bootstrap_from_recipes.
'''

# Other things a Bootstrap might need to track (maybe separately):
# ndk_main.c
# whitelist.txt
# blacklist.txt

@property
def dist_dir(self):
'''The dist dir at which to place the finished distribution.'''
if self.distribution is None:
warning('Tried to access {}.dist_dir, but {}.distribution '
'is None'.format(self, self))
exit(1)
return self.distribution.dist_dir

@property
def jni_dir(self):
return self.name + self.jni_subdir

def get_build_dir(self):
return join(self.ctx.build_dir, 'bootstrap_builds', self.name)

def get_dist_dir(self, name):
return join(self.ctx.dist_dir, name)

@property
def name(self):
modname = self.__class__.__module__
return modname.split(".", 2)[-1]

def prepare_build_dir(self):
'''Ensure that a build dir exists for the recipe. This same single
dir will be used for building all different archs.'''
self.build_dir = self.get_build_dir()
shprint(sh.cp, '-r',
join(self.bootstrap_dir, 'build'),
# join(self.ctx.root_dir,
# 'bootstrap_templates',
# self.name),
self.build_dir)
with current_directory(self.build_dir):
with open('project.properties', 'w') as fileh:
fileh.write('target=android-{}'.format(self.ctx.android_api))

def prepare_dist_dir(self, name):
# self.dist_dir = self.get_dist_dir(name)
ensure_dir(self.dist_dir)

def run_distribute(self):
# print('Default bootstrap being used doesn\'t know how '
# 'to distribute...failing.')
# exit(1)
with current_directory(self.dist_dir):
info('Saving distribution info')
with open('dist_info.json', 'w') as fileh:
json.dump({'dist_name': self.ctx.dist_name,
'bootstrap': self.ctx.bootstrap.name,
'archs': [arch.arch for arch in self.ctx.archs],
'recipes': self.ctx.recipe_build_order},
fileh)

@classmethod
def list_bootstraps(cls):
'''Find all the available bootstraps and return them.'''
forbidden_dirs = ('__pycache__', )
bootstraps_dir = join(dirname(__file__), 'bootstraps')
for name in listdir(bootstraps_dir):
if name in forbidden_dirs:
continue
filen = join(bootstraps_dir, name)
if isdir(filen):
yield name

@classmethod
def get_bootstrap_from_recipes(cls, recipes, ctx):
'''Returns a bootstrap whose recipe requirements do not conflict with
the given recipes.'''
info('Trying to find a bootstrap that matches the given recipes.')
bootstraps = [cls.get_bootstrap(name, ctx)
for name in cls.list_bootstraps()]
acceptable_bootstraps = []
for bs in bootstraps:
ok = True
if not bs.can_be_chosen_automatically:
ok = False
for recipe in bs.recipe_depends:
recipe = Recipe.get_recipe(recipe, ctx)
if any([conflict in recipes for conflict in recipe.conflicts]):
ok = False
break
for recipe in recipes:
recipe = Recipe.get_recipe(recipe, ctx)
if any([conflict in bs.recipe_depends
for conflict in recipe.conflicts]):
ok = False
break
if ok:
acceptable_bootstraps.append(bs)
info('Found {} acceptable bootstraps: {}'.format(
len(acceptable_bootstraps),
[bs.name for bs in acceptable_bootstraps]))
if acceptable_bootstraps:
info('Using the first of these: {}'
.format(acceptable_bootstraps[0].name))
return acceptable_bootstraps[0]
return None

@classmethod
def get_bootstrap(cls, name, ctx):
'''Returns an instance of a bootstrap with the given name.

This is the only way you should access a bootstrap class, as
it sets the bootstrap directory correctly.
'''
# AND: This method will need to check user dirs, and access
# bootstraps in a slightly different way
if name is None:
return None
if not hasattr(cls, 'bootstraps'):
cls.bootstraps = {}
if name in cls.bootstraps:
return cls.bootstraps[name]
mod = importlib.import_module('pythonforandroid.bootstraps.{}'
.format(name))
if len(logger.handlers) > 1:
logger.removeHandler(logger.handlers[1])
bootstrap = mod.bootstrap
bootstrap.bootstrap_dir = join(ctx.root_dir, 'bootstraps', name)
bootstrap.ctx = ctx
return bootstrap

def distribute_libs(self, arch, src_dirs, wildcard='*'):
'''Copy existing arch libs from build dirs to current dist dir.'''
info('Copying libs')
tgt_dir = join('libs', arch.arch)
ensure_dir(tgt_dir)
for src_dir in src_dirs:
for lib in glob.glob(join(src_dir, wildcard)):
shprint(sh.cp, '-a', lib, tgt_dir)

def distribute_javaclasses(self, javaclass_dir):
'''Copy existing javaclasses from build dir to current dist dir.'''
info('Copying java files')
for filename in glob.glob(javaclass_dir):
shprint(sh.cp, '-a', filename, 'src')

def distribute_aars(self, arch):
'''Process existing .aar bundles and copy to current dist dir.'''
info('Unpacking aars')
for aar in glob.glob(join(self.ctx.aars_dir, '*.aar')):
self._unpack_aar(aar, arch)

def _unpack_aar(self, aar, arch):
'''Unpack content of .aar bundle and copy to current dist dir.'''
with temp_directory() as temp_dir:
name = splitext(basename(aar))[0]
jar_name = name + '.jar'
info("unpack {} aar".format(name))
debug(" from {}".format(aar))
debug(" to {}".format(temp_dir))
shprint(sh.unzip, '-o', aar, '-d', temp_dir)

jar_src = join(temp_dir, 'classes.jar')
jar_tgt = join('libs', jar_name)
debug("copy {} jar".format(name))
debug(" from {}".format(jar_src))
debug(" to {}".format(jar_tgt))
ensure_dir('libs')
shprint(sh.cp, '-a', jar_src, jar_tgt)

so_src_dir = join(temp_dir, 'jni', arch.arch)
so_tgt_dir = join('libs', arch.arch)
debug("copy {} .so".format(name))
debug(" from {}".format(so_src_dir))
debug(" to {}".format(so_tgt_dir))
ensure_dir(so_tgt_dir)
so_files = glob.glob(join(so_src_dir, '*.so'))
for f in so_files:
shprint(sh.cp, '-a', f, so_tgt_dir)

def strip_libraries(self, arch):
info('Stripping libraries')
env = arch.get_env()
strip = which('arm-linux-androideabi-strip', env['PATH'])
if strip is None:
warning('Can\'t find strip in PATH...')
return
strip = sh.Command(strip)
filens = shprint(sh.find, join(self.dist_dir, 'private'),
join(self.dist_dir, 'libs'),
'-iname', '*.so', _env=env).stdout.decode('utf-8')
logger.info('Stripping libraries in private dir')
for filen in filens.split('\n'):
try:
strip(filen, _env=env)
except sh.ErrorReturnCode_1:
logger.debug('Failed to strip ' + filen)
3 changes: 2 additions & 1 deletion pythonforandroid/bootstraps/empty/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from pythonforandroid.toolchain import Bootstrap, shprint, current_directory, info, warning, ArchARM, logger, info_main, which
from pythonforandroid.toolchain import Bootstrap
from os.path import join, exists
from os import walk
import glob
import sh


class EmptyBootstrap(Bootstrap):
name = 'empty'

Expand Down
Loading