blob: 254187f26d3d966d02e0b99b0948a71feb2fb632 [file] [log] [blame]
#!/usr/bin/python
# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Script that syncs the repo manifest with .DEPS.git. Designed to be run
periodically from a host machine."""
import filecmp
import optparse
import os
import shutil
import sys
import StringIO
import tempfile
# Want to use correct version of libraries even when executed through symlink.
sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)),
'..', '..'))
import chromite.bin.chrome_set_ver as chrome_set_ver
import chromite.lib.cros_build_lib as cros_lib
import chromite.buildbot.constants as constants
import chromite.buildbot.manifest_version as manifest_version
import chromite.buildbot.repository as repository
_CHROMIUM_ROOT = 'chromium'
_CHROMIUM_SRC_ROOT = os.path.join(_CHROMIUM_ROOT, 'src')
_CHROMIUM_SRC_INTERNAL = os.path.join(_CHROMIUM_ROOT, 'src-internal')
_CHROMIUM_CROS_DEPS = os.path.join(_CHROMIUM_SRC_ROOT, 'tools/cros.DEPS/DEPS')
_BEGIN_MARKER = """\
<!-- @@@@ BEGIN AUTOGENERATED BROWSER PROJECTS - DON'T MODIFY! @@@@ -->\n\n"""
_END_MARKER = """\
<!-- @@@@ END AUTOGENERATED BROWSER PROJECTS - DON'T MODIFY! @@@@ -->\n"""
_EXTERNAL_HEADER = """\
<!-- Begin Chromium (browser) projects -->
<!-- Hardcoded revision="refs/heads/master" is intentional here -->\n\n"""
_EXTERNAL_PROJECT = """\
<project path="%(path)s"
name="%(name)s"
revision="refs/heads/master" />\n"""
_INTERNAL_HEADER = """\
<!-- Begin Chrome browser (PRIVATE) projects -->
<!-- Hardcoded revision="refs/heads/master" is intentional here -->\n\n"""
_INTERNAL_PROJECT = """\
<project remote="cros-internal"
path="%(path)s"
name="%(name)s"
revision="refs/heads/master" />\n"""
_EXTERNAL_MANIFEST_DIR = 'update-manifest'
_INTERNAL_MANIFEST_DIR = 'update-manifest-internal'
_EXTERNAL_MANIFEST_PROJECT = 'chromiumos/manifest'
_INTERNAL_MANIFEST_PROJECT = 'chromeos/manifest-internal'
_EXTERNAL_TEST_DIR = 'external'
_INTERNAL_TEST_DIR = 'internal'
_CHROMIUM_SRC_PROJECT = 'chromium/src'
_CHROMIUM_SRC_INTERNAL_PROJECT = 'chrome/src-internal'
def ConvertDepsToManifest(deps_file, manifest_file, template):
"""Convert dependencies in .DEPS.git files to manifest entries.
Arguments:
deps_file: The path to the .DEPS.git file.
manifest_file: The file object to write manifest entries to.
template: The template to use for manifest projects. One of
(_EXTERNAL_PROJECT, _INTERNAL_PROJECT)
"""
_, merged_deps = chrome_set_ver.GetParsedDeps(deps_file)
mappings = chrome_set_ver.GetPathToProjectMappings(merged_deps)
# Print and check for double checkouts
previous_projects = set([])
for rel_path, project in sorted(mappings.items(),
key=lambda mapping: mapping[1]):
rel_path = os.path.join('chromium', rel_path)
if project in previous_projects:
cros_lib.Warning('Found double checkout of %s to %s'
% (project, rel_path))
continue
manifest_file.write(template % {'path' : rel_path, 'name' : project})
previous_projects.add(project)
class ManifestException(Exception):
pass
def CheckForNonChromeProjects(xml_snippet_object):
"""Make sure we didn't remove non-chrome projects from the manifest.
Arguments:
xml_snippet_object: The file object containing manifest entries to examine.
"""
handler = cros_lib.ManifestHandler.ParseManifest(xml_snippet_object)
for project, attributes in handler.projects.iteritems():
if not attributes.get('path', '').startswith('chromium/'):
raise ManifestException('Project %s was about to be accidentally removed!'
% project)
class Manifest(object):
"""Encapsulates manifest update logic for an external or internal manifest."""
def __init__(self, repo_root, manifest_path, testroot, internal, dryrun=True):
self.repo_root = repo_root
self.testroot = testroot
self.manifest_path = manifest_path
self.manifest_dir = os.path.dirname(manifest_path)
self.new_manifest_path = os.path.join(self.manifest_dir,
'new_update_manifest.xml')
self.internal = internal
self.dryrun = dryrun
def CreateNewManifest(self):
"""Generates a new manifest with updated Chrome entries."""
overwritten_content = StringIO.StringIO()
overwritten_content.write('<?xml version="1.0" encoding="UTF-8"?>\n')
overwritten_content.write('<manifest>\n')
# Prepare git repo for push
manifest_version.PrepForChanges(self.manifest_dir, False)
with open(self.new_manifest_path, 'w') as new_manifest:
with open(self.manifest_path, 'r') as f:
line_iter = iter(f.readlines())
for line in line_iter:
if line.strip() == _BEGIN_MARKER.strip():
break
new_manifest.write(line)
else:
raise ManifestException('Chromium projects begin marker not found!')
new_manifest.write(_BEGIN_MARKER + _EXTERNAL_HEADER)
new_manifest.write(_EXTERNAL_PROJECT % {'path' : 'chromium/src',
'name' : 'chromium/src'})
ConvertDepsToManifest(os.path.join(
self.repo_root,
_CHROMIUM_SRC_ROOT,
'.DEPS.git'),
new_manifest, _EXTERNAL_PROJECT)
new_manifest.write('\n')
if self.internal:
new_manifest.write(_INTERNAL_HEADER)
new_manifest.write(_INTERNAL_PROJECT
% {'path' : 'chromium/src-internal',
'name' : 'chrome/src-internal'})
internal_deps = os.path.join(self.repo_root, _CHROMIUM_SRC_INTERNAL,
'.DEPS.git')
ConvertDepsToManifest(internal_deps, new_manifest, _INTERNAL_PROJECT)
new_manifest.write('\n')
new_manifest.write(_END_MARKER)
for line in line_iter:
if line.strip() == _END_MARKER.strip():
break
overwritten_content.write(line)
else:
raise ManifestException('Chromium projects end marker not found!')
# Check contents for non-chrome projects
overwritten_content.write('</manifest>\n')
overwritten_content.seek(0)
CheckForNonChromeProjects(overwritten_content)
for line in line_iter:
new_manifest.write(line)
def IsNewManifestDifferent(self):
"""Checks if newly generated manifest is different from old manifest."""
return not filecmp.cmp(self.new_manifest_path, self.manifest_path,
shallow=False)
def TestNewManifest(self):
"""Runs a 'repo sync' off of new manifest to verify things aren't broken."""
# Copy to .repo/manifest.xml and run repo sync
test_dir = os.path.join(
self.testroot,
_INTERNAL_TEST_DIR if self.internal else _EXTERNAL_TEST_DIR)
if not os.path.isdir(test_dir):
os.makedirs(test_dir)
git_url = (constants.MANIFEST_INT_URL if self.internal
else constants.MANIFEST_URL)
repo = repository.RepoRepository(git_url, test_dir)
try:
repo.Sync(local_manifest=self.new_manifest_path, jobs=12)
except Exception:
cros_lib.Error('Failed to sync with new manifest!')
raise
finally:
# Sync back to original manifest
repo.Sync(local_manifest=repository.RepoRepository.DEFAULT_MANIFEST,
jobs=12)
def PushChanges(self):
"""Push changes to manifest live."""
shutil.move(self.new_manifest_path, self.manifest_path)
cros_lib.RunCommand(['git', 'add', self.manifest_path],
cwd=self.manifest_dir)
cros_lib.RunCommand(['git', 'commit',
'-m',
'Auto-updating manifest to match .DEPS.git file'],
cwd=self.manifest_dir)
cros_lib.GitPushWithRetry(manifest_version.PUSH_BRANCH,
cwd=self.manifest_dir,
dryrun=self.dryrun)
def PerformUpdate(self):
try:
self.CreateNewManifest()
if self.IsNewManifestDifferent():
self.TestNewManifest()
self.PushChanges()
except ManifestException:
cros_lib.Error('Errors encountered while updating manifest!')
raise
def GetSource(repo_path, project_name, internal=False):
"""Checks out and updates a gerrit project.
Arguments:
repo_path: Absolute path of checkout.
project_name: Name of Gerrit project to pull down.
internal: Whether the project is an internal project.
"""
if not os.path.isdir(repo_path):
if internal:
project_url = os.path.join(constants.GERRIT_INT_SSH_URL, project_name)
else:
project_url = os.path.join(constants.GERRIT_SSH_URL, project_name)
cros_lib.RunCommand(['git', 'clone', project_url, repo_path])
cros_lib.RunCommand(['git', 'pull', '--ff-only'], cwd=repo_path)
def _CheckTestRootOption(_option, _opt_str, value, parser):
"""Validate and convert buildroot to full-path form."""
value = value.strip()
if not value or value == '/':
raise optparse.OptionValueError('Invalid buildroot specified')
parser.values.testroot = os.path.realpath(os.path.expanduser(value))
def main(argv=None):
if argv is None:
argv = sys.argv[1:]
usage = 'usage: %prog'
parser = optparse.OptionParser(usage=usage)
parser.add_option('-r', '--testroot', action='callback', dest='testroot',
type='string', callback=_CheckTestRootOption,
help=('Directory where test checkout is stored.'))
parser.add_option('-f', '--force', default=False, action='store_true',
help='Actually push manifest changes.')
parser.add_option('-v', '--verbose', default=False, action='store_true',
help='Run with debug output.')
(options, _inputs) = parser.parse_args(argv)
if not options.testroot:
cros_lib.Die('Please specify a test root!')
# Set cros_build_lib debug level to hide RunCommand spew.
if options.verbose:
cros_lib.DebugLevel.SetDebugLevel(cros_lib.DebugLevel.DEBUG)
else:
cros_lib.DebugLevel.SetDebugLevel(cros_lib.DebugLevel.WARNING)
repo_root = cros_lib.FindRepoCheckoutRoot()
chromium_src_root = os.path.join(repo_root, _CHROMIUM_SRC_ROOT)
if not os.path.isdir(chromium_src_root):
cros_lib.Die('chromium src/ dir not found')
external_manifest_dir = os.path.join(tempfile.gettempdir(),
_EXTERNAL_MANIFEST_DIR)
internal_manifest_dir = os.path.join(tempfile.gettempdir(),
_INTERNAL_MANIFEST_DIR)
# Sync manifest and .DEPS.git files
GetSource(external_manifest_dir, _EXTERNAL_MANIFEST_PROJECT)
GetSource(internal_manifest_dir, _INTERNAL_MANIFEST_PROJECT,
internal=True)
project_list = [_CHROMIUM_SRC_PROJECT, _CHROMIUM_SRC_INTERNAL_PROJECT]
cros_lib.RunCommand(['repo', 'sync'] + project_list, cwd=repo_root)
# Update external manifest
Manifest(repo_root, os.path.join(external_manifest_dir, 'oldlayout.xml'),
options.testroot, internal=False,
dryrun=not options.force).PerformUpdate()
# Update internal manifest
Manifest(repo_root, os.path.join(internal_manifest_dir, 'oldlayout.xml'),
options.testroot, internal=True,
dryrun=not options.force).PerformUpdate()
if __name__ == '__main__':
main()