blob: 65455687d9300b8c49aad2aaaca38bf72aeea4f0 [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
Shawn O. Pearce2450a292008-11-04 08:22:07 -080021from project import Project, MetaProject, R_HEADS
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070022from 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
Shawn O. Pearcee284ad12008-11-04 07:37:10 -080091 @property
92 def IsMirror(self):
93 return self.manifestProject.config.GetBoolean('repo.mirror')
94
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070095 def _Unload(self):
96 self._loaded = False
97 self._projects = {}
98 self._remotes = {}
99 self._default = None
100 self.branch = None
101
102 def _Load(self):
103 if not self._loaded:
Shawn O. Pearce2450a292008-11-04 08:22:07 -0800104 m = self.manifestProject
105 b = m.GetBranch(m.CurrentBranch).merge
106 if b.startswith(R_HEADS):
107 b = b[len(R_HEADS):]
108 self.branch = b
109
Shawn O. Pearce5cc66792008-10-23 16:19:27 -0700110 self._ParseManifest(True)
111
112 local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME)
113 if os.path.exists(local):
114 try:
115 real = self.manifestFile
116 self.manifestFile = local
117 self._ParseManifest(False)
118 finally:
119 self.manifestFile = real
120
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800121 if self.IsMirror:
122 self._AddMetaProjectMirror(self.repoProject)
123 self._AddMetaProjectMirror(self.manifestProject)
124
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700125 self._loaded = True
126
Shawn O. Pearce5cc66792008-10-23 16:19:27 -0700127 def _ParseManifest(self, is_root_file):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700128 root = xml.dom.minidom.parse(self.manifestFile)
129 if not root or not root.childNodes:
130 raise ManifestParseError, \
131 "no root node in %s" % \
132 self.manifestFile
133
134 config = root.childNodes[0]
135 if config.nodeName != 'manifest':
136 raise ManifestParseError, \
137 "no <manifest> in %s" % \
138 self.manifestFile
139
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700140 for node in config.childNodes:
141 if node.nodeName == 'remote':
142 remote = self._ParseRemote(node)
143 if self._remotes.get(remote.name):
144 raise ManifestParseError, \
145 'duplicate remote %s in %s' % \
146 (remote.name, self.manifestFile)
147 self._remotes[remote.name] = remote
148
149 for node in config.childNodes:
150 if node.nodeName == 'default':
151 if self._default is not None:
152 raise ManifestParseError, \
153 'duplicate default in %s' % \
154 (self.manifestFile)
155 self._default = self._ParseDefault(node)
156 if self._default is None:
157 self._default = _Default()
158
159 for node in config.childNodes:
160 if node.nodeName == 'project':
161 project = self._ParseProject(node)
162 if self._projects.get(project.name):
163 raise ManifestParseError, \
164 'duplicate project %s in %s' % \
165 (project.name, self.manifestFile)
166 self._projects[project.name] = project
167
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800168 def _AddMetaProjectMirror(self, m):
169 name = None
170 m_url = m.GetRemote(m.remote.name).url
171 if m_url.endswith('/.git'):
172 raise ManifestParseError, 'refusing to mirror %s' % m_url
173
174 if self._default and self._default.remote:
175 url = self._default.remote.fetchUrl
176 if not url.endswith('/'):
177 url += '/'
178 if m_url.startswith(url):
179 remote = self._default.remote
180 name = m_url[len(url):]
181
182 if name is None:
183 s = m_url.rindex('/') + 1
184 remote = Remote('origin', fetch = m_url[:s])
185 name = m_url[s:]
186
187 if name.endswith('.git'):
188 name = name[:-4]
189
190 if name not in self._projects:
191 m.PreSync()
192 gitdir = os.path.join(self.topdir, '%s.git' % name)
193 project = Project(manifest = self,
194 name = name,
195 remote = remote,
196 gitdir = gitdir,
197 worktree = None,
198 relpath = None,
199 revision = m.revision)
200 self._projects[project.name] = project
201
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700202 def _ParseRemote(self, node):
203 """
204 reads a <remote> element from the manifest file
205 """
206 name = self._reqatt(node, 'name')
207 fetch = self._reqatt(node, 'fetch')
208 review = node.getAttribute('review')
Shawn O. Pearceae6e0942008-11-06 10:25:35 -0800209 if review == '':
210 review = None
211
212 projectName = node.getAttribute('project-name')
213 if projectName == '':
214 projectName = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700215
216 r = Remote(name=name,
217 fetch=fetch,
Shawn O. Pearceae6e0942008-11-06 10:25:35 -0800218 review=review,
219 projectName=projectName)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700220
221 for n in node.childNodes:
222 if n.nodeName == 'require':
223 r.requiredCommits.append(self._reqatt(n, 'commit'))
224
225 return r
226
227 def _ParseDefault(self, node):
228 """
229 reads a <default> element from the manifest file
230 """
231 d = _Default()
232 d.remote = self._get_remote(node)
233 d.revision = node.getAttribute('revision')
234 return d
235
236 def _ParseProject(self, node):
237 """
238 reads a <project> element from the manifest file
239 """
240 name = self._reqatt(node, 'name')
241
242 remote = self._get_remote(node)
243 if remote is None:
244 remote = self._default.remote
245 if remote is None:
246 raise ManifestParseError, \
247 "no remote for project %s within %s" % \
248 (name, self.manifestFile)
249
250 revision = node.getAttribute('revision')
251 if not revision:
252 revision = self._default.revision
253 if not revision:
254 raise ManifestParseError, \
255 "no revision for project %s within %s" % \
256 (name, self.manifestFile)
257
258 path = node.getAttribute('path')
259 if not path:
260 path = name
261 if path.startswith('/'):
262 raise ManifestParseError, \
263 "project %s path cannot be absolute in %s" % \
264 (name, self.manifestFile)
265
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800266 if self.IsMirror:
267 relpath = None
268 worktree = None
269 gitdir = os.path.join(self.topdir, '%s.git' % name)
270 else:
271 worktree = os.path.join(self.topdir, path)
272 gitdir = os.path.join(self.repodir, 'projects/%s.git' % path)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700273
274 project = Project(manifest = self,
275 name = name,
276 remote = remote,
277 gitdir = gitdir,
278 worktree = worktree,
279 relpath = path,
280 revision = revision)
281
282 for n in node.childNodes:
283 if n.nodeName == 'remote':
284 r = self._ParseRemote(n)
285 if project.extraRemotes.get(r.name) \
286 or project.remote.name == r.name:
287 raise ManifestParseError, \
288 'duplicate remote %s in project %s in %s' % \
289 (r.name, project.name, self.manifestFile)
290 project.extraRemotes[r.name] = r
291 elif n.nodeName == 'copyfile':
292 self._ParseCopyFile(project, n)
293
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700294 return project
295
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700296 def _ParseCopyFile(self, project, node):
297 src = self._reqatt(node, 'src')
298 dest = self._reqatt(node, 'dest')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800299 if not self.IsMirror:
300 # src is project relative;
301 # dest is relative to the top of the tree
302 project.AddCopyFile(src, os.path.join(self.topdir, dest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700303
304 def _get_remote(self, node):
305 name = node.getAttribute('remote')
306 if not name:
307 return None
308
309 v = self._remotes.get(name)
310 if not v:
311 raise ManifestParseError, \
312 "remote %s not defined in %s" % \
313 (name, self.manifestFile)
314 return v
315
316 def _reqatt(self, node, attname):
317 """
318 reads a required attribute from the node.
319 """
320 v = node.getAttribute(attname)
321 if not v:
322 raise ManifestParseError, \
323 "no %s in <%s> within %s" % \
324 (attname, node.nodeName, self.manifestFile)
325 return v