blob: ea68b682417caf9fa9c5bbadb437f320cf61d7be [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')
209
210 r = Remote(name=name,
211 fetch=fetch,
212 review=review)
213
214 for n in node.childNodes:
215 if n.nodeName == 'require':
216 r.requiredCommits.append(self._reqatt(n, 'commit'))
217
218 return r
219
220 def _ParseDefault(self, node):
221 """
222 reads a <default> element from the manifest file
223 """
224 d = _Default()
225 d.remote = self._get_remote(node)
226 d.revision = node.getAttribute('revision')
227 return d
228
229 def _ParseProject(self, node):
230 """
231 reads a <project> element from the manifest file
232 """
233 name = self._reqatt(node, 'name')
234
235 remote = self._get_remote(node)
236 if remote is None:
237 remote = self._default.remote
238 if remote is None:
239 raise ManifestParseError, \
240 "no remote for project %s within %s" % \
241 (name, self.manifestFile)
242
243 revision = node.getAttribute('revision')
244 if not revision:
245 revision = self._default.revision
246 if not revision:
247 raise ManifestParseError, \
248 "no revision for project %s within %s" % \
249 (name, self.manifestFile)
250
251 path = node.getAttribute('path')
252 if not path:
253 path = name
254 if path.startswith('/'):
255 raise ManifestParseError, \
256 "project %s path cannot be absolute in %s" % \
257 (name, self.manifestFile)
258
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800259 if self.IsMirror:
260 relpath = None
261 worktree = None
262 gitdir = os.path.join(self.topdir, '%s.git' % name)
263 else:
264 worktree = os.path.join(self.topdir, path)
265 gitdir = os.path.join(self.repodir, 'projects/%s.git' % path)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700266
267 project = Project(manifest = self,
268 name = name,
269 remote = remote,
270 gitdir = gitdir,
271 worktree = worktree,
272 relpath = path,
273 revision = revision)
274
275 for n in node.childNodes:
276 if n.nodeName == 'remote':
277 r = self._ParseRemote(n)
278 if project.extraRemotes.get(r.name) \
279 or project.remote.name == r.name:
280 raise ManifestParseError, \
281 'duplicate remote %s in project %s in %s' % \
282 (r.name, project.name, self.manifestFile)
283 project.extraRemotes[r.name] = r
284 elif n.nodeName == 'copyfile':
285 self._ParseCopyFile(project, n)
286
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700287 return project
288
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700289 def _ParseCopyFile(self, project, node):
290 src = self._reqatt(node, 'src')
291 dest = self._reqatt(node, 'dest')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800292 if not self.IsMirror:
293 # src is project relative;
294 # dest is relative to the top of the tree
295 project.AddCopyFile(src, os.path.join(self.topdir, dest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700296
297 def _get_remote(self, node):
298 name = node.getAttribute('remote')
299 if not name:
300 return None
301
302 v = self._remotes.get(name)
303 if not v:
304 raise ManifestParseError, \
305 "remote %s not defined in %s" % \
306 (name, self.manifestFile)
307 return v
308
309 def _reqatt(self, node, attname):
310 """
311 reads a required attribute from the node.
312 """
313 v = node.getAttribute(attname)
314 if not v:
315 raise ManifestParseError, \
316 "no %s in <%s> within %s" % \
317 (attname, node.nodeName, self.manifestFile)
318 return v