blob: 1d02f9d4c3e3d4409cd9d6ebaaea6e16f6d0e1d1 [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
Shawn O. Pearce20951792009-07-03 15:26:17 -070020from git_config import GitConfig
21from git_config import IsId
Shawn O. Pearcef1a6b142009-06-03 16:01:11 -070022from manifest import Manifest
Shawn O. Pearce20951792009-07-03 15:26:17 -070023from project import RemoteSpec
24from project import Project
25from project import MetaProject
26from project import R_HEADS
27from project import HEAD
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070028from error import ManifestParseError
29
30MANIFEST_FILE_NAME = 'manifest.xml'
Shawn O. Pearce5cc66792008-10-23 16:19:27 -070031LOCAL_MANIFEST_NAME = 'local_manifest.xml'
Shawn O. Pearcecc6c7962009-07-03 15:29:02 -070032R_M = 'refs/remotes/m/'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070033
34class _Default(object):
35 """Project defaults within the manifest."""
36
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -070037 revisionExpr = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070038 remote = None
39
Shawn O. Pearced1f70d92009-05-19 14:58:02 -070040class _XmlRemote(object):
41 def __init__(self,
42 name,
43 fetch=None,
44 review=None):
45 self.name = name
46 self.fetchUrl = fetch
47 self.reviewUrl = review
48
49 def ToRemoteSpec(self, projectName):
50 url = self.fetchUrl
51 while url.endswith('/'):
52 url = url[:-1]
53 url += '/%s.git' % projectName
54 return RemoteSpec(self.name, url, self.reviewUrl)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070055
Shawn O. Pearcef1a6b142009-06-03 16:01:11 -070056class XmlManifest(Manifest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070057 """manages the repo configuration file"""
58
59 def __init__(self, repodir):
Shawn O. Pearcef1a6b142009-06-03 16:01:11 -070060 Manifest.__init__(self, repodir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070061
Shawn O. Pearcef1a6b142009-06-03 16:01:11 -070062 self._manifestFile = os.path.join(repodir, MANIFEST_FILE_NAME)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070063 self.manifestProject = MetaProject(self, 'manifests',
Shawn O. Pearcef5c25a62008-11-04 08:11:53 -080064 gitdir = os.path.join(repodir, 'manifests.git'),
65 worktree = os.path.join(repodir, 'manifests'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070066
67 self._Unload()
68
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070069 def Override(self, name):
70 """Use a different manifest, just for the current instantiation.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070071 """
72 path = os.path.join(self.manifestProject.worktree, name)
73 if not os.path.isfile(path):
74 raise ManifestParseError('manifest %s not found' % name)
75
Shawn O. Pearcef1a6b142009-06-03 16:01:11 -070076 old = self._manifestFile
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070077 try:
Shawn O. Pearcef1a6b142009-06-03 16:01:11 -070078 self._manifestFile = path
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070079 self._Unload()
80 self._Load()
81 finally:
Shawn O. Pearcef1a6b142009-06-03 16:01:11 -070082 self._manifestFile = old
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070083
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070084 def Link(self, name):
85 """Update the repo metadata to use a different manifest.
86 """
87 self.Override(name)
88
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070089 try:
Shawn O. Pearcef1a6b142009-06-03 16:01:11 -070090 if os.path.exists(self._manifestFile):
91 os.remove(self._manifestFile)
92 os.symlink('manifests/%s' % name, self._manifestFile)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070093 except OSError, e:
94 raise ManifestParseError('cannot link manifest %s' % name)
95
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -080096 def _RemoteToXml(self, r, doc, root):
97 e = doc.createElement('remote')
98 root.appendChild(e)
99 e.setAttribute('name', r.name)
100 e.setAttribute('fetch', r.fetchUrl)
101 if r.reviewUrl is not None:
102 e.setAttribute('review', r.reviewUrl)
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800103
104 def Save(self, fd, peg_rev=False):
105 """Write the current manifest out to the given file descriptor.
106 """
107 doc = xml.dom.minidom.Document()
108 root = doc.createElement('manifest')
109 doc.appendChild(root)
110
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700111 # Save out the notice. There's a little bit of work here to give it the
112 # right whitespace, which assumes that the notice is automatically indented
113 # by 4 by minidom.
114 if self.notice:
115 notice_element = root.appendChild(doc.createElement('notice'))
116 notice_lines = self.notice.splitlines()
117 indented_notice = ('\n'.join(" "*4 + line for line in notice_lines))[4:]
118 notice_element.appendChild(doc.createTextNode(indented_notice))
119
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800120 d = self.default
121 sort_remotes = list(self.remotes.keys())
122 sort_remotes.sort()
123
124 for r in sort_remotes:
125 self._RemoteToXml(self.remotes[r], doc, root)
126 if self.remotes:
127 root.appendChild(doc.createTextNode(''))
128
129 have_default = False
130 e = doc.createElement('default')
131 if d.remote:
132 have_default = True
133 e.setAttribute('remote', d.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700134 if d.revisionExpr:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800135 have_default = True
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700136 e.setAttribute('revision', d.revisionExpr)
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800137 if have_default:
138 root.appendChild(e)
139 root.appendChild(doc.createTextNode(''))
140
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700141 if self._manifest_server:
142 e = doc.createElement('manifest-server')
143 e.setAttribute('url', self._manifest_server)
144 root.appendChild(e)
145 root.appendChild(doc.createTextNode(''))
146
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800147 sort_projects = list(self.projects.keys())
148 sort_projects.sort()
149
150 for p in sort_projects:
151 p = self.projects[p]
152 e = doc.createElement('project')
153 root.appendChild(e)
154 e.setAttribute('name', p.name)
155 if p.relpath != p.name:
156 e.setAttribute('path', p.relpath)
157 if not d.remote or p.remote.name != d.remote.name:
158 e.setAttribute('remote', p.remote.name)
159 if peg_rev:
160 if self.IsMirror:
161 e.setAttribute('revision',
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700162 p.bare_git.rev_parse(p.revisionExpr + '^0'))
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800163 else:
164 e.setAttribute('revision',
165 p.work_git.rev_parse(HEAD + '^0'))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700166 elif not d.revisionExpr or p.revisionExpr != d.revisionExpr:
167 e.setAttribute('revision', p.revisionExpr)
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800168
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800169 for c in p.copyfiles:
170 ce = doc.createElement('copyfile')
171 ce.setAttribute('src', c.src)
172 ce.setAttribute('dest', c.dest)
173 e.appendChild(ce)
174
175 doc.writexml(fd, '', ' ', '\n', 'UTF-8')
176
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700177 @property
178 def projects(self):
179 self._Load()
180 return self._projects
181
182 @property
183 def remotes(self):
184 self._Load()
185 return self._remotes
186
187 @property
188 def default(self):
189 self._Load()
190 return self._default
191
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800192 @property
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700193 def notice(self):
194 self._Load()
195 return self._notice
196
197 @property
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700198 def manifest_server(self):
199 self._Load()
200 return self._manifest_server
201
Shawn O. Pearce75b87c82009-07-03 16:24:57 -0700202 def InitBranch(self):
203 m = self.manifestProject
204 if m.CurrentBranch is None:
205 return m.StartBranch('default')
206 return True
207
Shawn O. Pearcecc6c7962009-07-03 15:29:02 -0700208 def SetMRefs(self, project):
209 if self.branch:
210 project._InitAnyMRef(R_M + self.branch)
211
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700212 def _Unload(self):
213 self._loaded = False
214 self._projects = {}
215 self._remotes = {}
216 self._default = None
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700217 self._notice = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700218 self.branch = None
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700219 self._manifest_server = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700220
221 def _Load(self):
222 if not self._loaded:
Shawn O. Pearce2450a292008-11-04 08:22:07 -0800223 m = self.manifestProject
Shawn O. Pearce5f947bb2009-07-03 17:24:17 -0700224 b = m.GetBranch(m.CurrentBranch)
225 if b.remote and b.remote.name:
226 m.remote.name = b.remote.name
227 b = b.merge
Shawn O. Pearce21c5c342009-06-25 16:47:30 -0700228 if b is not None and b.startswith(R_HEADS):
Shawn O. Pearce2450a292008-11-04 08:22:07 -0800229 b = b[len(R_HEADS):]
230 self.branch = b
231
Shawn O. Pearce5cc66792008-10-23 16:19:27 -0700232 self._ParseManifest(True)
233
234 local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME)
235 if os.path.exists(local):
236 try:
Shawn O. Pearcef1a6b142009-06-03 16:01:11 -0700237 real = self._manifestFile
238 self._manifestFile = local
Shawn O. Pearce5cc66792008-10-23 16:19:27 -0700239 self._ParseManifest(False)
240 finally:
Shawn O. Pearcef1a6b142009-06-03 16:01:11 -0700241 self._manifestFile = real
Shawn O. Pearce5cc66792008-10-23 16:19:27 -0700242
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800243 if self.IsMirror:
244 self._AddMetaProjectMirror(self.repoProject)
245 self._AddMetaProjectMirror(self.manifestProject)
246
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700247 self._loaded = True
248
Shawn O. Pearce5cc66792008-10-23 16:19:27 -0700249 def _ParseManifest(self, is_root_file):
Shawn O. Pearcef1a6b142009-06-03 16:01:11 -0700250 root = xml.dom.minidom.parse(self._manifestFile)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700251 if not root or not root.childNodes:
252 raise ManifestParseError, \
253 "no root node in %s" % \
Shawn O. Pearcef1a6b142009-06-03 16:01:11 -0700254 self._manifestFile
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700255
256 config = root.childNodes[0]
257 if config.nodeName != 'manifest':
258 raise ManifestParseError, \
259 "no <manifest> in %s" % \
Shawn O. Pearcef1a6b142009-06-03 16:01:11 -0700260 self._manifestFile
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700261
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700262 for node in config.childNodes:
Shawn O. Pearce03eaf072008-11-20 11:42:22 -0800263 if node.nodeName == 'remove-project':
264 name = self._reqatt(node, 'name')
265 try:
266 del self._projects[name]
267 except KeyError:
268 raise ManifestParseError, \
269 'project %s not found' % \
270 (name)
271
272 for node in config.childNodes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700273 if node.nodeName == 'remote':
274 remote = self._ParseRemote(node)
275 if self._remotes.get(remote.name):
276 raise ManifestParseError, \
277 'duplicate remote %s in %s' % \
Shawn O. Pearcef1a6b142009-06-03 16:01:11 -0700278 (remote.name, self._manifestFile)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700279 self._remotes[remote.name] = remote
280
281 for node in config.childNodes:
282 if node.nodeName == 'default':
283 if self._default is not None:
284 raise ManifestParseError, \
285 'duplicate default in %s' % \
Shawn O. Pearcef1a6b142009-06-03 16:01:11 -0700286 (self._manifestFile)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700287 self._default = self._ParseDefault(node)
288 if self._default is None:
289 self._default = _Default()
290
291 for node in config.childNodes:
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700292 if node.nodeName == 'notice':
293 if self._notice is not None:
294 raise ManifestParseError, \
295 'duplicate notice in %s' % \
296 (self.manifestFile)
297 self._notice = self._ParseNotice(node)
298
299 for node in config.childNodes:
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700300 if node.nodeName == 'manifest-server':
301 url = self._reqatt(node, 'url')
302 if self._manifest_server is not None:
303 raise ManifestParseError, \
304 'duplicate manifest-server in %s' % \
305 (self.manifestFile)
306 self._manifest_server = url
307
308 for node in config.childNodes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700309 if node.nodeName == 'project':
310 project = self._ParseProject(node)
311 if self._projects.get(project.name):
312 raise ManifestParseError, \
313 'duplicate project %s in %s' % \
Shawn O. Pearcef1a6b142009-06-03 16:01:11 -0700314 (project.name, self._manifestFile)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700315 self._projects[project.name] = project
316
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800317 def _AddMetaProjectMirror(self, m):
318 name = None
319 m_url = m.GetRemote(m.remote.name).url
320 if m_url.endswith('/.git'):
321 raise ManifestParseError, 'refusing to mirror %s' % m_url
322
323 if self._default and self._default.remote:
324 url = self._default.remote.fetchUrl
325 if not url.endswith('/'):
326 url += '/'
327 if m_url.startswith(url):
328 remote = self._default.remote
329 name = m_url[len(url):]
330
331 if name is None:
332 s = m_url.rindex('/') + 1
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700333 remote = _XmlRemote('origin', m_url[:s])
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800334 name = m_url[s:]
335
336 if name.endswith('.git'):
337 name = name[:-4]
338
339 if name not in self._projects:
340 m.PreSync()
341 gitdir = os.path.join(self.topdir, '%s.git' % name)
342 project = Project(manifest = self,
343 name = name,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700344 remote = remote.ToRemoteSpec(name),
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800345 gitdir = gitdir,
346 worktree = None,
347 relpath = None,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700348 revisionExpr = m.revisionExpr,
349 revisionId = None)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800350 self._projects[project.name] = project
351
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700352 def _ParseRemote(self, node):
353 """
354 reads a <remote> element from the manifest file
355 """
356 name = self._reqatt(node, 'name')
357 fetch = self._reqatt(node, 'fetch')
358 review = node.getAttribute('review')
Shawn O. Pearceae6e0942008-11-06 10:25:35 -0800359 if review == '':
360 review = None
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700361 return _XmlRemote(name, fetch, review)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700362
363 def _ParseDefault(self, node):
364 """
365 reads a <default> element from the manifest file
366 """
367 d = _Default()
368 d.remote = self._get_remote(node)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700369 d.revisionExpr = node.getAttribute('revision')
370 if d.revisionExpr == '':
371 d.revisionExpr = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700372 return d
373
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700374 def _ParseNotice(self, node):
375 """
376 reads a <notice> element from the manifest file
377
378 The <notice> element is distinct from other tags in the XML in that the
379 data is conveyed between the start and end tag (it's not an empty-element
380 tag).
381
382 The white space (carriage returns, indentation) for the notice element is
383 relevant and is parsed in a way that is based on how python docstrings work.
384 In fact, the code is remarkably similar to here:
385 http://www.python.org/dev/peps/pep-0257/
386 """
387 # Get the data out of the node...
388 notice = node.childNodes[0].data
389
390 # Figure out minimum indentation, skipping the first line (the same line
391 # as the <notice> tag)...
392 minIndent = sys.maxint
393 lines = notice.splitlines()
394 for line in lines[1:]:
395 lstrippedLine = line.lstrip()
396 if lstrippedLine:
397 indent = len(line) - len(lstrippedLine)
398 minIndent = min(indent, minIndent)
399
400 # Strip leading / trailing blank lines and also indentation.
401 cleanLines = [lines[0].strip()]
402 for line in lines[1:]:
403 cleanLines.append(line[minIndent:].rstrip())
404
405 # Clear completely blank lines from front and back...
406 while cleanLines and not cleanLines[0]:
407 del cleanLines[0]
408 while cleanLines and not cleanLines[-1]:
409 del cleanLines[-1]
410
411 return '\n'.join(cleanLines)
412
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700413 def _ParseProject(self, node):
414 """
415 reads a <project> element from the manifest file
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700416 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700417 name = self._reqatt(node, 'name')
418
419 remote = self._get_remote(node)
420 if remote is None:
421 remote = self._default.remote
422 if remote is None:
423 raise ManifestParseError, \
424 "no remote for project %s within %s" % \
Shawn O. Pearcef1a6b142009-06-03 16:01:11 -0700425 (name, self._manifestFile)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700426
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700427 revisionExpr = node.getAttribute('revision')
428 if not revisionExpr:
429 revisionExpr = self._default.revisionExpr
430 if not revisionExpr:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700431 raise ManifestParseError, \
432 "no revision for project %s within %s" % \
Shawn O. Pearcef1a6b142009-06-03 16:01:11 -0700433 (name, self._manifestFile)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700434
435 path = node.getAttribute('path')
436 if not path:
437 path = name
438 if path.startswith('/'):
439 raise ManifestParseError, \
440 "project %s path cannot be absolute in %s" % \
Shawn O. Pearcef1a6b142009-06-03 16:01:11 -0700441 (name, self._manifestFile)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700442
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800443 if self.IsMirror:
444 relpath = None
445 worktree = None
446 gitdir = os.path.join(self.topdir, '%s.git' % name)
447 else:
Anthony Newnamb0f9a022010-11-29 13:15:24 -0600448 worktree = os.path.join(self.topdir, path).replace('\\', '/')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800449 gitdir = os.path.join(self.repodir, 'projects/%s.git' % path)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700450
451 project = Project(manifest = self,
452 name = name,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700453 remote = remote.ToRemoteSpec(name),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700454 gitdir = gitdir,
455 worktree = worktree,
456 relpath = path,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700457 revisionExpr = revisionExpr,
458 revisionId = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700459
460 for n in node.childNodes:
Shawn O. Pearce242b5262009-05-19 13:00:29 -0700461 if n.nodeName == 'copyfile':
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700462 self._ParseCopyFile(project, n)
463
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700464 return project
465
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700466 def _ParseCopyFile(self, project, node):
467 src = self._reqatt(node, 'src')
468 dest = self._reqatt(node, 'dest')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800469 if not self.IsMirror:
470 # src is project relative;
471 # dest is relative to the top of the tree
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800472 project.AddCopyFile(src, dest, os.path.join(self.topdir, dest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700473
474 def _get_remote(self, node):
475 name = node.getAttribute('remote')
476 if not name:
477 return None
478
479 v = self._remotes.get(name)
480 if not v:
481 raise ManifestParseError, \
482 "remote %s not defined in %s" % \
Shawn O. Pearcef1a6b142009-06-03 16:01:11 -0700483 (name, self._manifestFile)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700484 return v
485
486 def _reqatt(self, node, attname):
487 """
488 reads a required attribute from the node.
489 """
490 v = node.getAttribute(attname)
491 if not v:
492 raise ManifestParseError, \
493 "no %s in <%s> within %s" % \
Shawn O. Pearcef1a6b142009-06-03 16:01:11 -0700494 (attname, node.nodeName, self._manifestFile)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700495 return v