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