blob: 46976538886a155981597bd0581d391e3fb27724 [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
17import sys
18import xml.dom.minidom
19
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070020from git_config import GitConfig, IsId
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -080021from project import Project, MetaProject, R_HEADS, HEAD
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070022from remote import Remote
23from error import ManifestParseError
24
25MANIFEST_FILE_NAME = 'manifest.xml'
Shawn O. Pearce5cc66792008-10-23 16:19:27 -070026LOCAL_MANIFEST_NAME = 'local_manifest.xml'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070027
28class _Default(object):
29 """Project defaults within the manifest."""
30
31 revision = None
32 remote = None
33
34
Shawn O. Pearcec8a300f2009-05-18 13:19:57 -070035class XmlManifest(object):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070036 """manages the repo configuration file"""
37
38 def __init__(self, repodir):
39 self.repodir = os.path.abspath(repodir)
40 self.topdir = os.path.dirname(self.repodir)
41 self.manifestFile = os.path.join(self.repodir, MANIFEST_FILE_NAME)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070042 self.globalConfig = GitConfig.ForUser()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070043
44 self.repoProject = MetaProject(self, 'repo',
45 gitdir = os.path.join(repodir, 'repo/.git'),
46 worktree = os.path.join(repodir, 'repo'))
47
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070048 self.manifestProject = MetaProject(self, 'manifests',
Shawn O. Pearcef5c25a62008-11-04 08:11:53 -080049 gitdir = os.path.join(repodir, 'manifests.git'),
50 worktree = os.path.join(repodir, 'manifests'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070051
52 self._Unload()
53
54 def Link(self, name):
55 """Update the repo metadata to use a different manifest.
56 """
57 path = os.path.join(self.manifestProject.worktree, name)
58 if not os.path.isfile(path):
59 raise ManifestParseError('manifest %s not found' % name)
60
61 old = self.manifestFile
62 try:
63 self.manifestFile = path
64 self._Unload()
65 self._Load()
66 finally:
67 self.manifestFile = old
68
69 try:
70 if os.path.exists(self.manifestFile):
71 os.remove(self.manifestFile)
72 os.symlink('manifests/%s' % name, self.manifestFile)
73 except OSError, e:
74 raise ManifestParseError('cannot link manifest %s' % name)
75
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -080076 def _RemoteToXml(self, r, doc, root):
77 e = doc.createElement('remote')
78 root.appendChild(e)
79 e.setAttribute('name', r.name)
80 e.setAttribute('fetch', r.fetchUrl)
81 if r.reviewUrl is not None:
82 e.setAttribute('review', r.reviewUrl)
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -080083
84 def Save(self, fd, peg_rev=False):
85 """Write the current manifest out to the given file descriptor.
86 """
87 doc = xml.dom.minidom.Document()
88 root = doc.createElement('manifest')
89 doc.appendChild(root)
90
91 d = self.default
92 sort_remotes = list(self.remotes.keys())
93 sort_remotes.sort()
94
95 for r in sort_remotes:
96 self._RemoteToXml(self.remotes[r], doc, root)
97 if self.remotes:
98 root.appendChild(doc.createTextNode(''))
99
100 have_default = False
101 e = doc.createElement('default')
102 if d.remote:
103 have_default = True
104 e.setAttribute('remote', d.remote.name)
105 if d.revision:
106 have_default = True
107 e.setAttribute('revision', d.revision)
108 if have_default:
109 root.appendChild(e)
110 root.appendChild(doc.createTextNode(''))
111
112 sort_projects = list(self.projects.keys())
113 sort_projects.sort()
114
115 for p in sort_projects:
116 p = self.projects[p]
117 e = doc.createElement('project')
118 root.appendChild(e)
119 e.setAttribute('name', p.name)
120 if p.relpath != p.name:
121 e.setAttribute('path', p.relpath)
122 if not d.remote or p.remote.name != d.remote.name:
123 e.setAttribute('remote', p.remote.name)
124 if peg_rev:
125 if self.IsMirror:
126 e.setAttribute('revision',
127 p.bare_git.rev_parse(p.revision + '^0'))
128 else:
129 e.setAttribute('revision',
130 p.work_git.rev_parse(HEAD + '^0'))
131 elif not d.revision or p.revision != d.revision:
132 e.setAttribute('revision', p.revision)
133
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800134 for c in p.copyfiles:
135 ce = doc.createElement('copyfile')
136 ce.setAttribute('src', c.src)
137 ce.setAttribute('dest', c.dest)
138 e.appendChild(ce)
139
140 doc.writexml(fd, '', ' ', '\n', 'UTF-8')
141
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700142 @property
143 def projects(self):
144 self._Load()
145 return self._projects
146
147 @property
148 def remotes(self):
149 self._Load()
150 return self._remotes
151
152 @property
153 def default(self):
154 self._Load()
155 return self._default
156
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800157 @property
158 def IsMirror(self):
159 return self.manifestProject.config.GetBoolean('repo.mirror')
160
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700161 def _Unload(self):
162 self._loaded = False
163 self._projects = {}
164 self._remotes = {}
165 self._default = None
166 self.branch = None
167
168 def _Load(self):
169 if not self._loaded:
Shawn O. Pearce2450a292008-11-04 08:22:07 -0800170 m = self.manifestProject
171 b = m.GetBranch(m.CurrentBranch).merge
172 if b.startswith(R_HEADS):
173 b = b[len(R_HEADS):]
174 self.branch = b
175
Shawn O. Pearce5cc66792008-10-23 16:19:27 -0700176 self._ParseManifest(True)
177
178 local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME)
179 if os.path.exists(local):
180 try:
181 real = self.manifestFile
182 self.manifestFile = local
183 self._ParseManifest(False)
184 finally:
185 self.manifestFile = real
186
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800187 if self.IsMirror:
188 self._AddMetaProjectMirror(self.repoProject)
189 self._AddMetaProjectMirror(self.manifestProject)
190
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700191 self._loaded = True
192
Shawn O. Pearce5cc66792008-10-23 16:19:27 -0700193 def _ParseManifest(self, is_root_file):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700194 root = xml.dom.minidom.parse(self.manifestFile)
195 if not root or not root.childNodes:
196 raise ManifestParseError, \
197 "no root node in %s" % \
198 self.manifestFile
199
200 config = root.childNodes[0]
201 if config.nodeName != 'manifest':
202 raise ManifestParseError, \
203 "no <manifest> in %s" % \
204 self.manifestFile
205
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700206 for node in config.childNodes:
Shawn O. Pearce03eaf072008-11-20 11:42:22 -0800207 if node.nodeName == 'remove-project':
208 name = self._reqatt(node, 'name')
209 try:
210 del self._projects[name]
211 except KeyError:
212 raise ManifestParseError, \
213 'project %s not found' % \
214 (name)
215
216 for node in config.childNodes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700217 if node.nodeName == 'remote':
218 remote = self._ParseRemote(node)
219 if self._remotes.get(remote.name):
220 raise ManifestParseError, \
221 'duplicate remote %s in %s' % \
222 (remote.name, self.manifestFile)
223 self._remotes[remote.name] = remote
224
225 for node in config.childNodes:
226 if node.nodeName == 'default':
227 if self._default is not None:
228 raise ManifestParseError, \
229 'duplicate default in %s' % \
230 (self.manifestFile)
231 self._default = self._ParseDefault(node)
232 if self._default is None:
233 self._default = _Default()
234
235 for node in config.childNodes:
236 if node.nodeName == 'project':
237 project = self._ParseProject(node)
238 if self._projects.get(project.name):
239 raise ManifestParseError, \
240 'duplicate project %s in %s' % \
241 (project.name, self.manifestFile)
242 self._projects[project.name] = project
243
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800244 def _AddMetaProjectMirror(self, m):
245 name = None
246 m_url = m.GetRemote(m.remote.name).url
247 if m_url.endswith('/.git'):
248 raise ManifestParseError, 'refusing to mirror %s' % m_url
249
250 if self._default and self._default.remote:
251 url = self._default.remote.fetchUrl
252 if not url.endswith('/'):
253 url += '/'
254 if m_url.startswith(url):
255 remote = self._default.remote
256 name = m_url[len(url):]
257
258 if name is None:
259 s = m_url.rindex('/') + 1
260 remote = Remote('origin', fetch = m_url[:s])
261 name = m_url[s:]
262
263 if name.endswith('.git'):
264 name = name[:-4]
265
266 if name not in self._projects:
267 m.PreSync()
268 gitdir = os.path.join(self.topdir, '%s.git' % name)
269 project = Project(manifest = self,
270 name = name,
271 remote = remote,
272 gitdir = gitdir,
273 worktree = None,
274 relpath = None,
275 revision = m.revision)
276 self._projects[project.name] = project
277
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700278 def _ParseRemote(self, node):
279 """
280 reads a <remote> element from the manifest file
281 """
282 name = self._reqatt(node, 'name')
283 fetch = self._reqatt(node, 'fetch')
284 review = node.getAttribute('review')
Shawn O. Pearceae6e0942008-11-06 10:25:35 -0800285 if review == '':
286 review = None
Shawn O. Pearce242b5262009-05-19 13:00:29 -0700287 return Remote(name=name, fetch=fetch, review=review)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700288
289 def _ParseDefault(self, node):
290 """
291 reads a <default> element from the manifest file
292 """
293 d = _Default()
294 d.remote = self._get_remote(node)
295 d.revision = node.getAttribute('revision')
Shawn O. Pearce5d40e262008-11-06 11:07:42 -0800296 if d.revision == '':
297 d.revision = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700298 return d
299
300 def _ParseProject(self, node):
301 """
302 reads a <project> element from the manifest file
303 """
304 name = self._reqatt(node, 'name')
305
306 remote = self._get_remote(node)
307 if remote is None:
308 remote = self._default.remote
309 if remote is None:
310 raise ManifestParseError, \
311 "no remote for project %s within %s" % \
312 (name, self.manifestFile)
313
314 revision = node.getAttribute('revision')
315 if not revision:
316 revision = self._default.revision
317 if not revision:
318 raise ManifestParseError, \
319 "no revision for project %s within %s" % \
320 (name, self.manifestFile)
321
322 path = node.getAttribute('path')
323 if not path:
324 path = name
325 if path.startswith('/'):
326 raise ManifestParseError, \
327 "project %s path cannot be absolute in %s" % \
328 (name, self.manifestFile)
329
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800330 if self.IsMirror:
331 relpath = None
332 worktree = None
333 gitdir = os.path.join(self.topdir, '%s.git' % name)
334 else:
335 worktree = os.path.join(self.topdir, path)
336 gitdir = os.path.join(self.repodir, 'projects/%s.git' % path)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700337
338 project = Project(manifest = self,
339 name = name,
340 remote = remote,
341 gitdir = gitdir,
342 worktree = worktree,
343 relpath = path,
344 revision = revision)
345
346 for n in node.childNodes:
Shawn O. Pearce242b5262009-05-19 13:00:29 -0700347 if n.nodeName == 'copyfile':
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700348 self._ParseCopyFile(project, n)
349
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700350 return project
351
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700352 def _ParseCopyFile(self, project, node):
353 src = self._reqatt(node, 'src')
354 dest = self._reqatt(node, 'dest')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800355 if not self.IsMirror:
356 # src is project relative;
357 # dest is relative to the top of the tree
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800358 project.AddCopyFile(src, dest, os.path.join(self.topdir, dest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700359
360 def _get_remote(self, node):
361 name = node.getAttribute('remote')
362 if not name:
363 return None
364
365 v = self._remotes.get(name)
366 if not v:
367 raise ManifestParseError, \
368 "remote %s not defined in %s" % \
369 (name, self.manifestFile)
370 return v
371
372 def _reqatt(self, node, attname):
373 """
374 reads a required attribute from the node.
375 """
376 v = node.getAttribute(attname)
377 if not v:
378 raise ManifestParseError, \
379 "no %s in <%s> within %s" % \
380 (attname, node.nodeName, self.manifestFile)
381 return v