blob: 3d5e092c000ba04730d22f304cc3105e835efaa0 [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
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070020from git_config import GitConfig, IsId
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070021from project import Project, MetaProject, R_TAGS
22from remote import Remote
23from error import ManifestParseError
24
25MANIFEST_FILE_NAME = 'manifest.xml'
Shawn O. Pearce5cc66792008-10-23 16:19:27 -070026LOCAL_MANIFEST_NAME = 'local_manifest.xml'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070027
28class _Default(object):
29 """Project defaults within the manifest."""
30
31 revision = None
32 remote = None
33
34
35class Manifest(object):
36 """manages the repo configuration file"""
37
38 def __init__(self, repodir):
39 self.repodir = os.path.abspath(repodir)
40 self.topdir = os.path.dirname(self.repodir)
41 self.manifestFile = os.path.join(self.repodir, MANIFEST_FILE_NAME)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070042 self.globalConfig = GitConfig.ForUser()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070043
44 self.repoProject = MetaProject(self, 'repo',
45 gitdir = os.path.join(repodir, 'repo/.git'),
46 worktree = os.path.join(repodir, 'repo'))
47
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070048 self.manifestProject = MetaProject(self, 'manifests',
Shawn O. Pearcef5c25a62008-11-04 08:11:53 -080049 gitdir = os.path.join(repodir, 'manifests.git'),
50 worktree = os.path.join(repodir, 'manifests'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070051
52 self._Unload()
53
54 def Link(self, name):
55 """Update the repo metadata to use a different manifest.
56 """
57 path = os.path.join(self.manifestProject.worktree, name)
58 if not os.path.isfile(path):
59 raise ManifestParseError('manifest %s not found' % name)
60
61 old = self.manifestFile
62 try:
63 self.manifestFile = path
64 self._Unload()
65 self._Load()
66 finally:
67 self.manifestFile = old
68
69 try:
70 if os.path.exists(self.manifestFile):
71 os.remove(self.manifestFile)
72 os.symlink('manifests/%s' % name, self.manifestFile)
73 except OSError, e:
74 raise ManifestParseError('cannot link manifest %s' % name)
75
76 @property
77 def projects(self):
78 self._Load()
79 return self._projects
80
81 @property
82 def remotes(self):
83 self._Load()
84 return self._remotes
85
86 @property
87 def default(self):
88 self._Load()
89 return self._default
90
91 def _Unload(self):
92 self._loaded = False
93 self._projects = {}
94 self._remotes = {}
95 self._default = None
96 self.branch = None
97
98 def _Load(self):
99 if not self._loaded:
Shawn O. Pearce5cc66792008-10-23 16:19:27 -0700100 self._ParseManifest(True)
101
102 local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME)
103 if os.path.exists(local):
104 try:
105 real = self.manifestFile
106 self.manifestFile = local
107 self._ParseManifest(False)
108 finally:
109 self.manifestFile = real
110
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700111 self._loaded = True
112
Shawn O. Pearce5cc66792008-10-23 16:19:27 -0700113 def _ParseManifest(self, is_root_file):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700114 root = xml.dom.minidom.parse(self.manifestFile)
115 if not root or not root.childNodes:
116 raise ManifestParseError, \
117 "no root node in %s" % \
118 self.manifestFile
119
120 config = root.childNodes[0]
121 if config.nodeName != 'manifest':
122 raise ManifestParseError, \
123 "no <manifest> in %s" % \
124 self.manifestFile
125
Shawn O. Pearce5cc66792008-10-23 16:19:27 -0700126 if is_root_file:
127 self.branch = config.getAttribute('branch')
128 if not self.branch:
129 self.branch = 'default'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700130
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
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700239 return project
240
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241 def _ParseCopyFile(self, project, node):
242 src = self._reqatt(node, 'src')
243 dest = self._reqatt(node, 'dest')
244 # src is project relative, and dest is relative to the top of the tree
245 project.AddCopyFile(src, os.path.join(self.topdir, dest))
246
247 def _get_remote(self, node):
248 name = node.getAttribute('remote')
249 if not name:
250 return None
251
252 v = self._remotes.get(name)
253 if not v:
254 raise ManifestParseError, \
255 "remote %s not defined in %s" % \
256 (name, self.manifestFile)
257 return v
258
259 def _reqatt(self, node, attname):
260 """
261 reads a required attribute from the node.
262 """
263 v = node.getAttribute(attname)
264 if not v:
265 raise ManifestParseError, \
266 "no %s in <%s> within %s" % \
267 (attname, node.nodeName, self.manifestFile)
268 return v