1 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 """This module allows adding and deleting of projects to the local manifest."""
12 import xml.etree.ElementTree as ElementTree
13 from chromite.lib import cros_build_lib
14 from chromite.lib import git
17 class Manifest(object):
18 """Class which provides an abstraction for manipulating the local manifest."""
21 def FromPath(cls, path, empty_if_missing=False):
22 if os.path.isfile(path):
25 elif empty_if_missing:
26 cros_build_lib.Die('Manifest file, %r, not found' % path)
29 def __init__(self, text=None):
30 self._text = text or '<manifest>\n</manifest>'
31 self.nodes = ElementTree.fromstring(self._text)
33 def AddNonWorkonProject(self, name, path, remote=None, revision=None):
34 """Add a new nonworkon project element to the manifest tree."""
35 element = ElementTree.Element('project', name=name, path=path,
37 element.attrib['workon'] = 'False'
38 if revision is not None:
39 element.attrib['revision'] = revision
40 self.nodes.append(element)
43 def GetProject(self, name, path=None):
44 """Accessor method for getting a project node from the manifest tree.
47 project element node from ElementTree, otherwise, None
50 # Use a unique value that can't ever match.
52 for project in self.nodes.findall('project'):
53 if project.attrib['name'] == name or project.attrib['path'] == path:
58 # Reset the tail for each node, then just do a hacky replace.
60 for project in self.nodes.findall('project'):
62 if project is not None:
63 # Tweak the last project to not have the trailing space.
65 # Fix manifest tag text and tail.
66 self.nodes.text = '\n '
67 self.nodes.tail = '\n'
68 return ElementTree.tostring(self.nodes)
70 def GetProjects(self):
71 return list(self.nodes.findall('project'))
74 def _AddProjectsToManifestGroups(options, *args):
75 """Enable the given manifest groups for the configured repository."""
77 groups_to_enable = ['name:%s' % x for x in args]
79 git_config = options.git_config
81 cmd = ['config', '-f', git_config, '--get', 'manifest.groups']
82 enabled_groups = git.RunGit('.', cmd, error_code_ok=True).output.split(',')
84 # Note that ordering actually matters, thus why the following code
85 # is written this way.
86 # Per repo behaviour, enforce an appropriate platform group if
87 # we're converting from a default manifest group to a limited one.
88 # Finally, note we reprocess the existing groups; this is to allow
89 # us to cleanup any user screwups, or our own screwups.
91 ['minilayout', 'platform-%s' % (platform.system().lower(),)] +
92 enabled_groups + list(groups_to_enable))
94 processed_groups = set()
97 for group in requested_groups:
98 if group not in processed_groups:
99 finalized_groups.append(group)
100 processed_groups.add(group)
102 cmd = ['config', '-f', git_config, 'manifest.groups',
103 ','.join(finalized_groups)]
107 def _UpgradeMinilayout(options):
108 """Convert a repo checkout away from minilayout.xml to default.xml."""
110 full_tree = Manifest.FromPath(options.default_manifest_path)
111 local_manifest_exists = os.path.exists(options.local_manifest_path)
114 if local_manifest_exists:
115 local_tree = Manifest.FromPath(options.local_manifest_path)
116 # Identify which projects need to be transferred across.
117 projects = local_tree.GetProjects()
118 new_groups = [x.attrib['name'] for x in projects]
119 allowed = set(x.attrib['name'] for x in full_tree.GetProjects())
120 transferred = [x for x in projects if x.attrib['name'] in allowed]
121 for project in transferred:
122 # Mangle local_manifest object, removing those projects;
123 # note we'll still be adding those projects to the default groups,
124 # including those that didn't intersect the main manifest.
125 local_tree.nodes.remove(project)
127 _AddProjectsToManifestGroups(options, *new_groups)
129 if local_manifest_exists:
130 # Rewrite the local_manifest now; if there is no settings left in
131 # the local_manifest, wipe it.
132 if local_tree.nodes.getchildren():
133 with open(options.local_manifest_path, 'w') as f:
134 f.write(local_tree.ToString())
136 os.unlink(options.local_manifest_path)
138 # Finally, move the symlink.
139 os.unlink(options.manifest_sym_path)
140 os.symlink('manifests/default.xml', options.manifest_sym_path)
141 logging.info("Converted the checkout to manifest groups based minilayout.")
145 parser = optparse.OptionParser(usage='usage: %prog add [options] <name> '
146 '<--workon | <path> --remote <remote> >')
147 parser.add_option('-w', '--workon', action='store_true', dest='workon',
148 default=False, help='Is this a workon package?')
149 parser.add_option('-r', '--remote', dest='remote',
151 parser.add_option('-v', '--revision', dest='revision',
153 help="Use to override the manifest defined default "
154 "revision used for a given project.")
155 parser.add_option('--upgrade-minilayout', default=False, action='store_true',
156 help="Upgrade a minilayout checkout into a full.xml "
157 "checkout utilizing manifest groups.")
158 (options, args) = parser.parse_args(argv)
160 repo_dir = git.FindRepoDir(os.getcwd())
162 parser.error("This script must be invoked from within a repository "
165 options.git_config = os.path.join(repo_dir, 'manifests.git', 'config')
166 options.repo_dir = repo_dir
167 options.local_manifest_path = os.path.join(repo_dir, 'local_manifest.xml')
168 # This constant is used only when we're doing an upgrade away from
169 # minilayout.xml to default.xml.
170 options.default_manifest_path = os.path.join(repo_dir, 'manifests',
172 options.manifest_sym_path = os.path.join(repo_dir, 'manifest.xml')
174 active_manifest = os.path.basename(os.readlink(options.manifest_sym_path))
175 upgrade_required = active_manifest == 'minilayout.xml'
177 if options.upgrade_minilayout:
179 parser.error("--upgrade-minilayout takes no arguments.")
180 if not upgrade_required:
181 print "This repository checkout isn't using minilayout.xml; nothing to do"
183 _UpgradeMinilayout(options)
185 elif upgrade_required:
187 "Your repository checkout is using the old minilayout.xml workflow; "
188 "auto-upgrading it.")
189 cros_build_lib.RunCommand(
190 [sys.argv[0], '--upgrade-minilayout'], cwd=os.getcwd(),
194 parser.error("No command specified.")
195 elif args[0] != 'add':
196 parser.error("Only supported subcommand is add right now.")
200 "Argument count is wrong for --workon; must be add <project>")
201 name, path = args[1], None
203 if options.remote is None:
204 parser.error('Adding non-workon projects requires a remote.')
207 "Argument count is wrong for non-workon mode; "
208 "must be add <project> <path> --remote <remote-arg>")
209 name, path = args[1:]
211 revision = options.revision
212 if revision is not None:
213 if (not git.IsRefsTags(revision) and
214 not git.IsSHA1(revision)):
215 revision = git.StripRefsHeads(revision, False)
217 main_manifest = Manifest.FromPath(options.manifest_sym_path,
218 empty_if_missing=False)
219 local_manifest = Manifest.FromPath(options.local_manifest_path)
221 main_element = main_manifest.GetProject(name, path=path)
224 if main_element is None:
225 parser.error('No project named %r in the default manifest.' % name)
226 _AddProjectsToManifestGroups(options, main_element.attrib['name'])
228 elif main_element is not None:
229 if options.remote is not None:
230 # Likely this project wasn't meant to be remote, so workon main element
231 print "Project already exists in manifest. Using that as workon project."
232 _AddProjectsToManifestGroups(options, main_element.attrib['name'])
234 # Conflict will occur; complain.
235 parser.error("Requested project name=%r path=%r will conflict with "
236 "your current manifest %s" % (name, path, active_manifest))
238 elif local_manifest.GetProject(name, path=path) is not None:
239 parser.error("Requested project name=%r path=%r conflicts with "
240 "your local_manifest.xml" % (name, path))
243 element = local_manifest.AddNonWorkonProject(name=name, path=path,
244 remote=options.remote,
246 _AddProjectsToManifestGroups(options, element.attrib['name'])
248 with open(options.local_manifest_path, 'w') as f:
249 f.write(local_manifest.ToString())