3 # Copyright 2015 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 """Install *_incremental.apk targets as well as their dependent files."""
17 sys
.path
.append(os
.path
.join(os
.path
.dirname(__file__
), os
.pardir
))
18 from devil
.android
import apk_helper
19 from devil
.android
import device_utils
20 from devil
.android
import device_errors
21 from devil
.android
.sdk
import version_codes
22 from devil
.utils
import reraiser_thread
23 from pylib
import constants
24 from pylib
.utils
import run_tests_helper
25 from pylib
.utils
import time_profile
27 sys
.path
.append(os
.path
.join(os
.path
.dirname(__file__
), os
.pardir
, 'gyp'))
28 from util
import build_utils
31 def _TransformDexPaths(paths
):
32 """Given paths like ["/a/b/c", "/a/c/d"], returns ["b.c", "c.d"]."""
33 prefix_len
= len(os
.path
.commonprefix(paths
))
34 return [p
[prefix_len
:].replace(os
.sep
, '.') for p
in paths
]
38 parser
= argparse
.ArgumentParser()
39 parser
.add_argument('apk_path',
40 help='The path to the APK to install.')
41 parser
.add_argument('--split',
44 help='A glob matching the apk splits. '
45 'Can be specified multiple times.')
46 parser
.add_argument('--lib-dir',
47 help='Path to native libraries directory.')
48 parser
.add_argument('--dex-files',
49 help='List of dex files to push.',
52 parser
.add_argument('-d', '--device', dest
='device',
53 help='Target device for apk to install on.')
54 parser
.add_argument('--uninstall',
57 help='Remove the app and all side-loaded files.')
58 parser
.add_argument('--output-directory',
59 help='Path to the root build directory.')
60 parser
.add_argument('--no-threading',
63 help='Do not install and push concurrently')
64 parser
.add_argument('-v',
69 help='Verbose level (multiple times for more)')
71 args
= parser
.parse_args()
73 run_tests_helper
.SetLogLevel(args
.verbose_count
)
74 constants
.SetBuildType('Debug')
75 if args
.output_directory
:
76 constants
.SetOutputDirectory(args
.output_directory
)
78 main_timer
= time_profile
.TimeProfile()
79 install_timer
= time_profile
.TimeProfile()
80 push_native_timer
= time_profile
.TimeProfile()
81 push_dex_timer
= time_profile
.TimeProfile()
84 # Retries are annoying when commands fail for legitimate reasons. Might want
85 # to enable them if this is ever used on bots though.
86 device
= device_utils
.DeviceUtils(args
.device
, default_retries
=0)
88 devices
= device_utils
.DeviceUtils
.HealthyDevices(default_retries
=0)
90 raise device_errors
.NoDevicesError()
91 elif len(devices
) == 1:
94 all_devices
= device_utils
.DeviceUtils
.parallel(devices
)
95 msg
= ('More than one device available.\n'
96 'Use --device=SERIAL to select a device.\n'
97 'Available devices:\n')
98 descriptions
= all_devices
.pMap(lambda d
: d
.build_description
).pGet(None)
99 for d
, desc
in zip(devices
, descriptions
):
100 msg
+= ' %s (%s)\n' % (d
, desc
)
103 apk_help
= apk_helper
.ApkHelper(args
.apk_path
)
104 apk_package
= apk_help
.GetPackageName()
105 device_incremental_dir
= '/data/local/tmp/incremental-app-%s' % apk_package
108 device
.Uninstall(apk_package
)
109 device
.RunShellCommand(['rm', '-rf', device_incremental_dir
],
111 logging
.info('Uninstall took %s seconds.', main_timer
.GetDelta())
114 if device
.build_version_sdk
>= version_codes
.MARSHMALLOW
:
115 if apk_help
.HasIsolatedProcesses():
116 raise Exception('Cannot use perform incremental installs on Android M+ '
117 'without first disabling isolated processes. Use GN arg: '
118 'disable_incremental_isolated_processes=true to do so.')
120 # Install .apk(s) if any of them have changed.
122 install_timer
.Start()
125 for split_glob
in args
.splits
:
126 splits
.extend((f
for f
in glob
.glob(split_glob
)))
127 device
.InstallSplitApk(args
.apk_path
, splits
, reinstall
=True,
128 allow_cached_props
=True)
130 device
.Install(args
.apk_path
, reinstall
=True)
131 install_timer
.Stop(log
=False)
133 # Push .so and .dex files to the device (if they have changed).
136 push_native_timer
.Start()
137 device_lib_dir
= posixpath
.join(device_incremental_dir
, 'lib')
138 device
.PushChangedFiles([(args
.lib_dir
, device_lib_dir
)],
139 delete_device_stale
=True)
140 push_native_timer
.Stop(log
=False)
143 push_dex_timer
.Start()
144 # Put all .dex files to be pushed into a temporary directory so that we
145 # can use delete_device_stale=True.
146 with build_utils
.TempDir() as temp_dir
:
147 device_dex_dir
= posixpath
.join(device_incremental_dir
, 'dex')
148 # Ensure no two files have the same name.
149 transformed_names
= _TransformDexPaths(args
.dex_files
)
150 for src_path
, dest_name
in zip(args
.dex_files
, transformed_names
):
151 shutil
.copyfile(src_path
, os
.path
.join(temp_dir
, dest_name
))
152 device
.PushChangedFiles([(temp_dir
, device_dex_dir
)],
153 delete_device_stale
=True)
154 push_dex_timer
.Stop(log
=False)
156 # Create 2 lock files:
157 # * install.lock tells the app to pause on start-up (until we release it).
158 # * firstrun.lock is used by the app to pause all secondary processes until
159 # the primary process finishes loading the .dex / .so files.
160 def create_lock_files():
161 # Creates or zeros out lock files.
164 'echo -n >$D/install.lock 2>$D/firstrun.lock')
165 device
.RunShellCommand(cmd
% device_incremental_dir
, check_return
=True)
167 # The firstrun.lock is released by the app itself.
168 def release_installer_lock():
169 device
.RunShellCommand('echo > %s/install.lock' % device_incremental_dir
,
173 # Concurrency here speeds things up quite a bit, but DeviceUtils hasn't
174 # been designed for multi-threading. Enabling only because this is a
175 # developer-only tool.
176 if args
.no_threading
:
180 reraiser_thread
.RunAsync((do_install
, do_push_files
))
181 release_installer_lock()
182 logging
.info('Took %s seconds (install=%s, libs=%s, dex=%s)',
183 main_timer
.GetDelta(), install_timer
.GetDelta(),
184 push_native_timer
.GetDelta(), push_dex_timer
.GetDelta())
187 if __name__
== '__main__':