Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #!/usr/bin/python | |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 3 # Use of this source code is governed by a BSD-style license that can be | |
| 4 # found in the LICENSE file. | |
| 5 | |
| 6 # On Android we build unit test bundles as shared libraries. To run | |
| 7 # tests, we launch a special "test runner" apk which loads the library | |
| 8 # then jumps into it. Since java is required for many tests | |
| 9 # (e.g. PathUtils.java), a "pure native" test bundle is inadequate. | |
| 10 # | |
| 11 # This script, generate_native_test.py, is used to generate the source | |
| 12 # for an apk that wraps a unit test shared library bundle. That | |
| 13 # allows us to have a single boiler-plate application be used across | |
| 14 # all unit test bundles. | |
|
bulach
2012/04/12 15:51:42
as above, I'd suggest calling ant and building the
| |
| 15 | |
| 16 import logging | |
| 17 import optparse | |
| 18 import os | |
| 19 import re | |
| 20 import shutil | |
| 21 import sys | |
| 22 | |
| 23 | |
| 24 class NativeTestApkGenerator(object): | |
| 25 """Generate a native test apk source tree. | |
| 26 | |
| 27 TODO(jrg): develop this more so the activity name is replaced as | |
| 28 well. That will allow multiple test runners to be installed at the | |
| 29 same time. (The complication is that it involves renaming a java | |
| 30 class, which implies regeneration of a jni header, and on and on...) | |
| 31 """ | |
| 32 | |
| 33 # Files or directories we need to copy to create a complete apk test shell. | |
| 34 _SOURCE_FILES = ['AndroidManifest.xml', | |
| 35 'native_test_apk.xml', | |
| 36 'res', # res/values/strings.xml | |
| 37 'java', # .../ChromeNativeTestActivity.java | |
| 38 ] | |
| 39 | |
| 40 # Files in the destion directory that have a "replaceme" string | |
| 41 # which should be replaced by the basename of the shared library. | |
| 42 # Note we also update the filename if 'replaceme' is itself found in | |
| 43 # the filename. | |
| 44 _REPLACEME_FILES = ['AndroidManifest.xml', | |
| 45 'native_test_apk.xml', | |
| 46 'res/values/strings.xml'] | |
| 47 | |
| 48 def __init__(self, native_library, jars, output_directory): | |
| 49 self._native_library = native_library | |
| 50 self._jars = jars | |
| 51 self._output_directory = output_directory | |
| 52 self._root_name = None | |
| 53 if self._native_library: | |
| 54 self._root_name = self._LibraryRoot() | |
| 55 logging.warn('root name: %s' % self._root_name) | |
| 56 | |
| 57 | |
| 58 def _LibraryRoot(self): | |
| 59 """Return a root name for a shared library. | |
| 60 | |
| 61 The root name should be suitable for substitution in apk files | |
| 62 like the manifest. For example, blah/foo/libbase_unittests.so | |
| 63 becomes base_unittests. | |
| 64 """ | |
| 65 rootfinder = re.match('.?lib(.+).so', | |
| 66 os.path.basename(self._native_library)) | |
| 67 if rootfinder: | |
| 68 return rootfinder.group(1) | |
| 69 else: | |
| 70 return None | |
| 71 | |
| 72 def _CopyTemplateFiles(self): | |
| 73 """Copy files needed to build a new apk. | |
| 74 | |
| 75 TODO(jrg): add more smarts so we don't change file timestamps if | |
| 76 the files don't change? | |
| 77 """ | |
| 78 srcdir = os.path.dirname(os.path.realpath( __file__)) | |
| 79 if not os.path.exists(self._output_directory): | |
| 80 os.makedirs(self._output_directory) | |
| 81 for f in self._SOURCE_FILES: | |
| 82 src = os.path.join(srcdir, f) | |
| 83 dest = os.path.join(self._output_directory, f) | |
| 84 if os.path.isfile(src): | |
| 85 if os.path.exists(dest): | |
| 86 os.remove(dest) | |
| 87 logging.warn('%s --> %s' % (src, dest)) | |
| 88 shutil.copyfile(src, dest) | |
| 89 else: # directory | |
| 90 if os.path.exists(dest): | |
| 91 # One more sanity check since we're deleting a directory... | |
| 92 if not '/out/' in dest: | |
| 93 raise Exception('Unbelievable output directory; bailing for safety') | |
|
bulach
2012/04/12 15:51:42
nit: probably best to do this sanity check at the
John Grabowski
2012/04/13 01:11:37
Makes me feel safer to do it "right before" the rm
| |
| 94 shutil.rmtree(dest) | |
| 95 logging.warn('%s --> %s' % (src, dest)) | |
| 96 shutil.copytree(src, dest) | |
| 97 | |
| 98 def _ReplaceStrings(self): | |
| 99 """Replace 'replaceme' strings in generated files with a root libname. | |
| 100 | |
| 101 If we have no root libname (e.g. no shlib was specified), do nothing. | |
| 102 """ | |
| 103 if not self._root_name: | |
| 104 return | |
| 105 logging.warn('Replacing \'replaceme\' with ' + self._root_name) | |
|
bulach
2012/04/12 15:51:42
nit: use " inside the string, avoids escaping..
John Grabowski
2012/04/13 01:11:37
Done.
| |
| 106 for f in self._REPLACEME_FILES: | |
| 107 dest = os.path.join(self._output_directory, f) | |
| 108 contents = open(dest).read() | |
| 109 contents = contents.replace('replaceme', self._root_name) | |
| 110 dest = dest.replace('replaceme', self._root_name) # update the filename! | |
| 111 open(dest, "w").write(contents) | |
| 112 | |
| 113 def _CopyLibraryAndJars(self): | |
| 114 """Copy the shlib and jars into the apk (if relevant)""" | |
|
bulach
2012/04/12 15:51:42
not sure what you mean "into the apk".. the apk it
John Grabowski
2012/04/13 01:11:37
clarified
| |
| 115 if self._native_library: | |
| 116 destdir = os.path.join(self._output_directory, 'libs/armeabi') | |
| 117 if not os.path.exists(destdir): | |
| 118 os.makedirs(destdir) | |
| 119 dest = os.path.join(destdir, os.path.basename(self._native_library)) | |
| 120 logging.warn('%s --> %s' % (self._native_library, dest)) | |
| 121 shutil.copyfile(self._native_library, dest) | |
| 122 if self._jars: | |
| 123 destdir = os.path.join(self._output_directory, 'libs') | |
| 124 if not os.path.exists(destdir): | |
| 125 os.makedirs(destdir) | |
| 126 for jar in self._jars.split(','): | |
| 127 dest = os.path.join(destdir, os.path.basename(jar)) | |
| 128 logging.warn('%s --> %s' % (jar, dest)) | |
| 129 shutil.copyfile(jar, dest) | |
| 130 | |
| 131 def CreateBundle(self): | |
| 132 self._CopyTemplateFiles() | |
| 133 self._ReplaceStrings() | |
| 134 self._CopyLibraryAndJars() | |
| 135 | |
| 136 | |
| 137 def main(argv): | |
| 138 parser = optparse.OptionParser() | |
| 139 parser.add_option('--verbose', | |
| 140 help='Be verbose') | |
| 141 parser.add_option('--native_library', | |
| 142 help='Full name of native shared library test bundle') | |
| 143 parser.add_option('--jars', | |
| 144 help='Comma seperated list of jars to include') | |
|
bulach
2012/04/12 15:51:42
nit: instead of splitting here, maybe just use --j
John Grabowski
2012/04/13 01:11:37
Done.
| |
| 145 parser.add_option('--output', | |
| 146 help='Output directory for generated files.') | |
| 147 options, args = parser.parse_args(argv) # pylint: disable=W0612 | |
|
bulach
2012/04/12 15:51:42
is W0612 unused locals? would replacing args with
John Grabowski
2012/04/13 01:11:37
Done.
| |
| 148 | |
| 149 # It is not an error to specify no native library; the apk should | |
| 150 # still be generated and build. It will, however, print | |
| 151 # NATIVE_LOADER_FAILED wnen run. | |
|
Yaron
2012/04/12 01:29:53
s/wnen/when
John Grabowski
2012/04/13 01:11:37
Done.
| |
| 152 if not options.output: | |
| 153 raise Exception('No output directory specified for generated files') | |
| 154 | |
| 155 if options.verbose: | |
| 156 logging.basicConfig(level=logging.DEBUG, format=' %(message)s') | |
| 157 | |
| 158 ntag = NativeTestApkGenerator(native_library=options.native_library, | |
| 159 jars=options.jars, | |
| 160 output_directory=options.output) | |
| 161 ntag.CreateBundle() | |
| 162 logging.warn('COMPLETE.') | |
| 163 | |
| 164 if __name__ == '__main__': | |
| 165 sys.exit(main(sys.argv)) | |
| OLD | NEW |