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