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