read dependency/conflict/architecture metadata from "info"
[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         # custom/multiple packages
123         for path in glob(os.path.join(c.meta.path, '*.package')):
124                 name = os.path.basename(path)[:-8]
125                 add_package(c, packages, name, path)
126
127         if not c.packages:
128                 # default package
129                 path = os.path.join(c.meta.path, 'info')
130                 if os.path.exists(path):
131                         add_package(c, packages, c.name, path)
132         elif config.debug:
133                 print 'Component', c.name, 'provides:', ' '.join(c.packages)
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 add_package(c, packages, name, path):
141         pkg = parse_package(name, c, path)
142         if pkg:
143                 c.packages[name] = pkg
144                 packages[name] = pkg
145
146 def parse_package(name, component, path):
147         pkg = Package(name, component)
148         execfile(path, pkg.__dict__, pkg.__dict__)
149
150         if pkg.architectures:
151                 arch = config.boards[config.board].arch
152                 if arch not in pkg.architectures:
153                         return None
154
155         return pkg
156
157 class Dependency(object):
158         regex = re.compile(r'([@]?)([^\s:]+)[:]?([<>=]*)([^\s:]*)[:]?(.*)')
159
160         def __init__(self, spec):
161                 match = self.regex.match(spec)
162                 if not match:
163                         raise Error('Bad dependency specification: ' + spec)
164
165                 self.build, self.name, self.tag_op, self.tag, flags \
166                         = match.groups()
167                 self.flags = flags.split()
168
169         def check(self, packages):
170                 # TODO: check version and flags
171                 return self.name in packages
172
173 def init_depends(packages):
174         for pkg in packages.itervalues():
175                 for spec in to_seq(pkg.depends):
176                         depname = Dependency(spec).name
177                         deppkg = packages.get(depname)
178
179                         if not deppkg:
180                                 log.error('Package %s depends on ' \
181                                           'non-existent package %s' % \
182                                           (pkg.name, depname))
183                                 continue
184
185                         if deppkg.component == pkg.component:
186                                 continue
187
188                         pkg.component.add_depend(deppkg.component)
189
190         fail = False
191         for pkg in packages.itervalues():
192                 for spec in to_seq(pkg.depends):
193                         if not Dependency(spec).check(packages):
194                                 fail = True
195                                 log.error('Dependency %s failed for %s' % \
196                                           (spec, pkg.name))
197
198                 for spec in to_seq(pkg.conflicts):
199                         if Dependency(spec).check(packages):
200                                 fail = True
201                                 log.error('Package %s conflicts with %s' % \
202                                           (pkg.name, spec))
203
204         if fail:
205                 raise Error('Invalid component tree')
206
207 def init_rank_depends():
208         components_by_rank = {}
209         for c in config.components.itervalues():
210                 l = components_by_rank.get(c.rank)
211                 if l is None:
212                         l = []
213                         components_by_rank[c.rank] = l
214                 l.append(c)
215
216         ranks = list(components_by_rank.keys())
217         ranks.sort()
218
219         for i in xrange(1, len(ranks)):
220                 curr_rank = ranks[i]
221                 for curr_comp in components_by_rank[curr_rank]:
222                         for j in xrange(i):
223                                 prev_rank = ranks[j]
224                                 for prev_comp in components_by_rank[prev_rank]:
225                                         curr_comp.add_depend(prev_comp)
226
227 def fill_in_depends(components):
228         depends = True
229         while depends:
230                 depends = set()
231
232                 for c in components:
233                         for dep in c.get_depends():
234                                 if dep not in components:
235                                         depends.add(dep)
236
237                 components |= depends
238
239 def init_rdepends(components):
240         for c in components:
241                 for dep in c.get_depends():
242                         dep.add_rdepend(c)