blob: 8c5a8d6d43401f9c0f9a4c07294b3bcf556009ef [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
21from import_tar import ImportTar
22from import_zip import ImportZip
23from project import Project, MetaProject, R_TAGS
24from remote import Remote
25from error import ManifestParseError
26
27MANIFEST_FILE_NAME = 'manifest.xml'
Shawn O. Pearce5cc66792008-10-23 16:19:27 -070028LOCAL_MANIFEST_NAME = 'local_manifest.xml'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070029
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)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070044 self.globalConfig = GitConfig.ForUser()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070045
46 self.repoProject = MetaProject(self, 'repo',
47 gitdir = os.path.join(repodir, 'repo/.git'),
48 worktree = os.path.join(repodir, 'repo'))
49
50 wt = os.path.join(repodir, 'manifests')
51 gd_new = os.path.join(repodir, 'manifests.git')
52 gd_old = os.path.join(wt, '.git')
53 if os.path.exists(gd_new) or not os.path.exists(gd_old):
54 gd = gd_new
55 else:
56 gd = gd_old
57 self.manifestProject = MetaProject(self, 'manifests',
58 gitdir = gd,
59 worktree = wt)
60
61 self._Unload()
62
63 def Link(self, name):
64 """Update the repo metadata to use a different manifest.
65 """
66 path = os.path.join(self.manifestProject.worktree, name)
67 if not os.path.isfile(path):
68 raise ManifestParseError('manifest %s not found' % name)
69
70 old = self.manifestFile
71 try:
72 self.manifestFile = path
73 self._Unload()
74 self._Load()
75 finally:
76 self.manifestFile = old
77
78 try:
79 if os.path.exists(self.manifestFile):
80 os.remove(self.manifestFile)
81 os.symlink('manifests/%s' % name, self.manifestFile)
82 except OSError, e:
83 raise ManifestParseError('cannot link manifest %s' % name)
84
85 @property
86 def projects(self):
87 self._Load()
88 return self._projects
89
90 @property
91 def remotes(self):
92 self._Load()
93 return self._remotes
94
95 @property
96 def default(self):
97 self._Load()
98 return self._default
99
100 def _Unload(self):
101 self._loaded = False
102 self._projects = {}
103 self._remotes = {}
104 self._default = None
105 self.branch = None
106
107 def _Load(self):
108 if not self._loaded:
Shawn O. Pearce5cc66792008-10-23 16:19:27 -0700109 self._ParseManifest(True)
110
111 local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME)
112 if os.path.exists(local):
113 try:
114 real = self.manifestFile
115 self.manifestFile = local
116 self._ParseManifest(False)
117 finally:
118 self.manifestFile = real
119
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700120 self._loaded = True
121
Shawn O. Pearce5cc66792008-10-23 16:19:27 -0700122 def _ParseManifest(self, is_root_file):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700123 root = xml.dom.minidom.parse(self.manifestFile)
124 if not root or not root.childNodes:
125 raise ManifestParseError, \
126 "no root node in %s" % \
127 self.manifestFile
128
129 config = root.childNodes[0]
130 if config.nodeName != 'manifest':
131 raise ManifestParseError, \
132 "no <manifest> in %s" % \
133 self.manifestFile
134
Shawn O. Pearce5cc66792008-10-23 16:19:27 -0700135 if is_root_file:
136 self.branch = config.getAttribute('branch')
137 if not self.branch:
138 self.branch = 'default'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700139
140 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
168 def _ParseRemote(self, node):
169 """
170 reads a <remote> element from the manifest file
171 """
172 name = self._reqatt(node, 'name')
173 fetch = self._reqatt(node, 'fetch')
174 review = node.getAttribute('review')
175
176 r = Remote(name=name,
177 fetch=fetch,
178 review=review)
179
180 for n in node.childNodes:
181 if n.nodeName == 'require':
182 r.requiredCommits.append(self._reqatt(n, 'commit'))
183
184 return r
185
186 def _ParseDefault(self, node):
187 """
188 reads a <default> element from the manifest file
189 """
190 d = _Default()
191 d.remote = self._get_remote(node)
192 d.revision = node.getAttribute('revision')
193 return d
194
195 def _ParseProject(self, node):
196 """
197 reads a <project> element from the manifest file
198 """
199 name = self._reqatt(node, 'name')
200
201 remote = self._get_remote(node)
202 if remote is None:
203 remote = self._default.remote
204 if remote is None:
205 raise ManifestParseError, \
206 "no remote for project %s within %s" % \
207 (name, self.manifestFile)
208
209 revision = node.getAttribute('revision')
210 if not revision:
211 revision = self._default.revision
212 if not revision:
213 raise ManifestParseError, \
214 "no revision for project %s within %s" % \
215 (name, self.manifestFile)
216
217 path = node.getAttribute('path')
218 if not path:
219 path = name
220 if path.startswith('/'):
221 raise ManifestParseError, \
222 "project %s path cannot be absolute in %s" % \
223 (name, self.manifestFile)
224
225 worktree = os.path.join(self.topdir, path)
226 gitdir = os.path.join(self.repodir, 'projects/%s.git' % path)
227
228 project = Project(manifest = self,
229 name = name,
230 remote = remote,
231 gitdir = gitdir,
232 worktree = worktree,
233 relpath = path,
234 revision = revision)
235
236 for n in node.childNodes:
237 if n.nodeName == 'remote':
238 r = self._ParseRemote(n)
239 if project.extraRemotes.get(r.name) \
240 or project.remote.name == r.name:
241 raise ManifestParseError, \
242 'duplicate remote %s in project %s in %s' % \
243 (r.name, project.name, self.manifestFile)
244 project.extraRemotes[r.name] = r
245 elif n.nodeName == 'copyfile':
246 self._ParseCopyFile(project, n)
247
248 to_resolve = []
249 by_version = {}
250
251 for n in node.childNodes:
252 if n.nodeName == 'import':
253 self._ParseImport(project, n, to_resolve, by_version)
254
255 for pair in to_resolve:
256 sn, pr = pair
257 try:
258 sn.SetParent(by_version[pr].commit)
259 except KeyError:
260 raise ManifestParseError, \
261 'snapshot %s not in project %s in %s' % \
262 (pr, project.name, self.manifestFile)
263
264 return project
265
266 def _ParseImport(self, project, import_node, to_resolve, by_version):
267 first_url = None
268 for node in import_node.childNodes:
269 if node.nodeName == 'mirror':
270 first_url = self._reqatt(node, 'url')
271 break
272 if not first_url:
273 raise ManifestParseError, \
274 'mirror url required for project %s in %s' % \
275 (project.name, self.manifestFile)
276
277 imp = None
278 for cls in [ImportTar, ImportZip]:
279 if cls.CanAccept(first_url):
280 imp = cls()
281 break
282 if not imp:
283 raise ManifestParseError, \
284 'snapshot %s unsupported for project %s in %s' % \
285 (first_url, project.name, self.manifestFile)
286
287 imp.SetProject(project)
288
289 for node in import_node.childNodes:
290 if node.nodeName == 'remap':
291 old = node.getAttribute('strip')
292 new = node.getAttribute('insert')
293 imp.RemapPath(old, new)
294
295 elif node.nodeName == 'mirror':
296 imp.AddUrl(self._reqatt(node, 'url'))
297
298 for node in import_node.childNodes:
299 if node.nodeName == 'snapshot':
300 sn = imp.Clone()
301 sn.SetVersion(self._reqatt(node, 'version'))
302 sn.SetCommit(node.getAttribute('check'))
303
304 pr = node.getAttribute('prior')
305 if pr:
306 if IsId(pr):
307 sn.SetParent(pr)
308 else:
309 to_resolve.append((sn, pr))
310
311 rev = R_TAGS + sn.TagName
312
313 if rev in project.snapshots:
314 raise ManifestParseError, \
315 'duplicate snapshot %s for project %s in %s' % \
316 (sn.version, project.name, self.manifestFile)
317 project.snapshots[rev] = sn
318 by_version[sn.version] = sn
319
320 def _ParseCopyFile(self, project, node):
321 src = self._reqatt(node, 'src')
322 dest = self._reqatt(node, 'dest')
323 # src is project relative, and dest is relative to the top of the tree
324 project.AddCopyFile(src, os.path.join(self.topdir, dest))
325
326 def _get_remote(self, node):
327 name = node.getAttribute('remote')
328 if not name:
329 return None
330
331 v = self._remotes.get(name)
332 if not v:
333 raise ManifestParseError, \
334 "remote %s not defined in %s" % \
335 (name, self.manifestFile)
336 return v
337
338 def _reqatt(self, node, attname):
339 """
340 reads a required attribute from the node.
341 """
342 v = node.getAttribute(attname)
343 if not v:
344 raise ManifestParseError, \
345 "no %s in <%s> within %s" % \
346 (attname, node.nodeName, self.manifestFile)
347 return v