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