blob: f69bbf6068c9c3750f12a1a7450a6f66e5fe6133 [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 -070018from chromite.buildbot import constants
Aviv Keshet940c17f2013-04-11 18:41:42 -070019from chromite.buildbot import portage_utilities
Aviv Keshetb1238c32013-04-01 11:42:13 -070020from chromite.lib import cros_build_lib
21from chromite.lib import git
22
23import argparse
24
Aviv Keshet940c17f2013-04-11 18:41:42 -070025if cros_build_lib.IsInsideChroot():
26 # Only import portage after we've checked that we're inside the chroot.
27 import portage
28
Aviv Keshetb1238c32013-04-01 11:42:13 -070029INCLUDE_PATTERNS_FILENAME = 'autotest-quickmerge-includepatterns'
30AUTOTEST_PROJECT_NAME = 'chromiumos/third_party/autotest'
Aviv Keshet940c17f2013-04-11 18:41:42 -070031AUTOTEST_TESTS_EBUILD = 'chromeos-base/autotest-tests'
Aviv Keshetb1238c32013-04-01 11:42:13 -070032
Aviv Keshet787ffcd2013-04-08 15:14:56 -070033
34# Data structure describing a single rsync filesystem change.
35#
36# change_description: An 11 character string, the rsync change description
37# for the particular file.
38# absolute_path: The absolute path of the created or modified file.
39ItemizedChange = namedtuple('ItemizedChange', ['change_description',
40 'absolute_path'])
41
42
43# Data structure describing the rsync new/modified files or directories.
44#
45# new_files: A list of ItemizedChange objects for new files.
46# modified_files: A list of ItemizedChange objects for modified files.
47# new_directories: A list of ItemizedChange objects for new directories.
48ItemizedChangeReport = namedtuple('ItemizedChangeReport',
49 ['new_files', 'modified_files',
50 'new_directories'])
51
52
53def ItemizeChangesFromRsyncOutput(rsync_output, destination_path):
54 """Convert the output of an rsync with `-i` to a ItemizedChangeReport object.
55
56 Arguments:
57 rsync_output: String stdout of rsync command that was run with `-i` option.
58 destination_path: String absolute path of the destination directory for the
59 rsync operations. This argument is necessary because
60 rsync's output only gives the relative path of
61 touched/added files.
62
63 Returns:
64 ItemizedChangeReport object giving the absolute paths of files that were
65 created or modified by rsync.
66 """
67 modified_matches = re.findall(r'([.>]f[^+]{9}) (.*)', rsync_output)
68 new_matches = re.findall(r'(>f\+{9}) (.*)', rsync_output)
69 new_symlink_matches = re.findall(r'(cL\+{9}) (.*) -> .*', rsync_output)
70 new_dir_matches = re.findall(r'(cd\+{9}) (.*)', rsync_output)
71
72 absolute_modified = [ItemizedChange(c, os.path.join(destination_path, f))
73 for (c, f) in modified_matches]
74
75 # Note: new symlinks are treated as new files.
76 absolute_new = [ItemizedChange(c, os.path.join(destination_path, f))
77 for (c, f) in new_matches + new_symlink_matches]
78
79 absolute_new_dir = [ItemizedChange(c, os.path.join(destination_path, f))
80 for (c, f) in new_dir_matches]
81
82 return ItemizedChangeReport(new_files=absolute_new,
83 modified_files=absolute_modified,
84 new_directories=absolute_new_dir)
85
86
Aviv Keshet940c17f2013-04-11 18:41:42 -070087def UpdatePackageContents(change_report, package_cp,
88 portage_root=None):
89 """
90 Add newly created files/directors to package contents.
91
92 Given an ItemizedChangeReport, add the newly created files and directories
93 to the CONTENTS of an installed portage package, such that these files are
94 considered owned by that package.
95
96 Arguments:
97 changereport: ItemizedChangeReport object for the changes to be
98 made to the package.
99 package_cp: A string similar to 'chromeos-base/autotest-tests' giving
100 the package category and name of the package to be altered.
101 portage_root: Portage root path, corresponding to the board that
102 we are working on. Defaults to '/'
103 """
104 if portage_root is None:
105 portage_root = portage.root # pylint: disable-msg=E1101
106 # Ensure that portage_root ends with trailing slash.
107 portage_root = os.path.join(portage_root, '')
108
109 # Create vartree object corresponding to portage_root
110 trees = portage.create_trees(portage_root, portage_root)
111 vartree = trees[portage_root]['vartree']
112
113 # List matching installed packages in cpv format
114 matching_packages = vartree.dbapi.cp_list(package_cp)
115
116 if not matching_packages:
117 raise ValueError('No matching package for %s in portage_root %s' % (
118 package_cp, portage_root))
119
120 if len(matching_packages) > 1:
121 raise ValueError('Too many matching packages for %s in portage_root '
122 '%s' % (package_cp, portage_root))
123
124 # Convert string match to package dblink
125 package_cpv = matching_packages[0]
126 package_split = portage_utilities.SplitCPV(package_cpv)
127 package = portage.dblink(package_split.category, # pylint: disable-msg=E1101
128 package_split.pv, settings=vartree.settings,
129 vartree=vartree)
130
131 # Append new contents to package contents dictionary
132 contents = package.getcontents().copy()
133 for _, filename in change_report.new_files:
134 contents.setdefault(filename, (u'obj', '0', '0'))
135 for _, dirname in change_report.new_directories:
136 # String trailing slashes if present.
137 dirname = dirname.rstrip('/')
138 contents.setdefault(dirname, (u'dir',))
139
140 # Write new contents dictionary to file
141 vartree.dbapi.writeContentsToContentsFile(package, contents)
142
143
Aviv Keshetb1238c32013-04-01 11:42:13 -0700144def RsyncQuickmerge(source_path, sysroot_autotest_path,
145 include_pattern_file=None, pretend=False,
Aviv Keshet60968ec2013-04-11 18:44:14 -0700146 overwrite=False):
Aviv Keshetb1238c32013-04-01 11:42:13 -0700147 """Run rsync quickmerge command, with specified arguments.
148 Command will take form `rsync -a [options] --exclude=**.pyc
149 --exclude=**.pyo
150 [optional --include-from argument]
151 --exclude=* [source_path] [sysroot_autotest_path]`
152
153 Arguments:
154 pretend: True to use the '-n' option to rsync, to perform dry run.
155 overwrite: True to omit '-u' option, overwrite all files in sysroot,
156 not just older files.
Aviv Keshetb1238c32013-04-01 11:42:13 -0700157 """
158 command = ['rsync', '-a']
159
160 if pretend:
161 command += ['-n']
162
163 if not overwrite:
164 command += ['-u']
165
Aviv Keshet60968ec2013-04-11 18:44:14 -0700166 command += ['-i']
Aviv Keshetb1238c32013-04-01 11:42:13 -0700167
168 command += ['--exclude=**.pyc']
169 command += ['--exclude=**.pyo']
170
Aviv Keshet787ffcd2013-04-08 15:14:56 -0700171 # Exclude files with a specific substring in their name, because
172 # they create an ambiguous itemized report. (see unit test file for details)
173 command += ['--exclude=** -> *']
174
Aviv Keshetb1238c32013-04-01 11:42:13 -0700175 if include_pattern_file:
176 command += ['--include-from=%s' % include_pattern_file]
177
178 command += ['--exclude=*']
179
180 command += [source_path, sysroot_autotest_path]
181
Aviv Keshet60968ec2013-04-11 18:44:14 -0700182 return cros_build_lib.SudoRunCommand(command, redirect_stdout=True)
Aviv Keshetb1238c32013-04-01 11:42:13 -0700183
184
185def ParseArguments(argv):
186 """Parse command line arguments
187
188 Returns: parsed arguments.
189 """
190 parser = argparse.ArgumentParser(description='Perform a fast approximation '
191 'to emerge-$board autotest-all, by '
192 'rsyncing source tree to sysroot.')
193
194 parser.add_argument('--board', metavar='BOARD', default=None, required=True)
195 parser.add_argument('--pretend', action='store_true',
196 help='Dry run only, do not modify sysroot autotest.')
197 parser.add_argument('--overwrite', action='store_true',
198 help='Overwrite existing files even if newer.')
199 parser.add_argument('--quiet', action='store_true',
200 help='Suppress output of list of modified files.')
201
202 return parser.parse_args(argv)
203
204
205def main(argv):
206 cros_build_lib.AssertInsideChroot()
207
208 args = ParseArguments(argv)
209
Aviv Keshet940c17f2013-04-11 18:41:42 -0700210 if not os.geteuid()==0:
211 try:
212 cros_build_lib.SudoRunCommand([sys.executable] + sys.argv)
213 except cros_build_lib.RunCommandError:
214 return 1
215 return 0
216
Aviv Keshetb1238c32013-04-01 11:42:13 -0700217 if not args.board:
Aviv Keshet940c17f2013-04-11 18:41:42 -0700218 print 'No board specified, and no default board. Aborting.'
Aviv Keshetb1238c32013-04-01 11:42:13 -0700219 return 1
220
221 manifest = git.ManifestCheckout.Cached(constants.SOURCE_ROOT)
222 source_path = manifest.GetProjectPath(AUTOTEST_PROJECT_NAME, absolute=True)
223 source_path = os.path.join(source_path, '')
224
225 script_path = os.path.dirname(__file__)
226 include_pattern_file = os.path.join(script_path, INCLUDE_PATTERNS_FILENAME)
227
228 # TODO: Determine the following string programatically.
Aviv Keshet940c17f2013-04-11 18:41:42 -0700229 sysroot_path = os.path.join('/build', args.board, '')
230 sysroot_autotest_path = os.path.join(sysroot_path, 'usr', 'local',
Aviv Keshetb1238c32013-04-01 11:42:13 -0700231 'autotest', '')
232
Aviv Keshet60968ec2013-04-11 18:44:14 -0700233 rsync_output = RsyncQuickmerge(source_path, sysroot_autotest_path,
234 include_pattern_file, args.pretend, args.overwrite)
Aviv Keshetb1238c32013-04-01 11:42:13 -0700235
Aviv Keshet60968ec2013-04-11 18:44:14 -0700236 change_report = ItemizeChangesFromRsyncOutput(rsync_output.output,
237 sysroot_autotest_path)
238
Aviv Keshet940c17f2013-04-11 18:41:42 -0700239 if not args.pretend:
240 UpdatePackageContents(change_report, AUTOTEST_TESTS_EBUILD,
241 sysroot_path)
Aviv Keshetb1238c32013-04-01 11:42:13 -0700242
Aviv Keshet940c17f2013-04-11 18:41:42 -0700243 if args.pretend:
244 print 'The following message is pretend only. No filesystem changes made.'
245 print 'Quickmerge complete. Created or modified %s files.' % (
246 len(change_report.new_files) + len(change_report.modified_files))
Aviv Keshetb1238c32013-04-01 11:42:13 -0700247
248if __name__ == '__main__':
249 sys.exit(main(sys.argv))