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