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