new "changes" command for listing unpushed commits
[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 os
8 import sys
9 from sets import Set as set
10
11 import cache
12 import components
13 import config
14 import git
15 import log
16 from build import build
17 from rootfs import RootFS
18
19 Error = RuntimeError
20
21 def main():
22         config.parse('config.local')
23
24         command, params, targets = parse_args(sys.argv)
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         if command == 'install':
51                 build(targets)
52         elif command == 'clone':
53                 clone(targets)
54         elif command == 'clean':
55                 clean(targets)
56         elif command == 'rebase':
57                 rebase(targets)
58         elif command == 'pull':
59                 pull(targets)
60         elif command == 'changes':
61                 changes(targets)
62         elif command == 'source-dist':
63                 source_dist(targets)
64         elif command == 'rootfs':
65                 build_rootfs(**params)
66         else:
67                 raise Error('Invalid command: ' + command)
68
69 def help(file, args):
70         print >>file, '''
71 Copyright (C) 2006-2008 Movial Oy
72
73 Usage: %(progname)s [<options>] <command> [<params>] [<components>]
74
75 If no components are specified, all of them will be targeted.  All component
76 metadata will be downloaded regardless of the specified command and components.
77
78 Options:
79         -v              Verbose output.
80         -d              Debug output.
81         -r URL          Specify the root for component git repos to clone from.
82                         If this option is not given, the roots specified in the
83                         components file will be used.  This option may be
84                         specified multiple times.
85         -f              Build (or be quiet about) components with dirty files.
86         -k              Build as much as possible after an error.
87         -j N            Execute N build jobs in parallel.
88         -mj N           Run component builds with make -j N.
89         -h, --help      Print this help text.
90
91 Commands and parameters:
92         clone           Download the components' git repositories.
93         install         Download, build and install the components.
94         clean           Remove all non-tracked files from the component git
95                         repository directories.
96         rebase          Update repositories from server by rebasing.
97         pull            Update repositories from server by merging.
98         changes         Show commits which are not on the server.
99         source-dist     Download and package the component sources.
100         rootfs          Build a rootfs from the current target installation.
101                 -n              Do not clean env.faked and stripped directory.
102                 -r              Only generate a stripped rootfs.
103                 -j              Only generate a jffs2 image
104                 -d              Only generate a rootstrap (development rootfs).
105 ''' % {'progname': args[0]}
106
107 def parse_args(args):
108         def parse_jobs(arg):
109                 jobs = int(arg)
110                 if jobs <= 0:
111                         raise Error('Please specify a valid number of jobs')
112                 return jobs
113
114         command = None
115         params = {}
116         targets = []
117
118         i = 1
119         while i < len(args):
120                 if args[i].startswith('-'):
121                         if not command:
122                                 if args[i] == '-v':
123                                         config.verbose = True
124                                 elif args[i] == '-d':
125                                         config.debug = True
126                                 elif args[i] == '-r':
127                                         i += 1
128                                         config.roots.append(args[i])
129                                 elif args[i] == '-f':
130                                         config.force = True
131                                 elif args[i] == '-k':
132                                         config.keep_going = True
133                                 elif args[i] == '-j':
134                                         i += 1
135                                         config.jobs = parse_jobs(args[i])
136                                 elif args[i] == '-mj':
137                                         i += 1
138                                         config.make_jobs = parse_jobs(args[i])
139                                 elif args[i] in ('-h', '--help'):
140                                         help(sys.stdout, args)
141                                         sys.exit(0)
142                                 else:
143                                         raise Error('Bad option: ' + args[i])
144                         elif command == 'rootfs':
145                                 if args[i] == '-n':
146                                         i += 1
147                                         params['clean'] = False
148                                 elif args[i] == '-r':
149                                         i += 1
150                                         params['rootfs_only'] = True
151                                 elif args[i] == '-j':
152                                         i += 1
153                                         params['jffs2_only'] = True
154                                 elif args[i] == '-d':
155                                         i += 1
156                                         params['devrootfs_only'] = True
157                         else:
158                                 raise Error('Command takes no parameters')
159                 elif not command:
160                         command = args[i]
161                 else:
162                         targets.append(args[i])
163                 i += 1
164
165         if not command:
166                 help(sys.stderr, args)
167                 sys.exit(1)
168
169         return command, params, targets
170
171 def execute(args):
172         if config.debug:
173                 print 'Executing:', ' '.join(args)
174
175         if os.spawnvp(os.P_WAIT, args[0], args) != 0:
176                 raise Error('Failed: ' + ' '.join(args))
177
178 def remove_tree(path):
179         execute(['rm', '-rf', path])
180
181 def clone(targets):
182         for name in targets:
183                 c = config.components[name]
184                 clone_component(c)
185
186 def clone_component(c, overwrite=False):
187         have_repo = c.repo.exists()
188         have_meta = c.meta.exists()
189
190         if not overwrite and have_repo and have_meta:
191                 return
192
193         if overwrite and os.path.exists(c.repo.path):
194                 print 'Removing', c.repo
195
196                 remove_tree(c.repo.path)
197                 have_repo = False
198                 have_meta = False
199
200         if not have_repo:
201                 c.repo.clone()
202         if not have_meta:
203                 c.meta.clone()
204
205 def clean(targets):
206         changed = False
207
208         for name in targets:
209                 c = config.components[name]
210                 c.repo.clean()
211                 cache.remove(c)
212
213                 if not config.force:
214                         changed = check_dirty(c)
215
216         if changed:
217                 raise Error()
218
219 def for_each_repository(func, targets=None):
220         ret = None
221
222         for name in targets:
223                 c = config.components[name]
224                 if c.repo.exists():
225                         value = func(c.repo)
226                         if value:
227                                 ret = value
228                 value = func(c.meta)
229                 if value:
230                         ret = value
231
232         return ret
233
234 def rebase(targets):
235         for_each_repository(lambda repo: repo.rebase(), targets)
236
237 def pull(targets):
238         for_each_repository(lambda repo: repo.pull(), targets)
239
240 def changes(targets):
241         changed = False
242
243         for name in targets:
244                 c = config.components[name]
245
246                 if c.repo.exists():
247                         if check_changes(c.repo):
248                                 changed = True
249
250                 if check_changes(c.meta):
251                         changed = True
252
253                 if not config.force and check_dirty(c):
254                         changed = True
255
256         if changed:
257                 raise Error()
258
259 def check_changes(repo):
260         ret = repo.changes()
261         if ret:
262                 log.message('Changes in %s' % repo)
263         return ret
264
265 def check_dirty(c):
266         ret = c.is_dirty()
267         if ret:
268                 log.message('Dirty files in %s' % c.name)
269         return ret
270
271 def source_dist(targets):
272         location = 'dist'
273         if not os.path.exists(location):
274                 os.makedirs(location)
275
276         for name in targets:
277                 c = config.components[name]
278                 dist_changes(c, location)
279                 dist_sources(c, location)
280
281 def dist_changes(c, location):
282         path = os.path.join(location, c.name) + '.changes'
283         c.repo.dump_log(path)
284
285 def dist_sources(c, location):
286         rev = c.repo.describe()
287         if rev:
288                 rev = '_' + rev
289         else:
290                 rev = ''
291
292         name = c.name + rev
293         path = os.path.join(location, name) + '.tar.bz2'
294         if os.path.exists(path):
295                 os.remove(path)
296
297         c.repo.archive(name, path)
298
299 def build_rootfs(clean=True, rootfs_only=False, jffs2_only=False, devrootfs_only=False):
300         config.parse('rootfs')
301
302         f = os.popen('sb-conf cu')
303         target = f.read()
304         f.close()
305
306         rootfs = RootFS(target.strip())
307         rootfs.include_paths(config.include_paths)
308         rootfs.include_files(config.include_files)
309         rootfs.filter_paths(config.exclude_paths)
310         rootfs.filter_files(config.exclude_files)
311         rootfs.filter_expressions(config.exclude_expressions)
312         rootfs.add_paths(config.created_paths)
313         rootfs.set_devices(config.devices)
314         rootfs.set_change_owner(config.change_owner)
315         rootfs.set_erase_size(config.boards[config.board].flash_erase_size)
316         rootfs.set_pad_size(config.boards[config.board].flash_pad_size)
317
318         if rootfs_only:
319                 rootfs.generate(clean, build_target="rootfs")
320         elif jffs2_only:
321                 rootfs.generate(clean, build_target="jffs2")
322         elif devrootfs_only:
323                 rootfs.generate(clean, build_target="devrootfs")
324         else:
325                 rootfs.generate(clean, build_target="all")