android-gcc
안드로이드(Android) NDK 의 목표중의 하나는 ARM 바이너리를 만드는 크로스툴체인(cross-toolchains)을 제공하는 것이다. 하지만 이 툴체인은 NDK가 제공하는 빌드환경과 결합되어 있다. 때문에 따로 분리하여 독립적인 환경에서 사용하려고 하면 불편한 점이 많다. Andrew Ross 의 agcc 는 이러한 불편을 해소하는 한가지 방법을 제공한다. 리눅스 배포판에 포함된 gcc와 유사한 동작을 하는 펄 스크립트를 제공하는 것이다. 이 펄 스크립트가 안드로이드에 필요한 명령행 옵션들을 적절한 위치에 추가하게 된다. 이런 접근법은 외부 라이브러리를 빌드 하는 작업에서 꽤 편리하고 효과적인 수단을 제공한다. 하지만 agcc 는 NDK가 아닌 안드로이드 소스트리에 포함된 툴체인과 함께 사용되도록 준비되었고, 최근의 안드로이드 변경에 따른 수정판도 발견되지 않는다는 문제점이 있다. 이 곳에서 제공되는 android-gcc 는 NDK 최신 버전에 포함된 툴체인을 사용하도록 파이썬(Python)으로 재 작성되었다. android-gcc 가 동작하기 위한 선행조건은 다음과 같다.
- Python 2.5 이상
- NDK 1.6 이상 (Windows 와 Linux 버전만 확인되었다.)
- NDK 의 arm-eabi-gcc 가 포함된 디렉토리가 PATH 에 포함되어 있어야 한다.
gcc를 흉내 내므로 사용법을 따로 설명하지는 않지만, 몇 가지 기억해둘 내용이 있다.
- android-gcc 를 적당한 위치에 다운로드 한 후 chmod +x 로 실행 가능하도록 만들어주면 설치가 완료된다. 윈도우즈 쓰시는 분들은 저장할 때 도스파일 형식으로 바뀌지 않도록 주의해주세요. wget 권장합니다.
- 환경 변수 APP_OPTIM 을 release 또는 debug 으로 설정할 수 있다. APP_OPTIM이 정의되지 않은 경우, 명령행 옵션 -DNDEBUG 가 주어지면 release 모드로, 그렇지 않으면 debug 모드로 빌드 한다.
- 환경변수 TARGET_PLATFORM 을 통해 플랫폼을 지정할 수 있다. 기본은 android-3 이고 android-4 를 지정할 수도 있다.
- 환경변수 LOCAL_ARM_MODE 를 thumb 또는 arm 으로 설정할 수 있다. 정의되지 않은 경우 release 모드에서는 thumb 가, debug 모드에서는 arm이 선택된다.
- 환경변수 ALLOW_UNDEFINED 를 yes로설정하면링크시에 –-no-undefined 옵션을제거한다.
- 공유 라이브러리를 빌드하는 명령행 옵션은 -shared 다.
- 버전은 호환되는 NDK 버전 뒤에 숫자를 붙여서 나타낸다. 즉 1.6.0 은 NDK 1.6과 호환되는 첫 번째 버전이다.
#!/usr/bin/env python
# coding=utf-8
#
# android-gcc 1.6.0
# http://www.flowdas.com/android-gcc
# Copyright (c) 2009, 오 동권(Dong-gweon Oh) prospero@flowdas.com
#
import sys, os
def cache(fn):
def cached_fn():
if not hasattr(fn, 'cache'):
fn.cache = fn()
return fn.cache
return cached_fn
def substitute(command):
return os.popen(command).read().strip()
@cache
def TARGET_PLATFORM():
return os.environ.get('TARGET_PLATFORM', 'android-3')
@cache
def SYSROOT():
for dir in os.environ.get('PATH', '').split(os.pathsep):
if not dir: continue
if os.path.exists(os.path.join(dir, target_compiler())):
toolroot = os.path.abspath(os.path.join(dir, '..'))
return os.path.abspath(os.path.join(toolroot, '../../../platforms/%s/arch-arm' % TARGET_PLATFORM()))
sys.exit('Cannot determine SYSROOT')
@cache
def target_libgcc():
return substitute('arm-eabi-gcc -mthumb-interwork -print-libgcc-file-name')
@cache
def CPPFLAGS():
return [
'-I%s/usr/include' % SYSROOT(),
'-D__ARM_ARCH_5__ -D__ARM_ARCH_5T__ -D__ARM_ARCH_5E__ -D__ARM_ARCH_5TE__ -DANDROID',
]
@cache
def CFLAGS_options():
flags = []
APP_OPTIM = os.environ.get('APP_OPTIM')
if APP_OPTIM is None:
if '-DNDEBUG' in sys.argv[1:]:
release = True
else:
release = False
elif APP_OPTIM == 'release':
release = True
elif APP_OPTIM == 'debug':
release = False
else:
sys.exit('Unknown APP_OPTIM')
LOCAL_ARM_MODE = os.environ.get('LOCAL_ARM_MODE')
if LOCAL_ARM_MODE is None:
thumb = release
elif LOCAL_ARM_MODE == 'thumb':
thumb = True
elif LOCAL_ARM_MODE == 'arm':
thumb = False
else:
sys.exit('Unknown LOCAL_ARM_MODE')
if release:
if '-DNDEBUG' not in sys.argv[1:]:
flags.append('-DNDEBUG')
flags.append('-fomit-frame-pointer')
if thumb:
flags.append('-Os')
else:
flags.append('-O2')
else:
flags.append('-O0 -fno-omit-frame-pointer')
if thumb:
flags.append('-mthumb -fno-strict-aliasing -finline-limit=64')
else:
flags.append('-marm -fstrict-aliasing -funswitch-loops -finline-limit=300')
return flags
@cache
def CFLAGS():
cflags = [
CPPFLAGS(),
'-march=armv5te -mtune=xscale -msoft-float -fpic -mthumb-interwork -ffunction-sections -funwind-tables -fstack-protector -fno-short-enums -g',
CFLAGS_options(),
CXXFLAGS(),
]
return cflags
@cache
def CXXFLAGS():
if target_compiler() == 'arm-eabi-g++':
return '-fno-exceptions -fno-rtti'
else:
return []
@cache
def no_undefined():
if os.environ.get('ALLOW_UNDEFINED') == 'yes':
return ''
else:
return '-Wl,--no-undefined'
@cache
def LDFLAGS():
return '-nostdlib -Bdynamic -Wl,-dynamic-linker,/system/bin/linker -Wl,--gc-sections -Wl,-z,nocopyreloc %(SYSROOT)s/usr/lib/libc.so %(SYSROOT)s/usr/lib/libstdc++.so %(SYSROOT)s/usr/lib/libm.so %(SYSROOT)s/usr/lib/crtbegin_dynamic.o -L%(SYSROOT)s/usr/lib' % {'SYSROOT' : SYSROOT()}
@cache
def target_libgcc():
return substitute('arm-eabi-gcc -mthumb-interwork -print-libgcc-file-name')
@cache
def LDFLAGS_epilog():
return [
no_undefined(),
'-Wl,-rpath-link=%(SYSROOT)s/usr/lib %(libgcc)s %(SYSROOT)s/usr/lib/crtend_android.o' % {'SYSROOT':SYSROOT(), 'libgcc':target_libgcc()},
]
@cache
def LDSHFLAGS():
return '-nostdlib -Wl,-shared,-Bsymbolic -L%(SYSROOT)s/usr/lib' % {'SYSROOT':SYSROOT()}
@cache
def LDSHFLAGS_epilog():
return [
'-Wl,--whole-archive -Wl,--no-whole-archive %(SYSROOT)s/usr/lib/libc.so %(SYSROOT)s/usr/lib/libstdc++.so %(SYSROOT)s/usr/lib/libm.so' % {'SYSROOT':SYSROOT()},
no_undefined(),
'-Wl,-rpath-link=%(SYSROOT)s/usr/lib %(libgcc)s' % {'SYSROOT':SYSROOT(), 'libgcc':target_libgcc()},
]
@cache
def target_compiler():
return os.path.basename(sys.argv[0]).replace('android-', 'arm-eabi-')
def cmd_build_empty():
return [
target_compiler(),
sys.argv[1:],
]
def cmd_build_executable():
return [
target_compiler(),
CFLAGS(),
LDFLAGS(),
sys.argv[1:],
LDFLAGS_epilog(),
]
def cmd_build_c():
return [
target_compiler(),
CFLAGS(),
sys.argv[1:],
]
def cmd_build_E():
return [
target_compiler(),
CPPFLAGS(),
sys.argv[1:],
]
def cmd_build_S():
return cmd_build_c()
def cmd_build_shared():
argv = sys.argv[1:]
argv.remove('-shared')
return [
target_compiler(),
CFLAGS(),
LDSHFLAGS(),
argv,
LDSHFLAGS_epilog(),
]
def cmd_build():
mode = None
for opt in ('-c', '-E', '-S', '-shared'):
if opt in sys.argv[1:]:
mode = opt
break
if mode is None:
mode = '-empty'
skip = False
for arg in sys.argv[1:]:
if arg.startswith('-'):
if arg in ('-o', '-x'):
skip = True
elif skip:
skip = False
else:
mode = '-executable'
break
return eval('cmd_build_%s()' % mode[1:]),
def deepjoin(obj):
if type(obj) == type(''): return obj
return ' '.join(map(deepjoin, obj))
cmd = deepjoin(cmd_build())
cmd = cmd.replace('"', '"\\"')
os.system(cmd)