exception-safe Component.is_dirty()
[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 config
10 import log
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, tag='master', tags={}, flags=[]):
25                 commits = {}
26                 commits.update(tags)
27                 commits[None] = tag
28
29                 self.name = name
30
31                 self.source = Repository(
32                         'source/%s' % name,
33                         os.path.join(config.top_dir, 'src', name),
34                         commits,
35                         exclude=['meta'])
36
37                 self.meta = Repository(
38                         'meta/%s' % name,
39                         os.path.join(config.top_dir, 'src', name, 'meta'),
40                         commits)
41
42                 self.flags = flags
43
44                 self.packages = {}
45                 self.depends = set()
46
47         def __str__(self):
48                 return str(self.source)
49
50         def is_dirty(self):
51                 if self.__dirty is None:
52                         for repo in (self.source, self.meta):
53                                 if repo.dirty_files():
54                                         self.__dirty = True
55                                         break
56
57                         if self.__dirty is None:
58                                 self.__dirty = False
59
60                 return self.__dirty
61
62         def add_depend(self, c):
63                 self.depends.add(c)
64
65         def remove_depend(self, c):
66                 self.depends.remove(c)
67                 return not self.depends
68
69         def get_depends(self):
70                 return self.depends
71
72         def add_rdepend(self, c):
73                 if self.rdepends is None:
74                         self.rdepends = set()
75
76                 self.rdepends.add(c)
77
78         def get_rdepends(self):
79                 return self.rdepends or ()
80
81         def clear_rdepends(self):
82                 if self.rdepends:
83                         del self.rdepends
84
85 class PlatformProvidedComponent(Component):
86         """A Component that is provided by the platform.
87            The sources will not be built during install."""
88
89         cached = True
90
91 def init():
92         targets = []
93         packages = {}
94
95         for c in config.components.itervalues():
96                 if not c.meta.exists():
97                         c.meta.clone()
98
99                 init_packages(c, targets, packages)
100
101         init_depends(packages)
102
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 = ''
111                 self.conflicts = ''
112                 self.architectures = []
113
114 def init_packages(c, targets, packages):
115         for path in glob(os.path.join(c.meta.path, '*.package')):
116                 name = os.path.basename(path)[:-8]
117
118                 pkg = parse_package(name, c, path)
119                 if not pkg:
120                         continue
121
122                 c.packages[name] = pkg
123                 packages[name] = pkg
124
125                 if config.debug:
126                         print 'Component', c.name, 'provides', name
127
128         if c.packages:
129                 targets.append(c.name)
130         elif config.debug:
131                 print 'Component', c.name, 'does not provide any packages'
132
133 def parse_package(name, component, path):
134         pkg = Package(name, component)
135         execfile(path, pkg.__dict__, pkg.__dict__)
136
137         if pkg.architectures:
138                 arch = config.boards[config.board].arch
139                 if arch not in pkg.architectures:
140                         return None
141
142         return pkg
143
144 class Dependency(object):
145         regex = re.compile(r'([@]?)([^\s:]+)[:]?([<>=]*)([^\s:]*)[:]?(.*)')
146
147         def __init__(self, spec):
148                 match = self.regex.match(spec)
149                 if not match:
150                         raise Error('Bad dependency specification: ' + spec)
151
152                 self.build, self.name, self.tag_op, self.tag, flags \
153                         = match.groups()
154                 self.flags = flags.split()
155
156         def check(self, packages):
157                 # TODO: check version and flags
158                 return self.name in packages
159
160 def init_depends(packages):
161         for pkg in packages.itervalues():
162                 for spec in pkg.depends.split():
163                         depname = Dependency(spec).name
164                         deppkg = packages.get(depname)
165
166                         if not deppkg:
167                                 log.error('Package %s depends on ' \
168                                           'non-existent package %s' % \
169                                           (pkg.name, depname))
170                                 continue
171
172                         if deppkg.component == pkg.component:
173                                 continue
174
175                         pkg.component.add_depend(deppkg.component)
176
177         fail = False
178         for pkg in packages.itervalues():
179                 for spec in pkg.depends.split():
180                         if not Dependency(spec).check(packages):
181                                 fail = True
182                                 log.error('Dependency %s failed for %s' % \
183                                           (spec, pkg.name))
184
185                 for spec in pkg.conflicts.split():
186                         if Dependency(spec).check(packages):
187                                 fail = True
188                                 log.error('Package %s conflicts with %s' % \
189                                           (pkg.name, spec))
190
191         if fail:
192                 raise Error('Invalid component tree')
193
194 def fill_in_depends(components):
195         depends = True
196         while depends:
197                 depends = set()
198
199                 for c in components:
200                         for dep in c.get_depends():
201                                 if dep not in components:
202                                         depends.add(dep)
203
204                 components |= depends
205
206 def init_rdepends(components):
207         for c in components:
208                 for dep in c.get_depends():
209                         dep.add_rdepend(c)