lbry-android-sdk/p4a/pythonforandroid/archs.py
2022-12-02 15:15:34 -05:00

304 lines
9.7 KiB
Python

from distutils.spawn import find_executable
from os import environ
from os.path import join
from multiprocessing import cpu_count
from pythonforandroid.recipe import Recipe
from pythonforandroid.util import BuildInterruptingException, build_platform
class Arch:
command_prefix = None
'''The prefix for NDK commands such as gcc.'''
arch = ""
'''Name of the arch such as: `armeabi-v7a`, `arm64-v8a`, `x86`...'''
arch_cflags = []
'''Specific arch `cflags`, expect to be overwrote in subclass if needed.'''
common_cflags = [
'-target {target}',
'-fomit-frame-pointer'
]
common_cppflags = [
'-DANDROID',
'-I{ctx.ndk.sysroot_include_dir}',
'-I{python_includes}',
]
common_ldflags = ['-L{ctx_libs_dir}']
common_ldlibs = ['-lm']
common_ldshared = [
'-pthread',
'-shared',
'-Wl,-O1',
'-Wl,-Bsymbolic-functions',
]
def __init__(self, ctx):
self.ctx = ctx
# Allows injecting additional linker paths used by any recipe.
# This can also be modified by recipes (like the librt recipe)
# to make sure that some sort of global resource is available &
# linked for all others.
self.extra_global_link_paths = []
def __str__(self):
return self.arch
@property
def ndk_lib_dir(self):
return join(self.ctx.ndk.sysroot_lib_dir, self.command_prefix)
@property
def ndk_lib_dir_versioned(self):
return join(self.ndk_lib_dir, str(self.ctx.ndk_api))
@property
def include_dirs(self):
return [
"{}/{}".format(
self.ctx.include_dir,
d.format(arch=self))
for d in self.ctx.include_dirs]
@property
def target(self):
# As of NDK r19, the toolchains installed by default with the
# NDK may be used in-place. The make_standalone_toolchain.py script
# is no longer needed for interfacing with arbitrary build systems.
# See: https://developer.android.com/ndk/guides/other_build_systems
return '{triplet}{ndk_api}'.format(
triplet=self.command_prefix, ndk_api=self.ctx.ndk_api
)
@property
def clang_exe(self):
"""Full path of the clang compiler depending on the android's ndk
version used."""
return self.get_clang_exe()
@property
def clang_exe_cxx(self):
"""Full path of the clang++ compiler depending on the android's ndk
version used."""
return self.get_clang_exe(plus_plus=True)
def get_clang_exe(self, with_target=False, plus_plus=False):
"""Returns the full path of the clang/clang++ compiler, supports two
kwargs:
- `with_target`: prepend `target` to clang
- `plus_plus`: will return the clang++ compiler (defaults to `False`)
"""
compiler = 'clang'
if with_target:
compiler = '{target}-{compiler}'.format(
target=self.target, compiler=compiler
)
if plus_plus:
compiler += '++'
return join(self.ctx.ndk.llvm_bin_dir, compiler)
def get_env(self, with_flags_in_cc=True):
env = {}
# HOME: User's home directory
#
# Many tools including p4a store outputs in the user's home
# directory. This is found from the HOME environment variable
# and falls back to the system account database. Setting HOME
# can be used to globally divert these tools to use a different
# path. Furthermore, in containerized environments the user may
# not exist in the account database, so if HOME isn't set than
# these tools will fail.
if 'HOME' in environ:
env['HOME'] = environ['HOME']
# CFLAGS/CXXFLAGS: the processor flags
env['CFLAGS'] = ' '.join(self.common_cflags).format(target=self.target)
if self.arch_cflags:
# each architecture may have has his own CFLAGS
env['CFLAGS'] += ' ' + ' '.join(self.arch_cflags)
env['CXXFLAGS'] = env['CFLAGS']
# CPPFLAGS (for macros and includes)
env['CPPFLAGS'] = ' '.join(self.common_cppflags).format(
ctx=self.ctx,
command_prefix=self.command_prefix,
python_includes=join(
self.ctx.get_python_install_dir(self.arch),
'include/python{}'.format(self.ctx.python_recipe.version[0:3]),
),
)
# LDFLAGS: Link the extra global link paths first before anything else
# (such that overriding system libraries with them is possible)
env['LDFLAGS'] = (
' '
+ " ".join(
[
"-L'"
+ link_path.replace("'", "'\"'\"'")
+ "'" # no shlex.quote in py2
for link_path in self.extra_global_link_paths
]
)
+ ' ' + ' '.join(self.common_ldflags).format(
ctx_libs_dir=self.ctx.get_libs_dir(self.arch)
)
)
# LDLIBS: Library flags or names given to compilers when they are
# supposed to invoke the linker.
env['LDLIBS'] = ' '.join(self.common_ldlibs)
# CCACHE
ccache = ''
if self.ctx.ccache and bool(int(environ.get('USE_CCACHE', '1'))):
# print('ccache found, will optimize builds')
ccache = self.ctx.ccache + ' '
env['USE_CCACHE'] = '1'
env['NDK_CCACHE'] = self.ctx.ccache
env.update(
{k: v for k, v in environ.items() if k.startswith('CCACHE_')}
)
# Compiler: `CC` and `CXX` (and make sure that the compiler exists)
env['PATH'] = self.ctx.env['PATH']
cc = find_executable(self.clang_exe, path=env['PATH'])
if cc is None:
print('Searching path are: {!r}'.format(env['PATH']))
raise BuildInterruptingException(
'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.'.format(self.clang_exe))
if with_flags_in_cc:
env['CC'] = '{ccache}{exe} {cflags}'.format(
exe=self.clang_exe,
ccache=ccache,
cflags=env['CFLAGS'])
env['CXX'] = '{ccache}{execxx} {cxxflags}'.format(
execxx=self.clang_exe_cxx,
ccache=ccache,
cxxflags=env['CXXFLAGS'])
else:
env['CC'] = '{ccache}{exe}'.format(
exe=self.clang_exe,
ccache=ccache)
env['CXX'] = '{ccache}{execxx}'.format(
execxx=self.clang_exe_cxx,
ccache=ccache)
# Android's LLVM binutils
env['AR'] = self.ctx.ndk.llvm_ar
env['RANLIB'] = self.ctx.ndk.llvm_ranlib
env['STRIP'] = f'{self.ctx.ndk.llvm_strip} --strip-unneeded'
env['READELF'] = self.ctx.ndk.llvm_readelf
env['OBJCOPY'] = self.ctx.ndk.llvm_objcopy
env['MAKE'] = 'make -j{}'.format(str(cpu_count()))
# Android's arch/toolchain
env['ARCH'] = self.arch
env['NDK_API'] = 'android-{}'.format(str(self.ctx.ndk_api))
# Custom linker options
env['LDSHARED'] = env['CC'] + ' ' + ' '.join(self.common_ldshared)
# Host python (used by some recipes)
hostpython_recipe = Recipe.get_recipe(
'host' + self.ctx.python_recipe.name, self.ctx)
env['BUILDLIB_PATH'] = join(
hostpython_recipe.get_build_dir(self.arch),
'native-build',
'build',
'lib.{}-{}'.format(
build_platform,
self.ctx.python_recipe.major_minor_version_string,
),
)
# for reproducible builds
if 'SOURCE_DATE_EPOCH' in environ:
for k in 'LC_ALL TZ SOURCE_DATE_EPOCH PYTHONHASHSEED BUILD_DATE BUILD_TIME'.split():
if k in environ:
env[k] = environ[k]
return env
class ArchARM(Arch):
arch = "armeabi"
command_prefix = 'arm-linux-androideabi'
@property
def target(self):
target_data = self.command_prefix.split('-')
return '{triplet}{ndk_api}'.format(
triplet='-'.join(['armv7a', target_data[1], target_data[2]]),
ndk_api=self.ctx.ndk_api,
)
class ArchARMv7_a(ArchARM):
arch = 'armeabi-v7a'
arch_cflags = [
'-march=armv7-a',
'-mfloat-abi=softfp',
'-mfpu=vfp',
'-mthumb',
'-fPIC',
]
class Archx86(Arch):
arch = 'x86'
command_prefix = 'i686-linux-android'
arch_cflags = [
'-march=i686',
'-mssse3',
'-mfpmath=sse',
'-m32',
'-fPIC',
]
class Archx86_64(Arch):
arch = 'x86_64'
command_prefix = 'x86_64-linux-android'
arch_cflags = [
'-march=x86-64',
'-msse4.2',
'-mpopcnt',
'-m64',
'-fPIC',
]
class ArchAarch_64(Arch):
arch = 'arm64-v8a'
command_prefix = 'aarch64-linux-android'
arch_cflags = [
'-march=armv8-a',
'-fPIC'
# '-I' + join(dirname(__file__), 'includes', 'arm64-v8a'),
]
# Note: This `EXTRA_CFLAGS` below should target the commented `include`
# above in `arch_cflags`. The original lines were added during the Sdl2's
# bootstrap creation, and modified/commented during the migration to the
# NDK r19 build system, because it seems that we don't need it anymore,
# do we need them?
# def get_env(self, with_flags_in_cc=True):
# env = super().get_env(with_flags_in_cc)
# env['EXTRA_CFLAGS'] = self.arch_cflags[-1]
# return env