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