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.
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.
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.
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
30 class NativeTestApkGenerator(object):
31 """Generate a native test apk source tree.
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...)
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
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
50 _REPLACEME_FILES
= ['AndroidManifest.xml',
51 'native_test_apk.xml',
52 'res/values/strings.xml']
54 def __init__(self
, native_library
, strip_binary
, output_directory
,
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
)
65 def _LibraryRoot(self
):
66 """Return a root name for a shared library.
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.
72 rootfinder
= re
.match('.?lib(.+).so',
73 os
.path
.basename(self
._native
_library
))
75 return rootfinder
.group(1)
79 def _CopyTemplateFilesAndClearDir(self
):
80 """Copy files needed to build a new apk.
82 Uses rsync to avoid unnecessary io. This call also clears outstanding
83 files in the directory.
85 srcdir
= os
.path
.abspath(os
.path
.dirname(__file__
))
86 destdir
= self
._output
_directory
87 if not os
.path
.exists(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
))
96 def _ReplaceStrings(self
):
97 """Replace 'replaceme' strings in generated files with a root libname.
99 If we have no root libname (e.g. no shlib was specified), do nothing.
101 if not self
._root
_name
:
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
)
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
):
117 dest
= os
.path
.join(destdir
, os
.path
.basename(self
._native
_library
))
118 logging
.info('strip %s --> %s', self
._native
_library
, dest
)
120 [self
._strip
_binary
, '--strip-unneeded', self
._native
_library
, '-o',
123 def CreateBundle(self
):
124 """Create the apk bundle source and assemble components."""
125 self
._CopyTemplateFilesAndClearDir
()
126 self
._ReplaceStrings
()
129 def Compile(self
, ant_args
):
130 """Build the generated apk with ant.
133 ant_args: extra args to pass to ant
138 cmd
.append("-DAPP_ABI=" + self
._target
_abi
)
139 cmd
.extend(['-buildfile',
140 os
.path
.join(self
._output
_directory
, 'native_test_apk.xml')])
142 p
= subprocess
.Popen(cmd
, stderr
=subprocess
.STDOUT
)
143 (stdout
, _
) = p
.communicate()
145 if p
.returncode
!= 0:
146 logging
.error('Ant return code %d', p
.returncode
)
147 sys
.exit(p
.returncode
)
150 parser
= optparse
.OptionParser()
151 parser
.add_option('--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')
166 options
, _
= parser
.parse_args(argv
)
168 # It is not an error to specify no native library; the apk should
169 # still be generated and build. It will, however, print
170 # NATIVE_LOADER_FAILED when run.
171 if not options
.output
:
172 raise Exception('No output directory specified for generated files')
175 logging
.basicConfig(level
=logging
.DEBUG
, format
=' %(message)s')
177 if not options
.strip_binary
:
178 options
.strip_binary
= os
.getenv('STRIP')
179 if not options
.strip_binary
:
180 raise Exception('No tool for stripping the libraries has been supplied')
182 # Remove all quotes from the jars string and pass the list to ant as
184 # TODO(cjhopman): Remove this when all targets pass the list of jars as an
188 jar_list
= options
.jars
.replace('"', '').split()
189 options
.ant_args
.append('-DINPUT_JARS_PATHS=' + " ".join(jar_list
))
192 ntag
= NativeTestApkGenerator(native_library
=options
.native_library
,
193 strip_binary
=options
.strip_binary
,
194 output_directory
=options
.output
,
195 target_abi
=options
.app_abi
)
197 ntag
.Compile(options
.ant_args
)
198 logging
.info('COMPLETE.')
200 if __name__
== '__main__':
201 sys
.exit(main(sys
.argv
))