waifulib: xcompile: merge changes from engine branch: better host toolchain support, SONAME support, DEST_OS now set as android, DEST_OS2 was removed

This commit is contained in:
Alibek Omarov 2019-10-07 22:52:10 +03:00
parent b8a8cf9904
commit d0450db52b
1 changed files with 164 additions and 124 deletions

View File

@ -13,27 +13,27 @@
try: from fwgslib import get_flags_by_compiler
except: from waflib.extras.fwgslib import get_flags_by_compiler
from waflib import Logs
from waflib import Logs, TaskGen
from waflib.Tools import c_config
from collections import OrderedDict
import os
import sys
# Output:
# CROSSCOMPILING -- set to true, if crosscompiling is enabled
# DEST_OS2 -- as some operating systems is built on top of another, it's better to not change DEST_OS,
# instead of this DEST_OS2 is defined with target value
# For example: android is built on top of linux and have many things in common,
# but it can't be considered as default GNU/Linux.
# Possible values:
# DEST_OS2 DEST_OS
# 'android' 'linux'
ANDROID_NDK_ENVVARS = ['ANDROID_NDK_HOME', 'ANDROID_NDK']
ANDROID_NDK_SUPPORTED = [10, 19, 20]
ANDROID_NDK_HARDFP_MAX = 11 # latest version that supports hardfp
ANDROID_NDK_GCC_MAX = 17 # latest NDK that ships with GCC
ANDROID_NDK_UNIFIED_SYSROOT_MIN = 15
ANDROID_NDK_SYSROOT_FLAG_MAX = 19 # latest NDK that need --sysroot flag
ANDROID_NDK_API_MIN = { 10: 3, 19: 16, 20: 16 } # minimal API level ndk revision supports
ANDROID_64BIT_API_MIN = 21 # minimal API level that supports 64-bit targets
# This class does support ONLY r10e and r19c NDK
# This class does support ONLY r10e and r19c/r20 NDK
class Android:
ctx = None # waf context
arch = None
toolchain = None
api = None
toolchain_path = None
ndk_home = None
ndk_rev = 0
is_hardfloat = False
@ -42,14 +42,16 @@ class Android:
def __init__(self, ctx, arch, toolchain, api):
self.ctx = ctx
self.api = api
self.toolchain = toolchain
self.arch = arch
for i in ['ANDROID_NDK_HOME', 'ANDROID_NDK']:
for i in ANDROID_NDK_ENVVARS:
self.ndk_home = os.getenv(i)
if self.ndk_home != None:
break
if not self.ndk_home:
ctx.fatal('Set ANDROID_NDK_HOME environment variable pointing to the root of Android NDK!')
else:
ctx.fatal('Set %s environment variable pointing to the root of Android NDK!' %
' or '.join(ANDROID_NDK_ENVVARS))
# TODO: this were added at some point of NDK development
# but I don't know at which version
@ -63,36 +65,29 @@ class Android:
if 'Pkg.Revision' in trimed_tokens:
self.ndk_rev = int(trimed_tokens[1].split('.')[0])
if self.ndk_rev not in ANDROID_NDK_SUPPORTED:
ctx.fatal('Unknown NDK revision: %d' % (self.ndk_rev))
else:
self.ndk_rev = 10
self.ndk_rev = ANDROID_NDK_SUPPORTED[0]
if self.ndk_rev not in [10, 19, 20]:
ctx.fatal('Unknown NDK revision: {}'.format(self.ndk_rev))
if 'clang' in self.toolchain or self.ndk_rev > ANDROID_NDK_GCC_MAX:
self.clang = True
self.arch = arch
if self.arch == 'armeabi-v7a-hard':
if self.ndk_rev <= 10:
if self.ndk_rev <= ANDROID_NDK_HARDFP_MAX:
self.arch = 'armeabi-v7a' # Only armeabi-v7a have hard float ABI
self.is_hardfloat = True
else:
raise Exception('NDK does not support hardfloat ABI')
ctx.fatal('NDK does not support hardfloat ABI')
self.toolchain = toolchain
if self.api < ANDROID_NDK_API_MIN[self.ndk_rev]:
self.api = ANDROID_NDK_API_MIN[self.ndk_rev]
Logs.warn('API level automatically was set to %d due to NDK support' % self.api)
if self.ndk_rev >= 19 or 'clang' in self.toolchain:
self.clang = True
if self.is_arm64() or self.is_amd64() and self.api < 21:
Logs.warn('API level for 64-bit target automatically was set to 21')
self.api = 21
elif self.ndk_rev >= 19 and self.api < 16:
Logs.warn('API level automatically was set to 16 due to NDK support')
self.api = 16
self.toolchain_path = self.gen_toolchain_path()
# TODO: Crystax support?
# TODO: Support for everything else than linux-x86_64?
# TODO: Determine if I actually need to implement listed above
if self.is_arm64() or self.is_amd64() and self.api < ANDROID_64BIT_API_MIN:
self.api = ANDROID_64BIT_API_MIN
Logs.warn('API level for 64-bit target automatically was set to %d' % self.api)
def is_host(self):
'''
@ -133,56 +128,62 @@ class Android:
def is_hardfp(self):
return self.is_hardfloat
def ndk_triplet(self):
def ndk_triplet(self, llvm_toolchain = False, toolchain_folder = False):
if self.is_x86():
triplet = 'i686-linux-android'
elif self.is_arm():
triplet = 'arm-linux-androideabi'
if toolchain_folder:
return 'x86'
else:
triplet = self.arch + '-linux-android'
return triplet
return 'i686-linux-android'
elif self.is_arm():
if llvm_toolchain:
return 'armv7a-linux-androideabi'
else:
return 'arm-linux-androideabi'
elif self.is_amd64() and toolchain_folder:
return 'x86_64'
else:
return self.arch + '-linux-android'
def gen_host_toolchain(self):
# With host toolchain we don't care about OS
# so just download NDK for Linux x86_64
if self.is_host():
return 'linux-x86_64'
if sys.platform.startswith('win32') or sys.platform.startswith('cygwin'):
osname = 'windows'
elif sys.platform.startswith('darwin'):
osname = 'darwin'
elif sys.platform.startswith('linux'):
osname = 'linux'
else:
self.ctx.fatal('Unsupported by NDK host platform')
if sys.maxsize > 2**32:
arch = 'x86_64'
else: arch = 'x86'
return '%s-%s' % (osname, arch)
def gen_gcc_toolchain_path(self):
path = 'toolchains'
if sys.platform.startswith('linux'):
toolchain_host = 'linux'
elif sys.platform.startswith('darwin'):
toolchain_host = 'darwin'
elif sys.platform.startswith('win32') or sys.platform.startswith('cygwin'):
toolchain_host = 'windows'
else: raise Exception('Unsupported by NDK host platform')
toolchain_host += '-'
# Assuming we are building on x86
if sys.maxsize > 2**32:
toolchain_host += 'x86_64'
else: toolchain_host += 'x86'
toolchain_host = self.gen_host_toolchain()
if self.is_clang():
if self.ndk_rev < 19:
raise Exception('Clang is not supported for this NDK')
toolchain_folder = 'llvm'
else:
if self.is_x86() or self.is_amd64():
toolchain_folder = self.arch + '-' + self.toolchain
elif self.is_arm():
toolchain_folder = 'arm-linux-androideabi-' + self.toolchain
if self.is_host():
toolchain = '4.9'
else:
toolchain_folder = self.arch + '-linux-android-' + self.toolchain
toolchain = self.toolchain
toolchain_folder = '%s-%s' % (self.ndk_triplet(toolchain_folder = True), toolchain)
return os.path.abspath(os.path.join(self.ndk_home, path, toolchain_folder, 'prebuilt', toolchain_host))
def gen_toolchain_path(self):
if self.is_clang():
if self.is_x86():
triplet = 'i686-linux-android{}-'.format(self.api)
elif self.is_arm():
triplet = 'armv7a-linux-androideabi{}-'.format(self.api)
else:
triplet = self.arch + '-linux-android{}-'.format(self.api)
triplet = '%s%d-' % (self.ndk_triplet(llvm_toolchain = True), self.api)
else:
triplet = self.ndk_triplet() + '-'
return os.path.join(self.gen_gcc_toolchain_path(), 'bin', triplet)
@ -192,17 +193,17 @@ class Android:
def cc(self):
if self.is_host():
return 'clang'
return self.toolchain_path + ('clang' if self.is_clang() else 'gcc')
return 'clang --target=%s%d' % (self.ndk_triplet(), self.api)
return self.gen_toolchain_path() + ('clang' if self.is_clang() else 'gcc')
def cxx(self):
if self.is_host():
return 'clang++'
return self.toolchain_path + ('clang++' if self.is_clang() else 'g++')
return 'clang++ --target=%s%d' % (self.ndk_triplet(), self.api)
return self.gen_toolchain_path() + ('clang++' if self.is_clang() else 'g++')
def strip(self):
if self.is_host():
return 'strip'
return 'llvm-strip'
return os.path.join(self.gen_binutils_path(), 'strip')
def system_stl(self):
@ -215,46 +216,53 @@ class Android:
arch = 'arm'
elif self.is_arm64():
arch = 'arm64'
path = 'platforms/android-{}/arch-{}'.format(self.api, arch)
path = 'platforms/android-%s/arch-%s' % (self.api, arch)
return os.path.abspath(os.path.join(self.ndk_home, path))
def sysroot(self):
if self.ndk_rev >= 19 or self.is_host():
if self.ndk_rev >= ANDROID_NDK_UNIFIED_SYSROOT_MIN:
return os.path.abspath(os.path.join(self.ndk_home, 'sysroot'))
else:
return self.libsysroot()
def clang_host_triplet(self):
triplet = ''
if self.is_arm():
triplet += 'arm'
elif self.is_x86():
triplet += 'i686'
else:
triplet += self.arch
triplet += '-linux-android'
return triplet
def cflags(self):
def cflags(self, cxx = False):
cflags = []
if self.is_host():
cflags += ['-nostdlib', '--target=%s' % self.clang_host_triplet()]
if self.ndk_rev < 20:
cflags += ['--sysroot={0}'.format(self.sysroot())]
elif self.is_host():
cflags += ['-isysroot={0}'.format(self.sysroot())]
cflags += ['-DANDROID', '-D__ANDROID__']
cflags += ['-I{0}'.format(self.system_stl())]
if self.ndk_rev <= ANDROID_NDK_SYSROOT_FLAG_MAX:
cflags += ['--sysroot=%s' % (self.sysroot())]
else:
if self.is_host():
cflags += [
'--sysroot=%s/sysroot' % (self.gen_gcc_toolchain_path()),
'-I%s/usr/include/' % (self.sysroot())
]
cflags += ['-I%s' % (self.system_stl()), '-DANDROID', '-D__ANDROID__']
if cxx and not self.is_clang() and self.toolchain not in ['4.8','4.9']:
cflags += ['-fno-sized-deallocation']
if self.is_arm():
if self.arch == 'armeabi-v7a':
# ARMv7 support
cflags += ['-mthumb', '-mfpu=neon', '-mcpu=cortex-a9', '-DHAVE_EFFICIENT_UNALIGNED_ACCESS', '-DVECTORIZE_SINCOS']
if not self.is_clang():
if not self.is_clang() and not self.is_host():
cflags += [ '-mvectorize-with-neon-quad' ]
if self.is_hardfloat:
cflags += ['-D_NDK_MATH_NO_SOFTFP=1', '-mhard-float', '-mfloat-abi=hard', '-DLOAD_HARDFP', '-DSOFTFP_LINK']
if self.is_hardfp():
cflags += ['-D_NDK_MATH_NO_SOFTFP=1', '-mfloat-abi=hard', '-DLOAD_HARDFP', '-DSOFTFP_LINK']
if self.is_host():
# Clang builtin redefine w/ different calling convention bug
# NOTE: I did not added complex.h functions here, despite
# that NDK devs forgot to put __NDK_FPABI_MATH__ for complex
# math functions
# I personally don't need complex numbers support, but if you want it
# just run sed to patch header
for f in ['strtod', 'strtof', 'strtold']:
cflags += ['-fno-builtin-%s' % f]
else:
cflags += ['-mfloat-abi=softfp']
else:
@ -268,26 +276,31 @@ class Android:
def linkflags(self):
linkflags = []
if self.is_host():
linkflags += ['-fuse-ld=lld', '-nostdlib', '--target=%s' % self.clang_host_triplet(),
'--gcc-toolchain=%s' % self.gen_gcc_toolchain_path()]
linkflags += ['--gcc-toolchain=%s' % self.gen_gcc_toolchain_path()]
if self.ndk_rev < 20 or self.is_host():
linkflags += ['--sysroot={0}'.format(self.libsysroot())]
if self.ndk_rev <= ANDROID_NDK_SYSROOT_FLAG_MAX:
linkflags += ['--sysroot=%s' % (self.sysroot())]
elif self.is_host():
linkflags += ['--sysroot=%s/sysroot' % (self.gen_gcc_toolchain_path())]
if self.is_host():
linkflags += ['-L{0}/usr/lib/{1}'.format(self.sysroot(), self.ndk_triplet()),
'-L{0}/sysroot/usr/lib/{1}/'.format(self.gen_gcc_toolchain_path(), self.ndk_triplet())]
if self.is_clang() or self.is_host():
linkflags += ['-fuse-ld=lld']
linkflags += ['-Wl,--hash-style=both','-Wl,--no-undefined']
return linkflags
def ldflags(self):
ldflags = ['-lgcc', '-no-canonical-prefixes']
if self.is_clang():
ldflags += ['-stdlib=libstdc++']
if self.is_arm():
if self.arch == 'armeabi-v7a':
ldflags += ['-march=armv7-a']
if not self.is_host(): # lld only
ldflags += ['-march=armv7-a', '-mthumb']
if not self.is_clang() and not self.is_host(): # lld only
ldflags += ['-Wl,--fix-cortex-a8']
ldflags += ['-mthumb']
if self.is_hardfloat:
if self.is_hardfp():
ldflags += ['-Wl,--no-warn-mismatch', '-lm_hard']
else:
ldflags += ['-march=armv5te']
@ -304,18 +317,17 @@ def configure(conf):
if len(values) != 3:
conf.fatal('Invalid --android paramater value!')
valid_archs = ['x86', 'x86_64', 'armeabi', 'armeabi-v7a', 'armeabi-v7a-hard', 'aarch64', 'mipsel', 'mips64el']
valid_archs = ['x86', 'x86_64', 'armeabi', 'armeabi-v7a', 'armeabi-v7a-hard', 'aarch64']
if values[0] not in valid_archs:
conf.fatal('Unknown arch: {}. Supported: {}'.format(values[0], ', '.join(valid_archs)))
conf.fatal('Unknown arch: %s. Supported: %r' % (values[0], ', '.join(valid_archs)))
android = Android(conf, values[0], values[1], int(values[2]))
setattr(conf, 'android', android)
conf.android = android = Android(conf, values[0], values[1], int(values[2]))
conf.environ['CC'] = android.cc()
conf.environ['CXX'] = android.cxx()
conf.environ['STRIP'] = android.strip()
conf.env.CFLAGS += android.cflags()
conf.env.CXXFLAGS += android.cflags()
conf.env.CXXFLAGS += android.cflags(True)
conf.env.LINKFLAGS += android.linkflags()
conf.env.LDFLAGS += android.ldflags()
@ -324,20 +336,27 @@ def configure(conf):
conf.env.LIB_M = ['m_hard']
else: conf.env.LIB_M = ['m']
conf.env.PREFIX = '/lib/{}'.format(android.arch)
conf.env.PREFIX = '/lib/%s' % (android.arch)
conf.msg('Selected Android NDK', '{}, version: {}'.format(android.ndk_home, android.ndk_rev))
conf.msg('Selected Android NDK', '%s, version: %d' % (android.ndk_home, android.ndk_rev))
# no need to print C/C++ compiler, as it would be printed by compiler_c/cxx
conf.msg('... C/C++ flags', ' '.join(android.cflags()).replace(android.ndk_home, '$NDK'))
conf.msg('... link flags', ' '.join(android.linkflags()).replace(android.ndk_home, '$NDK'))
conf.msg('... ld flags', ' '.join(android.ldflags()).replace(android.ndk_home, '$NDK'))
conf.msg('... C/C++ flags', ' '.join(android.cflags()).replace(android.ndk_home, '$NDK/'))
conf.msg('... link flags', ' '.join(android.linkflags()).replace(android.ndk_home, '$NDK/'))
conf.msg('... ld flags', ' '.join(android.ldflags()).replace(android.ndk_home, '$NDK/'))
# conf.env.ANDROID_OPTS = android
conf.env.DEST_OS2 = 'android'
# else:
# conf.load('compiler_c compiler_cxx') # Use host compiler :)
MACRO_TO_DESTOS = OrderedDict({ '__ANDROID__' : 'android' })
for k in c_config.MACRO_TO_DESTOS:
MACRO_TO_DESTOS[k] = c_config.MACRO_TO_DESTOS[k] # ordering is important
c_config.MACRO_TO_DESTOS = MACRO_TO_DESTOS
def post_compiler_cxx_configure(conf):
conf.msg('Target OS', conf.env.DEST_OS)
conf.msg('Target CPU', conf.env.DEST_CPU)
conf.msg('Target binfmt', conf.env.DEST_BINFMT)
if conf.options.ANDROID_OPTS:
if conf.android.ndk_rev == 19:
conf.env.CXXFLAGS_cxxshlib += ['-static-libstdc++']
@ -345,6 +364,10 @@ def post_compiler_cxx_configure(conf):
return
def post_compiler_c_configure(conf):
conf.msg('Target OS', conf.env.DEST_OS)
conf.msg('Target CPU', conf.env.DEST_CPU)
conf.msg('Target binfmt', conf.env.DEST_BINFMT)
return
from waflib.Tools import compiler_cxx, compiler_c
@ -362,3 +385,20 @@ def patch_compiler_c_configure(conf):
setattr(compiler_cxx, 'configure', patch_compiler_cxx_configure)
setattr(compiler_c, 'configure', patch_compiler_c_configure)
@TaskGen.feature('cshlib', 'cxxshlib', 'dshlib', 'fcshlib', 'vnum')
@TaskGen.after_method('apply_link', 'propagate_uselib_vars')
@TaskGen.before_method('apply_vnum')
def apply_android_soname(self):
"""
Enforce SONAME on Android
"""
if self.env.DEST_OS != 'android':
return
setattr(self, 'vnum', None) # remove vnum, so SONAME would not be overwritten
link = self.link_task
node = link.outputs[0]
libname = node.name
v = self.env.SONAME_ST % libname
self.env.append_value('LINKFLAGS', v.split())