| #!/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.""" |
| |
| |
| import logging |
| import multiprocessing |
| import os |
| import sys |
| import StringIO |
| |
| from chromite.scripts import chrome_set_ver |
| from chromite.lib import commandline |
| from chromite.lib import cros_build_lib |
| from chromite.lib import git |
| from chromite.lib import osutils |
| from chromite.buildbot import constants |
| from chromite.buildbot import repository |
| |
| |
| |
| _MANIFEST_TEMPLATE = """\ |
| <?xml version="1.0" encoding="UTF-8"?> |
| <manifest> |
| <!-- @@@@ THIS CONTENT IS AUTOGENERATED; DO NOT MODIFY! @@@@ --> |
| |
| <include name="%(include_target)s" /> |
| %(content)s |
| </manifest> |
| """ |
| |
| _TYPE_MARKER = """\ |
| |
| <!-- @@@@ THIS IS THE %(type)s CONTENT @@@@ --> |
| |
| """ |
| |
| _PROJECT = """\ |
| <project remote="%(remote)s" |
| path="%(path)s" |
| name="%(name)s" |
| revision="%(ref)s" /> |
| """ |
| |
| |
| _EXTERNAL_MANIFEST_PROJECT = 'chromiumos/manifest' |
| _INTERNAL_MANIFEST_PROJECT = 'chromeos/manifest-internal' |
| |
| TEST_BRANCH = "temp-newly-synced" |
| MANIFEST_FILE = 'gerrit-source.xml' |
| INCLUDE_TARGET = 'full.xml' |
| |
| |
| def _MkProject(path, name, remote, branch='master'): |
| return _PROJECT % {'path':path, 'name':name, 'remote':remote, |
| 'ref':'refs/heads/%s' % (branch,)} |
| |
| |
| def ConvertDepsToManifest(deps_file, project_blacklist, manifest_file, |
| remote): |
| """Convert dependencies in .DEPS.git files to manifest entries. |
| |
| Arguments: |
| deps_file: The path to the .DEPS.git file. |
| project_blacklist: Set of project names that we shouldn't write |
| entries for; specifically since they're already in our parent. |
| manifest_file: The file object to write manifest entries to. |
| remote: The remote value to write to the new manifest. |
| """ |
| _, merged_deps = chrome_set_ver.GetParsedDeps(deps_file) |
| mappings = chrome_set_ver.GetPathToProjectMappings(merged_deps) |
| |
| # Check for double checkouts and blacklisted projects. |
| previous_projects = set(project_blacklist) |
| for rel_path, project in sorted(mappings.iteritems(), key=lambda x: x[1]): |
| rel_path = os.path.join('chromium', rel_path) |
| if project.startswith('chromiumos'): |
| cros_build_lib.Warning('Skipping project %s in %s', project, deps_file) |
| elif project in previous_projects: |
| cros_build_lib.Warning('Found double checkout of %s to %s', |
| project, rel_path) |
| else: |
| manifest_file.write(_MkProject(rel_path, project, remote)) |
| previous_projects.add(project) |
| |
| |
| class Manifest(object): |
| """Encapsulates manifest update logic for an external or internal manifest.""" |
| |
| EXTERNAL_REMOTE = 'chromium' |
| INTERNAL_REMOTE = 'chrome' |
| |
| CHROMIUM_PROJECT = CHROMIUM_ROOT = 'chromium/src' |
| CHROME_ROOT = 'chromium/src-internal' |
| CHROME_PROJECT = 'chrome/src-internal' |
| |
| def __init__(self, repo_root, manifest_dir, internal, dryrun=True, |
| reference=None): |
| self.repo_root = repo_root |
| self.manifest_dir = manifest_dir |
| self.manifest_path = os.path.join(self.manifest_dir, MANIFEST_FILE) |
| self.internal = internal |
| self.dryrun = dryrun |
| self.reference = reference |
| |
| def SyncSources(self): |
| repo = repository.RepoRepository( |
| self.manifest_dir, self.repo_root, referenced_repo=self.reference, |
| manifest=MANIFEST_FILE, branch='master') |
| # Trigger the network sync |
| repo.Sync(jobs=multiprocessing.cpu_count() + 1, network_only=True) |
| projects = [self.CHROMIUM_ROOT] |
| if self.internal: |
| projects.append(self.CHROME_ROOT) |
| |
| for project in projects: |
| path = os.path.join(self.repo_root, project) |
| if os.path.exists(path): |
| try: |
| git.CleanAndCheckoutUpstream(path, refresh_upstream=False) |
| continue |
| except cros_build_lib.RunCommandError: |
| cros_build_lib.Error("Failed cleaning %r; wiping.", path) |
| cros_build_lib.SudoRunCommand(['rm', '-rf', path], print_cmd=False) |
| |
| cros_build_lib.RunCommand(['repo', 'sync', '-ld', project], |
| cwd=self.repo_root) |
| |
| def CreateNewManifest(self): |
| """Generates a new manifest with updated Chrome entries.""" |
| # Prepare git repo for push |
| git.CreatePushBranch( |
| TEST_BRANCH, self.manifest_dir, |
| remote_push_branch=('origin', 'refs/heads/master')) |
| |
| content = StringIO.StringIO() |
| content.write(_TYPE_MARKER % {'type': 'EXTERNAL'}) |
| content.write(_MkProject(self.CHROMIUM_ROOT, self.CHROMIUM_PROJECT, |
| self.EXTERNAL_REMOTE, branch='git-svn')) |
| # Grab the repo manifest, and ensure that we're not adding a project that |
| # our inherit target already has. |
| include_target = os.path.join(self.manifest_dir, INCLUDE_TARGET) |
| existing_projects = frozenset(git.Manifest.Cached( |
| include_target, manifest_include_dir=self.manifest_dir).projects) |
| |
| ConvertDepsToManifest( |
| os.path.join(self.repo_root, self.CHROMIUM_ROOT, '.DEPS.git'), |
| existing_projects, content, self.EXTERNAL_REMOTE) |
| |
| if self.internal: |
| content.write(_TYPE_MARKER % {'type': 'INTERNAL'}) |
| content.write(_MkProject(self.CHROME_ROOT, self.CHROME_PROJECT, |
| self.INTERNAL_REMOTE)) |
| ConvertDepsToManifest( |
| os.path.join(self.repo_root, self.CHROME_ROOT, |
| '.DEPS.git'), |
| existing_projects, content, self.INTERNAL_REMOTE) |
| |
| osutils.WriteFile( |
| self.manifest_path, |
| _MANIFEST_TEMPLATE % {'content':content.getvalue(), |
| 'include_target':INCLUDE_TARGET}) |
| |
| def IsNewManifestDifferent(self): |
| """Checks if newly generated manifest is different from old manifest.""" |
| return bool( |
| git.RunGit(self.manifest_dir, ['status', '--porcelain']).output.strip()) |
| |
| def CommitNewManifest(self): |
| git.RunGit(self.manifest_dir, ['add', self.manifest_path]) |
| git.RunGit(self.manifest_dir, [ |
| 'commit', '-m', |
| 'Auto-updating manifest to match .DEPS.git file']) |
| |
| def TestNewManifest(self): |
| """Runs a 'repo sync' off of new manifest to verify things aren't broken.""" |
| |
| # Do as cheap a sync as possible; network only is good enough, |
| # allow shallow cloning if we don't have a reference, and sync |
| # strictly the target branch. |
| repo = repository.RepoRepository( |
| self.manifest_dir, self.repo_root, branch=TEST_BRANCH, |
| referenced_repo=self.reference, manifest=MANIFEST_FILE) |
| try: |
| repo.Sync(jobs=multiprocessing.cpu_count() + 1, network_only=True) |
| except Exception: |
| cros_build_lib.Error('Failed to sync with new manifest!') |
| raise |
| |
| def PushChanges(self): |
| """Push changes to manifest live.""" |
| git.PushWithRetry( |
| TEST_BRANCH, self.manifest_dir, dryrun=self.dryrun) |
| |
| def PerformUpdate(self): |
| self.SyncSources() |
| self.CreateNewManifest() |
| if not self.IsNewManifestDifferent(): |
| return |
| self.CommitNewManifest() |
| self.TestNewManifest() |
| self.PushChanges() |
| |
| |
| def main(argv=None): |
| if argv is None: |
| argv = sys.argv[1:] |
| |
| usage = 'usage: %prog' |
| parser = commandline.OptionParser(usage=usage) |
| |
| parser.add_option('-r', '--testroot', action='store', type='path', |
| help=('Directory where test checkout is stored.')) |
| parser.add_option('-f', '--force', default=True, action='store_false', |
| dest='dryrun', |
| help='If enabled, allow committing.') |
| parser.add_option( |
| '--internal-manifest-url', |
| default='%s/%s' % (constants.GERRIT_INT_SSH_URL, |
| _INTERNAL_MANIFEST_PROJECT), |
| help='Url to fetch the internal manifest from.') |
| parser.add_option( |
| '--external-manifest-url', |
| default='%s/%s' % (constants.GERRIT_SSH_URL, |
| _EXTERNAL_MANIFEST_PROJECT), |
| help='Url to fetch the external manifest from.') |
| parser.add_option('-v', '--verbose', default=False, action='store_true', |
| help='Run with debug output.') |
| parser.add_option('--reference', default=None, |
| help="Repository to reference") |
| (options, _inputs) = parser.parse_args(argv) |
| |
| if not options.testroot: |
| parser.error('Please specify a test root!') |
| |
| # Set cros_build_lib debug level to hide RunCommand spew. |
| logging.getLogger().setLevel( |
| logging.DEBUG if options.verbose else logging.WARN) |
| |
| if options.reference is None: |
| options.reference = git.FindRepoCheckoutRoot(os.getcwd()) |
| |
| def process_target(internal, manifest_url, reference): |
| subdir = 'internal' if internal else 'external' |
| root = os.path.join(options.testroot, subdir) |
| repo_dir = os.path.join(root, 'repo') |
| osutils.SafeMakedirs(repo_dir) |
| manifest_dir = os.path.join(root, 'manifest') |
| |
| if os.path.exists(manifest_dir): |
| git.CleanAndCheckoutUpstream(manifest_dir) |
| git.RunGit(manifest_dir, |
| ['checkout', '-B', 'master', '-t', 'origin/master']) |
| else: |
| repository.CloneGitRepo(manifest_dir, manifest_url) |
| m = Manifest(repo_dir, manifest_dir, internal, |
| reference=reference, |
| dryrun=options.dryrun) |
| m.PerformUpdate() |
| |
| if options.dryrun: |
| print "%s manifest is now:" % subdir |
| print osutils.ReadFile(os.path.join(manifest_dir, MANIFEST_FILE)) |
| |
| # Process internal first so that any references to the passed in repo |
| # are utilized in full, w/ external then slaving from the new internal. |
| process_target(True, options.internal_manifest_url, options.reference) |
| process_target(False, options.external_manifest_url, |
| os.path.join(options.testroot, 'internal', 'repo')) |