summaryrefslogtreecommitdiffstats
path: root/manifest_xml.py
diff options
context:
space:
mode:
Diffstat (limited to 'manifest_xml.py')
-rw-r--r--manifest_xml.py250
1 files changed, 184 insertions, 66 deletions
diff --git a/manifest_xml.py b/manifest_xml.py
index dd163bed..53f33537 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -13,6 +13,7 @@
13# See the License for the specific language governing permissions and 13# See the License for the specific language governing permissions and
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function
16import itertools 17import itertools
17import os 18import os
18import re 19import re
@@ -27,6 +28,7 @@ from error import ManifestParseError
27 28
28MANIFEST_FILE_NAME = 'manifest.xml' 29MANIFEST_FILE_NAME = 'manifest.xml'
29LOCAL_MANIFEST_NAME = 'local_manifest.xml' 30LOCAL_MANIFEST_NAME = 'local_manifest.xml'
31LOCAL_MANIFESTS_DIR_NAME = 'local_manifests'
30 32
31urlparse.uses_relative.extend(['ssh', 'git']) 33urlparse.uses_relative.extend(['ssh', 'git'])
32urlparse.uses_netloc.extend(['ssh', 'git']) 34urlparse.uses_netloc.extend(['ssh', 'git'])
@@ -38,6 +40,7 @@ class _Default(object):
38 remote = None 40 remote = None
39 sync_j = 1 41 sync_j = 1
40 sync_c = False 42 sync_c = False
43 sync_s = False
41 44
42class _XmlRemote(object): 45class _XmlRemote(object):
43 def __init__(self, 46 def __init__(self,
@@ -53,15 +56,28 @@ class _XmlRemote(object):
53 self.reviewUrl = review 56 self.reviewUrl = review
54 self.resolvedFetchUrl = self._resolveFetchUrl() 57 self.resolvedFetchUrl = self._resolveFetchUrl()
55 58
59 def __eq__(self, other):
60 return self.__dict__ == other.__dict__
61
62 def __ne__(self, other):
63 return self.__dict__ != other.__dict__
64
56 def _resolveFetchUrl(self): 65 def _resolveFetchUrl(self):
57 url = self.fetchUrl.rstrip('/') 66 url = self.fetchUrl.rstrip('/')
58 manifestUrl = self.manifestUrl.rstrip('/') 67 manifestUrl = self.manifestUrl.rstrip('/')
68 p = manifestUrl.startswith('persistent-http')
69 if p:
70 manifestUrl = manifestUrl[len('persistent-'):]
71
59 # urljoin will get confused if there is no scheme in the base url 72 # urljoin will get confused if there is no scheme in the base url
60 # ie, if manifestUrl is of the form <hostname:port> 73 # ie, if manifestUrl is of the form <hostname:port>
61 if manifestUrl.find(':') != manifestUrl.find('/') - 1: 74 if manifestUrl.find(':') != manifestUrl.find('/') - 1:
62 manifestUrl = 'gopher://' + manifestUrl 75 manifestUrl = 'gopher://' + manifestUrl
63 url = urlparse.urljoin(manifestUrl, url) 76 url = urlparse.urljoin(manifestUrl, url)
64 return re.sub(r'^gopher://', '', url) 77 url = re.sub(r'^gopher://', '', url)
78 if p:
79 url = 'persistent-' + url
80 return url
65 81
66 def ToRemoteSpec(self, projectName): 82 def ToRemoteSpec(self, projectName):
67 url = self.resolvedFetchUrl.rstrip('/') + '/' + projectName 83 url = self.resolvedFetchUrl.rstrip('/') + '/' + projectName
@@ -110,11 +126,11 @@ class XmlManifest(object):
110 self.Override(name) 126 self.Override(name)
111 127
112 try: 128 try:
113 if os.path.exists(self.manifestFile): 129 if os.path.lexists(self.manifestFile):
114 os.remove(self.manifestFile) 130 os.remove(self.manifestFile)
115 os.symlink('manifests/%s' % name, self.manifestFile) 131 os.symlink('manifests/%s' % name, self.manifestFile)
116 except OSError: 132 except OSError as e:
117 raise ManifestParseError('cannot link manifest %s' % name) 133 raise ManifestParseError('cannot link manifest %s: %s' % (name, str(e)))
118 134
119 def _RemoteToXml(self, r, doc, root): 135 def _RemoteToXml(self, r, doc, root):
120 e = doc.createElement('remote') 136 e = doc.createElement('remote')
@@ -130,9 +146,8 @@ class XmlManifest(object):
130 mp = self.manifestProject 146 mp = self.manifestProject
131 147
132 groups = mp.config.GetString('manifest.groups') 148 groups = mp.config.GetString('manifest.groups')
133 if not groups: 149 if groups:
134 groups = 'all' 150 groups = [x for x in re.split(r'[,\s]+', groups) if x]
135 groups = [x for x in re.split(r'[,\s]+', groups) if x]
136 151
137 doc = xml.dom.minidom.Document() 152 doc = xml.dom.minidom.Document()
138 root = doc.createElement('manifest') 153 root = doc.createElement('manifest')
@@ -170,6 +185,9 @@ class XmlManifest(object):
170 if d.sync_c: 185 if d.sync_c:
171 have_default = True 186 have_default = True
172 e.setAttribute('sync-c', 'true') 187 e.setAttribute('sync-c', 'true')
188 if d.sync_s:
189 have_default = True
190 e.setAttribute('sync-s', 'true')
173 if have_default: 191 if have_default:
174 root.appendChild(e) 192 root.appendChild(e)
175 root.appendChild(doc.createTextNode('')) 193 root.appendChild(doc.createTextNode(''))
@@ -180,20 +198,25 @@ class XmlManifest(object):
180 root.appendChild(e) 198 root.appendChild(e)
181 root.appendChild(doc.createTextNode('')) 199 root.appendChild(doc.createTextNode(''))
182 200
183 sort_projects = list(self.projects.keys()) 201 def output_projects(parent, parent_node, projects):
184 sort_projects.sort() 202 for p in projects:
185 203 output_project(parent, parent_node, self.projects[p])
186 for p in sort_projects:
187 p = self.projects[p]
188 204
205 def output_project(parent, parent_node, p):
189 if not p.MatchesGroups(groups): 206 if not p.MatchesGroups(groups):
190 continue 207 return
208
209 name = p.name
210 relpath = p.relpath
211 if parent:
212 name = self._UnjoinName(parent.name, name)
213 relpath = self._UnjoinRelpath(parent.relpath, relpath)
191 214
192 e = doc.createElement('project') 215 e = doc.createElement('project')
193 root.appendChild(e) 216 parent_node.appendChild(e)
194 e.setAttribute('name', p.name) 217 e.setAttribute('name', name)
195 if p.relpath != p.name: 218 if relpath != name:
196 e.setAttribute('path', p.relpath) 219 e.setAttribute('path', relpath)
197 if not d.remote or p.remote.name != d.remote.name: 220 if not d.remote or p.remote.name != d.remote.name:
198 e.setAttribute('remote', p.remote.name) 221 e.setAttribute('remote', p.remote.name)
199 if peg_rev: 222 if peg_rev:
@@ -231,6 +254,19 @@ class XmlManifest(object):
231 if p.sync_c: 254 if p.sync_c:
232 e.setAttribute('sync-c', 'true') 255 e.setAttribute('sync-c', 'true')
233 256
257 if p.sync_s:
258 e.setAttribute('sync-s', 'true')
259
260 if p.subprojects:
261 sort_projects = [subp.name for subp in p.subprojects]
262 sort_projects.sort()
263 output_projects(p, e, sort_projects)
264
265 sort_projects = [key for key in self.projects.keys()
266 if not self.projects[key].parent]
267 sort_projects.sort()
268 output_projects(None, root, sort_projects)
269
234 if self._repo_hooks_project: 270 if self._repo_hooks_project:
235 root.appendChild(doc.createTextNode('')) 271 root.appendChild(doc.createTextNode(''))
236 e = doc.createElement('repo-hooks') 272 e = doc.createElement('repo-hooks')
@@ -299,9 +335,30 @@ class XmlManifest(object):
299 335
300 local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME) 336 local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME)
301 if os.path.exists(local): 337 if os.path.exists(local):
338 print('warning: %s is deprecated; put local manifests in %s instead'
339 % (LOCAL_MANIFEST_NAME, LOCAL_MANIFESTS_DIR_NAME),
340 file=sys.stderr)
302 nodes.append(self._ParseManifestXml(local, self.repodir)) 341 nodes.append(self._ParseManifestXml(local, self.repodir))
303 342
304 self._ParseManifest(nodes) 343 local_dir = os.path.abspath(os.path.join(self.repodir, LOCAL_MANIFESTS_DIR_NAME))
344 try:
345 for local_file in sorted(os.listdir(local_dir)):
346 if local_file.endswith('.xml'):
347 try:
348 local = os.path.join(local_dir, local_file)
349 nodes.append(self._ParseManifestXml(local, self.repodir))
350 except ManifestParseError as e:
351 print('%s' % str(e), file=sys.stderr)
352 except OSError:
353 pass
354
355 try:
356 self._ParseManifest(nodes)
357 except ManifestParseError as e:
358 # There was a problem parsing, unload ourselves in case they catch
359 # this error and try again later, we will show the correct error
360 self._Unload()
361 raise e
305 362
306 if self.IsMirror: 363 if self.IsMirror:
307 self._AddMetaProjectMirror(self.repoProject) 364 self._AddMetaProjectMirror(self.repoProject)
@@ -310,7 +367,11 @@ class XmlManifest(object):
310 self._loaded = True 367 self._loaded = True
311 368
312 def _ParseManifestXml(self, path, include_root): 369 def _ParseManifestXml(self, path, include_root):
313 root = xml.dom.minidom.parse(path) 370 try:
371 root = xml.dom.minidom.parse(path)
372 except (OSError, xml.parsers.expat.ExpatError) as e:
373 raise ManifestParseError("error parsing manifest %s: %s" % (path, e))
374
314 if not root or not root.childNodes: 375 if not root or not root.childNodes:
315 raise ManifestParseError("no root node in %s" % (path,)) 376 raise ManifestParseError("no root node in %s" % (path,))
316 377
@@ -323,35 +384,38 @@ class XmlManifest(object):
323 nodes = [] 384 nodes = []
324 for node in manifest.childNodes: # pylint:disable=W0631 385 for node in manifest.childNodes: # pylint:disable=W0631
325 # We only get here if manifest is initialised 386 # We only get here if manifest is initialised
326 if node.nodeName == 'include': 387 if node.nodeName == 'include':
327 name = self._reqatt(node, 'name') 388 name = self._reqatt(node, 'name')
328 fp = os.path.join(include_root, name) 389 fp = os.path.join(include_root, name)
329 if not os.path.isfile(fp): 390 if not os.path.isfile(fp):
330 raise ManifestParseError, \ 391 raise ManifestParseError, \
331 "include %s doesn't exist or isn't a file" % \ 392 "include %s doesn't exist or isn't a file" % \
332 (name,) 393 (name,)
333 try: 394 try:
334 nodes.extend(self._ParseManifestXml(fp, include_root)) 395 nodes.extend(self._ParseManifestXml(fp, include_root))
335 # should isolate this to the exact exception, but that's 396 # should isolate this to the exact exception, but that's
336 # tricky. actual parsing implementation may vary. 397 # tricky. actual parsing implementation may vary.
337 except (KeyboardInterrupt, RuntimeError, SystemExit): 398 except (KeyboardInterrupt, RuntimeError, SystemExit):
338 raise 399 raise
339 except Exception as e: 400 except Exception as e:
340 raise ManifestParseError( 401 raise ManifestParseError(
341 "failed parsing included manifest %s: %s", (name, e)) 402 "failed parsing included manifest %s: %s", (name, e))
342 else: 403 else:
343 nodes.append(node) 404 nodes.append(node)
344 return nodes 405 return nodes
345 406
346 def _ParseManifest(self, node_list): 407 def _ParseManifest(self, node_list):
347 for node in itertools.chain(*node_list): 408 for node in itertools.chain(*node_list):
348 if node.nodeName == 'remote': 409 if node.nodeName == 'remote':
349 remote = self._ParseRemote(node) 410 remote = self._ParseRemote(node)
350 if self._remotes.get(remote.name): 411 if remote:
351 raise ManifestParseError( 412 if remote.name in self._remotes:
352 'duplicate remote %s in %s' % 413 if remote != self._remotes[remote.name]:
353 (remote.name, self.manifestFile)) 414 raise ManifestParseError(
354 self._remotes[remote.name] = remote 415 'remote %s already exists with different attributes' %
416 (remote.name))
417 else:
418 self._remotes[remote.name] = remote
355 419
356 for node in itertools.chain(*node_list): 420 for node in itertools.chain(*node_list):
357 if node.nodeName == 'default': 421 if node.nodeName == 'default':
@@ -375,19 +439,24 @@ class XmlManifest(object):
375 if node.nodeName == 'manifest-server': 439 if node.nodeName == 'manifest-server':
376 url = self._reqatt(node, 'url') 440 url = self._reqatt(node, 'url')
377 if self._manifest_server is not None: 441 if self._manifest_server is not None:
378 raise ManifestParseError( 442 raise ManifestParseError(
379 'duplicate manifest-server in %s' % 443 'duplicate manifest-server in %s' %
380 (self.manifestFile)) 444 (self.manifestFile))
381 self._manifest_server = url 445 self._manifest_server = url
382 446
447 def recursively_add_projects(project):
448 if self._projects.get(project.name):
449 raise ManifestParseError(
450 'duplicate project %s in %s' %
451 (project.name, self.manifestFile))
452 self._projects[project.name] = project
453 for subproject in project.subprojects:
454 recursively_add_projects(subproject)
455
383 for node in itertools.chain(*node_list): 456 for node in itertools.chain(*node_list):
384 if node.nodeName == 'project': 457 if node.nodeName == 'project':
385 project = self._ParseProject(node) 458 project = self._ParseProject(node)
386 if self._projects.get(project.name): 459 recursively_add_projects(project)
387 raise ManifestParseError(
388 'duplicate project %s in %s' %
389 (project.name, self.manifestFile))
390 self._projects[project.name] = project
391 if node.nodeName == 'repo-hooks': 460 if node.nodeName == 'repo-hooks':
392 # Get the name of the project and the (space-separated) list of enabled. 461 # Get the name of the project and the (space-separated) list of enabled.
393 repo_hooks_project = self._reqatt(node, 'in-project') 462 repo_hooks_project = self._reqatt(node, 'in-project')
@@ -414,9 +483,8 @@ class XmlManifest(object):
414 try: 483 try:
415 del self._projects[name] 484 del self._projects[name]
416 except KeyError: 485 except KeyError:
417 raise ManifestParseError( 486 raise ManifestParseError('remove-project element specifies non-existent '
418 'project %s not found' % 487 'project: %s' % name)
419 (name))
420 488
421 # If the manifest removes the hooks project, treat it as if it deleted 489 # If the manifest removes the hooks project, treat it as if it deleted
422 # the repo-hooks element too. 490 # the repo-hooks element too.
@@ -496,6 +564,12 @@ class XmlManifest(object):
496 d.sync_c = False 564 d.sync_c = False
497 else: 565 else:
498 d.sync_c = sync_c.lower() in ("yes", "true", "1") 566 d.sync_c = sync_c.lower() in ("yes", "true", "1")
567
568 sync_s = node.getAttribute('sync-s')
569 if not sync_s:
570 d.sync_s = False
571 else:
572 d.sync_s = sync_s.lower() in ("yes", "true", "1")
499 return d 573 return d
500 574
501 def _ParseNotice(self, node): 575 def _ParseNotice(self, node):
@@ -537,11 +611,19 @@ class XmlManifest(object):
537 611
538 return '\n'.join(cleanLines) 612 return '\n'.join(cleanLines)
539 613
540 def _ParseProject(self, node): 614 def _JoinName(self, parent_name, name):
615 return os.path.join(parent_name, name)
616
617 def _UnjoinName(self, parent_name, name):
618 return os.path.relpath(name, parent_name)
619
620 def _ParseProject(self, node, parent = None):
541 """ 621 """
542 reads a <project> element from the manifest file 622 reads a <project> element from the manifest file
543 """ 623 """
544 name = self._reqatt(node, 'name') 624 name = self._reqatt(node, 'name')
625 if parent:
626 name = self._JoinName(parent.name, name)
545 627
546 remote = self._get_remote(node) 628 remote = self._get_remote(node)
547 if remote is None: 629 if remote is None:
@@ -579,44 +661,80 @@ class XmlManifest(object):
579 else: 661 else:
580 sync_c = sync_c.lower() in ("yes", "true", "1") 662 sync_c = sync_c.lower() in ("yes", "true", "1")
581 663
664 sync_s = node.getAttribute('sync-s')
665 if not sync_s:
666 sync_s = self._default.sync_s
667 else:
668 sync_s = sync_s.lower() in ("yes", "true", "1")
669
582 upstream = node.getAttribute('upstream') 670 upstream = node.getAttribute('upstream')
583 671
584 groups = '' 672 groups = ''
585 if node.hasAttribute('groups'): 673 if node.hasAttribute('groups'):
586 groups = node.getAttribute('groups') 674 groups = node.getAttribute('groups')
587 groups = [x for x in re.split('[,\s]+', groups) if x] 675 groups = [x for x in re.split(r'[,\s]+', groups) if x]
588
589 default_groups = ['all', 'name:%s' % name, 'path:%s' % path]
590 groups.extend(set(default_groups).difference(groups))
591 676
592 if self.IsMirror: 677 if parent is None:
593 worktree = None 678 relpath, worktree, gitdir = self.GetProjectPaths(name, path)
594 gitdir = os.path.join(self.topdir, '%s.git' % name)
595 else: 679 else:
596 worktree = os.path.join(self.topdir, path).replace('\\', '/') 680 relpath, worktree, gitdir = self.GetSubprojectPaths(parent, path)
597 gitdir = os.path.join(self.repodir, 'projects/%s.git' % path) 681
682 default_groups = ['all', 'name:%s' % name, 'path:%s' % relpath]
683 groups.extend(set(default_groups).difference(groups))
598 684
599 project = Project(manifest = self, 685 project = Project(manifest = self,
600 name = name, 686 name = name,
601 remote = remote.ToRemoteSpec(name), 687 remote = remote.ToRemoteSpec(name),
602 gitdir = gitdir, 688 gitdir = gitdir,
603 worktree = worktree, 689 worktree = worktree,
604 relpath = path, 690 relpath = relpath,
605 revisionExpr = revisionExpr, 691 revisionExpr = revisionExpr,
606 revisionId = None, 692 revisionId = None,
607 rebase = rebase, 693 rebase = rebase,
608 groups = groups, 694 groups = groups,
609 sync_c = sync_c, 695 sync_c = sync_c,
610 upstream = upstream) 696 sync_s = sync_s,
697 upstream = upstream,
698 parent = parent)
611 699
612 for n in node.childNodes: 700 for n in node.childNodes:
613 if n.nodeName == 'copyfile': 701 if n.nodeName == 'copyfile':
614 self._ParseCopyFile(project, n) 702 self._ParseCopyFile(project, n)
615 if n.nodeName == 'annotation': 703 if n.nodeName == 'annotation':
616 self._ParseAnnotation(project, n) 704 self._ParseAnnotation(project, n)
705 if n.nodeName == 'project':
706 project.subprojects.append(self._ParseProject(n, parent = project))
617 707
618 return project 708 return project
619 709
710 def GetProjectPaths(self, name, path):
711 relpath = path
712 if self.IsMirror:
713 worktree = None
714 gitdir = os.path.join(self.topdir, '%s.git' % name)
715 else:
716 worktree = os.path.join(self.topdir, path).replace('\\', '/')
717 gitdir = os.path.join(self.repodir, 'projects', '%s.git' % path)
718 return relpath, worktree, gitdir
719
720 def GetSubprojectName(self, parent, submodule_path):
721 return os.path.join(parent.name, submodule_path)
722
723 def _JoinRelpath(self, parent_relpath, relpath):
724 return os.path.join(parent_relpath, relpath)
725
726 def _UnjoinRelpath(self, parent_relpath, relpath):
727 return os.path.relpath(relpath, parent_relpath)
728
729 def GetSubprojectPaths(self, parent, path):
730 relpath = self._JoinRelpath(parent.relpath, path)
731 gitdir = os.path.join(parent.gitdir, 'subprojects', '%s.git' % path)
732 if self.IsMirror:
733 worktree = None
734 else:
735 worktree = os.path.join(parent.worktree, path).replace('\\', '/')
736 return relpath, worktree, gitdir
737
620 def _ParseCopyFile(self, project, node): 738 def _ParseCopyFile(self, project, node):
621 src = self._reqatt(node, 'src') 739 src = self._reqatt(node, 'src')
622 dest = self._reqatt(node, 'dest') 740 dest = self._reqatt(node, 'dest')