| OLD | NEW |
| (Empty) |
| 1 #!/usr/bin/env python | |
| 2 # | |
| 3 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 4 # Use of this source code is governed by a BSD-style license that can be | |
| 5 # found in the LICENSE file. | |
| 6 | |
| 7 # On Android we build unit test bundles as shared libraries. To run | |
| 8 # tests, we launch a special "test runner" apk which loads the library | |
| 9 # then jumps into it. Since java is required for many tests | |
| 10 # (e.g. PathUtils.java), a "pure native" test bundle is inadequate. | |
| 11 # | |
| 12 # This script, generate_native_test.py, is used to generate the source | |
| 13 # for an apk that wraps a unit test shared library bundle. That | |
| 14 # allows us to have a single boiler-plate application be used across | |
| 15 # all unit test bundles. | |
| 16 | |
| 17 import logging | |
| 18 import optparse | |
| 19 import os | |
| 20 import re | |
| 21 import subprocess | |
| 22 import sys | |
| 23 | |
| 24 # cmd_helper.py is under ../../build/android/ | |
| 25 sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', | |
| 26 '..', 'build', 'android'))) | |
| 27 from pylib import cmd_helper # pylint: disable=F0401 | |
| 28 | |
| 29 | |
| 30 class NativeTestApkGenerator(object): | |
| 31 """Generate a native test apk source tree. | |
| 32 | |
| 33 TODO(jrg): develop this more so the activity name is replaced as | |
| 34 well. That will allow multiple test runners to be installed at the | |
| 35 same time. (The complication is that it involves renaming a java | |
| 36 class, which implies regeneration of a jni header, and on and on...) | |
| 37 """ | |
| 38 | |
| 39 # Files or directories we need to copy to create a complete apk test shell. | |
| 40 _SOURCE_FILES = ['AndroidManifest.xml', | |
| 41 'native_test_apk.xml', | |
| 42 'res', # res/values/strings.xml | |
| 43 'java', # .../ChromeNativeTestActivity.java | |
| 44 ] | |
| 45 | |
| 46 # Files in the destion directory that have a "replaceme" string | |
| 47 # which should be replaced by the basename of the shared library. | |
| 48 # Note we also update the filename if 'replaceme' is itself found in | |
| 49 # the filename. | |
| 50 _REPLACEME_FILES = ['AndroidManifest.xml', | |
| 51 'native_test_apk.xml', | |
| 52 'res/values/strings.xml'] | |
| 53 | |
| 54 def __init__(self, native_library, strip_binary, output_directory, | |
| 55 target_abi): | |
| 56 self._native_library = native_library | |
| 57 self._strip_binary = strip_binary | |
| 58 self._output_directory = os.path.abspath(output_directory) | |
| 59 self._target_abi = target_abi | |
| 60 self._root_name = None | |
| 61 if self._native_library: | |
| 62 self._root_name = self._LibraryRoot() | |
| 63 logging.info('root name: %s', self._root_name) | |
| 64 | |
| 65 def _LibraryRoot(self): | |
| 66 """Return a root name for a shared library. | |
| 67 | |
| 68 The root name should be suitable for substitution in apk files | |
| 69 like the manifest. For example, blah/foo/libbase_unittests.so | |
| 70 becomes base_unittests. | |
| 71 """ | |
| 72 rootfinder = re.match('.?lib(.+).so', | |
| 73 os.path.basename(self._native_library)) | |
| 74 if rootfinder: | |
| 75 return rootfinder.group(1) | |
| 76 else: | |
| 77 return None | |
| 78 | |
| 79 def _CopyTemplateFilesAndClearDir(self): | |
| 80 """Copy files needed to build a new apk. | |
| 81 | |
| 82 Uses rsync to avoid unnecessary io. This call also clears outstanding | |
| 83 files in the directory. | |
| 84 """ | |
| 85 srcdir = os.path.abspath(os.path.dirname(__file__)) | |
| 86 destdir = self._output_directory | |
| 87 if not os.path.exists(destdir): | |
| 88 os.makedirs(destdir) | |
| 89 elif not '/out/' in destdir: | |
| 90 raise Exception('Unbelievable output directory; bailing for safety') | |
| 91 logging.info('rsync %s --> %s', self._SOURCE_FILES, destdir) | |
| 92 logging.info(cmd_helper.GetCmdOutput( | |
| 93 ['rsync', '-aRv', '--delete', '--exclude', '.svn'] + | |
| 94 self._SOURCE_FILES + [destdir], cwd=srcdir)) | |
| 95 | |
| 96 def _ReplaceStrings(self): | |
| 97 """Replace 'replaceme' strings in generated files with a root libname. | |
| 98 | |
| 99 If we have no root libname (e.g. no shlib was specified), do nothing. | |
| 100 """ | |
| 101 if not self._root_name: | |
| 102 return | |
| 103 logging.info('Replacing "replaceme" with ' + self._root_name) | |
| 104 for f in self._REPLACEME_FILES: | |
| 105 dest = os.path.join(self._output_directory, f) | |
| 106 contents = open(dest).read() | |
| 107 contents = contents.replace('replaceme', self._root_name) | |
| 108 dest = dest.replace('replaceme', self._root_name) # update the filename! | |
| 109 open(dest, 'w').write(contents) | |
| 110 | |
| 111 def _CopyLibrary(self): | |
| 112 """Copy the shlib into the apk source tree (if relevant).""" | |
| 113 if self._native_library: | |
| 114 destdir = os.path.join(self._output_directory, 'libs/' + self._target_abi) | |
| 115 if not os.path.exists(destdir): | |
| 116 os.makedirs(destdir) | |
| 117 dest = os.path.join(destdir, os.path.basename(self._native_library)) | |
| 118 logging.info('strip %s --> %s', self._native_library, dest) | |
| 119 cmd_helper.RunCmd( | |
| 120 [self._strip_binary, '--strip-unneeded', self._native_library, '-o', | |
| 121 dest]) | |
| 122 | |
| 123 def CreateBundle(self): | |
| 124 """Create the apk bundle source and assemble components.""" | |
| 125 self._CopyTemplateFilesAndClearDir() | |
| 126 self._ReplaceStrings() | |
| 127 self._CopyLibrary() | |
| 128 | |
| 129 def Compile(self, ant_args): | |
| 130 """Build the generated apk with ant. | |
| 131 | |
| 132 Args: | |
| 133 ant_args: extra args to pass to ant | |
| 134 """ | |
| 135 cmd = ['ant'] | |
| 136 if ant_args: | |
| 137 cmd.extend(ant_args) | |
| 138 cmd.append("-DAPP_ABI=" + self._target_abi) | |
| 139 cmd.extend(['-buildfile', | |
| 140 os.path.join(self._output_directory, 'native_test_apk.xml')]) | |
| 141 logging.info(cmd) | |
| 142 p = subprocess.Popen(cmd, stderr=subprocess.STDOUT) | |
| 143 (stdout, _) = p.communicate() | |
| 144 logging.info(stdout) | |
| 145 if p.returncode != 0: | |
| 146 logging.error('Ant return code %d', p.returncode) | |
| 147 sys.exit(p.returncode) | |
| 148 | |
| 149 def main(argv): | |
| 150 parser = optparse.OptionParser() | |
| 151 parser.add_option('--verbose', | |
| 152 help='Be verbose') | |
| 153 parser.add_option('--native_library', | |
| 154 help='Full name of native shared library test bundle') | |
| 155 parser.add_option('--jars', | |
| 156 help='Space separated list of jars to be included') | |
| 157 parser.add_option('--output', | |
| 158 help='Output directory for generated files.') | |
| 159 parser.add_option('--app_abi', default='armeabi', | |
| 160 help='ABI for native shared library') | |
| 161 parser.add_option('--strip-binary', | |
| 162 help='Binary to use for stripping the native libraries.') | |
| 163 parser.add_option('--ant-args', action='append', | |
| 164 help='extra args for ant') | |
| 165 parser.add_option('--stamp-file', | |
| 166 help='Path to file to touch on success.') | |
| 167 parser.add_option('--no-compile', action='store_true', | |
| 168 help='Use this flag to disable ant compilation.') | |
| 169 | |
| 170 options, _ = parser.parse_args(argv) | |
| 171 | |
| 172 # It is not an error to specify no native library; the apk should | |
| 173 # still be generated and build. It will, however, print | |
| 174 # NATIVE_LOADER_FAILED when run. | |
| 175 if not options.output: | |
| 176 raise Exception('No output directory specified for generated files') | |
| 177 | |
| 178 if options.verbose: | |
| 179 logging.basicConfig(level=logging.DEBUG, format=' %(message)s') | |
| 180 | |
| 181 if not options.strip_binary: | |
| 182 options.strip_binary = os.getenv('STRIP') | |
| 183 if not options.strip_binary: | |
| 184 raise Exception('No tool for stripping the libraries has been supplied') | |
| 185 | |
| 186 ntag = NativeTestApkGenerator(native_library=options.native_library, | |
| 187 strip_binary=options.strip_binary, | |
| 188 output_directory=options.output, | |
| 189 target_abi=options.app_abi) | |
| 190 | |
| 191 ntag.CreateBundle() | |
| 192 if not options.no_compile: | |
| 193 ntag.Compile(options.ant_args) | |
| 194 | |
| 195 if options.stamp_file: | |
| 196 with file(options.stamp_file, 'a'): | |
| 197 os.utime(options.stamp_file, None) | |
| 198 | |
| 199 logging.info('COMPLETE.') | |
| 200 | |
| 201 if __name__ == '__main__': | |
| 202 sys.exit(main(sys.argv)) | |
| OLD | NEW |