Component() supports 'rank' parameter
[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 def init():
90         targets = []
91         packages = {}
92
93         for c in config.components.itervalues():
94                 if not c.meta.exists():
95                         c.meta.clone()
96
97                 init_packages(c, targets, packages)
98
99         init_depends(packages)
100         init_rank_depends()
101
102         targets.sort()
103         return targets
104
105 class Package(object):
106         def __init__(self, name, component):
107                 self.name = name
108                 self.component = component
109
110                 self.depends = None
111                 self.conflicts = None
112                 self.architectures = None
113
114 def to_seq(value):
115         if value is None:
116                 return tuple()
117         if isinstance(value, str):
118                 return value.split()
119         return value
120
121 def init_packages(c, targets, packages):
122         for path in glob(os.path.join(c.meta.path, '*.package')):
123                 name = os.path.basename(path)[:-8]
124
125                 pkg = parse_package(name, c, path)
126                 if not pkg:
127                         continue
128
129                 c.packages[name] = pkg
130                 packages[name] = pkg
131
132                 if config.debug:
133                         print 'Component', c.name, 'provides', name
134
135         if c.packages:
136                 targets.append(c.name)
137         elif config.debug:
138                 print 'Component', c.name, 'does not provide any packages'
139
140 def parse_package(name, component, path):
141         pkg = Package(name, component)
142         execfile(path, pkg.__dict__, pkg.__dict__)
143
144         if pkg.architectures:
145                 arch = config.boards[config.board].arch
146                 if arch not in pkg.architectures:
147                         return None
148
149         return pkg
150
151 class Dependency(object):
152         regex = re.compile(r'([@]?)([^\s:]+)[:]?([<>=]*)([^\s:]*)[:]?(.*)')
153
154         def __init__(self, spec):
155                 match = self.regex.match(spec)
156                 if not match:
157                         raise Error('Bad dependency specification: ' + spec)
158
159                 self.build, self.name, self.tag_op, self.tag, flags \
160                         = match.groups()
161                 self.flags = flags.split()
162
163         def check(self, packages):
164                 # TODO: check version and flags
165                 return self.name in packages
166
167 def init_depends(packages):
168         for pkg in packages.itervalues():
169                 for spec in to_seq(pkg.depends):
170                         depname = Dependency(spec).name
171                         deppkg = packages.get(depname)
172
173                         if not deppkg:
174                                 log.error('Package %s depends on ' \
175                                           'non-existent package %s' % \
176                                           (pkg.name, depname))
177                                 continue
178
179                         if deppkg.component == pkg.component:
180                                 continue
181
182                         pkg.component.add_depend(deppkg.component)
183
184         fail = False
185         for pkg in packages.itervalues():
186                 for spec in to_seq(pkg.depends):
187                         if not Dependency(spec).check(packages):
188                                 fail = True
189                                 log.error('Dependency %s failed for %s' % \
190                                           (spec, pkg.name))
191
192                 for spec in to_seq(pkg.conflicts):
193                         if Dependency(spec).check(packages):
194                                 fail = True
195                                 log.error('Package %s conflicts with %s' % \
196                                           (pkg.name, spec))
197
198         if fail:
199                 raise Error('Invalid component tree')
200
201 def init_rank_depends():
202         components_by_rank = {}
203         for c in config.components.itervalues():
204                 l = components_by_rank.get(c.rank)
205                 if l is None:
206                         l = []
207                         components_by_rank[c.rank] = l
208                 l.append(c)
209
210         ranks = list(components_by_rank.keys())
211         ranks.sort()
212
213         for i in xrange(1, len(ranks)):
214                 curr_rank = ranks[i]
215                 for curr_comp in components_by_rank[curr_rank]:
216                         for j in xrange(i):
217                                 prev_rank = ranks[j]
218                                 for prev_comp in components_by_rank[prev_rank]:
219                                         curr_comp.add_depend(prev_comp)
220
221 def fill_in_depends(components):
222         depends = True
223         while depends:
224                 depends = set()
225
226                 for c in components:
227                         for dep in c.get_depends():
228                                 if dep not in components:
229                                         depends.add(dep)
230
231                 components |= depends
232
233 def init_rdepends(components):
234         for c in components:
235                 for dep in c.get_depends():
236                         dep.add_rdepend(c)