blob: be93e193d88edd2593b67c7e9ec82b0564f2afab [file] [log] [blame]
Aviv Keshetb1238c32013-04-01 11:42:13 -07001#!/usr/bin/python
2
3# Copyright (c) 2013 The Chromium OS 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.
6
7
8"""
9Simple script to be run inside the chroot. Used as a fast approximation of
10emerge-$board autotest-all, by simply rsync'ing changes from trunk to sysroot.
11"""
12
13import os
Aviv Keshet787ffcd2013-04-08 15:14:56 -070014import re
Aviv Keshetb1238c32013-04-01 11:42:13 -070015import sys
Aviv Keshet787ffcd2013-04-08 15:14:56 -070016from collections import namedtuple
17
Aviv Keshetb1238c32013-04-01 11:42:13 -070018
19from chromite.buildbot import constants
20from chromite.lib import cros_build_lib
21from chromite.lib import git
22
23import argparse
24
25INCLUDE_PATTERNS_FILENAME = 'autotest-quickmerge-includepatterns'
26AUTOTEST_PROJECT_NAME = 'chromiumos/third_party/autotest'
27
Aviv Keshet787ffcd2013-04-08 15:14:56 -070028
29# Data structure describing a single rsync filesystem change.
30#
31# change_description: An 11 character string, the rsync change description
32# for the particular file.
33# absolute_path: The absolute path of the created or modified file.
34ItemizedChange = namedtuple('ItemizedChange', ['change_description',
35 'absolute_path'])
36
37
38# Data structure describing the rsync new/modified files or directories.
39#
40# new_files: A list of ItemizedChange objects for new files.
41# modified_files: A list of ItemizedChange objects for modified files.
42# new_directories: A list of ItemizedChange objects for new directories.
43ItemizedChangeReport = namedtuple('ItemizedChangeReport',
44 ['new_files', 'modified_files',
45 'new_directories'])
46
47
48def ItemizeChangesFromRsyncOutput(rsync_output, destination_path):
49 """Convert the output of an rsync with `-i` to a ItemizedChangeReport object.
50
51 Arguments:
52 rsync_output: String stdout of rsync command that was run with `-i` option.
53 destination_path: String absolute path of the destination directory for the
54 rsync operations. This argument is necessary because
55 rsync's output only gives the relative path of
56 touched/added files.
57
58 Returns:
59 ItemizedChangeReport object giving the absolute paths of files that were
60 created or modified by rsync.
61 """
62 modified_matches = re.findall(r'([.>]f[^+]{9}) (.*)', rsync_output)
63 new_matches = re.findall(r'(>f\+{9}) (.*)', rsync_output)
64 new_symlink_matches = re.findall(r'(cL\+{9}) (.*) -> .*', rsync_output)
65 new_dir_matches = re.findall(r'(cd\+{9}) (.*)', rsync_output)
66
67 absolute_modified = [ItemizedChange(c, os.path.join(destination_path, f))
68 for (c, f) in modified_matches]
69
70 # Note: new symlinks are treated as new files.
71 absolute_new = [ItemizedChange(c, os.path.join(destination_path, f))
72 for (c, f) in new_matches + new_symlink_matches]
73
74 absolute_new_dir = [ItemizedChange(c, os.path.join(destination_path, f))
75 for (c, f) in new_dir_matches]
76
77 return ItemizedChangeReport(new_files=absolute_new,
78 modified_files=absolute_modified,
79 new_directories=absolute_new_dir)
80
81
Aviv Keshetb1238c32013-04-01 11:42:13 -070082def RsyncQuickmerge(source_path, sysroot_autotest_path,
83 include_pattern_file=None, pretend=False,
84 overwrite=False, quiet=False):
85 """Run rsync quickmerge command, with specified arguments.
86 Command will take form `rsync -a [options] --exclude=**.pyc
87 --exclude=**.pyo
88 [optional --include-from argument]
89 --exclude=* [source_path] [sysroot_autotest_path]`
90
91 Arguments:
92 pretend: True to use the '-n' option to rsync, to perform dry run.
93 overwrite: True to omit '-u' option, overwrite all files in sysroot,
94 not just older files.
95 quiet: True to omit the '-i' option, silence rsync change log.
96 """
97 command = ['rsync', '-a']
98
99 if pretend:
100 command += ['-n']
101
102 if not overwrite:
103 command += ['-u']
104
105 if not quiet:
106 command += ['-i']
107
108 command += ['--exclude=**.pyc']
109 command += ['--exclude=**.pyo']
110
Aviv Keshet787ffcd2013-04-08 15:14:56 -0700111 # Exclude files with a specific substring in their name, because
112 # they create an ambiguous itemized report. (see unit test file for details)
113 command += ['--exclude=** -> *']
114
Aviv Keshetb1238c32013-04-01 11:42:13 -0700115 if include_pattern_file:
116 command += ['--include-from=%s' % include_pattern_file]
117
118 command += ['--exclude=*']
119
120 command += [source_path, sysroot_autotest_path]
121
122 cros_build_lib.SudoRunCommand(command)
123
124
125def ParseArguments(argv):
126 """Parse command line arguments
127
128 Returns: parsed arguments.
129 """
130 parser = argparse.ArgumentParser(description='Perform a fast approximation '
131 'to emerge-$board autotest-all, by '
132 'rsyncing source tree to sysroot.')
133
134 parser.add_argument('--board', metavar='BOARD', default=None, required=True)
135 parser.add_argument('--pretend', action='store_true',
136 help='Dry run only, do not modify sysroot autotest.')
137 parser.add_argument('--overwrite', action='store_true',
138 help='Overwrite existing files even if newer.')
139 parser.add_argument('--quiet', action='store_true',
140 help='Suppress output of list of modified files.')
141
142 return parser.parse_args(argv)
143
144
145def main(argv):
146 cros_build_lib.AssertInsideChroot()
147
148 args = ParseArguments(argv)
149
150 if not args.board:
151 print "No board specified, and no default board. Aborting."
152 return 1
153
154 manifest = git.ManifestCheckout.Cached(constants.SOURCE_ROOT)
155 source_path = manifest.GetProjectPath(AUTOTEST_PROJECT_NAME, absolute=True)
156 source_path = os.path.join(source_path, '')
157
158 script_path = os.path.dirname(__file__)
159 include_pattern_file = os.path.join(script_path, INCLUDE_PATTERNS_FILENAME)
160
161 # TODO: Determine the following string programatically.
162 sysroot_autotest_path = os.path.join('/build', args.board, 'usr', 'local',
163 'autotest', '')
164
165 RsyncQuickmerge(source_path, sysroot_autotest_path, include_pattern_file,
166 args.pretend, args.overwrite, args.quiet)
167
168 print 'Done'
169
170
171if __name__ == '__main__':
172 sys.exit(main(sys.argv))