flexible config scripts
[matrix.git] / matrix / matrix.py
1 # Copyright (C) 2006-2008 Movial Oy
2 # Authors: Timo Savola <tsavola@movial.fi>
3 #          Toni Timonen
4 #          Kalle Vahlman <kalle.vahlman@movial.fi>
5 #          Tuomas Kulve <tuomas.kulve@movial.fi>
6
7 import optparse
8 import os
9 import sys
10 from sets import Set as set
11
12 import cache
13 import components
14 import git
15 import log
16 from config import config
17 from config import parse as config_parse
18 from build import build, build_only
19
20 Error = RuntimeError
21
22 def main():
23         command, targets, options = parse_args()
24
25         if 'debug' in options:
26                 config.debug = options['debug']
27
28         config_parse('config', True)
29
30         for name in options:
31                 setattr(config, name, options[name])
32
33         cache.init()
34         all_targets = components.init()
35
36         for i in xrange(len(targets)):
37                 name = targets[i]
38                 if name.startswith('src' + os.path.sep):
39                         name = name.split(os.path.sep)[1]
40                         targets[i] = name
41
42                 if name not in config.components:
43                         raise Error('Component "%s" does not exist' % name)
44
45         if not targets:
46                 targets = all_targets
47
48         command_funcs = {
49                 'install':      build,
50                 'install-only': build_only,
51                 'clone':        clone,
52                 'clean':        clean,
53                 'rebase':       rebase,
54                 'pull':         pull,
55                 'changes':      changes,
56                 'source-dist':  source_dist,
57         }
58
59         func = command_funcs.get(command)
60         if not func:
61                 raise Error('Invalid command: "%s"' % command)
62
63         func(targets)
64
65 def print_help(file, parser):
66         parser.print_help(file=file)
67         print >>file, '''
68 commands:
69   clone         download the components' git repositories
70   install       download, build and install the components
71   install-only  download, build and install the specified components without
72                 dependencies
73   clean         remove all non-tracked files from the component git repository
74                 directories
75   rebase        update repositories from server by rebasing
76   pull          update repositories from server by merging
77   changes       show commits which are not on the server
78   source-dist   download and package the component sources
79 '''
80
81 def parse_args():
82         options = {}
83
84         class Values(object):
85                 def __setattr__(self, name, value):
86                         options[name] = value
87
88         def help(option, opt, value, parser):
89                 print_help(sys.stdout, parser)
90                 sys.exit(0)
91
92         def add_root(option, opt, value, parser):
93                 config.roots.append(value)
94
95         def set_jobs(option, opt, value, parser):
96                 if value < 1:
97                         raise optparse.OptionValueError(
98                                 'option %s: invalid job count: %d' % \
99                                 (opt, value))
100                 setattr(config, option.dest, value)
101
102         parser = optparse.OptionParser(
103                 usage='%prog [<options>] <command> [<components>]',
104                 add_help_option=False)
105
106         parser.add_option(
107                 '-h', '--help',
108                 help='show this help message and exit',
109                 action='callback', callback=help)
110
111         parser.add_option(
112                 '-v', '--verbose',
113                 help='show build output',
114                 dest='verbose', action='store_true')
115
116         parser.add_option(
117                 '-d', '--debug',
118                 help='debug output',
119                 dest='debug', action='store_true')
120
121         parser.add_option(
122                 '-r', '--root',
123                 help='add a location for finding git repos when cloning ' \
124                      '(may be specified multiple times)', metavar='URL',
125                 dest='roots', action='callback', callback=add_root,
126                 type='string')
127
128         parser.add_option(
129                 '-f', '--force',
130                 help='build (or be quiet about) components with dirty files',
131                 dest='force', action='store_true')
132
133         parser.add_option(
134                 '-k', '--keep-going',
135                 help='build as much as possible after an error',
136                 dest='keep_going', action='store_true')
137
138         parser.add_option(
139                 '-j', '--jobs',
140                 help='execute up to N build jobs in parallel', metavar='N',
141                 dest='jobs', action='callback', callback=set_jobs,
142                 type='int')
143
144         parser.add_option(
145                 '-J', '--make-jobs',
146                 help='run component builds with make -j N', metavar='N',
147                 dest='make_jobs', action='callback', callback=set_jobs,
148                 type='int')
149
150         values, args = parser.parse_args(values=Values())
151         if not args:
152                 print_help(sys.stderr, parser)
153                 sys.exit(1)
154
155         return args[0], args[1:], options
156
157 def execute(args):
158         if config.debug:
159                 print 'Executing:', ' '.join(args)
160
161         if os.spawnvp(os.P_WAIT, args[0], args) != 0:
162                 raise Error('Failed: ' + ' '.join(args))
163
164 def remove_tree(path):
165         execute(['rm', '-rf', path])
166
167 def clone(targets):
168         for name in targets:
169                 c = config.components[name]
170                 clone_component(c)
171
172 def clone_component(c, overwrite=False):
173         have_source = c.source.exists()
174         have_meta = c.meta.exists()
175
176         if not overwrite and have_source and have_meta:
177                 return
178
179         if overwrite and os.path.exists(c.source.path):
180                 print 'Removing', c
181
182                 remove_tree(c.source.path)
183                 have_source = False
184                 have_meta = False
185
186         if not have_source:
187                 c.source.clone()
188         if not have_meta:
189                 c.meta.clone()
190
191 def clean(targets):
192         changed = False
193
194         for name in targets:
195                 c = config.components[name]
196                 c.source.clean()
197                 cache.remove(c)
198
199                 if not config.force:
200                         changed = check_dirty(c)
201
202         if changed:
203                 raise Error()
204
205 def for_each_repository(func, targets=None):
206         ret = None
207
208         for name in targets:
209                 c = config.components[name]
210                 if c.source.exists():
211                         value = func(c.source)
212                         if value:
213                                 ret = value
214                 value = func(c.meta)
215                 if value:
216                         ret = value
217
218         return ret
219
220 def rebase(targets):
221         for_each_repository(lambda repo: repo.rebase(), targets)
222
223 def pull(targets):
224         for_each_repository(lambda repo: repo.pull(), targets)
225
226 def changes(targets):
227         changed = False
228
229         for name in targets:
230                 c = config.components[name]
231
232                 if c.source.exists():
233                         if check_changes(c.source):
234                                 changed = True
235
236                 if check_changes(c.meta):
237                         changed = True
238
239                 if not config.force and check_dirty(c):
240                         changed = True
241
242         if changed:
243                 raise Error()
244
245 def check_changes(repo):
246         ret = repo.changes()
247         if ret:
248                 log.message('Changes in %s' % repo)
249         return ret
250
251 def check_dirty(c):
252         ret = c.is_dirty()
253         if ret:
254                 log.message('Dirty files in %s' % c.name)
255         return ret
256
257 def source_dist(targets):
258         location = 'dist'
259         if not os.path.exists(location):
260                 os.makedirs(location)
261
262         for name in targets:
263                 c = config.components[name]
264                 dist_changes(c, location)
265                 dist_sources(c, location)
266
267 def dist_changes(c, location):
268         path = os.path.join(location, c.name) + '.changes'
269         c.source.dump_log(path)
270
271 def dist_sources(c, location):
272         rev = c.source.describe()
273         if rev:
274                 rev = '_' + rev
275         else:
276                 rev = ''
277
278         name = c.name + rev
279         path = os.path.join(location, name) + '.tar.bz2'
280         if os.path.exists(path):
281                 os.remove(path)
282
283         c.source.archive(name, path)