diff options
| -rw-r--r-- | docs/manifest-format.txt | 25 | ||||
| -rw-r--r-- | manifest_xml.py | 32 | ||||
| -rw-r--r-- | subcmds/sync.py | 52 |
3 files changed, 106 insertions, 3 deletions
diff --git a/docs/manifest-format.txt b/docs/manifest-format.txt index da0e69ff..211344ee 100644 --- a/docs/manifest-format.txt +++ b/docs/manifest-format.txt | |||
| @@ -22,6 +22,7 @@ following DTD: | |||
| 22 | <!DOCTYPE manifest [ | 22 | <!DOCTYPE manifest [ |
| 23 | <!ELEMENT manifest (remote*, | 23 | <!ELEMENT manifest (remote*, |
| 24 | default?, | 24 | default?, |
| 25 | manifest-server?, | ||
| 25 | remove-project*, | 26 | remove-project*, |
| 26 | project*)> | 27 | project*)> |
| 27 | 28 | ||
| @@ -33,6 +34,9 @@ following DTD: | |||
| 33 | <!ELEMENT default (EMPTY)> | 34 | <!ELEMENT default (EMPTY)> |
| 34 | <!ATTLIST default remote IDREF #IMPLIED> | 35 | <!ATTLIST default remote IDREF #IMPLIED> |
| 35 | <!ATTLIST default revision CDATA #IMPLIED> | 36 | <!ATTLIST default revision CDATA #IMPLIED> |
| 37 | |||
| 38 | <!ELEMENT manifest-server (EMPTY)> | ||
| 39 | <!ATTLIST url CDATA #REQUIRED> | ||
| 36 | 40 | ||
| 37 | <!ELEMENT project (EMPTY)> | 41 | <!ELEMENT project (EMPTY)> |
| 38 | <!ATTLIST project name CDATA #REQUIRED> | 42 | <!ATTLIST project name CDATA #REQUIRED> |
| @@ -89,6 +93,27 @@ Attribute `revision`: Name of a Git branch (e.g. `master` or | |||
| 89 | revision attribute will use this revision. | 93 | revision attribute will use this revision. |
| 90 | 94 | ||
| 91 | 95 | ||
| 96 | Element manifest-server | ||
| 97 | ----------------------- | ||
| 98 | |||
| 99 | At most one manifest-server may be specified. The url attribute | ||
| 100 | is used to specify the URL of a manifest server, which is an | ||
| 101 | XML RPC service that will return a manifest in which each project | ||
| 102 | is pegged to a known good revision for the current branch and | ||
| 103 | target. | ||
| 104 | |||
| 105 | The manifest server should implement: | ||
| 106 | |||
| 107 | GetApprovedManifest(branch, target) | ||
| 108 | |||
| 109 | The target to use is defined by environment variables TARGET_PRODUCT | ||
| 110 | and TARGET_BUILD_VARIANT. These variables are used to create a string | ||
| 111 | of the form $TARGET_PRODUCT-$TARGET_BUILD_VARIANT, e.g. passion-userdebug. | ||
| 112 | If one of those variables or both are not present, the program will call | ||
| 113 | GetApprovedManifest without the target paramater and the manifest server | ||
| 114 | should choose a reasonable default target. | ||
| 115 | |||
| 116 | |||
| 92 | Element project | 117 | Element project |
| 93 | --------------- | 118 | --------------- |
| 94 | 119 | ||
diff --git a/manifest_xml.py b/manifest_xml.py index 7d02f9d6..d0c9debe 100644 --- a/manifest_xml.py +++ b/manifest_xml.py | |||
| @@ -65,8 +65,8 @@ class XmlManifest(object): | |||
| 65 | 65 | ||
| 66 | self._Unload() | 66 | self._Unload() |
| 67 | 67 | ||
| 68 | def Link(self, name): | 68 | def Override(self, name): |
| 69 | """Update the repo metadata to use a different manifest. | 69 | """Use a different manifest, just for the current instantiation. |
| 70 | """ | 70 | """ |
| 71 | path = os.path.join(self.manifestProject.worktree, name) | 71 | path = os.path.join(self.manifestProject.worktree, name) |
| 72 | if not os.path.isfile(path): | 72 | if not os.path.isfile(path): |
| @@ -80,6 +80,11 @@ class XmlManifest(object): | |||
| 80 | finally: | 80 | finally: |
| 81 | self.manifestFile = old | 81 | self.manifestFile = old |
| 82 | 82 | ||
| 83 | def Link(self, name): | ||
| 84 | """Update the repo metadata to use a different manifest. | ||
| 85 | """ | ||
| 86 | self.Override(name) | ||
| 87 | |||
| 83 | try: | 88 | try: |
| 84 | if os.path.exists(self.manifestFile): | 89 | if os.path.exists(self.manifestFile): |
| 85 | os.remove(self.manifestFile) | 90 | os.remove(self.manifestFile) |
| @@ -123,6 +128,12 @@ class XmlManifest(object): | |||
| 123 | root.appendChild(e) | 128 | root.appendChild(e) |
| 124 | root.appendChild(doc.createTextNode('')) | 129 | root.appendChild(doc.createTextNode('')) |
| 125 | 130 | ||
| 131 | if self._manifest_server: | ||
| 132 | e = doc.createElement('manifest-server') | ||
| 133 | e.setAttribute('url', self._manifest_server) | ||
| 134 | root.appendChild(e) | ||
| 135 | root.appendChild(doc.createTextNode('')) | ||
| 136 | |||
| 126 | sort_projects = list(self.projects.keys()) | 137 | sort_projects = list(self.projects.keys()) |
| 127 | sort_projects.sort() | 138 | sort_projects.sort() |
| 128 | 139 | ||
| @@ -169,6 +180,11 @@ class XmlManifest(object): | |||
| 169 | return self._default | 180 | return self._default |
| 170 | 181 | ||
| 171 | @property | 182 | @property |
| 183 | def manifest_server(self): | ||
| 184 | self._Load() | ||
| 185 | return self._manifest_server | ||
| 186 | |||
| 187 | @property | ||
| 172 | def IsMirror(self): | 188 | def IsMirror(self): |
| 173 | return self.manifestProject.config.GetBoolean('repo.mirror') | 189 | return self.manifestProject.config.GetBoolean('repo.mirror') |
| 174 | 190 | ||
| @@ -178,6 +194,7 @@ class XmlManifest(object): | |||
| 178 | self._remotes = {} | 194 | self._remotes = {} |
| 179 | self._default = None | 195 | self._default = None |
| 180 | self.branch = None | 196 | self.branch = None |
| 197 | self._manifest_server = None | ||
| 181 | 198 | ||
| 182 | def _Load(self): | 199 | def _Load(self): |
| 183 | if not self._loaded: | 200 | if not self._loaded: |
| @@ -247,6 +264,15 @@ class XmlManifest(object): | |||
| 247 | self._default = _Default() | 264 | self._default = _Default() |
| 248 | 265 | ||
| 249 | for node in config.childNodes: | 266 | for node in config.childNodes: |
| 267 | if node.nodeName == 'manifest-server': | ||
| 268 | url = self._reqatt(node, 'url') | ||
| 269 | if self._manifest_server is not None: | ||
| 270 | raise ManifestParseError, \ | ||
| 271 | 'duplicate manifest-server in %s' % \ | ||
| 272 | (self.manifestFile) | ||
| 273 | self._manifest_server = url | ||
| 274 | |||
| 275 | for node in config.childNodes: | ||
| 250 | if node.nodeName == 'project': | 276 | if node.nodeName == 'project': |
| 251 | project = self._ParseProject(node) | 277 | project = self._ParseProject(node) |
| 252 | if self._projects.get(project.name): | 278 | if self._projects.get(project.name): |
| @@ -315,7 +341,7 @@ class XmlManifest(object): | |||
| 315 | def _ParseProject(self, node): | 341 | def _ParseProject(self, node): |
| 316 | """ | 342 | """ |
| 317 | reads a <project> element from the manifest file | 343 | reads a <project> element from the manifest file |
| 318 | """ | 344 | """ |
| 319 | name = self._reqatt(node, 'name') | 345 | name = self._reqatt(node, 'name') |
| 320 | 346 | ||
| 321 | remote = self._get_remote(node) | 347 | remote = self._get_remote(node) |
diff --git a/subcmds/sync.py b/subcmds/sync.py index ceb81eaa..deff171a 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py | |||
| @@ -17,9 +17,11 @@ from optparse import SUPPRESS_HELP | |||
| 17 | import os | 17 | import os |
| 18 | import re | 18 | import re |
| 19 | import shutil | 19 | import shutil |
| 20 | import socket | ||
| 20 | import subprocess | 21 | import subprocess |
| 21 | import sys | 22 | import sys |
| 22 | import time | 23 | import time |
| 24 | import xmlrpclib | ||
| 23 | 25 | ||
| 24 | from git_command import GIT | 26 | from git_command import GIT |
| 25 | from project import HEAD | 27 | from project import HEAD |
| @@ -57,6 +59,10 @@ back to the manifest revision. This option is especially helpful | |||
| 57 | if the project is currently on a topic branch, but the manifest | 59 | if the project is currently on a topic branch, but the manifest |
| 58 | revision is temporarily needed. | 60 | revision is temporarily needed. |
| 59 | 61 | ||
| 62 | The -s/--smart-sync option can be used to sync to a known good | ||
| 63 | build as specified by the manifest-server element in the current | ||
| 64 | manifest. | ||
| 65 | |||
| 60 | SSH Connections | 66 | SSH Connections |
| 61 | --------------- | 67 | --------------- |
| 62 | 68 | ||
| @@ -97,6 +103,9 @@ later is required to fix a server side protocol bug. | |||
| 97 | p.add_option('-d','--detach', | 103 | p.add_option('-d','--detach', |
| 98 | dest='detach_head', action='store_true', | 104 | dest='detach_head', action='store_true', |
| 99 | help='detach projects back to manifest revision') | 105 | help='detach projects back to manifest revision') |
| 106 | p.add_option('-s', '--smart-sync', | ||
| 107 | dest='smart_sync', action='store_true', | ||
| 108 | help='smart sync using manifest from a known good build') | ||
| 100 | 109 | ||
| 101 | g = p.add_option_group('repo Version options') | 110 | g = p.add_option_group('repo Version options') |
| 102 | g.add_option('--no-repo-verify', | 111 | g.add_option('--no-repo-verify', |
| @@ -182,6 +191,49 @@ uncommitted changes are present' % project.relpath | |||
| 182 | print >>sys.stderr, 'error: cannot combine -n and -l' | 191 | print >>sys.stderr, 'error: cannot combine -n and -l' |
| 183 | sys.exit(1) | 192 | sys.exit(1) |
| 184 | 193 | ||
| 194 | if opt.smart_sync: | ||
| 195 | if not self.manifest.manifest_server: | ||
| 196 | print >>sys.stderr, \ | ||
| 197 | 'error: cannot smart sync: no manifest server defined in manifest' | ||
| 198 | sys.exit(1) | ||
| 199 | try: | ||
| 200 | server = xmlrpclib.Server(self.manifest.manifest_server) | ||
| 201 | p = self.manifest.manifestProject | ||
| 202 | b = p.GetBranch(p.CurrentBranch) | ||
| 203 | branch = b.merge | ||
| 204 | |||
| 205 | env = dict(os.environ) | ||
| 206 | if (env.has_key('TARGET_PRODUCT') and | ||
| 207 | env.has_key('TARGET_BUILD_VARIANT')): | ||
| 208 | target = '%s-%s' % (env['TARGET_PRODUCT'], | ||
| 209 | env['TARGET_BUILD_VARIANT']) | ||
| 210 | [success, manifest_str] = server.GetApprovedManifest(branch, target) | ||
| 211 | else: | ||
| 212 | [success, manifest_str] = server.GetApprovedManifest(branch) | ||
| 213 | |||
| 214 | if success: | ||
| 215 | manifest_name = "smart_sync_override.xml" | ||
| 216 | manifest_path = os.path.join(self.manifest.manifestProject.worktree, | ||
| 217 | manifest_name) | ||
| 218 | try: | ||
| 219 | f = open(manifest_path, 'w') | ||
| 220 | try: | ||
| 221 | f.write(manifest_str) | ||
| 222 | self.manifest.Override(manifest_name) | ||
| 223 | finally: | ||
| 224 | f.close() | ||
| 225 | except IOError: | ||
| 226 | print >>sys.stderr, 'error: cannot write manifest to %s' % \ | ||
| 227 | manifest_path | ||
| 228 | sys.exit(1) | ||
| 229 | else: | ||
| 230 | print >>sys.stderr, 'error: %s' % manifest_str | ||
| 231 | sys.exit(1) | ||
| 232 | except socket.error: | ||
| 233 | print >>sys.stderr, 'error: cannot connect to manifest server %s' % ( | ||
| 234 | self.manifest.manifest_server) | ||
| 235 | sys.exit(1) | ||
| 236 | |||
| 185 | rp = self.manifest.repoProject | 237 | rp = self.manifest.repoProject |
| 186 | rp.PreSync() | 238 | rp.PreSync() |
| 187 | 239 | ||
