72f72171ae0291e4f315218ab8ce908108ba09b1
[matrix.git] / matrix / components.py
1 # Copyright (C) 2006-2008 Movial Oy
2 # Authors: Timo Savola <tsavola@movial.fi>
3
4 import os
5 import re
6 from glob import glob
7 from sets import Set as set
8
9 import log
10 from config import config
11 from repositories import Repository
12
13 Error = RuntimeError
14
15 class Component(object):
16         __dirty = None
17
18         cached = None
19
20         rdepends = None
21         weight = None
22         state = None
23
24         def __init__(self, name, branch=None, flags=[], rank=0):
25                 self.name = name
26
27                 self.source = Repository(
28                         'source/%s' % name,
29                         os.path.join(config.top_dir, 'src', name),
30                         branch,
31                         exclude=['meta'])
32
33                 self.meta = Repository(
34                         'meta/%s' % name,
35                         os.path.join(config.top_dir, 'src', name, 'meta'),
36                         branch)
37
38                 self.flags = flags
39
40                 self.packages = {}
41                 self.depends = set()
42
43                 self.rank = rank
44
45         def __str__(self):
46                 return str(self.source)
47
48         def is_dirty(self):
49                 if self.__dirty is None:
50                         for repo in (self.source, self.meta):
51                                 if repo.is_dirty():
52                                         self.__dirty = True
53                                         break
54
55                         if self.__dirty is None:
56                                 self.__dirty = False
57
58                 return self.__dirty
59
60         def add_depend(self, c):
61                 if self.rank < c.rank:
62                         c.rank = self.rank
63                         if config.debug:
64                                 print 'Rank fixup:', c, 'inherits rank from', self
65
66                 self.depends.add(c)
67
68         def remove_depend(self, c):
69                 self.depends.remove(c)
70                 return not self.depends
71
72         def get_depends(self):
73                 return self.depends
74
75         def add_rdepend(self, c):
76                 if self.rdepends is None:
77                         self.rdepends = set()
78
79                 self.rdepends.add(c)
80
81         def get_rdepends(self):
82                 return self.rdepends or ()
83
84         def clear_rdepends(self):
85                 if self.rdepends:
86                         del self.rdepends
87
88 class PlatformProvidedComponent(Component):
89         """A Component that is provided by the platform.
90            The sources will not be built during install."""
91
92         cached = True
93
94 by_name = None
95
96 def init(targets=[]):
97         global by_name
98         by_name = Resolver().resolve(targets)
99
100 class Resolver(object):
101         def __init__(self):
102                 self.components = {}
103                 self.packages = {}
104
105         def resolve(self, targets):
106                 # add all manually specified components before dependencies so
107                 # that all custom Component() arguments will come into effect
108                 #
109                 for c in config.components.itervalues():
110                         self.add_component(c)
111
112                 # iterate over a _copy_ of the _current_ package list;
113                 # automatic components' dependencies will be initialized
114                 # recursively as we go.
115                 #
116                 for p in self.packages.values():
117                         self.init_package_depends(p)
118
119                 for name in targets:
120                         if name not in self.components:
121                                 self.add_automatic_component(name)
122
123                 self.init_rank_depends()
124                 self.check_depends()
125
126                 return self.components
127
128         def add_component(self, c):
129                 if not c.meta.exists():
130                         c.meta.clone()
131
132                 # custom packages?
133                 #
134                 for path in glob(os.path.join(c.meta.path, '*.package')):
135                         name = os.path.basename(path)[:-8]
136                         self.add_package(c, name, path)
137
138                         if config.debug:
139                                 print 'Component', c.name, 'provides', name
140
141                 # default package?
142                 #
143                 if not c.packages:
144                         path = os.path.join(c.meta.path, 'info')
145                         if os.path.exists(path):
146                                 self.add_package(c, c.name, path)
147                         else:
148                                 self.add_package(c, c.name)
149
150                 self.components[c.name] = c
151
152         def add_package(self, c, name, path=None):
153                 p = parse_package(name, c, path)
154                 if p:
155                         c.packages[name] = p
156                         self.packages[name] = p
157
158         def init_package_depends(self, p):
159                 for spec in to_seq(p.depends):
160                         depname = Dependency(spec).name
161                         deppkg = self.packages.get(depname)
162
163                         depcomp = None
164                         if deppkg:
165                                 if deppkg.component != p.component:
166                                         depcomp = deppkg.component
167                         else:
168                                 depcomp = self.add_automatic_component(depname)
169                                 if not depcomp:
170                                         log.error('Package %s depends on ' \
171                                                   'non-existent package %s' % \
172                                                   (p.name, depname))
173
174                         if depcomp:
175                                 p.component.add_depend(depcomp)
176
177         def add_automatic_component(self, name):
178                 if config.debug:
179                         print 'Looking for automatic component:', name
180
181                 c = Component(name)
182                 if not c.meta.exists():
183                         try:
184                                 c.meta.clone()
185                         except Error, e:
186                                 log.error(str(e))
187                                 return None
188
189                 self.add_component(c)
190
191                 for p in c.packages.values():
192                         self.init_package_depends(p)
193
194                 return c
195
196         def init_rank_depends(self):
197                 by_rank = {}
198                 for c in self.components.itervalues():
199                         l = by_rank.get(c.rank)
200                         if l is None:
201                                 l = []
202                                 by_rank[c.rank] = l
203                         l.append(c)
204
205                 ranks = by_rank.keys()
206                 ranks.sort()
207
208                 for i in xrange(1, len(ranks)):
209                         curr_rank = ranks[i]
210                         for curr_comp in by_rank[curr_rank]:
211                                 for j in xrange(i):
212                                         prev_rank = ranks[j]
213                                         for prev_comp in by_rank[prev_rank]:
214                                                 curr_comp.add_depend(prev_comp)
215
216         def check_depends(self):
217                 fail = False
218                 for p in self.packages.itervalues():
219                         for spec in to_seq(p.depends):
220                                 if not Dependency(spec).check(self.packages):
221                                         fail = True
222                                         log.error('Dependency %s failed for %s' % \
223                                                   (spec, p.name))
224
225                         for spec in to_seq(p.conflicts):
226                                 if Dependency(spec).check(self.packages):
227                                         fail = True
228                                         log.error('Package %s conflicts with %s' % \
229                                                   (p.name, spec))
230
231                 if fail:
232                         raise Error('Invalid component tree')
233
234 class Package(object):
235         def __init__(self, name, component):
236                 self.name = name
237                 self.component = component
238
239                 self.depends = None
240                 self.conflicts = None
241                 self.architectures = None
242
243 def to_seq(value):
244         if value is None:
245                 return tuple()
246         if isinstance(value, str):
247                 return value.split()
248         return value
249
250 def parse_package(name, component, path=None):
251         p = Package(name, component)
252
253         if path:
254                 execfile(path, p.__dict__, p.__dict__)
255
256         if p.architectures:
257                 arch = config.boards[config.board].arch
258                 if arch not in p.architectures:
259                         return None
260
261         return p
262
263 class Dependency(object):
264         regex = re.compile(r'([@]?)([^\s:]+)[:]?([<>=]*)([^\s:]*)[:]?(.*)')
265
266         def __init__(self, spec):
267                 match = self.regex.match(spec)
268                 if not match:
269                         raise Error('Bad dependency specification: ' + spec)
270
271                 self.build, self.name, self.tag_op, self.tag, flags \
272                         = match.groups()
273                 self.flags = flags.split()
274
275         def check(self, packages):
276                 # TODO: check version and flags
277                 return self.name in packages
278
279 def fill_in_depends(components):
280         depends = True
281         while depends:
282                 depends = set()
283
284                 for c in components:
285                         for dep in c.get_depends():
286                                 if dep not in components:
287                                         depends.add(dep)
288
289                 components |= depends
290
291 def init_rdepends(components):
292         for c in components:
293                 for dep in c.get_depends():
294                         dep.add_rdepend(c)