Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(60)

Side by Side Diff: build/android/pylib/android_commands.py

Issue 21307002: [android] Push only updated files in PushIfNeeded when few files have changed. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: addressed comments Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """Provides an interface to communicate with the device via the adb command. 5 """Provides an interface to communicate with the device via the adb command.
6 6
7 Assumes adb binary is currently on system path. 7 Assumes adb binary is currently on system path.
8 """ 8 """
9 9
10 import collections 10 import collections
(...skipping 166 matching lines...) Expand 10 before | Expand all | Expand 10 after
177 if isinstance(utc_offset, str) and len(utc_offset) == 5: 177 if isinstance(utc_offset, str) and len(utc_offset) == 5:
178 utc_delta = datetime.timedelta(hours=int(utc_offset[1:3]), 178 utc_delta = datetime.timedelta(hours=int(utc_offset[1:3]),
179 minutes=int(utc_offset[3:5])) 179 minutes=int(utc_offset[3:5]))
180 if utc_offset[0:1] == '-': 180 if utc_offset[0:1] == '-':
181 utc_delta = -utc_delta 181 utc_delta = -utc_delta
182 lastmod -= utc_delta 182 lastmod -= utc_delta
183 files[filename] = (int(file_match.group('size')), lastmod) 183 files[filename] = (int(file_match.group('size')), lastmod)
184 return files 184 return files
185 185
186 186
187 def _ComputeFileListHash(md5sum_output): 187 def _ComputeFileListHash(md5sum_output):
frankf 2013/08/06 00:17:23 Rename this to _ParseMd5sumOutput()
188 """Returns a list of tuples from the provided md5sum output. 188 """Returns a list of tuples from the provided md5sum output.
189 189
190 Args: 190 Args:
191 md5sum_output: output directly from md5sum binary. 191 md5sum_output: output directly from md5sum binary.
192 192
193 Returns: 193 Returns:
194 List of namedtuples (hash, path). 194 List of namedtuples (hash, path).
frankf 2013/08/06 00:17:23 What is path? Is it absolute or relative? Provide
craigdh 2013/08/06 21:17:29 Done.
195 """ 195 """
196 HashAndPath = collections.namedtuple('HashAndPath', ['hash', 'path']) 196 HashAndPath = collections.namedtuple('HashAndPath', ['hash', 'path'])
197 split_lines = [line.split(' ') for line in md5sum_output] 197 split_lines = [line.split(' ') for line in md5sum_output]
198 return [HashAndPath._make(s) for s in split_lines if len(s) == 2] 198 return [HashAndPath._make(s) for s in split_lines if len(s) == 2]
199 199
200 200
201 def _HasAdbPushSucceeded(command_output): 201 def _HasAdbPushSucceeded(command_output):
202 """Returns whether adb push has succeeded from the provided output.""" 202 """Returns whether adb push has succeeded from the provided output."""
203 # TODO(frankf): We should look at the return code instead of the command 203 # TODO(frankf): We should look at the return code instead of the command
204 # output for many of the commands in this file. 204 # output for many of the commands in this file.
(...skipping 526 matching lines...) Expand 10 before | Expand all | Expand 10 after
731 self.RunShellCommand('pm clear ' + package) 731 self.RunShellCommand('pm clear ' + package)
732 732
733 def SendKeyEvent(self, keycode): 733 def SendKeyEvent(self, keycode):
734 """Sends keycode to the device. 734 """Sends keycode to the device.
735 735
736 Args: 736 Args:
737 keycode: Numeric keycode to send (see "enum" at top of file). 737 keycode: Numeric keycode to send (see "enum" at top of file).
738 """ 738 """
739 self.RunShellCommand('input keyevent %d' % keycode) 739 self.RunShellCommand('input keyevent %d' % keycode)
740 740
741 def CheckMd5Sum(self, local_path, device_path): 741 def _RunMd5Sum(self, host_path, device_path):
frankf 2013/08/06 00:17:23 Why not inline this in GetFilesChanged?
craigdh 2013/08/06 21:17:29 Could, this seems reusable and keeps the functions
742 """Compares the md5sum of a local path against a device path. 742 """Gets the md5sum of a host path and device path.
743 743
744 Args: 744 Args:
745 local_path: Path (file or directory) on the host. 745 host_path: Path (file or directory) on the host.
746 device_path: Path on the device. 746 device_path: Path on the device.
747 747
748 Returns: 748 Returns:
749 True if the md5sums match. 749 A tuple containing lists of the host and device md5sum results as
750 created by _ComputeFileListHash().
750 """ 751 """
751 if not self._md5sum_build_dir: 752 if not self._md5sum_build_dir:
752 default_build_type = os.environ.get('BUILD_TYPE', 'Debug') 753 default_build_type = os.environ.get('BUILD_TYPE', 'Debug')
753 build_dir = '%s/%s/' % ( 754 build_dir = '%s/%s/' % (
754 cmd_helper.OutDirectory().get(), default_build_type) 755 cmd_helper.OutDirectory().get(), default_build_type)
755 md5sum_dist_path = '%s/md5sum_dist' % build_dir 756 md5sum_dist_path = '%s/md5sum_dist' % build_dir
756 if not os.path.exists(md5sum_dist_path): 757 if not os.path.exists(md5sum_dist_path):
757 build_dir = '%s/Release/' % cmd_helper.OutDirectory().get() 758 build_dir = '%s/Release/' % cmd_helper.OutDirectory().get()
758 md5sum_dist_path = '%s/md5sum_dist' % build_dir 759 md5sum_dist_path = '%s/md5sum_dist' % build_dir
759 assert os.path.exists(md5sum_dist_path), 'Please build md5sum.' 760 assert os.path.exists(md5sum_dist_path), 'Please build md5sum.'
760 command = 'push %s %s' % (md5sum_dist_path, MD5SUM_DEVICE_FOLDER) 761 command = 'push %s %s' % (md5sum_dist_path, MD5SUM_DEVICE_FOLDER)
761 assert _HasAdbPushSucceeded(self._adb.SendCommand(command)) 762 assert _HasAdbPushSucceeded(self._adb.SendCommand(command))
762 self._md5sum_build_dir = build_dir 763 self._md5sum_build_dir = build_dir
763 764
764 cmd = (MD5SUM_LD_LIBRARY_PATH + ' ' + self._util_wrapper + ' ' + 765 cmd = (MD5SUM_LD_LIBRARY_PATH + ' ' + self._util_wrapper + ' ' +
765 MD5SUM_DEVICE_PATH + ' ' + device_path) 766 MD5SUM_DEVICE_PATH + ' ' + device_path)
766 device_hash_tuples = _ComputeFileListHash( 767 device_hash_tuples = _ComputeFileListHash(
767 self.RunShellCommand(cmd, timeout_time=2 * 60)) 768 self.RunShellCommand(cmd, timeout_time=2 * 60))
768 assert os.path.exists(local_path), 'Local path not found %s' % local_path 769 assert os.path.exists(host_path), 'Local path not found %s' % host_path
769 md5sum_output = cmd_helper.GetCmdOutput( 770 md5sum_output = cmd_helper.GetCmdOutput(
770 ['%s/md5sum_bin_host' % self._md5sum_build_dir, local_path]) 771 ['%s/md5sum_bin_host' % self._md5sum_build_dir, host_path])
771 host_hash_tuples = _ComputeFileListHash(md5sum_output.splitlines()) 772 host_hash_tuples = _ComputeFileListHash(md5sum_output.splitlines())
773 return (host_hash_tuples, device_hash_tuples)
774
775 def GetFilesChanged(self, host_path, device_path):
776 """Compares the md5sum of a host path against a device path.
777
778 Note: Ignores extra files on the device.
779
780 Args:
781 host_path: Path (file or directory) on the host.
782 device_path: Path on the device.
783
784 Returns:
785 A list of tuples of the form (host_path, device_path) for files whose
786 md5sums do not match.
787 """
788 host_hash_tuples, device_hash_tuples = self._RunMd5Sum(
789 host_path, device_path)
772 790
773 # Ignore extra files on the device. 791 # Ignore extra files on the device.
774 if len(device_hash_tuples) > len(host_hash_tuples): 792 if len(device_hash_tuples) > len(host_hash_tuples):
775 host_files = [os.path.relpath(os.path.normpath(p.path), 793 host_files = [os.path.relpath(os.path.normpath(p.path),
776 os.path.normpath(local_path)) for p in host_hash_tuples] 794 os.path.normpath(host_path)) for p in host_hash_tuples]
777 795
778 def _host_has(fname): 796 def HostHas(fname):
779 return any(path in fname for path in host_files) 797 return any(path in fname for path in host_files)
780 798
781 hashes_on_device = [h.hash for h in device_hash_tuples if 799 device_hash_tuples = [h for h in device_hash_tuples if HostHas(h.path)]
782 _host_has(h.path)]
783 else:
784 hashes_on_device = [h.hash for h in device_hash_tuples]
785 800
786 # Compare md5sums between host and device files. 801 # Constructs the target device path from a given host path. Don't use when
787 hashes_on_host = [h.hash for h in host_hash_tuples] 802 # only a single file is given as the base name given in device_path may
788 hashes_on_device.sort() 803 # differ from that in host_path.
789 hashes_on_host.sort() 804 def HostToDevicePath(host_path):
790 return hashes_on_device == hashes_on_host 805 return os.path.join(os.path.dirname(device_path), os.path.relpath(
806 host_path, os.path.dirname(os.path.normpath(host_path))))
791 807
792 def PushIfNeeded(self, local_path, device_path): 808 device_hashes = [h.hash for h in device_hash_tuples]
793 """Pushes |local_path| to |device_path|. 809 return [(t.path, HostToDevicePath(t.path) if os.path.isdir(host_path) else
810 device_path)
811 for t in host_hash_tuples if t.hash not in device_hashes]
812
813 def CheckMd5Sum(self, host_path, device_path):
frankf 2013/08/06 00:17:23 Can you get rid of this and just call GetFilesChan
craigdh 2013/08/06 21:17:29 Done.
814 """Compares the md5sum of a host path against a device path.
815
816 Args:
817 host_path: Path (file or directory) on the host.
818 device_path: Path on the device.
819
820 Returns:
821 True if the md5sums match.
822 """
823 return not self.GetFilesChanged(host_path, device_path)
824
825 def PushIfNeeded(self, host_path, device_path):
826 """Pushes |host_path| to |device_path|.
794 827
795 Works for files and directories. This method skips copying any paths in 828 Works for files and directories. This method skips copying any paths in
796 |test_data_paths| that already exist on the device with the same hash. 829 |test_data_paths| that already exist on the device with the same hash.
797 830
798 All pushed files can be removed by calling RemovePushedFiles(). 831 All pushed files can be removed by calling RemovePushedFiles().
799 """ 832 """
800 assert os.path.exists(local_path), 'Local path not found %s' % local_path 833 MAX_INDIVIDUAL_PUSHES = 50
801 size = int(cmd_helper.GetCmdOutput(['du', '-sb', local_path]).split()[0]) 834 assert os.path.exists(host_path), 'Local path not found %s' % host_path
835
836 def GetHostSize(path):
837 return int(cmd_helper.GetCmdOutput(['du', '-sb', path]).split()[0])
838
839 size = GetHostSize(host_path)
802 self._pushed_files.append(device_path) 840 self._pushed_files.append(device_path)
803 self._potential_push_size += size 841 self._potential_push_size += size
804 842
805 if self.CheckMd5Sum(local_path, device_path): 843 missing_files = self.GetFilesChanged(host_path, device_path)
frankf 2013/08/06 00:17:23 this is misleading -> changed_files
craigdh 2013/08/06 21:17:29 Done.
844 if not missing_files:
806 return 845 return
807 846
808 self._actual_push_size += size 847 def Push(host, device):
809 # They don't match, so remove everything first and then create it. 848 # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout
810 if os.path.isdir(local_path): 849 # of 60 seconds which isn't sufficient for a lot of users of this method.
811 self.RunShellCommand('rm -r %s' % device_path, timeout_time=2 * 60) 850 push_command = 'push %s %s' % (host, device)
812 self.RunShellCommand('mkdir -p %s' % device_path) 851 self._LogShell(push_command)
813 852
814 # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout of 853 # Retry push with increasing backoff if the device is busy.
815 # 60 seconds which isn't sufficient for a lot of users of this method. 854 retry = 0
816 push_command = 'push %s %s' % (local_path, device_path) 855 while True:
817 self._LogShell(push_command) 856 output = self._adb.SendCommand(push_command, timeout_time=30 * 60)
857 if _HasAdbPushSucceeded(output):
858 return
859 if 'resource busy' in output and retry < 3:
860 retry += 1
861 wait_time = 5 * retry
862 logging.error('Push failed, retrying in %d seconds: %s' %
863 (wait_time, output))
864 time.sleep(wait_time)
865 else:
866 raise Exception('Push failed: %s' % output)
818 867
819 # Retry push with increasing backoff if the device is busy. 868 diff_size = 0
820 retry = 0 869 if len(missing_files) <= MAX_INDIVIDUAL_PUSHES:
821 while True: 870 diff_size = sum(GetHostSize(f[0]) for f in missing_files)
822 output = self._adb.SendCommand(push_command, timeout_time=30 * 60) 871
823 if _HasAdbPushSucceeded(output): 872 # TODO(craigdh): Replace this educated guess with a heuristic that
824 return 873 # approximates the push time for each method.
frankf 2013/08/06 00:17:23 Can you look how adb sync operates: https://androi
craigdh 2013/08/06 21:17:29 Seems to just compare the timestamps. From what I
825 if 'resource busy' in output and retry < 3: 874 if len(missing_files) > MAX_INDIVIDUAL_PUSHES or diff_size > 0.5 * size:
826 retry += 1 875 # We're pushing everything, remove everything first and then create it.
827 wait_time = 5 * retry 876 self._actual_push_size += size
828 logging.error('Push failed, retrying in %d seconds: %s' % 877 if os.path.isdir(host_path):
829 (wait_time, output)) 878 self.RunShellCommand('rm -r %s' % device_path, timeout_time=2 * 60)
830 time.sleep(wait_time) 879 self.RunShellCommand('mkdir -p %s' % device_path)
831 else: 880 Push(host_path, device_path)
832 raise Exception('Push failed: %s' % output) 881 else:
882 for f in missing_files:
883 Push(f[0], f[1])
884 self._actual_push_size += diff_size
833 885
834 def GetPushSizeInfo(self): 886 def GetPushSizeInfo(self):
835 """Get total size of pushes to the device done via PushIfNeeded() 887 """Get total size of pushes to the device done via PushIfNeeded()
836 888
837 Returns: 889 Returns:
838 A tuple: 890 A tuple:
839 1. Total size of push requests to PushIfNeeded (MB) 891 1. Total size of push requests to PushIfNeeded (MB)
840 2. Total size that was actually pushed (MB) 892 2. Total size that was actually pushed (MB)
841 """ 893 """
842 return (self._potential_push_size, self._actual_push_size) 894 return (self._potential_push_size, self._actual_push_size)
(...skipping 629 matching lines...) Expand 10 before | Expand all | Expand 10 after
1472 """ 1524 """
1473 def __init__(self, output): 1525 def __init__(self, output):
1474 self._output = output 1526 self._output = output
1475 1527
1476 def write(self, data): 1528 def write(self, data):
1477 data = data.replace('\r\r\n', '\n') 1529 data = data.replace('\r\r\n', '\n')
1478 self._output.write(data) 1530 self._output.write(data)
1479 1531
1480 def flush(self): 1532 def flush(self):
1481 self._output.flush() 1533 self._output.flush()
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698