1 # 2 # Copyright (C) 2015 The Android Open Source Project 3 # 4 # Licensed under the Apache License, Version 2.0 (the "License"); 5 # you may not use this file except in compliance with the License. 6 # You may obtain a copy of the License at 7 # 8 # http://www.apache.org/licenses/LICENSE-2.0 9 # 10 # Unless required by applicable law or agreed to in writing, software 11 # distributed under the License is distributed on an "AS IS" BASIS, 12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 # See the License for the specific language governing permissions and 14 # limitations under the License. 15 # 16 import argparse 17 import multiprocessing 18 import os 19 import shutil 20 import subprocess 21 import sys 22 import tempfile 23 import zipfile 24 25 26 THIS_DIR = os.path.realpath(os.path.dirname(__file__)) 27 28 29 # TODO: Make the x86 toolchain names just be the triple. 30 ALL_TOOLCHAINS = ( 31 'arm-linux-androideabi', 32 'aarch64-linux-android', 33 'mipsel-linux-android', 34 'mips64el-linux-android', 35 'x86', 36 'x86_64', 37 ) 38 39 40 ALL_TRIPLES = ( 41 'arm-linux-androideabi', 42 'aarch64-linux-android', 43 'mipsel-linux-android', 44 'mips64el-linux-android', 45 'i686-linux-android', 46 'x86_64-linux-android', 47 ) 48 49 50 ALL_ARCHITECTURES = ( 51 'arm', 52 'arm64', 53 'mips', 54 'mips64', 55 'x86', 56 'x86_64', 57 ) 58 59 60 ALL_ABIS = ( 61 'armeabi', 62 'armeabi-v7a', 63 'armeabi-v7a-hard', 64 'arm64-v8a', 65 'mips', 66 'mips64', 67 'x86', 68 'x86_64', 69 ) 70 71 72 def arch_to_toolchain(arch): 73 return dict(zip(ALL_ARCHITECTURES, ALL_TOOLCHAINS))[arch] 74 75 76 def arch_to_triple(arch): 77 return dict(zip(ALL_ARCHITECTURES, ALL_TRIPLES))[arch] 78 79 80 def toolchain_to_arch(toolchain): 81 return dict(zip(ALL_TOOLCHAINS, ALL_ARCHITECTURES))[toolchain] 82 83 84 def arch_to_abis(arch): 85 return { 86 'arm': ['armeabi', 'armeabi-v7a', 'armeabi-v7a-hard'], 87 'arm64': ['arm64-v8a'], 88 'mips': ['mips'], 89 'mips64': ['mips64'], 90 'x86': ['x86'], 91 'x86_64': ['x86_64'], 92 }[arch] 93 94 95 def abi_to_arch(arch): 96 return { 97 'armeabi': 'arm', 98 'armeabi-v7a': 'arm', 99 'armeabi-v7a-hard': 'arm', 100 'arm64-v8a': 'arm64', 101 'mips': 'mips', 102 'mips64': 'mips64', 103 'x86': 'x86', 104 'x86_64': 'x86_64', 105 }[arch] 106 107 108 def android_path(*args): 109 top = os.path.realpath(os.path.join(THIS_DIR, '../../..')) 110 return os.path.normpath(os.path.join(top, *args)) 111 112 113 def sysroot_path(toolchain): 114 arch = toolchain_to_arch(toolchain) 115 version = default_api_level(arch) 116 117 prebuilt_ndk = 'prebuilts/ndk/current' 118 sysroot_subpath = 'platforms/android-{}/arch-{}'.format(version, arch) 119 return android_path(prebuilt_ndk, sysroot_subpath) 120 121 122 def ndk_path(*args): 123 return android_path('ndk', *args) 124 125 126 def toolchain_path(*args): 127 return android_path('toolchain', *args) 128 129 130 def default_api_level(arch): 131 if '64' in arch: 132 return 21 133 else: 134 return 9 135 136 137 def jobs_arg(): 138 return '-j{}'.format(multiprocessing.cpu_count() * 2) 139 140 141 def build(cmd, args, intermediate_package=False): 142 package_dir = args.out_dir if intermediate_package else args.dist_dir 143 common_args = [ 144 '--verbose', 145 '--package-dir={}'.format(package_dir), 146 ] 147 148 build_env = dict(os.environ) 149 build_env['NDK_BUILDTOOLS_PATH'] = android_path('ndk/build/tools') 150 build_env['ANDROID_NDK_ROOT'] = ndk_path() 151 subprocess.check_call(cmd + common_args, env=build_env) 152 153 154 def _get_dir_from_env(default, env_var): 155 path = os.path.realpath(os.getenv(env_var, default)) 156 if not os.path.isdir(path): 157 os.makedirs(path) 158 return path 159 160 161 def get_out_dir(): 162 return _get_dir_from_env(android_path('out'), 'OUT_DIR') 163 164 165 def get_dist_dir(out_dir): 166 return _get_dir_from_env(os.path.join(out_dir, 'dist'), 'DIST_DIR') 167 168 169 def get_default_host(): 170 if sys.platform in ('linux', 'linux2'): 171 return 'linux' 172 elif sys.platform == 'darwin': 173 return 'darwin' 174 else: 175 raise RuntimeError('Unsupported host: {}'.format(sys.platform)) 176 177 178 def host_to_tag(host): 179 if host in ['darwin', 'linux']: 180 return host + '-x86_64' 181 elif host == 'windows': 182 return 'windows' 183 elif host == 'windows64': 184 return 'windows-x86_64' 185 else: 186 raise RuntimeError('Unsupported host: {}'.format(host)) 187 188 189 def make_repo_prop(out_dir): 190 file_name = 'repo.prop' 191 192 dist_dir = os.environ.get('DIST_DIR') 193 if dist_dir is not None: 194 dist_repo_prop = os.path.join(dist_dir, file_name) 195 shutil.copy(dist_repo_prop, out_dir) 196 else: 197 out_file = os.path.join(out_dir, file_name) 198 with open(out_file, 'w') as prop_file: 199 cmd = [ 200 'repo', 'forall', '-c', 201 'echo $REPO_PROJECT $(git rev-parse HEAD)', 202 ] 203 subprocess.check_call(cmd, stdout=prop_file) 204 205 206 def make_package(name, directory, out_dir): 207 """Pacakges an NDK module for release. 208 209 Makes a zipfile of the single NDK module that can be released in the SDK 210 manager. This will handle the details of creating the repo.prop file for 211 the package. 212 213 Args: 214 name: Name of the final package, excluding extension. 215 directory: Directory to be packaged. 216 out_dir: Directory to place package. 217 """ 218 if not os.path.isdir(directory): 219 raise ValueError('directory must be a directory: ' + directory) 220 221 path = os.path.join(out_dir, name + '.zip') 222 if os.path.exists(path): 223 os.unlink(path) 224 225 cwd = os.getcwd() 226 os.chdir(os.path.dirname(directory)) 227 basename = os.path.basename(directory) 228 try: 229 subprocess.check_call( 230 ['zip', '-x', '*.pyc', '-x', '*.pyo', '-x', '*.swp', 231 '-x', '*.git*', '-9qr', path, basename]) 232 finally: 233 os.chdir(cwd) 234 235 with zipfile.ZipFile(path, 'a', zipfile.ZIP_DEFLATED) as zip_file: 236 tmpdir = tempfile.mkdtemp() 237 try: 238 make_repo_prop(tmpdir) 239 arcname = os.path.join(basename, 'repo.prop') 240 zip_file.write(os.path.join(tmpdir, 'repo.prop'), arcname) 241 finally: 242 shutil.rmtree(tmpdir) 243 244 245 class ArgParser(argparse.ArgumentParser): 246 def __init__(self): 247 super(ArgParser, self).__init__() 248 249 self.add_argument( 250 '--host', choices=('darwin', 'linux', 'windows', 'windows64'), 251 default=get_default_host(), 252 help='Build binaries for given OS (e.g. linux).') 253 254 self.add_argument( 255 '--out-dir', help='Directory to place temporary build files.', 256 type=os.path.realpath, default=get_out_dir()) 257 258 # The default for --dist-dir has to be handled after parsing all 259 # arguments because the default is derived from --out-dir. This is 260 # handled in run(). 261 self.add_argument( 262 '--dist-dir', help='Directory to place the packaged artifact.', 263 type=os.path.realpath) 264 265 266 def run(main_func, arg_parser=ArgParser): 267 if 'ANDROID_BUILD_TOP' not in os.environ: 268 top = os.path.join(os.path.dirname(__file__), '../../..') 269 os.environ['ANDROID_BUILD_TOP'] = os.path.realpath(top) 270 271 args = arg_parser().parse_args() 272 273 if args.dist_dir is None: 274 args.dist_dir = get_dist_dir(args.out_dir) 275 276 # We want any paths to be relative to the invoked build script. 277 main_filename = os.path.realpath(sys.modules['__main__'].__file__) 278 os.chdir(os.path.dirname(main_filename)) 279 280 main_func(args) 281