blob: a3dc5ddd32afbf5c5e810fd2e105ad2d3b78689a [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
Aviv Keshete7b20192013-04-24 14:05:53 -070013import argparse
Aviv Keshete00caeb2013-04-17 14:03:25 -070014import logging
Aviv Keshetb1238c32013-04-01 11:42:13 -070015import os
Aviv Keshet787ffcd2013-04-08 15:14:56 -070016import re
Aviv Keshetb1238c32013-04-01 11:42:13 -070017import sys
Aviv Keshet787ffcd2013-04-08 15:14:56 -070018from collections import namedtuple
19
Aviv Keshetb1238c32013-04-01 11:42:13 -070020from chromite.buildbot import constants
Aviv Keshet940c17f2013-04-11 18:41:42 -070021from chromite.buildbot import portage_utilities
Aviv Keshetb1238c32013-04-01 11:42:13 -070022from chromite.lib import cros_build_lib
23from chromite.lib import git
Aviv Keshet557e6882013-04-25 13:26:09 -070024from chromite.lib import osutils
Aviv Keshetb1238c32013-04-01 11:42:13 -070025
Aviv Keshetb1238c32013-04-01 11:42:13 -070026
Aviv Keshet940c17f2013-04-11 18:41:42 -070027if cros_build_lib.IsInsideChroot():
28 # Only import portage after we've checked that we're inside the chroot.
29 import portage
30
Aviv Keshetb1238c32013-04-01 11:42:13 -070031INCLUDE_PATTERNS_FILENAME = 'autotest-quickmerge-includepatterns'
32AUTOTEST_PROJECT_NAME = 'chromiumos/third_party/autotest'
Aviv Keshet5f3cf722013-05-09 17:35:25 -070033AUTOTEST_EBUILD = 'chromeos-base/autotest'
Aviv Keshet3cc4e9e2013-04-24 10:47:23 -070034DOWNGRADE_EBUILDS = ['chromeos-base/autotest',
35 'chromeos-base/autotest-tests',
36 'chromeos-base/autotest-chrome',
37 'chromeos-base/autotest-factory',
38 'chromeos-base/autotest-telemetry',
39 'chromeos-base/autotest-tests-ltp',
40 'chromeos-base/autotest-tests-ownershipapi']
Aviv Keshet787ffcd2013-04-08 15:14:56 -070041
42# Data structure describing a single rsync filesystem change.
43#
44# change_description: An 11 character string, the rsync change description
45# for the particular file.
46# absolute_path: The absolute path of the created or modified file.
47ItemizedChange = namedtuple('ItemizedChange', ['change_description',
48 'absolute_path'])
49
50
51# Data structure describing the rsync new/modified files or directories.
52#
53# new_files: A list of ItemizedChange objects for new files.
54# modified_files: A list of ItemizedChange objects for modified files.
55# new_directories: A list of ItemizedChange objects for new directories.
56ItemizedChangeReport = namedtuple('ItemizedChangeReport',
57 ['new_files', 'modified_files',
58 'new_directories'])
59
Aviv Keshet84bdfc52013-06-04 13:19:38 -070060class PortagePackageAPIError(Exception):
61 """Exception thrown when unable to retrieve a portage package API."""
62
Aviv Keshet787ffcd2013-04-08 15:14:56 -070063
Aviv Keshet75d65962013-04-17 16:15:23 -070064def GetStalePackageNames(change_list, autotest_sysroot):
Aviv Keshete7b20192013-04-24 14:05:53 -070065 """Given a rsync change report, returns the names of stale test packages.
Aviv Keshet75d65962013-04-17 16:15:23 -070066
67 This function pulls out test package names for client-side tests, stored
68 within the client/site_tests directory tree, that had any files added or
69 modified and for whom any existing bzipped test packages may now be stale.
70
71 Arguments:
72 change_list: A list of ItemizedChange objects corresponding to changed
73 or modified files.
74 autotest_sysroot: Absolute path of autotest in the sysroot,
75 e.g. '/build/lumpy/usr/local/autotest'
76
77 Returns:
78 A list of test package names, eg ['factory_Leds', 'login_UserPolicyKeys'].
79 May contain duplicate entries if multiple files within a test directory
80 were modified.
81 """
82 exp = os.path.abspath(autotest_sysroot) + r'/client/site_tests/(.*?)/.*'
83 matches = [re.match(exp, change.absolute_path) for change in change_list]
84 return [match.group(1) for match in matches if match]
85
86
Aviv Keshet787ffcd2013-04-08 15:14:56 -070087def ItemizeChangesFromRsyncOutput(rsync_output, destination_path):
88 """Convert the output of an rsync with `-i` to a ItemizedChangeReport object.
89
90 Arguments:
91 rsync_output: String stdout of rsync command that was run with `-i` option.
92 destination_path: String absolute path of the destination directory for the
93 rsync operations. This argument is necessary because
94 rsync's output only gives the relative path of
95 touched/added files.
96
97 Returns:
98 ItemizedChangeReport object giving the absolute paths of files that were
99 created or modified by rsync.
100 """
101 modified_matches = re.findall(r'([.>]f[^+]{9}) (.*)', rsync_output)
102 new_matches = re.findall(r'(>f\+{9}) (.*)', rsync_output)
103 new_symlink_matches = re.findall(r'(cL\+{9}) (.*) -> .*', rsync_output)
104 new_dir_matches = re.findall(r'(cd\+{9}) (.*)', rsync_output)
105
106 absolute_modified = [ItemizedChange(c, os.path.join(destination_path, f))
107 for (c, f) in modified_matches]
108
109 # Note: new symlinks are treated as new files.
110 absolute_new = [ItemizedChange(c, os.path.join(destination_path, f))
111 for (c, f) in new_matches + new_symlink_matches]
112
113 absolute_new_dir = [ItemizedChange(c, os.path.join(destination_path, f))
114 for (c, f) in new_dir_matches]
115
116 return ItemizedChangeReport(new_files=absolute_new,
117 modified_files=absolute_modified,
118 new_directories=absolute_new_dir)
119
120
Aviv Keshete00caeb2013-04-17 14:03:25 -0700121def GetPackageAPI(portage_root, package_cp):
Aviv Keshete7b20192013-04-24 14:05:53 -0700122 """Gets portage API handles for the given package.
Aviv Keshet940c17f2013-04-11 18:41:42 -0700123
124 Arguments:
Aviv Keshete00caeb2013-04-17 14:03:25 -0700125 portage_root: Root directory of portage tree. Eg '/' or '/build/lumpy'
126 package_cp: A string similar to 'chromeos-base/autotest-tests'.
127
128 Returns:
129 Returns (package, vartree) tuple, where
130 package is of type portage.dbapi.vartree.dblink
131 vartree is of type portage.dbapi.vartree.vartree
Aviv Keshet940c17f2013-04-11 18:41:42 -0700132 """
133 if portage_root is None:
Aviv Keshete7b20192013-04-24 14:05:53 -0700134 # pylint: disable-msg=E1101
135 portage_root = portage.root
Aviv Keshet940c17f2013-04-11 18:41:42 -0700136 # Ensure that portage_root ends with trailing slash.
137 portage_root = os.path.join(portage_root, '')
138
Aviv Keshete7b20192013-04-24 14:05:53 -0700139 # Create a vartree object corresponding to portage_root.
Aviv Keshet940c17f2013-04-11 18:41:42 -0700140 trees = portage.create_trees(portage_root, portage_root)
141 vartree = trees[portage_root]['vartree']
142
Aviv Keshete7b20192013-04-24 14:05:53 -0700143 # List the matching installed packages in cpv format.
Aviv Keshet940c17f2013-04-11 18:41:42 -0700144 matching_packages = vartree.dbapi.cp_list(package_cp)
145
146 if not matching_packages:
Aviv Keshet84bdfc52013-06-04 13:19:38 -0700147 raise PortagePackageAPIError('No matching package for %s in portage_root '
148 '%s' % (package_cp, portage_root))
Aviv Keshet940c17f2013-04-11 18:41:42 -0700149
150 if len(matching_packages) > 1:
Aviv Keshet84bdfc52013-06-04 13:19:38 -0700151 raise PortagePackageAPIError('Too many matching packages for %s in '
152 'portage_root %s' % (package_cp,
153 portage_root))
Aviv Keshet940c17f2013-04-11 18:41:42 -0700154
Aviv Keshete7b20192013-04-24 14:05:53 -0700155 # Convert string match to package dblink.
Aviv Keshet940c17f2013-04-11 18:41:42 -0700156 package_cpv = matching_packages[0]
157 package_split = portage_utilities.SplitCPV(package_cpv)
Aviv Keshete7b20192013-04-24 14:05:53 -0700158 # pylint: disable-msg=E1101
159 package = portage.dblink(package_split.category,
Aviv Keshet940c17f2013-04-11 18:41:42 -0700160 package_split.pv, settings=vartree.settings,
161 vartree=vartree)
162
Aviv Keshete00caeb2013-04-17 14:03:25 -0700163 return package, vartree
164
165
166def DowngradePackageVersion(portage_root, package_cp,
167 downgrade_to_version='0'):
Aviv Keshete7b20192013-04-24 14:05:53 -0700168 """Downgrade the specified portage package version.
Aviv Keshete00caeb2013-04-17 14:03:25 -0700169
170 Arguments:
171 portage_root: Root directory of portage tree. Eg '/' or '/build/lumpy'
172 package_cp: A string similar to 'chromeos-base/autotest-tests'.
173 downgrade_to_version: String version to downgrade to. Default: '0'
174
175 Returns:
Aviv Keshet557e6882013-04-25 13:26:09 -0700176 True on success. False on failure (nonzero return code from `mv` command).
Aviv Keshete00caeb2013-04-17 14:03:25 -0700177 """
Aviv Keshet84bdfc52013-06-04 13:19:38 -0700178 try:
179 package, _ = GetPackageAPI(portage_root, package_cp)
180 except PortagePackageAPIError:
181 # Unable to fetch a corresponding portage package API for this
182 # package_cp (either no such package, or name ambigious and matches).
183 # So, just fail out.
184 return False
Aviv Keshete00caeb2013-04-17 14:03:25 -0700185
186 source_directory = package.dbdir
187 destination_path = os.path.join(
188 package.dbroot, package_cp + '-' + downgrade_to_version)
189 if os.path.abspath(source_directory) == os.path.abspath(destination_path):
Aviv Keshet557e6882013-04-25 13:26:09 -0700190 return True
Aviv Keshete00caeb2013-04-17 14:03:25 -0700191 command = ['mv', source_directory, destination_path]
Aviv Keshet557e6882013-04-25 13:26:09 -0700192 code = cros_build_lib.SudoRunCommand(command, error_code_ok=True).returncode
193 return code == 0
Aviv Keshete00caeb2013-04-17 14:03:25 -0700194
195
Aviv Keshete7b20192013-04-24 14:05:53 -0700196def UpdatePackageContents(change_report, package_cp, portage_root=None):
197 """Add newly created files/directors to package contents.
Aviv Keshete00caeb2013-04-17 14:03:25 -0700198
199 Given an ItemizedChangeReport, add the newly created files and directories
200 to the CONTENTS of an installed portage package, such that these files are
201 considered owned by that package.
202
203 Arguments:
204 changereport: ItemizedChangeReport object for the changes to be
205 made to the package.
206 package_cp: A string similar to 'chromeos-base/autotest-tests' giving
207 the package category and name of the package to be altered.
208 portage_root: Portage root path, corresponding to the board that
209 we are working on. Defaults to '/'
210 """
211 package, vartree = GetPackageAPI(portage_root, package_cp)
212
Aviv Keshete7b20192013-04-24 14:05:53 -0700213 # Append new contents to package contents dictionary.
Aviv Keshet940c17f2013-04-11 18:41:42 -0700214 contents = package.getcontents().copy()
215 for _, filename in change_report.new_files:
216 contents.setdefault(filename, (u'obj', '0', '0'))
217 for _, dirname in change_report.new_directories:
Aviv Keshete7b20192013-04-24 14:05:53 -0700218 # Strip trailing slashes if present.
219 contents.setdefault(dirname.rstrip('/'), (u'dir',))
Aviv Keshet940c17f2013-04-11 18:41:42 -0700220
Aviv Keshete7b20192013-04-24 14:05:53 -0700221 # Write new contents dictionary to file.
Aviv Keshet940c17f2013-04-11 18:41:42 -0700222 vartree.dbapi.writeContentsToContentsFile(package, contents)
223
224
Aviv Keshet19276752013-05-16 11:12:23 -0700225def RemoveBzipPackages(autotest_sysroot):
226 """Remove all bzipped test/dep/profiler packages from sysroot autotest.
Aviv Keshet75d65962013-04-17 16:15:23 -0700227
228 Arguments:
Aviv Keshet75d65962013-04-17 16:15:23 -0700229 autotest_sysroot: Absolute path of autotest in the sysroot,
230 e.g. '/build/lumpy/usr/local/autotest'
231 """
Aviv Keshet19276752013-05-16 11:12:23 -0700232 osutils.RmDir(os.path.join(autotest_sysroot, 'packages'),
233 ignore_missing=True)
234 osutils.SafeUnlink(os.path.join(autotest_sysroot, 'packages.checksum'))
Aviv Keshet75d65962013-04-17 16:15:23 -0700235
236
Aviv Keshetb1238c32013-04-01 11:42:13 -0700237def RsyncQuickmerge(source_path, sysroot_autotest_path,
238 include_pattern_file=None, pretend=False,
Aviv Keshet60968ec2013-04-11 18:44:14 -0700239 overwrite=False):
Aviv Keshetb1238c32013-04-01 11:42:13 -0700240 """Run rsync quickmerge command, with specified arguments.
Aviv Keshete7b20192013-04-24 14:05:53 -0700241
Aviv Keshetb1238c32013-04-01 11:42:13 -0700242 Command will take form `rsync -a [options] --exclude=**.pyc
243 --exclude=**.pyo
244 [optional --include-from argument]
245 --exclude=* [source_path] [sysroot_autotest_path]`
246
247 Arguments:
248 pretend: True to use the '-n' option to rsync, to perform dry run.
249 overwrite: True to omit '-u' option, overwrite all files in sysroot,
250 not just older files.
Aviv Keshet557e6882013-04-25 13:26:09 -0700251
252 Returns:
253 The cros_build_lib.CommandResult object resulting from the rsync command.
Aviv Keshetb1238c32013-04-01 11:42:13 -0700254 """
255 command = ['rsync', '-a']
256
257 if pretend:
258 command += ['-n']
259
260 if not overwrite:
261 command += ['-u']
262
Aviv Keshet60968ec2013-04-11 18:44:14 -0700263 command += ['-i']
Aviv Keshetb1238c32013-04-01 11:42:13 -0700264
265 command += ['--exclude=**.pyc']
266 command += ['--exclude=**.pyo']
267
Aviv Keshet787ffcd2013-04-08 15:14:56 -0700268 # Exclude files with a specific substring in their name, because
269 # they create an ambiguous itemized report. (see unit test file for details)
270 command += ['--exclude=** -> *']
271
Aviv Keshetb1238c32013-04-01 11:42:13 -0700272 if include_pattern_file:
273 command += ['--include-from=%s' % include_pattern_file]
274
275 command += ['--exclude=*']
276
277 command += [source_path, sysroot_autotest_path]
278
Aviv Keshet60968ec2013-04-11 18:44:14 -0700279 return cros_build_lib.SudoRunCommand(command, redirect_stdout=True)
Aviv Keshetb1238c32013-04-01 11:42:13 -0700280
281
282def ParseArguments(argv):
283 """Parse command line arguments
284
285 Returns: parsed arguments.
286 """
287 parser = argparse.ArgumentParser(description='Perform a fast approximation '
288 'to emerge-$board autotest-all, by '
289 'rsyncing source tree to sysroot.')
290
291 parser.add_argument('--board', metavar='BOARD', default=None, required=True)
292 parser.add_argument('--pretend', action='store_true',
293 help='Dry run only, do not modify sysroot autotest.')
294 parser.add_argument('--overwrite', action='store_true',
295 help='Overwrite existing files even if newer.')
Aviv Keshete00caeb2013-04-17 14:03:25 -0700296 parser.add_argument('--verbose', action='store_true',
297 help='Print detailed change report.')
Aviv Keshetb1238c32013-04-01 11:42:13 -0700298
299 return parser.parse_args(argv)
300
301
302def main(argv):
303 cros_build_lib.AssertInsideChroot()
304
305 args = ParseArguments(argv)
306
Aviv Keshete7b20192013-04-24 14:05:53 -0700307 if os.geteuid() != 0:
Aviv Keshet940c17f2013-04-11 18:41:42 -0700308 try:
309 cros_build_lib.SudoRunCommand([sys.executable] + sys.argv)
310 except cros_build_lib.RunCommandError:
311 return 1
312 return 0
313
Aviv Keshetb1238c32013-04-01 11:42:13 -0700314 if not args.board:
Aviv Keshete00caeb2013-04-17 14:03:25 -0700315 print 'No board specified. Aborting.'
Aviv Keshetb1238c32013-04-01 11:42:13 -0700316 return 1
317
318 manifest = git.ManifestCheckout.Cached(constants.SOURCE_ROOT)
319 source_path = manifest.GetProjectPath(AUTOTEST_PROJECT_NAME, absolute=True)
320 source_path = os.path.join(source_path, '')
321
322 script_path = os.path.dirname(__file__)
323 include_pattern_file = os.path.join(script_path, INCLUDE_PATTERNS_FILENAME)
324
325 # TODO: Determine the following string programatically.
Aviv Keshet940c17f2013-04-11 18:41:42 -0700326 sysroot_path = os.path.join('/build', args.board, '')
327 sysroot_autotest_path = os.path.join(sysroot_path, 'usr', 'local',
Aviv Keshetb1238c32013-04-01 11:42:13 -0700328 'autotest', '')
329
Aviv Keshet60968ec2013-04-11 18:44:14 -0700330 rsync_output = RsyncQuickmerge(source_path, sysroot_autotest_path,
Aviv Keshete7b20192013-04-24 14:05:53 -0700331 include_pattern_file, args.pretend,
332 args.overwrite)
Aviv Keshetb1238c32013-04-01 11:42:13 -0700333
Aviv Keshete00caeb2013-04-17 14:03:25 -0700334 if args.verbose:
335 logging.info(rsync_output.output)
336
Aviv Keshet60968ec2013-04-11 18:44:14 -0700337 change_report = ItemizeChangesFromRsyncOutput(rsync_output.output,
338 sysroot_autotest_path)
339
Aviv Keshet940c17f2013-04-11 18:41:42 -0700340 if not args.pretend:
Aviv Keshet5f3cf722013-05-09 17:35:25 -0700341 UpdatePackageContents(change_report, AUTOTEST_EBUILD,
Aviv Keshet940c17f2013-04-11 18:41:42 -0700342 sysroot_path)
Aviv Keshet3cc4e9e2013-04-24 10:47:23 -0700343 for ebuild in DOWNGRADE_EBUILDS:
Aviv Keshet557e6882013-04-25 13:26:09 -0700344 if not DowngradePackageVersion(sysroot_path, ebuild):
Aviv Keshet3cc4e9e2013-04-24 10:47:23 -0700345 logging.warning('Unable to downgrade package %s version number.',
Aviv Keshete7b20192013-04-24 14:05:53 -0700346 ebuild)
Aviv Keshet19276752013-05-16 11:12:23 -0700347 RemoveBzipPackages(sysroot_autotest_path)
Aviv Keshetb1238c32013-04-01 11:42:13 -0700348
Aviv Keshet940c17f2013-04-11 18:41:42 -0700349 if args.pretend:
Aviv Keshete00caeb2013-04-17 14:03:25 -0700350 logging.info('The following message is pretend only. No filesystem '
Aviv Keshete7b20192013-04-24 14:05:53 -0700351 'changes made.')
Aviv Keshete00caeb2013-04-17 14:03:25 -0700352 logging.info('Quickmerge complete. Created or modified %s files.',
Aviv Keshete7b20192013-04-24 14:05:53 -0700353 len(change_report.new_files) +
354 len(change_report.modified_files))
Aviv Keshete00caeb2013-04-17 14:03:25 -0700355
356 return 0