blob: b928cdfee88f18460bf0b1ead8ec7e3994d6a73c [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
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. Pearce2450a292008-11-04 08:22:07 -0800100 m = self.manifestProject
101 b = m.GetBranch(m.CurrentBranch).merge
102 if b.startswith(R_HEADS):
103 b = b[len(R_HEADS):]
104 self.branch = b
105
Shawn O. Pearce5cc66792008-10-23 16:19:27 -0700106 self._ParseManifest(True)
107
108 local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME)
109 if os.path.exists(local):
110 try:
111 real = self.manifestFile
112 self.manifestFile = local
113 self._ParseManifest(False)
114 finally:
115 self.manifestFile = real
116
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700117 self._loaded = True
118
Shawn O. Pearce5cc66792008-10-23 16:19:27 -0700119 def _ParseManifest(self, is_root_file):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700120 root = xml.dom.minidom.parse(self.manifestFile)
121 if not root or not root.childNodes:
122 raise ManifestParseError, \
123 "no root node in %s" % \
124 self.manifestFile
125
126 config = root.childNodes[0]
127 if config.nodeName != 'manifest':
128 raise ManifestParseError, \
129 "no <manifest> in %s" % \
130 self.manifestFile
131
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700132 for node in config.childNodes:
133 if node.nodeName == 'remote':
134 remote = self._ParseRemote(node)
135 if self._remotes.get(remote.name):
136 raise ManifestParseError, \
137 'duplicate remote %s in %s' % \
138 (remote.name, self.manifestFile)
139 self._remotes[remote.name] = remote
140
141 for node in config.childNodes:
142 if node.nodeName == 'default':
143 if self._default is not None:
144 raise ManifestParseError, \
145 'duplicate default in %s' % \
146 (self.manifestFile)
147 self._default = self._ParseDefault(node)
148 if self._default is None:
149 self._default = _Default()
150
151 for node in config.childNodes:
152 if node.nodeName == 'project':
153 project = self._ParseProject(node)
154 if self._projects.get(project.name):
155 raise ManifestParseError, \
156 'duplicate project %s in %s' % \
157 (project.name, self.manifestFile)
158 self._projects[project.name] = project
159
160 def _ParseRemote(self, node):
161 """
162 reads a <remote> element from the manifest file
163 """
164 name = self._reqatt(node, 'name')
165 fetch = self._reqatt(node, 'fetch')
166 review = node.getAttribute('review')
167
168 r = Remote(name=name,
169 fetch=fetch,
170 review=review)
171
172 for n in node.childNodes:
173 if n.nodeName == 'require':
174 r.requiredCommits.append(self._reqatt(n, 'commit'))
175
176 return r
177
178 def _ParseDefault(self, node):
179 """
180 reads a <default> element from the manifest file
181 """
182 d = _Default()
183 d.remote = self._get_remote(node)
184 d.revision = node.getAttribute('revision')
185 return d
186
187 def _ParseProject(self, node):
188 """
189 reads a <project> element from the manifest file
190 """
191 name = self._reqatt(node, 'name')
192
193 remote = self._get_remote(node)
194 if remote is None:
195 remote = self._default.remote
196 if remote is None:
197 raise ManifestParseError, \
198 "no remote for project %s within %s" % \
199 (name, self.manifestFile)
200
201 revision = node.getAttribute('revision')
202 if not revision:
203 revision = self._default.revision
204 if not revision:
205 raise ManifestParseError, \
206 "no revision for project %s within %s" % \
207 (name, self.manifestFile)
208
209 path = node.getAttribute('path')
210 if not path:
211 path = name
212 if path.startswith('/'):
213 raise ManifestParseError, \
214 "project %s path cannot be absolute in %s" % \
215 (name, self.manifestFile)
216
217 worktree = os.path.join(self.topdir, path)
218 gitdir = os.path.join(self.repodir, 'projects/%s.git' % path)
219
220 project = Project(manifest = self,
221 name = name,
222 remote = remote,
223 gitdir = gitdir,
224 worktree = worktree,
225 relpath = path,
226 revision = revision)
227
228 for n in node.childNodes:
229 if n.nodeName == 'remote':
230 r = self._ParseRemote(n)
231 if project.extraRemotes.get(r.name) \
232 or project.remote.name == r.name:
233 raise ManifestParseError, \
234 'duplicate remote %s in project %s in %s' % \
235 (r.name, project.name, self.manifestFile)
236 project.extraRemotes[r.name] = r
237 elif n.nodeName == 'copyfile':
238 self._ParseCopyFile(project, n)
239
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700240 return project
241
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700242 def _ParseCopyFile(self, project, node):
243 src = self._reqatt(node, 'src')
244 dest = self._reqatt(node, 'dest')
245 # src is project relative, and dest is relative to the top of the tree
246 project.AddCopyFile(src, os.path.join(self.topdir, dest))
247
248 def _get_remote(self, node):
249 name = node.getAttribute('remote')
250 if not name:
251 return None
252
253 v = self._remotes.get(name)
254 if not v:
255 raise ManifestParseError, \
256 "remote %s not defined in %s" % \
257 (name, self.manifestFile)
258 return v
259
260 def _reqatt(self, node, attname):
261 """
262 reads a required attribute from the node.
263 """
264 v = node.getAttribute(attname)
265 if not v:
266 raise ManifestParseError, \
267 "no %s in <%s> within %s" % \
268 (attname, node.nodeName, self.manifestFile)
269 return v