jobfile.py revision 5618:1abb23c038d5
1# Copyright (c) 2005-2006 The Regents of The University of Michigan
2# All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are
6# met: redistributions of source code must retain the above copyright
7# notice, this list of conditions and the following disclaimer;
8# redistributions in binary form must reproduce the above copyright
9# notice, this list of conditions and the following disclaimer in the
10# documentation and/or other materials provided with the distribution;
11# neither the name of the copyright holders nor the names of its
12# contributors may be used to endorse or promote products derived from
13# this software without specific prior written permission.
14#
15# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26#
27# Authors: Nathan Binkert
28
29import sys
30
31from attrdict import optiondict
32from misc import crossproduct
33
34class Data(object):
35    def __init__(self, name, desc, **kwargs):
36        self.name = name
37        self.desc = desc
38        self.__dict__.update(kwargs)
39
40    def update(self, obj):
41        if not isinstance(obj, Data):
42            raise AttributeError, "can only update from Data object"
43
44        for key,val in obj.__dict__.iteritems():
45            if key.startswith('_') or key in ('name', 'desc'):
46                continue
47
48            if key not in self.__dict__:
49                self.__dict__[key] = val
50                continue
51
52            if not isinstance(val, dict):
53                if self.__dict__[key] == val:
54                    continue
55
56                raise AttributeError, \
57                      "%s specified more than once old: %s new: %s" % \
58                      (key, self.__dict__[key], val)
59
60            d = self.__dict__[key]
61            for k,v in val.iteritems():
62                if k in d:
63                    raise AttributeError, \
64                          "%s specified more than once in %s" % (k, key)
65                d[k] = v
66
67        if hasattr(self, 'system') and hasattr(obj, 'system'):
68            if self.system != obj.system:
69                raise AttributeError, \
70                      "conflicting values for system: '%s'/'%s'" % \
71                      (self.system, obj.system)
72
73    def printinfo(self):
74        if self.name:
75            print 'name: %s' % self.name
76        if self.desc:
77            print 'desc: %s' % self.desc
78        try:
79            if self.system:
80                print 'system: %s' % self.system
81        except AttributeError:
82            pass
83
84    def printverbose(self):
85        for key in self:
86            val = self[key]
87            if isinstance(val, dict):
88                import pprint
89                val = pprint.pformat(val)
90            print '%-20s = %s' % (key, val)
91        print
92
93    def __contains__(self, attr):
94        if attr.startswith('_'):
95            return False
96        return attr in self.__dict__
97
98    def __getitem__(self, key):
99        if key.startswith('_'):
100            raise KeyError, "Key '%s' not found" % attr
101        return self.__dict__[key]
102
103    def __iter__(self):
104        keys = self.__dict__.keys()
105        keys.sort()
106        for key in keys:
107            if not key.startswith('_'):
108                yield key
109
110    def optiondict(self):
111        result = optiondict()
112        for key in self:
113            result[key] = self[key]
114        return result
115
116    def __repr__(self):
117        d = {}
118        for key,value in self.__dict__.iteritems():
119            if not key.startswith('_'):
120                d[key] = value
121
122        return "<%s: %s>" % (type(self).__name__, d)
123
124    def __str__(self):
125        return self.name
126
127class Job(Data):
128    def __init__(self, options):
129        super(Job, self).__init__('', '')
130
131        config = options[0]._config
132        for opt in options:
133            if opt._config != config:
134                raise AttributeError, \
135                      "All options are not from the same Configuration"
136
137        self._config = config
138        self._groups = [ opt._group for opt in options ]
139        self._options = options
140
141        self.update(self._config)
142        for group in self._groups:
143            self.update(group)
144
145        self._is_checkpoint = True
146
147        for option in self._options:
148            self.update(option)
149            if not option._group._checkpoint:
150                self._is_checkpoint = False
151
152            if option._suboption:
153                self.update(option._suboption)
154                self._is_checkpoint = False
155
156        names = [ ]
157        for opt in self._options:
158            if opt.name:
159                names.append(opt.name)
160        self.name = ':'.join(names)
161
162        descs = [ ]
163        for opt in self._options:
164            if opt.desc:
165                descs.append(opt.desc)
166        self.desc = ', '.join(descs)
167
168        self._checkpoint = None
169        if not self._is_checkpoint:
170            opts = []
171            for opt in options:
172                cpt = opt._group._checkpoint
173                if not cpt:
174                    continue
175                if isinstance(cpt, Option):
176                    opt = cpt.clone(suboptions=False)
177                else:
178                    opt = opt.clone(suboptions=False)
179
180                opts.append(opt)
181
182            if opts:
183                self._checkpoint = Job(opts)
184
185    def clone(self):
186        return Job(self._options)
187
188    def printinfo(self):
189        super(Job, self).printinfo()
190        if self._checkpoint:
191            print 'checkpoint: %s' % self._checkpoint.name
192        print 'config: %s' % self._config.name
193        print 'groups: %s' % [ g.name for g in self._groups ]
194        print 'options: %s' % [ o.name for o in self._options ]
195        super(Job, self).printverbose()
196
197class SubOption(Data):
198    def __init__(self, name, desc, **kwargs):
199        super(SubOption, self).__init__(name, desc, **kwargs)
200        self._number = None
201
202class Option(Data):
203    def __init__(self, name, desc, **kwargs):
204        super(Option, self).__init__(name, desc, **kwargs)
205        self._suboptions = []
206        self._suboption = None
207        self._number = None
208
209    def __getattribute__(self, attr):
210        if attr == 'name':
211            name = self.__dict__[attr]
212            if self._suboption is not None:
213                name = '%s:%s' % (name, self._suboption.name)
214            return name
215
216        if attr == 'desc':
217            desc = [ self.__dict__[attr] ]
218            if self._suboption is not None and self._suboption.desc:
219                desc.append(self._suboption.desc)
220            return ', '.join(desc)
221
222        return super(Option, self).__getattribute__(attr)
223
224    def suboption(self, name, desc, **kwargs):
225        subo = SubOption(name, desc, **kwargs)
226        subo._config = self._config
227        subo._group = self._group
228        subo._option = self
229        subo._number = len(self._suboptions)
230        self._suboptions.append(subo)
231        return subo
232
233    def clone(self, suboptions=True):
234        option = Option(self.__dict__['name'], self.__dict__['desc'])
235        option.update(self)
236        option._group = self._group
237        option._config = self._config
238        option._number = self._number
239        if suboptions:
240            option._suboptions.extend(self._suboptions)
241            option._suboption = self._suboption
242        return option
243
244    def subopts(self):
245        if not self._suboptions:
246            return [ self ]
247
248        subopts = []
249        for subo in self._suboptions:
250            option = self.clone()
251            option._suboption = subo
252            subopts.append(option)
253
254        return subopts
255
256    def printinfo(self):
257        super(Option, self).printinfo()
258        print 'config: %s' % self._config.name
259        super(Option, self).printverbose()
260
261class Group(Data):
262    def __init__(self, name, desc, **kwargs):
263        super(Group, self).__init__(name, desc, **kwargs)
264        self._options = []
265        self._number = None
266        self._checkpoint = False
267
268    def option(self, name, desc, **kwargs):
269        opt = Option(name, desc, **kwargs)
270        opt._config = self._config
271        opt._group = self
272        opt._number = len(self._options)
273        self._options.append(opt)
274        return opt
275
276    def options(self):
277        return self._options
278
279    def subopts(self):
280        subopts = []
281        for opt in self._options:
282            for subo in opt.subopts():
283                subopts.append(subo)
284        return subopts
285
286    def printinfo(self):
287        super(Group, self).printinfo()
288        print 'config: %s' % self._config.name
289        print 'options: %s' % [ o.name for o in self._options ]
290        super(Group, self).printverbose()
291
292class Configuration(Data):
293    def __init__(self, name, desc, **kwargs):
294        super(Configuration, self).__init__(name, desc, **kwargs)
295        self._groups = []
296        self._posfilters = []
297        self._negfilters = []
298
299    def group(self, name, desc, **kwargs):
300        grp = Group(name, desc, **kwargs)
301        grp._config = self
302        grp._number = len(self._groups)
303        self._groups.append(grp)
304        return grp
305
306    def groups(self):
307        return self._groups
308
309    def checkchildren(self, kids):
310        for kid in kids:
311            if kid._config != self:
312                raise AttributeError, "child from the wrong configuration"
313
314    def sortgroups(self, groups):
315        groups = [ (grp._number, grp) for grp in groups ]
316        groups.sort()
317        return [ grp[1] for grp in groups ]
318
319    def options(self, groups=None, checkpoint=False):
320        if groups is None:
321            groups = self._groups
322        self.checkchildren(groups)
323        groups = self.sortgroups(groups)
324        if checkpoint:
325            groups = [ grp for grp in groups if grp._checkpoint ]
326            optgroups = [ g.options() for g in groups ]
327        else:
328            optgroups = [ g.subopts() for g in groups ]
329        if not optgroups:
330            return
331        for options in crossproduct(optgroups):
332            for opt in options:
333                cpt = opt._group._checkpoint
334                if not isinstance(cpt, bool) and cpt != opt:
335                    if checkpoint:
336                        break
337                    else:
338                        yield options
339            else:
340                if checkpoint:
341                    yield options
342
343    def addfilter(self, filt, pos=True):
344        import re
345        filt = re.compile(filt)
346        if pos:
347            self._posfilters.append(filt)
348        else:
349            self._negfilters.append(filt)
350
351    def jobfilter(self, job):
352        for filt in self._negfilters:
353            if filt.match(job.name):
354                return False
355
356        if not self._posfilters:
357            return True
358
359        for filt in self._posfilters:
360            if filt.match(job.name):
361                return True
362
363        return False
364
365    def checkpoints(self, groups=None):
366        for options in self.options(groups, True):
367            job = Job(options)
368            if self.jobfilter(job):
369                yield job
370
371    def jobs(self, groups=None):
372        for options in self.options(groups, False):
373            job = Job(options)
374            if self.jobfilter(job):
375                yield job
376
377    def alljobs(self, groups=None):
378        for options in self.options(groups, True):
379            yield Job(options)
380        for options in self.options(groups, False):
381            yield Job(options)
382
383    def find(self, jobname):
384        for job in self.alljobs():
385            if job.name == jobname:
386                return job
387        else:
388            raise AttributeError, "job '%s' not found" % jobname
389
390    def job(self, options):
391        self.checkchildren(options)
392        options = [ (opt._group._number, opt) for opt in options ]
393        options.sort()
394        options = [ opt[1] for opt in options ]
395        job = Job(options)
396        return job
397
398    def printinfo(self):
399        super(Configuration, self).printinfo()
400        print 'groups: %s' % [ g.name for g in self._groups ]
401        super(Configuration, self).printverbose()
402
403def JobFile(jobfile):
404    from os.path import expanduser, isfile, join as joinpath
405    filename = expanduser(jobfile)
406
407    # Can't find filename in the current path, search sys.path
408    if not isfile(filename):
409        for path in sys.path:
410            testname = joinpath(path, filename)
411            if isfile(testname):
412                filename = testname
413                break
414        else:
415            raise AttributeError, \
416                  "Could not find file '%s'" % jobfile
417
418    data = {}
419    execfile(filename, data)
420    if 'conf' not in data:
421        raise ImportError, 'cannot import name conf from %s' % jobfile
422    return data['conf']
423
424def main(conf=None):
425    usage = 'Usage: %s [-b] [-c] [-v]' % sys.argv[0]
426    if conf is None:
427        usage += ' <jobfile>'
428
429    try:
430        import getopt
431        opts, args = getopt.getopt(sys.argv[1:], '-bcv')
432    except getopt.GetoptError:
433        sys.exit(usage)
434
435    both = False
436    checkpoint = False
437    verbose = False
438    for opt,arg in opts:
439        if opt == '-b':
440            both = True
441            checkpoint = True
442        if opt == '-c':
443            checkpoint = True
444        if opt == '-v':
445            verbose = True
446
447    if conf is None:
448        if len(args) != 1:
449            raise AttributeError, usage
450        conf = JobFile(args[0])
451    else:
452        if len(args) != 0:
453            raise AttributeError, usage
454
455    if both:
456        jobs = conf.alljobs()
457    elif checkpoint:
458        jobs = conf.checkpoints()
459    else:
460        jobs = conf.jobs()
461
462    for job in jobs:
463        if verbose:
464            job.printinfo()
465        else:
466            cpt = ''
467            if job._checkpoint:
468                cpt = job._checkpoint.name
469            print job.name, cpt
470
471if __name__ == '__main__':
472    main()
473