Permalink
Browse files

Add multiple APP ABIs

  • Loading branch information...
richq committed Aug 23, 2011
1 parent 62a41ab commit f86dfd159daf4b49370d769279e2fea8c2a677ed
Showing with 305 additions and 53 deletions.
  1. +51 −7 README.md
  2. +110 −46 android.py
  3. +144 −0 tests/test_simple.py
View
@@ -2,17 +2,17 @@
## Installation
The easiest way to use this tool is to clone it into a site_scons/site_tools
The easiest way to use this tool is to clone it into a `site_scons/site_tools`
directory:
mkdir -p site_scons/site_tools
cd site_scons/site_tools
git clone git://github.com/richq/scons-android.git android
## Basic Usage of the AndroidApp Builder
## Basic Usage of the AndroidApp function
Add the tool to the Environment in your SConstruct file and use the AndroidApp
builder:
construction function:
var = Variables(None, ARGUMENTS)
var.AddVariables(PathVariable('ANDROID_SDK', 'Android SDK path', None))
@@ -42,12 +42,54 @@ In addition, you can incorporate NDK code quite easily:
This will use SCons's SharedLibrary builder to create a shared library from
your code. If you want to use an Android.mk-style build, which requires
creating an Android.mk file , then you can use `NdkBuildLegacy`:
creating an Android.mk file, then you can use `NdkBuildLegacy`:
lib = env.NdkBuildLegacy('libs/armeabi/libmyshared.so', ['jni/my_code.c'])
This is the old way of doing things and scons passes the native compilation
work off to GNU Make.
This is the older way of doing things and scons passes the native compilation
work off to GNU Make. You can use more of the ndk-build features, but at the
cost of extra rebuilds and having to maintain both SCons script and Makefiles.
## Application ABIs
To compile multiple architectures at once, you can use the `app_abis` argument.
This is a space-separated string value. Suitable values are `armeabi`,
`armeabi-v7a` and `x86`. The default is `armeabi`.
libs = env.NdkBuild('libmyshared.so', ['jni/my_code.c'],
app_abis='armeabi armeabi-v7a x86')
If you don't specify the explicit output libraries then they are placed in
sensibly named directories for the architecture. In the example above, these
are:
libs/armeabi/libmyshared.so
libs/armeabi-v7a/libmyshared.so
libs/x86/libmyshared.so
You can also specify the full path of each library, but they must match
up with the list of `app_abis`:
libs = env.NdkBuild(['arm/armeabi/libtest.so', 'intel/x86/libtest.so'],
['jni/test.c'], app_abi='armeabi x86')
env.AndroidApp('TestArm', native_folder='arm')
env.AndroidApp('TestIntel', native_folder='intel')
This way you can create multiple APK files each with a single library, rather
than fat APK files with multiple libraries for different architectures.
## AndroidManifest.xml with NdkBuild
In order to determine the minimum target platform the NdkBuild needs to know
where the AndroidManifest.xml file is. The default location is
`'#AndroidManifest.xml'`, which corresponds to an AndroidManifest in the top of
your project. The argument `manifest` can be used to override this:
env.NdkBuild('arm/armeabi/libtest.so',
['jni/test.c'],
manifest='#path/to/AndroidManifest.xml')
This is useful if your project does not use the standard Android layout.
## Environment Variables
@@ -62,10 +104,12 @@ The NDK/SDK paths are hopefully obvious. The key store and key name are used to
create the final signed release. Without these being set, a debug build is
created using the debug key. See [the official documentation][1] for more
details. To create a release build with your keys in 'my-release-key.keystore'
and the key to be used 'alias\_name', you would run:
and the key to be used 'alias\_name', you would run the following:
scons ANDROID_KEY_STORE=my-release-key.keystore ANDROID_KEY_NAME=alias_name
This assumes you are using SCons's `Variables` API to keep track of options.
## Installing to a Device
An `install` target is added which will run `adb install` for your generated
View
@@ -52,54 +52,115 @@ def get_android_target(fname):
targetSdk = target_from_properties(properties)
return (minSdk, targetSdk or minSdk)
def add_gnu_tools(env):
def add_gnu_tools(env, abi):
""" Add the NDK GNU compiler tools to the Environment """
gnu_tools = ['gcc', 'g++', 'gnulink', 'ar', 'gas']
for tool in gnu_tools:
env.Tool(tool)
toolchain = 'toolchains/arm-linux-androideabi-4.4.3/prebuilt/linux-x86/bin'
arm_prefix = os.path.join('$ANDROID_NDK', toolchain,
'arm-linux-androideabi-')
env['CC'] = arm_prefix+'gcc'
env['CXX'] = arm_prefix+'g++'
env['AS'] = arm_prefix+'as'
env['AR'] = arm_prefix+'ar'
env['RANLIB'] = arm_prefix+'ranlib'
env['OBJCOPY'] = arm_prefix+'objcopy'
env['STRIP'] = arm_prefix+'strip'
def NdkBuild(env, library=None, inputs=None):
arm_linux = 'arm-linux-androideabi-4.4.3'
x86 = 'x86-4.4.3'
prebuilt = 'prebuilt/linux-x86/bin'
arm_toolchain = os.path.join('toolchains', arm_linux, prebuilt)
x86_toolchain = os.path.join('toolchains', x86, prebuilt)
toolchains = {'armeabi': arm_toolchain,
'armeabi-v7a': arm_toolchain,
'x86': x86_toolchain}
prefixes = {'armeabi': 'arm-linux-androideabi-',
'armeabi-v7a': 'arm-linux-androideabi-',
'x86': 'i686-android-linux-'}
toolchain = toolchains[abi]
prefix = prefixes[abi]
tool_prefix = os.path.join('$ANDROID_NDK', toolchain, prefix)
env['CC'] = tool_prefix+'gcc'
env['CXX'] = tool_prefix+'g++'
env['AS'] = tool_prefix+'as'
env['AR'] = tool_prefix+'ar'
env['RANLIB'] = tool_prefix+'ranlib'
env['OBJCOPY'] = tool_prefix+'objcopy'
env['STRIP'] = tool_prefix+'strip'
def NdkBuild(env, library=None, inputs=None,
manifest='#AndroidManifest.xml',
app_abi='armeabi'):
""" Use the NDK to build a shared library from the given inputs. """
# ensure ANDROID_NDK is set
get_variable(env, 'ANDROID_NDK')
target_platform = '$ANDROID_NDK/platforms/android-$ANDROID_MIN_TARGET'
env['CPPPATH'] = ['$CPPPATH', target_platform + '/arch-arm/usr/include']
if 'CPPDEFINES' not in env:
env['CPPDEFINES'] = []
env['CPPDEFINES'] += ['-DANDROID']
android_cflags = '''-Wall -Wextra -fpic -mthumb-interwork
-ffunction-sections -funwind-tables -fstack-protector -fno-short-enums
-Wno-psabi -march=armv5te -mtune=xscale -msoft-float -mthumb -Os
-fomit-frame-pointer -fno-strict-aliasing -finline-limit=64
-Wa,--noexecstack'''.split()
android_cxxflags = '''-fno-rtti -fno-exceptions'''.split()
env['CFLAGS'] = ['$CFLAGS', android_cflags]
env['CXXFLAGS'] = ['$CXXFLAGS', android_cflags, android_cxxflags]
if 'LIBPATH' not in env:
env['LIBPATH'] = []
env['LIBPATH'] += [target_platform + '/arch-arm/usr/lib']
shflags = '''-Wl,-soname,${TARGET.file}
-shared
--sysroot=%s/arch-arm
-Wl,--no-undefined -Wl,-z,noexecstack''' % (target_platform)
env['SHLINKFLAGS'] = shflags.split()
lib = env.SharedLibrary('local/'+library, inputs, LIBS=['$LIBS', 'c'])
env.Command(library, lib, [Copy('$TARGET', "$SOURCE"),
'$STRIP --strip-unneeded $TARGET'])
return lib
android_manifest = env.File(manifest)
if not env.has_key('ANDROID_TARGET'):
min_target, target = get_android_target(android_manifest.abspath)
env['ANDROID_MIN_TARGET'] = min_target
env['ANDROID_TARGET'] = target
if type(library) == str:
library = [library]
app_abis = app_abi.split()
if len(app_abis) > len(library):
if len(library) > 1:
print "Error: libraries and app_abi do not coincide"
env.Exit(1)
libname = library[0]
library = [('libs/%s/' % abi) + libname for abi in app_abis]
results = []
android_common_cflags = ''' -Wall -Wextra -fpic -ffunction-sections -Os
-funwind-tables
-fno-short-enums -Wno-psabi
-fomit-frame-pointer -fno-strict-aliasing
-Wa,--noexecstack'''.split()
android_abi_cflags = {'armeabi': '''-mthumb-interwork -march=armv5te
-fstack-protector
-mtune=xscale -msoft-float -mthumb
-finline-limit=64''',
'armeabi-v7a': ''' -march=armv7-a -mfloat-abi=softfp
-fstack-protector
-mfpu=vfp -mthumb -finline-limit=64 ''',
'x86': ''' -finline-limit=300 '''}
libs_len = len(library)
for i in range(0, libs_len):
if libs_len > 1:
tmp_env = env.Clone()
else:
tmp_env = env
library_name = library[i]
abi = app_abis[i]
arch = 'arch-%s' % abi[0:3]
add_gnu_tools(tmp_env, abi)
if abi == 'x86':
if int(tmp_env['ANDROID_MIN_TARGET']) < 9:
tmp_env['ANDROID_MIN_TARGET'] = '9'
target_platform = '$ANDROID_NDK/platforms/android-$ANDROID_MIN_TARGET'
tmp_env['CPPPATH'] = ['$CPPPATH',
target_platform + '/%s/usr/include' % arch]
if 'CPPDEFINES' not in tmp_env:
tmp_env['CPPDEFINES'] = []
tmp_env['CPPDEFINES'] += ['-DANDROID']
android_cflags = android_abi_cflags[abi].split()
android_cflags.extend(android_common_cflags)
android_cxxflags = '''-fno-rtti -fno-exceptions'''.split()
tmp_env['CFLAGS'] = ['$CFLAGS', android_cflags]
tmp_env['CXXFLAGS'] = ['$CXXFLAGS', android_cflags, android_cxxflags]
if 'LIBPATH' not in tmp_env:
tmp_env['LIBPATH'] = []
tmp_env['LIBPATH'] += [target_platform + '/%s/usr/lib' % arch]
tmp_env['SHOBJSUFFIX'] = '.'+abi+'-os'
shflags = '''-Wl,-soname,${TARGET.file}
-shared
--sysroot=%s/%s
-Wl,--no-undefined -Wl,-z,noexecstack''' % (target_platform, arch)
tmp_env['SHLINKFLAGS'] = shflags.split()
lib = tmp_env.SharedLibrary('local/'+library_name, inputs,
LIBS=['$LIBS', 'c'])
tmp_env.Command(library_name, lib, [Copy('$TARGET', "$SOURCE"),
'$STRIP --strip-unneeded $TARGET'])
results.append(lib)
return results
def NdkBuildLegacy(env, library=None, inputs=None, app_root='#.',
build_dir='.'):
@@ -173,16 +234,19 @@ def AndroidApp(env, name,
if not env.has_key('ANDROID_TARGET'):
min_target, target = get_android_target(android_manifest.abspath)
env['ANDROID_MIN_TARGET'] = min_target
if 'ANDROID_MIN_TARGET' not in env:
env['ANDROID_MIN_TARGET'] = min_target
env['ANDROID_TARGET'] = target
env['ANDROID_JAR'] = os.path.join('$ANDROID_SDK',
'platforms/android-$ANDROID_TARGET/android.jar')
safe_name = name.replace('-', '_')
if not env.has_key('APP_PACKAGE'):
package = get_android_package(android_manifest.abspath)
else:
package = env['APP_PACKAGE']
rfile = os.path.join('gen', get_rfile(package))
gen = env.Dir('gen')
gen_name = safe_name + '_gen'
rfile = os.path.join(gen_name, get_rfile(package))
gen = env.Dir(gen_name)
# generate R.java
resource_dirs = [env.Dir(r) for r in env.Flatten([resources])]
@@ -199,7 +263,7 @@ def AndroidApp(env, name,
env.Depends(generated_rfile, android_manifest)
# compile java to classes
bin_classes = name.replace('-','_')+'_bin/classes'
bin_classes = safe_name+'_bin/classes'
classes = env.Java(target=bin_classes, source=[source],
JAVABOOTCLASSPATH='$ANDROID_JAR',
JAVASOURCEPATH=gen.path,
@@ -244,6 +308,7 @@ def AndroidApp(env, name,
if native_folder:
sofiles = env.Glob(native_folder + '/armeabi/*.so')
sofiles.extend(env.Glob(native_folder + '/armeabi-v7a/*.so'))
sofiles.extend(env.Glob(native_folder + '/x86/*.so'))
env.Depends(unaligned, env.Flatten([dex, tmp_package, sofiles]))
else:
env.Depends(unaligned, [dex, tmp_package])
@@ -302,7 +367,6 @@ def generate(env, **kw):
env.Tool('javac')
env.Tool('jar')
add_gnu_tools(env)
env['AAPT'] = '$ANDROID_SDK/platform-tools/aapt'
env['DX'] = '$ANDROID_SDK/platform-tools/dx'
env['ZIPALIGN'] = '$ANDROID_SDK/tools/zipalign'
Oops, something went wrong.

0 comments on commit f86dfd1

Please sign in to comment.