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