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