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