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. | |
15 | |
16 import logging | |
17 import optparse | |
18 import os | |
19 import re | |
20 import shutil | |
21 import subprocess | |
22 import sys | |
23 | |
24 | |
25 class NativeTestApkGenerator(object): | |
26 """Generate a native test apk source tree. | |
27 | |
28 TODO(jrg): develop this more so the activity name is replaced as | |
29 well. That will allow multiple test runners to be installed at the | |
30 same time. (The complication is that it involves renaming a java | |
31 class, which implies regeneration of a jni header, and on and on...) | |
32 """ | |
33 | |
34 # Files or directories we need to copy to create a complete apk test shell. | |
35 _SOURCE_FILES = ['AndroidManifest.xml', | |
36 'native_test_apk.xml', | |
37 'res', # res/values/strings.xml | |
38 'java', # .../ChromeNativeTestActivity.java | |
39 ] | |
40 | |
41 # Files in the destion directory that have a "replaceme" string | |
42 # which should be replaced by the basename of the shared library. | |
43 # Note we also update the filename if 'replaceme' is itself found in | |
44 # the filename. | |
45 _REPLACEME_FILES = ['AndroidManifest.xml', | |
46 'native_test_apk.xml', | |
47 'res/values/strings.xml'] | |
48 | |
49 def __init__(self, native_library, jars, output_directory): | |
50 self._native_library = native_library | |
51 self._jars = jars | |
52 self._output_directory = output_directory | |
53 self._root_name = None | |
54 if self._native_library: | |
55 self._root_name = self._LibraryRoot() | |
56 logging.warn('root name: %s' % self._root_name) | |
57 | |
58 | |
59 def _LibraryRoot(self): | |
60 """Return a root name for a shared library. | |
61 | |
62 The root name should be suitable for substitution in apk files | |
63 like the manifest. For example, blah/foo/libbase_unittests.so | |
64 becomes base_unittests. | |
65 """ | |
66 rootfinder = re.match('.?lib(.+).so', | |
67 os.path.basename(self._native_library)) | |
68 if rootfinder: | |
69 return rootfinder.group(1) | |
70 else: | |
71 return None | |
72 | |
73 def _CopyTemplateFiles(self): | |
74 """Copy files needed to build a new apk. | |
75 | |
76 TODO(jrg): add more smarts so we don't change file timestamps if | |
77 the files don't change? | |
78 """ | |
79 srcdir = os.path.dirname(os.path.realpath( __file__)) | |
80 if not os.path.exists(self._output_directory): | |
81 os.makedirs(self._output_directory) | |
82 for f in self._SOURCE_FILES: | |
83 src = os.path.join(srcdir, f) | |
84 dest = os.path.join(self._output_directory, f) | |
85 if os.path.isfile(src): | |
86 if os.path.exists(dest): | |
87 os.remove(dest) | |
88 logging.warn('%s --> %s' % (src, dest)) | |
89 shutil.copyfile(src, dest) | |
90 else: # directory | |
91 if os.path.exists(dest): | |
92 # One more sanity check since we're deleting a directory... | |
93 if not '/out/' in dest: | |
94 raise Exception('Unbelievable output directory; bailing for safety') | |
95 shutil.rmtree(dest) | |
96 logging.warn('%s --> %s' % (src, dest)) | |
97 shutil.copytree(src, dest) | |
98 | |
99 def _ReplaceStrings(self): | |
100 """Replace 'replaceme' strings in generated files with a root libname. | |
101 | |
102 If we have no root libname (e.g. no shlib was specified), do nothing. | |
103 """ | |
104 if not self._root_name: | |
105 return | |
106 logging.warn('Replacing "replaceme" with ' + self._root_name) | |
107 for f in self._REPLACEME_FILES: | |
108 dest = os.path.join(self._output_directory, f) | |
109 contents = open(dest).read() | |
110 contents = contents.replace('replaceme', self._root_name) | |
111 dest = dest.replace('replaceme', self._root_name) # update the filename! | |
112 open(dest, "w").write(contents) | |
113 | |
114 def _CopyLibraryAndJars(self): | |
115 """Copy the shlib and jars into the apk source tree (if relevant)""" | |
116 if self._native_library: | |
117 destdir = os.path.join(self._output_directory, 'libs/armeabi') | |
118 if not os.path.exists(destdir): | |
119 os.makedirs(destdir) | |
120 dest = os.path.join(destdir, os.path.basename(self._native_library)) | |
121 logging.warn('%s --> %s' % (self._native_library, dest)) | |
122 shutil.copyfile(self._native_library, dest) | |
123 if self._jars: | |
124 destdir = os.path.join(self._output_directory, 'libs') | |
125 if not os.path.exists(destdir): | |
126 os.makedirs(destdir) | |
127 for jar in self._jars: | |
128 dest = os.path.join(destdir, os.path.basename(jar)) | |
129 logging.warn('%s --> %s' % (jar, dest)) | |
130 shutil.copyfile(jar, dest) | |
131 | |
132 def CreateBundle(self): | |
133 """Create the apk bundle source and assemble components.""" | |
134 self._CopyTemplateFiles() | |
135 self._ReplaceStrings() | |
136 self._CopyLibraryAndJars() | |
137 | |
138 def Compile(self, ant_args): | |
139 """Build the generated apk with ant. | |
140 | |
141 Args: | |
142 ant_args: extra args to pass to ant | |
143 """ | |
144 cmd = ['ant'] | |
145 if ant_args: | |
146 cmd.append(ant_args) | |
147 cmd.extend(['-buildfile', | |
148 os.path.join(self._output_directory, 'native_test_apk.xml')]) | |
149 logging.warn(cmd) | |
150 p = subprocess.Popen(cmd, stderr=subprocess.STDOUT) | |
151 (stdout, _) = p.communicate() | |
152 logging.warn(stdout) | |
153 if p.returncode != 0: | |
154 logging.error('Ant return code %d' % p.returncode) | |
155 sys.exit(p.returncode) | |
156 | |
157 | |
158 def main(argv): | |
159 parser = optparse.OptionParser() | |
160 parser.add_option('--verbose', | |
161 help='Be verbose') | |
162 parser.add_option('--native_library', | |
163 help='Full name of native shared library test bundle') | |
164 parser.add_option('--jar', action='append', | |
165 help='Include this jar; can be specified multiple times') | |
166 parser.add_option('--output', | |
167 help='Output directory for generated files.') | |
168 parser.add_option('--ant-compile', action='store_true', | |
169 help='If specified, build the generated apk with ant') | |
170 parser.add_option('--ant-args', | |
171 help='extra args for ant') | |
172 | |
173 options, _ = parser.parse_args(argv) # pylint: disable=W0612 | |
bulach
2012/04/13 09:42:51
nit: remove the pylint annotation?
John Grabowski
2012/04/13 22:51:02
Done.
| |
174 | |
175 # It is not an error to specify no native library; the apk should | |
176 # still be generated and build. It will, however, print | |
177 # NATIVE_LOADER_FAILED when run. | |
178 if not options.output: | |
179 raise Exception('No output directory specified for generated files') | |
180 | |
181 if options.verbose: | |
182 logging.basicConfig(level=logging.DEBUG, format=' %(message)s') | |
183 | |
184 ntag = NativeTestApkGenerator(native_library=options.native_library, | |
185 jars=options.jar, | |
186 output_directory=options.output) | |
187 ntag.CreateBundle() | |
188 if options.ant_compile: | |
189 ntag.Compile(options.ant_args) | |
190 | |
191 logging.warn('COMPLETE.') | |
192 | |
193 if __name__ == '__main__': | |
194 sys.exit(main(sys.argv)) | |
OLD | NEW |