blob: ffff14a5bdf1a43f9df2fb1269f2832cd76f18f0 [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
48 wt = os.path.join(repodir, 'manifests')
49 gd_new = os.path.join(repodir, 'manifests.git')
50 gd_old = os.path.join(wt, '.git')
51 if os.path.exists(gd_new) or not os.path.exists(gd_old):
52 gd = gd_new
53 else:
54 gd = gd_old
55 self.manifestProject = MetaProject(self, 'manifests',
56 gitdir = gd,
57 worktree = wt)
58
59 self._Unload()
60
61 def Link(self, name):
62 """Update the repo metadata to use a different manifest.
63 """
64 path = os.path.join(self.manifestProject.worktree, name)
65 if not os.path.isfile(path):
66 raise ManifestParseError('manifest %s not found' % name)
67
68 old = self.manifestFile
69 try:
70 self.manifestFile = path
71 self._Unload()
72 self._Load()
73 finally:
74 self.manifestFile = old
75
76 try:
77 if os.path.exists(self.manifestFile):
78 os.remove(self.manifestFile)
79 os.symlink('manifests/%s' % name, self.manifestFile)
80 except OSError, e:
81 raise ManifestParseError('cannot link manifest %s' % name)
82
83 @property
84 def projects(self):
85 self._Load()
86 return self._projects
87
88 @property
89 def remotes(self):
90 self._Load()
91 return self._remotes
92
93 @property
94 def default(self):
95 self._Load()
96 return self._default
97
98 def _Unload(self):
99 self._loaded = False
100 self._projects = {}
101 self._remotes = {}
102 self._default = None
103 self.branch = None
104
105 def _Load(self):
106 if not self._loaded:
Shawn O. Pearce5cc66792008-10-23 16:19:27 -0700107 self._ParseManifest(True)
108
109 local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME)
110 if os.path.exists(local):
111 try:
112 real = self.manifestFile
113 self.manifestFile = local
114 self._ParseManifest(False)
115 finally:
116 self.manifestFile = real
117
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700118 self._loaded = True
119
Shawn O. Pearce5cc66792008-10-23 16:19:27 -0700120 def _ParseManifest(self, is_root_file):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700121 root = xml.dom.minidom.parse(self.manifestFile)
122 if not root or not root.childNodes:
123 raise ManifestParseError, \
124 "no root node in %s" % \
125 self.manifestFile
126
127 config = root.childNodes[0]
128 if config.nodeName != 'manifest':
129 raise ManifestParseError, \
130 "no <manifest> in %s" % \
131 self.manifestFile
132
Shawn O. Pearce5cc66792008-10-23 16:19:27 -0700133 if is_root_file:
134 self.branch = config.getAttribute('branch')
135 if not self.branch:
136 self.branch = 'default'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700137
138 for node in config.childNodes:
139 if node.nodeName == 'remote':
140 remote = self._ParseRemote(node)
141 if self._remotes.get(remote.name):
142 raise ManifestParseError, \
143 'duplicate remote %s in %s' % \
144 (remote.name, self.manifestFile)
145 self._remotes[remote.name] = remote
146
147 for node in config.childNodes:
148 if node.nodeName == 'default':
149 if self._default is not None:
150 raise ManifestParseError, \
151 'duplicate default in %s' % \
152 (self.manifestFile)
153 self._default = self._ParseDefault(node)
154 if self._default is None:
155 self._default = _Default()
156
157 for node in config.childNodes:
158 if node.nodeName == 'project':
159 project = self._ParseProject(node)
160 if self._projects.get(project.name):
161 raise ManifestParseError, \
162 'duplicate project %s in %s' % \
163 (project.name, self.manifestFile)
164 self._projects[project.name] = project
165
166 def _ParseRemote(self, node):
167 """
168 reads a <remote> element from the manifest file
169 """
170 name = self._reqatt(node, 'name')
171 fetch = self._reqatt(node, 'fetch')
172 review = node.getAttribute('review')
173
174 r = Remote(name=name,
175 fetch=fetch,
176 review=review)
177
178 for n in node.childNodes:
179 if n.nodeName == 'require':
180 r.requiredCommits.append(self._reqatt(n, 'commit'))
181
182 return r
183
184 def _ParseDefault(self, node):
185 """
186 reads a <default> element from the manifest file
187 """
188 d = _Default()
189 d.remote = self._get_remote(node)
190 d.revision = node.getAttribute('revision')
191 return d
192
193 def _ParseProject(self, node):
194 """
195 reads a <project> element from the manifest file
196 """
197 name = self._reqatt(node, 'name')
198
199 remote = self._get_remote(node)
200 if remote is None:
201 remote = self._default.remote
202 if remote is None:
203 raise ManifestParseError, \
204 "no remote for project %s within %s" % \
205 (name, self.manifestFile)
206
207 revision = node.getAttribute('revision')
208 if not revision:
209 revision = self._default.revision
210 if not revision:
211 raise ManifestParseError, \
212 "no revision for project %s within %s" % \
213 (name, self.manifestFile)
214
215 path = node.getAttribute('path')
216 if not path:
217 path = name
218 if path.startswith('/'):
219 raise ManifestParseError, \
220 "project %s path cannot be absolute in %s" % \
221 (name, self.manifestFile)
222
223 worktree = os.path.join(self.topdir, path)
224 gitdir = os.path.join(self.repodir, 'projects/%s.git' % path)
225
226 project = Project(manifest = self,
227 name = name,
228 remote = remote,
229 gitdir = gitdir,
230 worktree = worktree,
231 relpath = path,
232 revision = revision)
233
234 for n in node.childNodes:
235 if n.nodeName == 'remote':
236 r = self._ParseRemote(n)
237 if project.extraRemotes.get(r.name) \
238 or project.remote.name == r.name:
239 raise ManifestParseError, \
240 'duplicate remote %s in project %s in %s' % \
241 (r.name, project.name, self.manifestFile)
242 project.extraRemotes[r.name] = r
243 elif n.nodeName == 'copyfile':
244 self._ParseCopyFile(project, n)
245
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700246 return project
247
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700248 def _ParseCopyFile(self, project, node):
249 src = self._reqatt(node, 'src')
250 dest = self._reqatt(node, 'dest')
251 # src is project relative, and dest is relative to the top of the tree
252 project.AddCopyFile(src, os.path.join(self.topdir, dest))
253
254 def _get_remote(self, node):
255 name = node.getAttribute('remote')
256 if not name:
257 return None
258
259 v = self._remotes.get(name)
260 if not v:
261 raise ManifestParseError, \
262 "remote %s not defined in %s" % \
263 (name, self.manifestFile)
264 return v
265
266 def _reqatt(self, node, attname):
267 """
268 reads a required attribute from the node.
269 """
270 v = node.getAttribute(attname)
271 if not v:
272 raise ManifestParseError, \
273 "no %s in <%s> within %s" % \
274 (attname, node.nodeName, self.manifestFile)
275 return v