blob: 32a7e5131dc88c74937a8d003c802552d4664ba6 [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. Pearce2450a292008-11-04 08:22:07 -080021from project import Project, MetaProject, R_HEADS
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
35class Manifest(object):
36 """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
76 @property
77 def projects(self):
78 self._Load()
79 return self._projects
80
81 @property
82 def remotes(self):
83 self._Load()
84 return self._remotes
85
86 @property
87 def default(self):
88 self._Load()
89 return self._default
90
Shawn O. Pearcee284ad12008-11-04 07:37:10 -080091 @property
92 def IsMirror(self):
93 return self.manifestProject.config.GetBoolean('repo.mirror')
94
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070095 def _Unload(self):
96 self._loaded = False
97 self._projects = {}
98 self._remotes = {}
99 self._default = None
100 self.branch = None
101
102 def _Load(self):
103 if not self._loaded:
Shawn O. Pearce2450a292008-11-04 08:22:07 -0800104 m = self.manifestProject
105 b = m.GetBranch(m.CurrentBranch).merge
106 if b.startswith(R_HEADS):
107 b = b[len(R_HEADS):]
108 self.branch = b
109
Shawn O. Pearce5cc66792008-10-23 16:19:27 -0700110 self._ParseManifest(True)
111
112 local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME)
113 if os.path.exists(local):
114 try:
115 real = self.manifestFile
116 self.manifestFile = local
117 self._ParseManifest(False)
118 finally:
119 self.manifestFile = real
120
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800121 if self.IsMirror:
122 self._AddMetaProjectMirror(self.repoProject)
123 self._AddMetaProjectMirror(self.manifestProject)
124
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700125 self._loaded = True
126
Shawn O. Pearce5cc66792008-10-23 16:19:27 -0700127 def _ParseManifest(self, is_root_file):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700128 root = xml.dom.minidom.parse(self.manifestFile)
129 if not root or not root.childNodes:
130 raise ManifestParseError, \
131 "no root node in %s" % \
132 self.manifestFile
133
134 config = root.childNodes[0]
135 if config.nodeName != 'manifest':
136 raise ManifestParseError, \
137 "no <manifest> in %s" % \
138 self.manifestFile
139
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700140 for node in config.childNodes:
Shawn O. Pearce03eaf072008-11-20 11:42:22 -0800141 if node.nodeName == 'remove-project':
142 name = self._reqatt(node, 'name')
143 try:
144 del self._projects[name]
145 except KeyError:
146 raise ManifestParseError, \
147 'project %s not found' % \
148 (name)
149
150 for node in config.childNodes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700151 if node.nodeName == 'remote':
152 remote = self._ParseRemote(node)
153 if self._remotes.get(remote.name):
154 raise ManifestParseError, \
155 'duplicate remote %s in %s' % \
156 (remote.name, self.manifestFile)
157 self._remotes[remote.name] = remote
158
159 for node in config.childNodes:
160 if node.nodeName == 'default':
161 if self._default is not None:
162 raise ManifestParseError, \
163 'duplicate default in %s' % \
164 (self.manifestFile)
165 self._default = self._ParseDefault(node)
166 if self._default is None:
167 self._default = _Default()
168
169 for node in config.childNodes:
170 if node.nodeName == 'project':
171 project = self._ParseProject(node)
172 if self._projects.get(project.name):
173 raise ManifestParseError, \
174 'duplicate project %s in %s' % \
175 (project.name, self.manifestFile)
176 self._projects[project.name] = project
177
Shawn O. Pearce70939e22008-11-06 11:07:14 -0800178 for node in config.childNodes:
179 if node.nodeName == 'add-remote':
180 pn = self._reqatt(node, 'to-project')
181 project = self._projects.get(pn)
182 if not project:
183 raise ManifestParseError, \
184 'project %s not defined in %s' % \
185 (pn, self.manifestFile)
186 self._ParseProjectExtraRemote(project, node)
187
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800188 def _AddMetaProjectMirror(self, m):
189 name = None
190 m_url = m.GetRemote(m.remote.name).url
191 if m_url.endswith('/.git'):
192 raise ManifestParseError, 'refusing to mirror %s' % m_url
193
194 if self._default and self._default.remote:
195 url = self._default.remote.fetchUrl
196 if not url.endswith('/'):
197 url += '/'
198 if m_url.startswith(url):
199 remote = self._default.remote
200 name = m_url[len(url):]
201
202 if name is None:
203 s = m_url.rindex('/') + 1
204 remote = Remote('origin', fetch = m_url[:s])
205 name = m_url[s:]
206
207 if name.endswith('.git'):
208 name = name[:-4]
209
210 if name not in self._projects:
211 m.PreSync()
212 gitdir = os.path.join(self.topdir, '%s.git' % name)
213 project = Project(manifest = self,
214 name = name,
215 remote = remote,
216 gitdir = gitdir,
217 worktree = None,
218 relpath = None,
219 revision = m.revision)
220 self._projects[project.name] = project
221
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700222 def _ParseRemote(self, node):
223 """
224 reads a <remote> element from the manifest file
225 """
226 name = self._reqatt(node, 'name')
227 fetch = self._reqatt(node, 'fetch')
228 review = node.getAttribute('review')
Shawn O. Pearceae6e0942008-11-06 10:25:35 -0800229 if review == '':
230 review = None
231
232 projectName = node.getAttribute('project-name')
233 if projectName == '':
234 projectName = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700235
236 r = Remote(name=name,
237 fetch=fetch,
Shawn O. Pearceae6e0942008-11-06 10:25:35 -0800238 review=review,
239 projectName=projectName)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700240
241 for n in node.childNodes:
242 if n.nodeName == 'require':
243 r.requiredCommits.append(self._reqatt(n, 'commit'))
244
245 return r
246
247 def _ParseDefault(self, node):
248 """
249 reads a <default> element from the manifest file
250 """
251 d = _Default()
252 d.remote = self._get_remote(node)
253 d.revision = node.getAttribute('revision')
Shawn O. Pearce5d40e262008-11-06 11:07:42 -0800254 if d.revision == '':
255 d.revision = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700256 return d
257
258 def _ParseProject(self, node):
259 """
260 reads a <project> element from the manifest file
261 """
262 name = self._reqatt(node, 'name')
263
264 remote = self._get_remote(node)
265 if remote is None:
266 remote = self._default.remote
267 if remote is None:
268 raise ManifestParseError, \
269 "no remote for project %s within %s" % \
270 (name, self.manifestFile)
271
272 revision = node.getAttribute('revision')
273 if not revision:
274 revision = self._default.revision
275 if not revision:
276 raise ManifestParseError, \
277 "no revision for project %s within %s" % \
278 (name, self.manifestFile)
279
280 path = node.getAttribute('path')
281 if not path:
282 path = name
283 if path.startswith('/'):
284 raise ManifestParseError, \
285 "project %s path cannot be absolute in %s" % \
286 (name, self.manifestFile)
287
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800288 if self.IsMirror:
289 relpath = None
290 worktree = None
291 gitdir = os.path.join(self.topdir, '%s.git' % name)
292 else:
293 worktree = os.path.join(self.topdir, path)
294 gitdir = os.path.join(self.repodir, 'projects/%s.git' % path)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700295
296 project = Project(manifest = self,
297 name = name,
298 remote = remote,
299 gitdir = gitdir,
300 worktree = worktree,
301 relpath = path,
302 revision = revision)
303
304 for n in node.childNodes:
305 if n.nodeName == 'remote':
Shawn O. Pearce70939e22008-11-06 11:07:14 -0800306 self._ParseProjectExtraRemote(project, n)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700307 elif n.nodeName == 'copyfile':
308 self._ParseCopyFile(project, n)
309
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700310 return project
311
Shawn O. Pearce70939e22008-11-06 11:07:14 -0800312 def _ParseProjectExtraRemote(self, project, n):
313 r = self._ParseRemote(n)
314 if project.extraRemotes.get(r.name) \
315 or project.remote.name == r.name:
316 raise ManifestParseError, \
317 'duplicate remote %s in project %s in %s' % \
318 (r.name, project.name, self.manifestFile)
319 project.extraRemotes[r.name] = r
320
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700321 def _ParseCopyFile(self, project, node):
322 src = self._reqatt(node, 'src')
323 dest = self._reqatt(node, 'dest')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800324 if not self.IsMirror:
325 # src is project relative;
326 # dest is relative to the top of the tree
327 project.AddCopyFile(src, os.path.join(self.topdir, dest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700328
329 def _get_remote(self, node):
330 name = node.getAttribute('remote')
331 if not name:
332 return None
333
334 v = self._remotes.get(name)
335 if not v:
336 raise ManifestParseError, \
337 "remote %s not defined in %s" % \
338 (name, self.manifestFile)
339 return v
340
341 def _reqatt(self, node, attname):
342 """
343 reads a required attribute from the node.
344 """
345 v = node.getAttribute(attname)
346 if not v:
347 raise ManifestParseError, \
348 "no %s in <%s> within %s" % \
349 (attname, node.nodeName, self.manifestFile)
350 return v