blob: 2ac1453aea4c8abc5cb53ee495471df24cacfe7f [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:
141 if node.nodeName == 'remote':
142 remote = self._ParseRemote(node)
143 if self._remotes.get(remote.name):
144 raise ManifestParseError, \
145 'duplicate remote %s in %s' % \
146 (remote.name, self.manifestFile)
147 self._remotes[remote.name] = remote
148
149 for node in config.childNodes:
150 if node.nodeName == 'default':
151 if self._default is not None:
152 raise ManifestParseError, \
153 'duplicate default in %s' % \
154 (self.manifestFile)
155 self._default = self._ParseDefault(node)
156 if self._default is None:
157 self._default = _Default()
158
159 for node in config.childNodes:
160 if node.nodeName == 'project':
161 project = self._ParseProject(node)
162 if self._projects.get(project.name):
163 raise ManifestParseError, \
164 'duplicate project %s in %s' % \
165 (project.name, self.manifestFile)
166 self._projects[project.name] = project
167
Shawn O. Pearce70939e22008-11-06 11:07:14 -0800168 for node in config.childNodes:
169 if node.nodeName == 'add-remote':
170 pn = self._reqatt(node, 'to-project')
171 project = self._projects.get(pn)
172 if not project:
173 raise ManifestParseError, \
174 'project %s not defined in %s' % \
175 (pn, self.manifestFile)
176 self._ParseProjectExtraRemote(project, node)
177
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800178 def _AddMetaProjectMirror(self, m):
179 name = None
180 m_url = m.GetRemote(m.remote.name).url
181 if m_url.endswith('/.git'):
182 raise ManifestParseError, 'refusing to mirror %s' % m_url
183
184 if self._default and self._default.remote:
185 url = self._default.remote.fetchUrl
186 if not url.endswith('/'):
187 url += '/'
188 if m_url.startswith(url):
189 remote = self._default.remote
190 name = m_url[len(url):]
191
192 if name is None:
193 s = m_url.rindex('/') + 1
194 remote = Remote('origin', fetch = m_url[:s])
195 name = m_url[s:]
196
197 if name.endswith('.git'):
198 name = name[:-4]
199
200 if name not in self._projects:
201 m.PreSync()
202 gitdir = os.path.join(self.topdir, '%s.git' % name)
203 project = Project(manifest = self,
204 name = name,
205 remote = remote,
206 gitdir = gitdir,
207 worktree = None,
208 relpath = None,
209 revision = m.revision)
210 self._projects[project.name] = project
211
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700212 def _ParseRemote(self, node):
213 """
214 reads a <remote> element from the manifest file
215 """
216 name = self._reqatt(node, 'name')
217 fetch = self._reqatt(node, 'fetch')
218 review = node.getAttribute('review')
Shawn O. Pearceae6e0942008-11-06 10:25:35 -0800219 if review == '':
220 review = None
221
222 projectName = node.getAttribute('project-name')
223 if projectName == '':
224 projectName = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700225
226 r = Remote(name=name,
227 fetch=fetch,
Shawn O. Pearceae6e0942008-11-06 10:25:35 -0800228 review=review,
229 projectName=projectName)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700230
231 for n in node.childNodes:
232 if n.nodeName == 'require':
233 r.requiredCommits.append(self._reqatt(n, 'commit'))
234
235 return r
236
237 def _ParseDefault(self, node):
238 """
239 reads a <default> element from the manifest file
240 """
241 d = _Default()
242 d.remote = self._get_remote(node)
243 d.revision = node.getAttribute('revision')
244 return d
245
246 def _ParseProject(self, node):
247 """
248 reads a <project> element from the manifest file
249 """
250 name = self._reqatt(node, 'name')
251
252 remote = self._get_remote(node)
253 if remote is None:
254 remote = self._default.remote
255 if remote is None:
256 raise ManifestParseError, \
257 "no remote for project %s within %s" % \
258 (name, self.manifestFile)
259
260 revision = node.getAttribute('revision')
261 if not revision:
262 revision = self._default.revision
263 if not revision:
264 raise ManifestParseError, \
265 "no revision for project %s within %s" % \
266 (name, self.manifestFile)
267
268 path = node.getAttribute('path')
269 if not path:
270 path = name
271 if path.startswith('/'):
272 raise ManifestParseError, \
273 "project %s path cannot be absolute in %s" % \
274 (name, self.manifestFile)
275
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800276 if self.IsMirror:
277 relpath = None
278 worktree = None
279 gitdir = os.path.join(self.topdir, '%s.git' % name)
280 else:
281 worktree = os.path.join(self.topdir, path)
282 gitdir = os.path.join(self.repodir, 'projects/%s.git' % path)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700283
284 project = Project(manifest = self,
285 name = name,
286 remote = remote,
287 gitdir = gitdir,
288 worktree = worktree,
289 relpath = path,
290 revision = revision)
291
292 for n in node.childNodes:
293 if n.nodeName == 'remote':
Shawn O. Pearce70939e22008-11-06 11:07:14 -0800294 self._ParseProjectExtraRemote(project, n)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700295 elif n.nodeName == 'copyfile':
296 self._ParseCopyFile(project, n)
297
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700298 return project
299
Shawn O. Pearce70939e22008-11-06 11:07:14 -0800300 def _ParseProjectExtraRemote(self, project, n):
301 r = self._ParseRemote(n)
302 if project.extraRemotes.get(r.name) \
303 or project.remote.name == r.name:
304 raise ManifestParseError, \
305 'duplicate remote %s in project %s in %s' % \
306 (r.name, project.name, self.manifestFile)
307 project.extraRemotes[r.name] = r
308
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700309 def _ParseCopyFile(self, project, node):
310 src = self._reqatt(node, 'src')
311 dest = self._reqatt(node, 'dest')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800312 if not self.IsMirror:
313 # src is project relative;
314 # dest is relative to the top of the tree
315 project.AddCopyFile(src, os.path.join(self.topdir, dest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700316
317 def _get_remote(self, node):
318 name = node.getAttribute('remote')
319 if not name:
320 return None
321
322 v = self._remotes.get(name)
323 if not v:
324 raise ManifestParseError, \
325 "remote %s not defined in %s" % \
326 (name, self.manifestFile)
327 return v
328
329 def _reqatt(self, node, attname):
330 """
331 reads a required attribute from the node.
332 """
333 v = node.getAttribute(attname)
334 if not v:
335 raise ManifestParseError, \
336 "no %s in <%s> within %s" % \
337 (attname, node.nodeName, self.manifestFile)
338 return v