blob: 9b804da958ef41572df5a5f5e6a59f9ba9cd1a86 [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import os
Conley Owensdb728cd2011-09-26 16:34:01 -070017import re
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070018import sys
Conley Owensdb728cd2011-09-26 16:34:01 -070019import urlparse
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070020import xml.dom.minidom
21
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070022from git_config import GitConfig, IsId
Shawn O. Pearced1f70d92009-05-19 14:58:02 -070023from project import RemoteSpec, Project, MetaProject, R_HEADS, HEAD
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070024from error import ManifestParseError
25
26MANIFEST_FILE_NAME = 'manifest.xml'
Shawn O. Pearce5cc66792008-10-23 16:19:27 -070027LOCAL_MANIFEST_NAME = 'local_manifest.xml'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070028
Conley Owensdb728cd2011-09-26 16:34:01 -070029urlparse.uses_relative.extend(['ssh', 'git'])
30urlparse.uses_netloc.extend(['ssh', 'git'])
31
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070032class _Default(object):
33 """Project defaults within the manifest."""
34
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -070035 revisionExpr = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070036 remote = None
Shawn O. Pearce6392c872011-09-22 17:44:31 -070037 sync_j = 1
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070038
Shawn O. Pearced1f70d92009-05-19 14:58:02 -070039class _XmlRemote(object):
40 def __init__(self,
41 name,
42 fetch=None,
Conley Owensdb728cd2011-09-26 16:34:01 -070043 manifestUrl=None,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -070044 review=None):
45 self.name = name
46 self.fetchUrl = fetch
Conley Owensdb728cd2011-09-26 16:34:01 -070047 self.manifestUrl = manifestUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -070048 self.reviewUrl = review
Conley Owensceea3682011-10-20 10:45:47 -070049 self.resolvedFetchUrl = self._resolveFetchUrl()
Shawn O. Pearced1f70d92009-05-19 14:58:02 -070050
Conley Owensceea3682011-10-20 10:45:47 -070051 def _resolveFetchUrl(self):
52 url = self.fetchUrl.rstrip('/')
Conley Owensdb728cd2011-09-26 16:34:01 -070053 manifestUrl = self.manifestUrl.rstrip('/')
54 # urljoin will get confused if there is no scheme in the base url
55 # ie, if manifestUrl is of the form <hostname:port>
56 if manifestUrl.find(':') != manifestUrl.find('/') - 1:
57 manifestUrl = 'gopher://' + manifestUrl
58 url = urlparse.urljoin(manifestUrl, url)
Conley Owensceea3682011-10-20 10:45:47 -070059 return re.sub(r'^gopher://', '', url)
60
61 def ToRemoteSpec(self, projectName):
Conley Owens9d8f9142011-10-20 14:36:35 -070062 url = self.resolvedFetchUrl.rstrip('/') + '/' + projectName
Shawn O. Pearced1f70d92009-05-19 14:58:02 -070063 return RemoteSpec(self.name, url, self.reviewUrl)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070064
Shawn O. Pearcec8a300f2009-05-18 13:19:57 -070065class XmlManifest(object):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070066 """manages the repo configuration file"""
67
68 def __init__(self, repodir):
69 self.repodir = os.path.abspath(repodir)
70 self.topdir = os.path.dirname(self.repodir)
71 self.manifestFile = os.path.join(self.repodir, MANIFEST_FILE_NAME)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070072 self.globalConfig = GitConfig.ForUser()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070073
74 self.repoProject = MetaProject(self, 'repo',
75 gitdir = os.path.join(repodir, 'repo/.git'),
76 worktree = os.path.join(repodir, 'repo'))
77
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070078 self.manifestProject = MetaProject(self, 'manifests',
Shawn O. Pearcef5c25a62008-11-04 08:11:53 -080079 gitdir = os.path.join(repodir, 'manifests.git'),
80 worktree = os.path.join(repodir, 'manifests'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070081
82 self._Unload()
83
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070084 def Override(self, name):
85 """Use a different manifest, just for the current instantiation.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070086 """
87 path = os.path.join(self.manifestProject.worktree, name)
88 if not os.path.isfile(path):
89 raise ManifestParseError('manifest %s not found' % name)
90
91 old = self.manifestFile
92 try:
93 self.manifestFile = path
94 self._Unload()
95 self._Load()
96 finally:
97 self.manifestFile = old
98
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070099 def Link(self, name):
100 """Update the repo metadata to use a different manifest.
101 """
102 self.Override(name)
103
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700104 try:
105 if os.path.exists(self.manifestFile):
106 os.remove(self.manifestFile)
107 os.symlink('manifests/%s' % name, self.manifestFile)
108 except OSError, e:
109 raise ManifestParseError('cannot link manifest %s' % name)
110
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800111 def _RemoteToXml(self, r, doc, root):
112 e = doc.createElement('remote')
113 root.appendChild(e)
114 e.setAttribute('name', r.name)
115 e.setAttribute('fetch', r.fetchUrl)
116 if r.reviewUrl is not None:
117 e.setAttribute('review', r.reviewUrl)
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800118
119 def Save(self, fd, peg_rev=False):
120 """Write the current manifest out to the given file descriptor.
121 """
Colin Cross5acde752012-03-28 20:15:45 -0700122 mp = self.manifestProject
123
124 groups = mp.config.GetString('manifest.groups')
125 if groups:
126 groups = re.split('[,\s]+', groups)
127
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800128 doc = xml.dom.minidom.Document()
129 root = doc.createElement('manifest')
130 doc.appendChild(root)
131
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700132 # Save out the notice. There's a little bit of work here to give it the
133 # right whitespace, which assumes that the notice is automatically indented
134 # by 4 by minidom.
135 if self.notice:
136 notice_element = root.appendChild(doc.createElement('notice'))
137 notice_lines = self.notice.splitlines()
138 indented_notice = ('\n'.join(" "*4 + line for line in notice_lines))[4:]
139 notice_element.appendChild(doc.createTextNode(indented_notice))
140
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800141 d = self.default
142 sort_remotes = list(self.remotes.keys())
143 sort_remotes.sort()
144
145 for r in sort_remotes:
146 self._RemoteToXml(self.remotes[r], doc, root)
147 if self.remotes:
148 root.appendChild(doc.createTextNode(''))
149
150 have_default = False
151 e = doc.createElement('default')
152 if d.remote:
153 have_default = True
154 e.setAttribute('remote', d.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700155 if d.revisionExpr:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800156 have_default = True
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700157 e.setAttribute('revision', d.revisionExpr)
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700158 if d.sync_j > 1:
159 have_default = True
160 e.setAttribute('sync-j', '%d' % d.sync_j)
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800161 if have_default:
162 root.appendChild(e)
163 root.appendChild(doc.createTextNode(''))
164
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700165 if self._manifest_server:
166 e = doc.createElement('manifest-server')
167 e.setAttribute('url', self._manifest_server)
168 root.appendChild(e)
169 root.appendChild(doc.createTextNode(''))
170
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800171 sort_projects = list(self.projects.keys())
172 sort_projects.sort()
173
174 for p in sort_projects:
175 p = self.projects[p]
Colin Cross5acde752012-03-28 20:15:45 -0700176
177 if not p.MatchesGroups(groups):
178 continue
179
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800180 e = doc.createElement('project')
181 root.appendChild(e)
182 e.setAttribute('name', p.name)
183 if p.relpath != p.name:
184 e.setAttribute('path', p.relpath)
185 if not d.remote or p.remote.name != d.remote.name:
186 e.setAttribute('remote', p.remote.name)
187 if peg_rev:
188 if self.IsMirror:
189 e.setAttribute('revision',
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700190 p.bare_git.rev_parse(p.revisionExpr + '^0'))
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800191 else:
192 e.setAttribute('revision',
193 p.work_git.rev_parse(HEAD + '^0'))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700194 elif not d.revisionExpr or p.revisionExpr != d.revisionExpr:
195 e.setAttribute('revision', p.revisionExpr)
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800196
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800197 for c in p.copyfiles:
198 ce = doc.createElement('copyfile')
199 ce.setAttribute('src', c.src)
200 ce.setAttribute('dest', c.dest)
201 e.appendChild(ce)
202
Colin Cross5acde752012-03-28 20:15:45 -0700203 if p.groups:
204 e.setAttribute('groups', ','.join(p.groups))
205
James W. Mills24c13082012-04-12 15:04:13 -0500206 for a in p.annotations:
207 if a.keep == "true":
208 ae = doc.createElement('annotation')
209 ae.setAttribute('name', a.name)
210 ae.setAttribute('value', a.value)
211 e.appendChild(ae)
212
Doug Anderson37282b42011-03-04 11:54:18 -0800213 if self._repo_hooks_project:
214 root.appendChild(doc.createTextNode(''))
215 e = doc.createElement('repo-hooks')
216 e.setAttribute('in-project', self._repo_hooks_project.name)
217 e.setAttribute('enabled-list',
218 ' '.join(self._repo_hooks_project.enabled_repo_hooks))
219 root.appendChild(e)
220
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800221 doc.writexml(fd, '', ' ', '\n', 'UTF-8')
222
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700223 @property
224 def projects(self):
225 self._Load()
226 return self._projects
227
228 @property
229 def remotes(self):
230 self._Load()
231 return self._remotes
232
233 @property
234 def default(self):
235 self._Load()
236 return self._default
237
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800238 @property
Doug Anderson37282b42011-03-04 11:54:18 -0800239 def repo_hooks_project(self):
240 self._Load()
241 return self._repo_hooks_project
242
243 @property
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700244 def notice(self):
245 self._Load()
246 return self._notice
247
248 @property
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700249 def manifest_server(self):
250 self._Load()
Shawn O. Pearce34fb20f2011-11-30 13:41:02 -0800251 return self._manifest_server
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700252
253 @property
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800254 def IsMirror(self):
255 return self.manifestProject.config.GetBoolean('repo.mirror')
256
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700257 def _Unload(self):
258 self._loaded = False
259 self._projects = {}
260 self._remotes = {}
261 self._default = None
Doug Anderson37282b42011-03-04 11:54:18 -0800262 self._repo_hooks_project = None
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700263 self._notice = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700264 self.branch = None
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700265 self._manifest_server = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700266
267 def _Load(self):
268 if not self._loaded:
Shawn O. Pearce2450a292008-11-04 08:22:07 -0800269 m = self.manifestProject
270 b = m.GetBranch(m.CurrentBranch).merge
Shawn O. Pearce21c5c342009-06-25 16:47:30 -0700271 if b is not None and b.startswith(R_HEADS):
Shawn O. Pearce2450a292008-11-04 08:22:07 -0800272 b = b[len(R_HEADS):]
273 self.branch = b
274
Shawn O. Pearce5cc66792008-10-23 16:19:27 -0700275 self._ParseManifest(True)
276
277 local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME)
278 if os.path.exists(local):
279 try:
280 real = self.manifestFile
281 self.manifestFile = local
282 self._ParseManifest(False)
283 finally:
284 self.manifestFile = real
285
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800286 if self.IsMirror:
287 self._AddMetaProjectMirror(self.repoProject)
288 self._AddMetaProjectMirror(self.manifestProject)
289
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700290 self._loaded = True
291
Shawn O. Pearce5cc66792008-10-23 16:19:27 -0700292 def _ParseManifest(self, is_root_file):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700293 root = xml.dom.minidom.parse(self.manifestFile)
294 if not root or not root.childNodes:
Doug Anderson37282b42011-03-04 11:54:18 -0800295 raise ManifestParseError(
296 "no root node in %s" %
297 self.manifestFile)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700298
299 config = root.childNodes[0]
300 if config.nodeName != 'manifest':
Doug Anderson37282b42011-03-04 11:54:18 -0800301 raise ManifestParseError(
302 "no <manifest> in %s" %
303 self.manifestFile)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700304
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700305 for node in config.childNodes:
Shawn O. Pearce03eaf072008-11-20 11:42:22 -0800306 if node.nodeName == 'remove-project':
307 name = self._reqatt(node, 'name')
308 try:
309 del self._projects[name]
310 except KeyError:
Doug Anderson37282b42011-03-04 11:54:18 -0800311 raise ManifestParseError(
312 'project %s not found' %
313 (name))
314
315 # If the manifest removes the hooks project, treat it as if it deleted
316 # the repo-hooks element too.
317 if self._repo_hooks_project and (self._repo_hooks_project.name == name):
318 self._repo_hooks_project = None
Shawn O. Pearce03eaf072008-11-20 11:42:22 -0800319
320 for node in config.childNodes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700321 if node.nodeName == 'remote':
322 remote = self._ParseRemote(node)
323 if self._remotes.get(remote.name):
Doug Anderson37282b42011-03-04 11:54:18 -0800324 raise ManifestParseError(
325 'duplicate remote %s in %s' %
326 (remote.name, self.manifestFile))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700327 self._remotes[remote.name] = remote
328
329 for node in config.childNodes:
330 if node.nodeName == 'default':
331 if self._default is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800332 raise ManifestParseError(
333 'duplicate default in %s' %
334 (self.manifestFile))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700335 self._default = self._ParseDefault(node)
336 if self._default is None:
337 self._default = _Default()
338
339 for node in config.childNodes:
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700340 if node.nodeName == 'notice':
341 if self._notice is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800342 raise ManifestParseError(
343 'duplicate notice in %s' %
344 (self.manifestFile))
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700345 self._notice = self._ParseNotice(node)
346
347 for node in config.childNodes:
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700348 if node.nodeName == 'manifest-server':
349 url = self._reqatt(node, 'url')
350 if self._manifest_server is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800351 raise ManifestParseError(
352 'duplicate manifest-server in %s' %
353 (self.manifestFile))
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700354 self._manifest_server = url
355
356 for node in config.childNodes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700357 if node.nodeName == 'project':
358 project = self._ParseProject(node)
359 if self._projects.get(project.name):
Doug Anderson37282b42011-03-04 11:54:18 -0800360 raise ManifestParseError(
361 'duplicate project %s in %s' %
362 (project.name, self.manifestFile))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700363 self._projects[project.name] = project
364
Doug Anderson37282b42011-03-04 11:54:18 -0800365 for node in config.childNodes:
366 if node.nodeName == 'repo-hooks':
367 # Get the name of the project and the (space-separated) list of enabled.
368 repo_hooks_project = self._reqatt(node, 'in-project')
369 enabled_repo_hooks = self._reqatt(node, 'enabled-list').split()
370
371 # Only one project can be the hooks project
372 if self._repo_hooks_project is not None:
373 raise ManifestParseError(
374 'duplicate repo-hooks in %s' %
375 (self.manifestFile))
376
377 # Store a reference to the Project.
378 try:
379 self._repo_hooks_project = self._projects[repo_hooks_project]
380 except KeyError:
381 raise ManifestParseError(
382 'project %s not found for repo-hooks' %
383 (repo_hooks_project))
384
385 # Store the enabled hooks in the Project object.
386 self._repo_hooks_project.enabled_repo_hooks = enabled_repo_hooks
387
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800388 def _AddMetaProjectMirror(self, m):
389 name = None
390 m_url = m.GetRemote(m.remote.name).url
391 if m_url.endswith('/.git'):
392 raise ManifestParseError, 'refusing to mirror %s' % m_url
393
394 if self._default and self._default.remote:
Conley Owensceea3682011-10-20 10:45:47 -0700395 url = self._default.remote.resolvedFetchUrl
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800396 if not url.endswith('/'):
397 url += '/'
398 if m_url.startswith(url):
399 remote = self._default.remote
400 name = m_url[len(url):]
401
402 if name is None:
403 s = m_url.rindex('/') + 1
Conley Owensdb728cd2011-09-26 16:34:01 -0700404 manifestUrl = self.manifestProject.config.GetString('remote.origin.url')
405 remote = _XmlRemote('origin', m_url[:s], manifestUrl)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800406 name = m_url[s:]
407
408 if name.endswith('.git'):
409 name = name[:-4]
410
411 if name not in self._projects:
412 m.PreSync()
413 gitdir = os.path.join(self.topdir, '%s.git' % name)
414 project = Project(manifest = self,
415 name = name,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700416 remote = remote.ToRemoteSpec(name),
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800417 gitdir = gitdir,
418 worktree = None,
419 relpath = None,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700420 revisionExpr = m.revisionExpr,
421 revisionId = None)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800422 self._projects[project.name] = project
423
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700424 def _ParseRemote(self, node):
425 """
426 reads a <remote> element from the manifest file
427 """
428 name = self._reqatt(node, 'name')
429 fetch = self._reqatt(node, 'fetch')
430 review = node.getAttribute('review')
Shawn O. Pearceae6e0942008-11-06 10:25:35 -0800431 if review == '':
432 review = None
Conley Owensdb728cd2011-09-26 16:34:01 -0700433 manifestUrl = self.manifestProject.config.GetString('remote.origin.url')
434 return _XmlRemote(name, fetch, manifestUrl, review)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700435
436 def _ParseDefault(self, node):
437 """
438 reads a <default> element from the manifest file
439 """
440 d = _Default()
441 d.remote = self._get_remote(node)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700442 d.revisionExpr = node.getAttribute('revision')
443 if d.revisionExpr == '':
444 d.revisionExpr = None
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700445 sync_j = node.getAttribute('sync-j')
446 if sync_j == '' or sync_j is None:
447 d.sync_j = 1
448 else:
449 d.sync_j = int(sync_j)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700450 return d
451
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700452 def _ParseNotice(self, node):
453 """
454 reads a <notice> element from the manifest file
455
456 The <notice> element is distinct from other tags in the XML in that the
457 data is conveyed between the start and end tag (it's not an empty-element
458 tag).
459
460 The white space (carriage returns, indentation) for the notice element is
461 relevant and is parsed in a way that is based on how python docstrings work.
462 In fact, the code is remarkably similar to here:
463 http://www.python.org/dev/peps/pep-0257/
464 """
465 # Get the data out of the node...
466 notice = node.childNodes[0].data
467
468 # Figure out minimum indentation, skipping the first line (the same line
469 # as the <notice> tag)...
470 minIndent = sys.maxint
471 lines = notice.splitlines()
472 for line in lines[1:]:
473 lstrippedLine = line.lstrip()
474 if lstrippedLine:
475 indent = len(line) - len(lstrippedLine)
476 minIndent = min(indent, minIndent)
477
478 # Strip leading / trailing blank lines and also indentation.
479 cleanLines = [lines[0].strip()]
480 for line in lines[1:]:
481 cleanLines.append(line[minIndent:].rstrip())
482
483 # Clear completely blank lines from front and back...
484 while cleanLines and not cleanLines[0]:
485 del cleanLines[0]
486 while cleanLines and not cleanLines[-1]:
487 del cleanLines[-1]
488
489 return '\n'.join(cleanLines)
490
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700491 def _ParseProject(self, node):
492 """
493 reads a <project> element from the manifest file
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700494 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700495 name = self._reqatt(node, 'name')
496
497 remote = self._get_remote(node)
498 if remote is None:
499 remote = self._default.remote
500 if remote is None:
501 raise ManifestParseError, \
502 "no remote for project %s within %s" % \
503 (name, self.manifestFile)
504
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700505 revisionExpr = node.getAttribute('revision')
506 if not revisionExpr:
507 revisionExpr = self._default.revisionExpr
508 if not revisionExpr:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700509 raise ManifestParseError, \
510 "no revision for project %s within %s" % \
511 (name, self.manifestFile)
512
513 path = node.getAttribute('path')
514 if not path:
515 path = name
516 if path.startswith('/'):
517 raise ManifestParseError, \
518 "project %s path cannot be absolute in %s" % \
519 (name, self.manifestFile)
520
Mike Pontillod3153822012-02-28 11:53:24 -0800521 rebase = node.getAttribute('rebase')
522 if not rebase:
523 rebase = True
524 else:
525 rebase = rebase.lower() in ("yes", "true", "1")
526
Colin Cross5acde752012-03-28 20:15:45 -0700527 groups = node.getAttribute('groups')
528 if groups:
529 groups = re.split('[,\s]+', groups)
530 else:
531 groups = None
532
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800533 if self.IsMirror:
534 relpath = None
535 worktree = None
536 gitdir = os.path.join(self.topdir, '%s.git' % name)
537 else:
Anthony Newnamdf14a702011-01-09 17:31:57 -0800538 worktree = os.path.join(self.topdir, path).replace('\\', '/')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800539 gitdir = os.path.join(self.repodir, 'projects/%s.git' % path)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700540
541 project = Project(manifest = self,
542 name = name,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700543 remote = remote.ToRemoteSpec(name),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700544 gitdir = gitdir,
545 worktree = worktree,
546 relpath = path,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700547 revisionExpr = revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800548 revisionId = None,
Colin Cross5acde752012-03-28 20:15:45 -0700549 rebase = rebase,
550 groups = groups)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700551
552 for n in node.childNodes:
Shawn O. Pearce242b5262009-05-19 13:00:29 -0700553 if n.nodeName == 'copyfile':
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700554 self._ParseCopyFile(project, n)
James W. Mills24c13082012-04-12 15:04:13 -0500555 if n.nodeName == 'annotation':
556 self._ParseAnnotation(project, n)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700557
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700558 return project
559
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700560 def _ParseCopyFile(self, project, node):
561 src = self._reqatt(node, 'src')
562 dest = self._reqatt(node, 'dest')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800563 if not self.IsMirror:
564 # src is project relative;
565 # dest is relative to the top of the tree
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800566 project.AddCopyFile(src, dest, os.path.join(self.topdir, dest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700567
James W. Mills24c13082012-04-12 15:04:13 -0500568 def _ParseAnnotation(self, project, node):
569 name = self._reqatt(node, 'name')
570 value = self._reqatt(node, 'value')
571 try:
572 keep = self._reqatt(node, 'keep').lower()
573 except ManifestParseError:
574 keep = "true"
575 if keep != "true" and keep != "false":
576 raise ManifestParseError, "optional \"keep\" attribute must be \"true\" or \"false\""
577 project.AddAnnotation(name, value, keep)
578
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700579 def _get_remote(self, node):
580 name = node.getAttribute('remote')
581 if not name:
582 return None
583
584 v = self._remotes.get(name)
585 if not v:
586 raise ManifestParseError, \
587 "remote %s not defined in %s" % \
588 (name, self.manifestFile)
589 return v
590
591 def _reqatt(self, node, attname):
592 """
593 reads a required attribute from the node.
594 """
595 v = node.getAttribute(attname)
596 if not v:
597 raise ManifestParseError, \
598 "no %s in <%s> within %s" % \
599 (attname, node.nodeName, self.manifestFile)
600 return v