blob: 36f8ef8766ca7938c00555f7f44f104f035e0d2a [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
Sarah Owenscecd1d82012-11-01 22:59:27 -070016from __future__ import print_function
Colin Cross23acdd32012-04-21 00:33:54 -070017import itertools
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070018import os
Conley Owensdb728cd2011-09-26 16:34:01 -070019import re
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070020import sys
Conley Owensdb728cd2011-09-26 16:34:01 -070021import urlparse
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070022import xml.dom.minidom
23
David Pursehousee15c65a2012-08-22 10:46:11 +090024from git_config import GitConfig
David Pursehousee00aa6b2012-09-11 14:33:51 +090025from git_refs import R_HEADS, HEAD
26from project import RemoteSpec, Project, MetaProject
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070027from error import ManifestParseError
28
29MANIFEST_FILE_NAME = 'manifest.xml'
Shawn O. Pearce5cc66792008-10-23 16:19:27 -070030LOCAL_MANIFEST_NAME = 'local_manifest.xml'
David Pursehouse2d5a0df2012-11-13 02:50:36 +090031LOCAL_MANIFESTS_DIR_NAME = 'local_manifests'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070032
Conley Owensdb728cd2011-09-26 16:34:01 -070033urlparse.uses_relative.extend(['ssh', 'git'])
34urlparse.uses_netloc.extend(['ssh', 'git'])
35
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070036class _Default(object):
37 """Project defaults within the manifest."""
38
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -070039 revisionExpr = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070040 remote = None
Shawn O. Pearce6392c872011-09-22 17:44:31 -070041 sync_j = 1
Anatol Pomazau79770d22012-04-20 14:41:59 -070042 sync_c = False
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080043 sync_s = False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070044
Shawn O. Pearced1f70d92009-05-19 14:58:02 -070045class _XmlRemote(object):
46 def __init__(self,
47 name,
Yestin Sunb292b982012-07-02 07:32:50 -070048 alias=None,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -070049 fetch=None,
Conley Owensdb728cd2011-09-26 16:34:01 -070050 manifestUrl=None,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -070051 review=None):
52 self.name = name
53 self.fetchUrl = fetch
Conley Owensdb728cd2011-09-26 16:34:01 -070054 self.manifestUrl = manifestUrl
Yestin Sunb292b982012-07-02 07:32:50 -070055 self.remoteAlias = alias
Shawn O. Pearced1f70d92009-05-19 14:58:02 -070056 self.reviewUrl = review
Conley Owensceea3682011-10-20 10:45:47 -070057 self.resolvedFetchUrl = self._resolveFetchUrl()
Shawn O. Pearced1f70d92009-05-19 14:58:02 -070058
David Pursehouse717ece92012-11-13 08:49:16 +090059 def __eq__(self, other):
60 return self.__dict__ == other.__dict__
61
62 def __ne__(self, other):
63 return self.__dict__ != other.__dict__
64
Conley Owensceea3682011-10-20 10:45:47 -070065 def _resolveFetchUrl(self):
66 url = self.fetchUrl.rstrip('/')
Conley Owensdb728cd2011-09-26 16:34:01 -070067 manifestUrl = self.manifestUrl.rstrip('/')
68 # urljoin will get confused if there is no scheme in the base url
69 # ie, if manifestUrl is of the form
70 if manifestUrl.find(':') != manifestUrl.find('/') - 1:
David Pursehousec1b86a22012-11-14 11:36:51 +090071 manifestUrl = 'gopher://' + manifestUrl
Conley Owensdb728cd2011-09-26 16:34:01 -070072 url = urlparse.urljoin(manifestUrl, url)
Conley Owensceea3682011-10-20 10:45:47 -070073 return re.sub(r'^gopher://', '', url)
74
75 def ToRemoteSpec(self, projectName):
Conley Owens9d8f9142011-10-20 14:36:35 -070076 url = self.resolvedFetchUrl.rstrip('/') + '/' + projectName
Yestin Sunb292b982012-07-02 07:32:50 -070077 remoteName = self.name
78 if self.remoteAlias:
79 remoteName = self.remoteAlias
80 return RemoteSpec(remoteName, url, self.reviewUrl)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070081
Shawn O. Pearcec8a300f2009-05-18 13:19:57 -070082class XmlManifest(object):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070083 """manages the repo configuration file"""
84
85 def __init__(self, repodir):
86 self.repodir = os.path.abspath(repodir)
87 self.topdir = os.path.dirname(self.repodir)
88 self.manifestFile = os.path.join(self.repodir, MANIFEST_FILE_NAME)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070089 self.globalConfig = GitConfig.ForUser()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070090
91 self.repoProject = MetaProject(self, 'repo',
92 gitdir = os.path.join(repodir, 'repo/.git'),
93 worktree = os.path.join(repodir, 'repo'))
94
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070095 self.manifestProject = MetaProject(self, 'manifests',
Shawn O. Pearcef5c25a62008-11-04 08:11:53 -080096 gitdir = os.path.join(repodir, 'manifests.git'),
97 worktree = os.path.join(repodir, 'manifests'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070098
99 self._Unload()
100
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700101 def Override(self, name):
102 """Use a different manifest, just for the current instantiation.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700103 """
104 path = os.path.join(self.manifestProject.worktree, name)
105 if not os.path.isfile(path):
106 raise ManifestParseError('manifest %s not found' % name)
107
108 old = self.manifestFile
109 try:
110 self.manifestFile = path
111 self._Unload()
112 self._Load()
113 finally:
114 self.manifestFile = old
115
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700116 def Link(self, name):
117 """Update the repo metadata to use a different manifest.
118 """
119 self.Override(name)
120
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700121 try:
122 if os.path.exists(self.manifestFile):
123 os.remove(self.manifestFile)
124 os.symlink('manifests/%s' % name, self.manifestFile)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900125 except OSError:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700126 raise ManifestParseError('cannot link manifest %s' % name)
127
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800128 def _RemoteToXml(self, r, doc, root):
129 e = doc.createElement('remote')
130 root.appendChild(e)
131 e.setAttribute('name', r.name)
132 e.setAttribute('fetch', r.fetchUrl)
133 if r.reviewUrl is not None:
134 e.setAttribute('review', r.reviewUrl)
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800135
Brian Harring14a66742012-09-28 20:21:57 -0700136 def Save(self, fd, peg_rev=False, peg_rev_upstream=True):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800137 """Write the current manifest out to the given file descriptor.
138 """
Colin Cross5acde752012-03-28 20:15:45 -0700139 mp = self.manifestProject
140
141 groups = mp.config.GetString('manifest.groups')
Colin Crossc39864f2012-04-23 13:41:58 -0700142 if not groups:
Conley Owensbb1b5f52012-08-13 13:11:18 -0700143 groups = 'all'
Conley Owens971de8e2012-04-16 10:36:08 -0700144 groups = [x for x in re.split(r'[,\s]+', groups) if x]
Colin Cross5acde752012-03-28 20:15:45 -0700145
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800146 doc = xml.dom.minidom.Document()
147 root = doc.createElement('manifest')
148 doc.appendChild(root)
149
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700150 # Save out the notice. There's a little bit of work here to give it the
151 # right whitespace, which assumes that the notice is automatically indented
152 # by 4 by minidom.
153 if self.notice:
154 notice_element = root.appendChild(doc.createElement('notice'))
155 notice_lines = self.notice.splitlines()
156 indented_notice = ('\n'.join(" "*4 + line for line in notice_lines))[4:]
157 notice_element.appendChild(doc.createTextNode(indented_notice))
158
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800159 d = self.default
160 sort_remotes = list(self.remotes.keys())
161 sort_remotes.sort()
162
163 for r in sort_remotes:
164 self._RemoteToXml(self.remotes[r], doc, root)
165 if self.remotes:
166 root.appendChild(doc.createTextNode(''))
167
168 have_default = False
169 e = doc.createElement('default')
170 if d.remote:
171 have_default = True
172 e.setAttribute('remote', d.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700173 if d.revisionExpr:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800174 have_default = True
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700175 e.setAttribute('revision', d.revisionExpr)
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700176 if d.sync_j > 1:
177 have_default = True
178 e.setAttribute('sync-j', '%d' % d.sync_j)
Anatol Pomazau79770d22012-04-20 14:41:59 -0700179 if d.sync_c:
180 have_default = True
181 e.setAttribute('sync-c', 'true')
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800182 if d.sync_s:
183 have_default = True
184 e.setAttribute('sync-s', 'true')
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800185 if have_default:
186 root.appendChild(e)
187 root.appendChild(doc.createTextNode(''))
188
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700189 if self._manifest_server:
190 e = doc.createElement('manifest-server')
191 e.setAttribute('url', self._manifest_server)
192 root.appendChild(e)
193 root.appendChild(doc.createTextNode(''))
194
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800195 def output_projects(parent, parent_node, projects):
196 for p in projects:
197 output_project(parent, parent_node, self.projects[p])
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800198
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800199 def output_project(parent, parent_node, p):
Colin Cross5acde752012-03-28 20:15:45 -0700200 if not p.MatchesGroups(groups):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800201 return
202
203 name = p.name
204 relpath = p.relpath
205 if parent:
206 name = self._UnjoinName(parent.name, name)
207 relpath = self._UnjoinRelpath(parent.relpath, relpath)
Colin Cross5acde752012-03-28 20:15:45 -0700208
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800209 e = doc.createElement('project')
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800210 parent_node.appendChild(e)
211 e.setAttribute('name', name)
212 if relpath != name:
213 e.setAttribute('path', relpath)
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800214 if not d.remote or p.remote.name != d.remote.name:
215 e.setAttribute('remote', p.remote.name)
216 if peg_rev:
217 if self.IsMirror:
Brian Harring14a66742012-09-28 20:21:57 -0700218 value = p.bare_git.rev_parse(p.revisionExpr + '^0')
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800219 else:
Brian Harring14a66742012-09-28 20:21:57 -0700220 value = p.work_git.rev_parse(HEAD + '^0')
221 e.setAttribute('revision', value)
222 if peg_rev_upstream and value != p.revisionExpr:
223 # Only save the origin if the origin is not a sha1, and the default
224 # isn't our value, and the if the default doesn't already have that
225 # covered.
226 e.setAttribute('upstream', p.revisionExpr)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700227 elif not d.revisionExpr or p.revisionExpr != d.revisionExpr:
228 e.setAttribute('revision', p.revisionExpr)
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800229
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800230 for c in p.copyfiles:
231 ce = doc.createElement('copyfile')
232 ce.setAttribute('src', c.src)
233 ce.setAttribute('dest', c.dest)
234 e.appendChild(ce)
235
Conley Owensbb1b5f52012-08-13 13:11:18 -0700236 default_groups = ['all', 'name:%s' % p.name, 'path:%s' % p.relpath]
Dmitry Fink17f85ea2012-08-06 14:52:29 -0700237 egroups = [g for g in p.groups if g not in default_groups]
Conley Owens971de8e2012-04-16 10:36:08 -0700238 if egroups:
239 e.setAttribute('groups', ','.join(egroups))
Colin Cross5acde752012-03-28 20:15:45 -0700240
James W. Mills24c13082012-04-12 15:04:13 -0500241 for a in p.annotations:
242 if a.keep == "true":
243 ae = doc.createElement('annotation')
244 ae.setAttribute('name', a.name)
245 ae.setAttribute('value', a.value)
246 e.appendChild(ae)
247
Anatol Pomazau79770d22012-04-20 14:41:59 -0700248 if p.sync_c:
249 e.setAttribute('sync-c', 'true')
250
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800251 if p.sync_s:
252 e.setAttribute('sync-s', 'true')
253
254 if p.subprojects:
255 sort_projects = [subp.name for subp in p.subprojects]
256 sort_projects.sort()
257 output_projects(p, e, sort_projects)
258
259 sort_projects = [key for key in self.projects.keys()
260 if not self.projects[key].parent]
261 sort_projects.sort()
262 output_projects(None, root, sort_projects)
263
Doug Anderson37282b42011-03-04 11:54:18 -0800264 if self._repo_hooks_project:
265 root.appendChild(doc.createTextNode(''))
266 e = doc.createElement('repo-hooks')
267 e.setAttribute('in-project', self._repo_hooks_project.name)
268 e.setAttribute('enabled-list',
269 ' '.join(self._repo_hooks_project.enabled_repo_hooks))
270 root.appendChild(e)
271
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800272 doc.writexml(fd, '', ' ', '\n', 'UTF-8')
273
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700274 @property
275 def projects(self):
276 self._Load()
277 return self._projects
278
279 @property
280 def remotes(self):
281 self._Load()
282 return self._remotes
283
284 @property
285 def default(self):
286 self._Load()
287 return self._default
288
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800289 @property
Doug Anderson37282b42011-03-04 11:54:18 -0800290 def repo_hooks_project(self):
291 self._Load()
292 return self._repo_hooks_project
293
294 @property
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700295 def notice(self):
296 self._Load()
297 return self._notice
298
299 @property
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700300 def manifest_server(self):
301 self._Load()
Shawn O. Pearce34fb20f2011-11-30 13:41:02 -0800302 return self._manifest_server
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700303
304 @property
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800305 def IsMirror(self):
306 return self.manifestProject.config.GetBoolean('repo.mirror')
307
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700308 def _Unload(self):
309 self._loaded = False
310 self._projects = {}
311 self._remotes = {}
312 self._default = None
Doug Anderson37282b42011-03-04 11:54:18 -0800313 self._repo_hooks_project = None
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700314 self._notice = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700315 self.branch = None
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700316 self._manifest_server = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700317
318 def _Load(self):
319 if not self._loaded:
Shawn O. Pearce2450a292008-11-04 08:22:07 -0800320 m = self.manifestProject
321 b = m.GetBranch(m.CurrentBranch).merge
Shawn O. Pearce21c5c342009-06-25 16:47:30 -0700322 if b is not None and b.startswith(R_HEADS):
Shawn O. Pearce2450a292008-11-04 08:22:07 -0800323 b = b[len(R_HEADS):]
324 self.branch = b
325
Colin Cross23acdd32012-04-21 00:33:54 -0700326 nodes = []
Brian Harring475a47d2012-06-07 20:05:35 -0700327 nodes.append(self._ParseManifestXml(self.manifestFile,
328 self.manifestProject.worktree))
Shawn O. Pearce5cc66792008-10-23 16:19:27 -0700329
330 local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME)
331 if os.path.exists(local):
Sarah Owenscecd1d82012-11-01 22:59:27 -0700332 print('warning: %s is deprecated; put local manifests in %s instead'
333 % (LOCAL_MANIFEST_NAME, LOCAL_MANIFESTS_DIR_NAME),
334 file=sys.stderr)
Brian Harring475a47d2012-06-07 20:05:35 -0700335 nodes.append(self._ParseManifestXml(local, self.repodir))
Colin Cross23acdd32012-04-21 00:33:54 -0700336
David Pursehouse2d5a0df2012-11-13 02:50:36 +0900337 local_dir = os.path.abspath(os.path.join(self.repodir, LOCAL_MANIFESTS_DIR_NAME))
338 try:
David Pursehouse52f1e5d2012-11-14 04:53:24 +0900339 for local_file in sorted(os.listdir(local_dir)):
David Pursehouse2d5a0df2012-11-13 02:50:36 +0900340 if local_file.endswith('.xml'):
341 try:
342 nodes.append(self._ParseManifestXml(local_file, self.repodir))
343 except ManifestParseError as e:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700344 print('%s' % str(e), file=sys.stderr)
David Pursehouse2d5a0df2012-11-13 02:50:36 +0900345 except OSError:
346 pass
347
Colin Cross23acdd32012-04-21 00:33:54 -0700348 self._ParseManifest(nodes)
Shawn O. Pearce5cc66792008-10-23 16:19:27 -0700349
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800350 if self.IsMirror:
351 self._AddMetaProjectMirror(self.repoProject)
352 self._AddMetaProjectMirror(self.manifestProject)
353
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700354 self._loaded = True
355
Brian Harring475a47d2012-06-07 20:05:35 -0700356 def _ParseManifestXml(self, path, include_root):
David Pursehousef7fc8a92012-11-13 04:00:28 +0900357 try:
358 root = xml.dom.minidom.parse(path)
David Pursehouse2d5a0df2012-11-13 02:50:36 +0900359 except (OSError, xml.parsers.expat.ExpatError) as e:
David Pursehousef7fc8a92012-11-13 04:00:28 +0900360 raise ManifestParseError("error parsing manifest %s: %s" % (path, e))
361
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700362 if not root or not root.childNodes:
Brian Harring26448742011-04-28 05:04:41 -0700363 raise ManifestParseError("no root node in %s" % (path,))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700364
Jooncheol Park34acdd22012-08-27 02:25:59 +0900365 for manifest in root.childNodes:
366 if manifest.nodeName == 'manifest':
367 break
368 else:
Brian Harring26448742011-04-28 05:04:41 -0700369 raise ManifestParseError("no in %s" % (path,))
370
Colin Cross23acdd32012-04-21 00:33:54 -0700371 nodes = []
David Pursehouse4f7bdea2012-10-22 12:50:15 +0900372 for node in manifest.childNodes: # pylint:disable=W0631
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900373 # We only get here if manifest is initialised
David Pursehousec1b86a22012-11-14 11:36:51 +0900374 if node.nodeName == 'include':
375 name = self._reqatt(node, 'name')
376 fp = os.path.join(include_root, name)
377 if not os.path.isfile(fp):
378 raise ManifestParseError, \
379 "include %s doesn't exist or isn't a file" % \
380 (name,)
381 try:
382 nodes.extend(self._ParseManifestXml(fp, include_root))
383 # should isolate this to the exact exception, but that's
384 # tricky. actual parsing implementation may vary.
385 except (KeyboardInterrupt, RuntimeError, SystemExit):
386 raise
387 except Exception as e:
388 raise ManifestParseError(
389 "failed parsing included manifest %s: %s", (name, e))
390 else:
391 nodes.append(node)
Colin Cross23acdd32012-04-21 00:33:54 -0700392 return nodes
Brian Harring26448742011-04-28 05:04:41 -0700393
Colin Cross23acdd32012-04-21 00:33:54 -0700394 def _ParseManifest(self, node_list):
395 for node in itertools.chain(*node_list):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700396 if node.nodeName == 'remote':
397 remote = self._ParseRemote(node)
David Pursehouse717ece92012-11-13 08:49:16 +0900398 if remote:
399 if remote.name in self._remotes:
400 if remote != self._remotes[remote.name]:
401 raise ManifestParseError(
402 'remote %s already exists with different attributes' %
403 (remote.name))
404 else:
405 self._remotes[remote.name] = remote
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700406
Colin Cross23acdd32012-04-21 00:33:54 -0700407 for node in itertools.chain(*node_list):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700408 if node.nodeName == 'default':
409 if self._default is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800410 raise ManifestParseError(
411 'duplicate default in %s' %
412 (self.manifestFile))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700413 self._default = self._ParseDefault(node)
414 if self._default is None:
415 self._default = _Default()
416
Colin Cross23acdd32012-04-21 00:33:54 -0700417 for node in itertools.chain(*node_list):
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700418 if node.nodeName == 'notice':
419 if self._notice is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800420 raise ManifestParseError(
421 'duplicate notice in %s' %
422 (self.manifestFile))
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700423 self._notice = self._ParseNotice(node)
424
Colin Cross23acdd32012-04-21 00:33:54 -0700425 for node in itertools.chain(*node_list):
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700426 if node.nodeName == 'manifest-server':
427 url = self._reqatt(node, 'url')
428 if self._manifest_server is not None:
David Pursehousec1b86a22012-11-14 11:36:51 +0900429 raise ManifestParseError(
430 'duplicate manifest-server in %s' %
431 (self.manifestFile))
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700432 self._manifest_server = url
433
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800434 def recursively_add_projects(project):
435 if self._projects.get(project.name):
436 raise ManifestParseError(
437 'duplicate project %s in %s' %
438 (project.name, self.manifestFile))
439 self._projects[project.name] = project
440 for subproject in project.subprojects:
441 recursively_add_projects(subproject)
442
Colin Cross23acdd32012-04-21 00:33:54 -0700443 for node in itertools.chain(*node_list):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700444 if node.nodeName == 'project':
445 project = self._ParseProject(node)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800446 recursively_add_projects(project)
Doug Anderson37282b42011-03-04 11:54:18 -0800447 if node.nodeName == 'repo-hooks':
448 # Get the name of the project and the (space-separated) list of enabled.
449 repo_hooks_project = self._reqatt(node, 'in-project')
450 enabled_repo_hooks = self._reqatt(node, 'enabled-list').split()
451
452 # Only one project can be the hooks project
453 if self._repo_hooks_project is not None:
454 raise ManifestParseError(
455 'duplicate repo-hooks in %s' %
456 (self.manifestFile))
457
458 # Store a reference to the Project.
459 try:
460 self._repo_hooks_project = self._projects[repo_hooks_project]
461 except KeyError:
462 raise ManifestParseError(
463 'project %s not found for repo-hooks' %
464 (repo_hooks_project))
465
466 # Store the enabled hooks in the Project object.
467 self._repo_hooks_project.enabled_repo_hooks = enabled_repo_hooks
Colin Cross23acdd32012-04-21 00:33:54 -0700468 if node.nodeName == 'remove-project':
469 name = self._reqatt(node, 'name')
470 try:
471 del self._projects[name]
472 except KeyError:
David Pursehousef9107482012-11-16 19:12:32 +0900473 raise ManifestParseError('remove-project element specifies non-existent '
474 'project: %s' % name)
Colin Cross23acdd32012-04-21 00:33:54 -0700475
476 # If the manifest removes the hooks project, treat it as if it deleted
477 # the repo-hooks element too.
478 if self._repo_hooks_project and (self._repo_hooks_project.name == name):
479 self._repo_hooks_project = None
480
Doug Anderson37282b42011-03-04 11:54:18 -0800481
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800482 def _AddMetaProjectMirror(self, m):
483 name = None
484 m_url = m.GetRemote(m.remote.name).url
485 if m_url.endswith('/.git'):
486 raise ManifestParseError, 'refusing to mirror %s' % m_url
487
488 if self._default and self._default.remote:
Conley Owensceea3682011-10-20 10:45:47 -0700489 url = self._default.remote.resolvedFetchUrl
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800490 if not url.endswith('/'):
491 url += '/'
492 if m_url.startswith(url):
493 remote = self._default.remote
494 name = m_url[len(url):]
495
496 if name is None:
497 s = m_url.rindex('/') + 1
Conley Owensdb728cd2011-09-26 16:34:01 -0700498 manifestUrl = self.manifestProject.config.GetString('remote.origin.url')
Shawn O. Pearcef35b2d92012-08-02 11:46:22 -0700499 remote = _XmlRemote('origin', fetch=m_url[:s], manifestUrl=manifestUrl)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800500 name = m_url[s:]
501
502 if name.endswith('.git'):
503 name = name[:-4]
504
505 if name not in self._projects:
506 m.PreSync()
507 gitdir = os.path.join(self.topdir, '%s.git' % name)
508 project = Project(manifest = self,
509 name = name,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700510 remote = remote.ToRemoteSpec(name),
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800511 gitdir = gitdir,
512 worktree = None,
513 relpath = None,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700514 revisionExpr = m.revisionExpr,
515 revisionId = None)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800516 self._projects[project.name] = project
517
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700518 def _ParseRemote(self, node):
519 """
520 reads a element from the manifest file
521 """
522 name = self._reqatt(node, 'name')
Yestin Sunb292b982012-07-02 07:32:50 -0700523 alias = node.getAttribute('alias')
524 if alias == '':
525 alias = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700526 fetch = self._reqatt(node, 'fetch')
527 review = node.getAttribute('review')
Shawn O. Pearceae6e0942008-11-06 10:25:35 -0800528 if review == '':
529 review = None
Conley Owensdb728cd2011-09-26 16:34:01 -0700530 manifestUrl = self.manifestProject.config.GetString('remote.origin.url')
Yestin Sunb292b982012-07-02 07:32:50 -0700531 return _XmlRemote(name, alias, fetch, manifestUrl, review)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700532
533 def _ParseDefault(self, node):
534 """
535 reads a element from the manifest file
536 """
537 d = _Default()
538 d.remote = self._get_remote(node)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700539 d.revisionExpr = node.getAttribute('revision')
540 if d.revisionExpr == '':
541 d.revisionExpr = None
Anatol Pomazau79770d22012-04-20 14:41:59 -0700542
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700543 sync_j = node.getAttribute('sync-j')
544 if sync_j == '' or sync_j is None:
545 d.sync_j = 1
546 else:
547 d.sync_j = int(sync_j)
Anatol Pomazau79770d22012-04-20 14:41:59 -0700548
549 sync_c = node.getAttribute('sync-c')
550 if not sync_c:
551 d.sync_c = False
552 else:
553 d.sync_c = sync_c.lower() in ("yes", "true", "1")
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800554
555 sync_s = node.getAttribute('sync-s')
556 if not sync_s:
557 d.sync_s = False
558 else:
559 d.sync_s = sync_s.lower() in ("yes", "true", "1")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700560 return d
561
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700562 def _ParseNotice(self, node):
563 """
564 reads a element from the manifest file
565
566 The element is distinct from other tags in the XML in that the
567 data is conveyed between the start and end tag (it's not an empty-element
568 tag).
569
570 The white space (carriage returns, indentation) for the notice element is
571 relevant and is parsed in a way that is based on how python docstrings work.
572 In fact, the code is remarkably similar to here:
573 http://www.python.org/dev/peps/pep-0257/
574 """
575 # Get the data out of the node...
576 notice = node.childNodes[0].data
577
578 # Figure out minimum indentation, skipping the first line (the same line
579 # as the tag)...
580 minIndent = sys.maxint
581 lines = notice.splitlines()
582 for line in lines[1:]:
583 lstrippedLine = line.lstrip()
584 if lstrippedLine:
585 indent = len(line) - len(lstrippedLine)
586 minIndent = min(indent, minIndent)
587
588 # Strip leading / trailing blank lines and also indentation.
589 cleanLines = [lines[0].strip()]
590 for line in lines[1:]:
591 cleanLines.append(line[minIndent:].rstrip())
592
593 # Clear completely blank lines from front and back...
594 while cleanLines and not cleanLines[0]:
595 del cleanLines[0]
596 while cleanLines and not cleanLines[-1]:
597 del cleanLines[-1]
598
599 return '\n'.join(cleanLines)
600
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800601 def _JoinName(self, parent_name, name):
602 return os.path.join(parent_name, name)
603
604 def _UnjoinName(self, parent_name, name):
605 return os.path.relpath(name, parent_name)
606
607 def _ParseProject(self, node, parent = None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700608 """
609 reads a element from the manifest file
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700610 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700611 name = self._reqatt(node, 'name')
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800612 if parent:
613 name = self._JoinName(parent.name, name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700614
615 remote = self._get_remote(node)
616 if remote is None:
617 remote = self._default.remote
618 if remote is None:
619 raise ManifestParseError, \
620 "no remote for project %s within %s" % \
621 (name, self.manifestFile)
622
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700623 revisionExpr = node.getAttribute('revision')
624 if not revisionExpr:
625 revisionExpr = self._default.revisionExpr
626 if not revisionExpr:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700627 raise ManifestParseError, \
628 "no revision for project %s within %s" % \
629 (name, self.manifestFile)
630
631 path = node.getAttribute('path')
632 if not path:
633 path = name
634 if path.startswith('/'):
635 raise ManifestParseError, \
636 "project %s path cannot be absolute in %s" % \
637 (name, self.manifestFile)
638
Mike Pontillod3153822012-02-28 11:53:24 -0800639 rebase = node.getAttribute('rebase')
640 if not rebase:
641 rebase = True
642 else:
643 rebase = rebase.lower() in ("yes", "true", "1")
644
Anatol Pomazau79770d22012-04-20 14:41:59 -0700645 sync_c = node.getAttribute('sync-c')
646 if not sync_c:
647 sync_c = False
648 else:
649 sync_c = sync_c.lower() in ("yes", "true", "1")
650
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800651 sync_s = node.getAttribute('sync-s')
652 if not sync_s:
653 sync_s = self._default.sync_s
654 else:
655 sync_s = sync_s.lower() in ("yes", "true", "1")
656
Brian Harring14a66742012-09-28 20:21:57 -0700657 upstream = node.getAttribute('upstream')
658
Conley Owens971de8e2012-04-16 10:36:08 -0700659 groups = ''
660 if node.hasAttribute('groups'):
661 groups = node.getAttribute('groups')
David Pursehouse1d947b32012-10-25 12:23:11 +0900662 groups = [x for x in re.split(r'[,\s]+', groups) if x]
Brian Harring7da13142012-06-15 02:24:20 -0700663
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800664 if parent is None:
665 relpath, worktree, gitdir = self.GetProjectPaths(name, path)
Shawn O. Pearcecd81dd62012-10-26 12:18:00 -0700666 else:
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800667 relpath, worktree, gitdir = self.GetSubprojectPaths(parent, path)
668
669 default_groups = ['all', 'name:%s' % name, 'path:%s' % relpath]
670 groups.extend(set(default_groups).difference(groups))
Shawn O. Pearcecd81dd62012-10-26 12:18:00 -0700671
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700672 project = Project(manifest = self,
673 name = name,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700674 remote = remote.ToRemoteSpec(name),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700675 gitdir = gitdir,
676 worktree = worktree,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800677 relpath = relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700678 revisionExpr = revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800679 revisionId = None,
Colin Cross5acde752012-03-28 20:15:45 -0700680 rebase = rebase,
Anatol Pomazau79770d22012-04-20 14:41:59 -0700681 groups = groups,
Brian Harring14a66742012-09-28 20:21:57 -0700682 sync_c = sync_c,
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800683 sync_s = sync_s,
684 upstream = upstream,
685 parent = parent)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700686
687 for n in node.childNodes:
Shawn O. Pearce242b5262009-05-19 13:00:29 -0700688 if n.nodeName == 'copyfile':
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700689 self._ParseCopyFile(project, n)
James W. Mills24c13082012-04-12 15:04:13 -0500690 if n.nodeName == 'annotation':
691 self._ParseAnnotation(project, n)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800692 if n.nodeName == 'project':
693 project.subprojects.append(self._ParseProject(n, parent = project))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700694
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700695 return project
696
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800697 def GetProjectPaths(self, name, path):
698 relpath = path
699 if self.IsMirror:
700 worktree = None
701 gitdir = os.path.join(self.topdir, '%s.git' % name)
702 else:
703 worktree = os.path.join(self.topdir, path).replace('\\', '/')
704 gitdir = os.path.join(self.repodir, 'projects', '%s.git' % path)
705 return relpath, worktree, gitdir
706
707 def GetSubprojectName(self, parent, submodule_path):
708 return os.path.join(parent.name, submodule_path)
709
710 def _JoinRelpath(self, parent_relpath, relpath):
711 return os.path.join(parent_relpath, relpath)
712
713 def _UnjoinRelpath(self, parent_relpath, relpath):
714 return os.path.relpath(relpath, parent_relpath)
715
716 def GetSubprojectPaths(self, parent, path):
717 relpath = self._JoinRelpath(parent.relpath, path)
718 gitdir = os.path.join(parent.gitdir, 'subprojects', '%s.git' % path)
719 if self.IsMirror:
720 worktree = None
721 else:
722 worktree = os.path.join(parent.worktree, path).replace('\\', '/')
723 return relpath, worktree, gitdir
724
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700725 def _ParseCopyFile(self, project, node):
726 src = self._reqatt(node, 'src')
727 dest = self._reqatt(node, 'dest')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800728 if not self.IsMirror:
729 # src is project relative;
730 # dest is relative to the top of the tree
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800731 project.AddCopyFile(src, dest, os.path.join(self.topdir, dest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700732
James W. Mills24c13082012-04-12 15:04:13 -0500733 def _ParseAnnotation(self, project, node):
734 name = self._reqatt(node, 'name')
735 value = self._reqatt(node, 'value')
736 try:
737 keep = self._reqatt(node, 'keep').lower()
738 except ManifestParseError:
739 keep = "true"
740 if keep != "true" and keep != "false":
741 raise ManifestParseError, "optional \"keep\" attribute must be \"true\" or \"false\""
742 project.AddAnnotation(name, value, keep)
743
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700744 def _get_remote(self, node):
745 name = node.getAttribute('remote')
746 if not name:
747 return None
748
749 v = self._remotes.get(name)
750 if not v:
751 raise ManifestParseError, \
752 "remote %s not defined in %s" % \
753 (name, self.manifestFile)
754 return v
755
756 def _reqatt(self, node, attname):
757 """
758 reads a required attribute from the node.
759 """
760 v = node.getAttribute(attname)
761 if not v:
762 raise ManifestParseError, \
763 "no %s in <%s> within %s" % \
764 (attname, node.nodeName, self.manifestFile)
765 return v