blob: 45b0f9a5b0c66c0317479ad6620061a482c7a6ef [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
20from editor import Editor
21from git_config import GitConfig, IsId
22from import_tar import ImportTar
23from import_zip import ImportZip
24from project import Project, MetaProject, R_TAGS
25from remote import Remote
26from error import ManifestParseError
27
28MANIFEST_FILE_NAME = 'manifest.xml'
29
30class _Default(object):
31 """Project defaults within the manifest."""
32
33 revision = None
34 remote = None
35
36
37class Manifest(object):
38 """manages the repo configuration file"""
39
40 def __init__(self, repodir):
41 self.repodir = os.path.abspath(repodir)
42 self.topdir = os.path.dirname(self.repodir)
43 self.manifestFile = os.path.join(self.repodir, MANIFEST_FILE_NAME)
44
45 self.globalConfig = GitConfig.ForUser()
46 Editor.globalConfig = self.globalConfig
47
48 self.repoProject = MetaProject(self, 'repo',
49 gitdir = os.path.join(repodir, 'repo/.git'),
50 worktree = os.path.join(repodir, 'repo'))
51
52 wt = os.path.join(repodir, 'manifests')
53 gd_new = os.path.join(repodir, 'manifests.git')
54 gd_old = os.path.join(wt, '.git')
55 if os.path.exists(gd_new) or not os.path.exists(gd_old):
56 gd = gd_new
57 else:
58 gd = gd_old
59 self.manifestProject = MetaProject(self, 'manifests',
60 gitdir = gd,
61 worktree = wt)
62
63 self._Unload()
64
65 def Link(self, name):
66 """Update the repo metadata to use a different manifest.
67 """
68 path = os.path.join(self.manifestProject.worktree, name)
69 if not os.path.isfile(path):
70 raise ManifestParseError('manifest %s not found' % name)
71
72 old = self.manifestFile
73 try:
74 self.manifestFile = path
75 self._Unload()
76 self._Load()
77 finally:
78 self.manifestFile = old
79
80 try:
81 if os.path.exists(self.manifestFile):
82 os.remove(self.manifestFile)
83 os.symlink('manifests/%s' % name, self.manifestFile)
84 except OSError, e:
85 raise ManifestParseError('cannot link manifest %s' % name)
86
87 @property
88 def projects(self):
89 self._Load()
90 return self._projects
91
92 @property
93 def remotes(self):
94 self._Load()
95 return self._remotes
96
97 @property
98 def default(self):
99 self._Load()
100 return self._default
101
102 def _Unload(self):
103 self._loaded = False
104 self._projects = {}
105 self._remotes = {}
106 self._default = None
107 self.branch = None
108
109 def _Load(self):
110 if not self._loaded:
111 self._ParseManifest()
112 self._loaded = True
113
114 def _ParseManifest(self):
115 root = xml.dom.minidom.parse(self.manifestFile)
116 if not root or not root.childNodes:
117 raise ManifestParseError, \
118 "no root node in %s" % \
119 self.manifestFile
120
121 config = root.childNodes[0]
122 if config.nodeName != 'manifest':
123 raise ManifestParseError, \
124 "no <manifest> in %s" % \
125 self.manifestFile
126
127 self.branch = config.getAttribute('branch')
128 if not self.branch:
129 self.branch = 'default'
130
131 for node in config.childNodes:
132 if node.nodeName == 'remote':
133 remote = self._ParseRemote(node)
134 if self._remotes.get(remote.name):
135 raise ManifestParseError, \
136 'duplicate remote %s in %s' % \
137 (remote.name, self.manifestFile)
138 self._remotes[remote.name] = remote
139
140 for node in config.childNodes:
141 if node.nodeName == 'default':
142 if self._default is not None:
143 raise ManifestParseError, \
144 'duplicate default in %s' % \
145 (self.manifestFile)
146 self._default = self._ParseDefault(node)
147 if self._default is None:
148 self._default = _Default()
149
150 for node in config.childNodes:
151 if node.nodeName == 'project':
152 project = self._ParseProject(node)
153 if self._projects.get(project.name):
154 raise ManifestParseError, \
155 'duplicate project %s in %s' % \
156 (project.name, self.manifestFile)
157 self._projects[project.name] = project
158
159 def _ParseRemote(self, node):
160 """
161 reads a <remote> element from the manifest file
162 """
163 name = self._reqatt(node, 'name')
164 fetch = self._reqatt(node, 'fetch')
165 review = node.getAttribute('review')
166
167 r = Remote(name=name,
168 fetch=fetch,
169 review=review)
170
171 for n in node.childNodes:
172 if n.nodeName == 'require':
173 r.requiredCommits.append(self._reqatt(n, 'commit'))
174
175 return r
176
177 def _ParseDefault(self, node):
178 """
179 reads a <default> element from the manifest file
180 """
181 d = _Default()
182 d.remote = self._get_remote(node)
183 d.revision = node.getAttribute('revision')
184 return d
185
186 def _ParseProject(self, node):
187 """
188 reads a <project> element from the manifest file
189 """
190 name = self._reqatt(node, 'name')
191
192 remote = self._get_remote(node)
193 if remote is None:
194 remote = self._default.remote
195 if remote is None:
196 raise ManifestParseError, \
197 "no remote for project %s within %s" % \
198 (name, self.manifestFile)
199
200 revision = node.getAttribute('revision')
201 if not revision:
202 revision = self._default.revision
203 if not revision:
204 raise ManifestParseError, \
205 "no revision for project %s within %s" % \
206 (name, self.manifestFile)
207
208 path = node.getAttribute('path')
209 if not path:
210 path = name
211 if path.startswith('/'):
212 raise ManifestParseError, \
213 "project %s path cannot be absolute in %s" % \
214 (name, self.manifestFile)
215
216 worktree = os.path.join(self.topdir, path)
217 gitdir = os.path.join(self.repodir, 'projects/%s.git' % path)
218
219 project = Project(manifest = self,
220 name = name,
221 remote = remote,
222 gitdir = gitdir,
223 worktree = worktree,
224 relpath = path,
225 revision = revision)
226
227 for n in node.childNodes:
228 if n.nodeName == 'remote':
229 r = self._ParseRemote(n)
230 if project.extraRemotes.get(r.name) \
231 or project.remote.name == r.name:
232 raise ManifestParseError, \
233 'duplicate remote %s in project %s in %s' % \
234 (r.name, project.name, self.manifestFile)
235 project.extraRemotes[r.name] = r
236 elif n.nodeName == 'copyfile':
237 self._ParseCopyFile(project, n)
238
239 to_resolve = []
240 by_version = {}
241
242 for n in node.childNodes:
243 if n.nodeName == 'import':
244 self._ParseImport(project, n, to_resolve, by_version)
245
246 for pair in to_resolve:
247 sn, pr = pair
248 try:
249 sn.SetParent(by_version[pr].commit)
250 except KeyError:
251 raise ManifestParseError, \
252 'snapshot %s not in project %s in %s' % \
253 (pr, project.name, self.manifestFile)
254
255 return project
256
257 def _ParseImport(self, project, import_node, to_resolve, by_version):
258 first_url = None
259 for node in import_node.childNodes:
260 if node.nodeName == 'mirror':
261 first_url = self._reqatt(node, 'url')
262 break
263 if not first_url:
264 raise ManifestParseError, \
265 'mirror url required for project %s in %s' % \
266 (project.name, self.manifestFile)
267
268 imp = None
269 for cls in [ImportTar, ImportZip]:
270 if cls.CanAccept(first_url):
271 imp = cls()
272 break
273 if not imp:
274 raise ManifestParseError, \
275 'snapshot %s unsupported for project %s in %s' % \
276 (first_url, project.name, self.manifestFile)
277
278 imp.SetProject(project)
279
280 for node in import_node.childNodes:
281 if node.nodeName == 'remap':
282 old = node.getAttribute('strip')
283 new = node.getAttribute('insert')
284 imp.RemapPath(old, new)
285
286 elif node.nodeName == 'mirror':
287 imp.AddUrl(self._reqatt(node, 'url'))
288
289 for node in import_node.childNodes:
290 if node.nodeName == 'snapshot':
291 sn = imp.Clone()
292 sn.SetVersion(self._reqatt(node, 'version'))
293 sn.SetCommit(node.getAttribute('check'))
294
295 pr = node.getAttribute('prior')
296 if pr:
297 if IsId(pr):
298 sn.SetParent(pr)
299 else:
300 to_resolve.append((sn, pr))
301
302 rev = R_TAGS + sn.TagName
303
304 if rev in project.snapshots:
305 raise ManifestParseError, \
306 'duplicate snapshot %s for project %s in %s' % \
307 (sn.version, project.name, self.manifestFile)
308 project.snapshots[rev] = sn
309 by_version[sn.version] = sn
310
311 def _ParseCopyFile(self, project, node):
312 src = self._reqatt(node, 'src')
313 dest = self._reqatt(node, 'dest')
314 # src is project relative, and dest is relative to the top of the tree
315 project.AddCopyFile(src, os.path.join(self.topdir, dest))
316
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