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