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