Update master of plcapi.
[build.git] / pkgs.py
1 #!/usr/bin/python
2 #
3 # This is a replacement for the formerly bash-written function pl_parsePkgs () 
4
5 # Usage: $0  [-a arch] default_arch keyword fcdistro pldistro pkgs-file[..s]
6 # default_arch is $pl_DISTRO_ARCH, but can be overridden
7 #
8
9 #################### original language was (in this example, keyword=package)
10 ## to add to all distros
11 # package: p1 p2
12 ## to add in one distro
13 # package+f12: p1 p2
14 ## to remove in one distro
15 # package-f10: p1 p2
16 #################### replacement language
17 ## add in distro f10
18 # +package=f10: p1 p2
19 # or simply
20 # package=f10: p1 p2
21 ## add in fedora distros starting with f10
22 # +package>=f10: p1 p2
23 # or simply
24 # package>=f10: p1 p2
25 ## ditto but remove instead
26 # -package=centos5: p1 p2
27 # -package<=centos5: p1 p2
28
29 import sys
30 from sys import stderr
31 from optparse import OptionParser
32 import re
33
34 default_arch='x86_64'
35 known_arch = ['i386','x86_64']
36 default_fcdistro='f12'
37 known_fcdistros = [ 'centos5','centos6','f8', 'f9','f10','f11','f12', 'f13', 'f14', 'f16', 'sl6']
38 default_pldistro='onelab'
39
40 known_keywords=['groupname', 'groupdesc', 'kexclude', 'package', 'group', 'precious', 'junk', 'mirror', ]
41
42
43 m_fcdistro_cutter = re.compile('([a-z]+)([0-9]+)')
44 re_ident='[a-z]+'
45
46 class PkgsParser:
47
48     def __init__ (self,arch,fcdistro,pldistro,keyword,inputs,options):
49         self.arch=arch
50         self.fcdistro=fcdistro
51         self.pldistro=pldistro
52         self.keyword=keyword
53         self.inputs=inputs
54         # for verbose, new_line, and the like
55         self.options=options
56         ok=False
57         for known in known_fcdistros:
58             if fcdistro == known:
59                 (distro,version)=m_fcdistro_cutter.match(fcdistro).groups()
60                 ok=True
61         if ok:
62             self.distro=distro
63             self.version=int(version)
64         else:
65             print >> stderr, 'unrecognized fcdistro', fcdistro
66             sys.exit(1)
67
68     # qualifier is either '>=','<=', or '='
69     def match (self, qualifier, version):
70         if qualifier == '=':
71             return self.version == version
72         elif qualifier == '>=':
73             return self.version >= version
74         elif qualifier == '<=':
75             return self.version <= version
76         else:
77             raise Exception, 'Internal error - unexpected qualifier %r' % qualifier
78
79     m_comment=re.compile('\A\s*#')
80     m_blank=re.compile('\A\s*\Z')
81
82     m_ident=re.compile('\A'+re_ident+'\Z')
83     re_qualified = '\s*'
84     re_qualified += '(?P<plus_minus>[+-]?)'
85     re_qualified += '\s*'
86     re_qualified += '(?P<keyword>%s)'%re_ident
87     re_qualified += '\s*'
88     re_qualified += '(?P<qualifier>>=|<=|=)'
89     re_qualified += '\s*'
90     re_qualified += '(?P<fcdistro>%s[0-9]+)'%re_ident
91     re_qualified += '\s*'
92     m_qualified = re.compile('\A%s\Z'%re_qualified)
93
94     re_old = '[a-z]+[+-][a-z]+[0-9]+'
95     m_old = re.compile ('\A%s\Z'%re_old)
96     
97     # returns a tuple (included,excluded)
98     def parse (self,filename):
99         ok=True
100         included=[]
101         excluded=[]
102         lineno=0
103         try:
104             for line in file(filename).readlines():
105                 lineno += 1
106                 line=line.strip()
107                 if self.m_comment.match(line) or self.m_blank.match(line):
108                     continue
109                 try:
110                     [lefts,rights] = line.split(':',1)
111                     for left in lefts.split():
112                         ########## single ident
113                         if self.m_ident.match(left):
114                             if left not in known_keywords:
115                                 raise Exception,"Unknown keyword %r"%left
116                             elif left == self.keyword:
117                                 included += rights.split()
118                         else:
119                             m=self.m_qualified.match(left)
120                             if m:
121                                 (plus_minus,kw,qual,fcdistro) = m.groups()
122                                 if kw not in known_keywords:
123                                     raise Exception,"Unknown keyword in %r"%left
124                                 if fcdistro not in known_fcdistros:
125                                     raise Exception, 'Unknown fcdistro %r'%fcdistro
126                                 # skip if another keyword
127                                 if kw != self.keyword: continue
128                                 # does this fcdistro match ?
129                                 (distro,version)=m_fcdistro_cutter.match(fcdistro).groups()
130                                 version = int (version)
131                                 # skip if another distro family
132                                 if distro != self.distro: continue
133                                 # skip if the qualifier does not fit
134                                 if not self.match (qual, version): 
135                                     if self.options.verbose: print >> stderr,'%s:%d:qualifer %s does not apply'%(filename,lineno,left)
136                                     continue
137                                 # we're in, let's add (default) or remove (if plus_minus is minus)
138                                 if plus_minus == '-':
139                                     if self.options.verbose: print >> stderr,'%s:%d: from %s, excluding %r'%(filename,lineno,left,rights)
140                                     excluded += rights.split()
141                                 else:
142                                     if self.options.verbose: print >> stderr,'%s:%d: from %s, including %r'%(filename,lineno,left,rights)
143                                     included += rights.split()
144                             elif self.m_old.match(left):
145                                 raise Exception,'Old-fashioned syntax not supported anymore %r'%left
146                             else:
147                                 raise Exception,'error in left expression %r'%left
148                                 
149                 except Exception,e:
150                     ok=False
151                     print >> stderr, "%s:%d:syntax error: %r"%(filename,lineno,e)
152         except Exception,e:
153             ok=False
154             print >> stderr, 'Could not parse file',filename,e
155         return (ok,included,excluded)
156
157     def run (self):
158         ok=True
159         included=[]
160         excluded=[]
161         for input in self.inputs:
162             (o,i,e) = self.parse (input)
163             included += i
164             excluded += e
165             ok = ok and o
166         results = list (set(included).difference(set(excluded)))
167         
168         results = [ x.replace('@arch@',self.arch).\
169                         replace('@fcdistro@',self.fcdistro).\
170                         replace('@pldistro@',self.pldistro) for x in results]
171         results.sort()
172         # default is space-separated
173         if not self.options.new_line:
174             print " ".join(results)
175         # but for tests results are printed each on a line
176         else:
177             for result in results : print result
178         return ok
179
180 def main ():
181     usage="Usage: %prog [options] keyword input[...]"
182     parser=OptionParser (usage=usage)
183     parser.add_option ('-a','--arch',dest='arch',action='store',default=default_arch,
184                        help='target arch, e.g. i386 or x86_64')
185     parser.add_option ('-f','--fcdistro',dest='fcdistro',action='store', default=default_fcdistro,
186                        help='fcdistro, e.g. f12 or centos5')
187     parser.add_option ('-d','--pldistro',dest='pldistro',action='store', default=default_pldistro,
188                        help='pldistro, e.g. onelab or planetlab')
189     parser.add_option ('-v', '--verbose',dest='verbose',action='store_true',default=False,
190                        help='verbose when using qualifiers')
191     parser.add_option ('-n', '--new-line',dest='new_line',action='store_true',default=False,
192                        help='print outputs separated with newlines rather than with a space')
193     (options,args) = parser.parse_args()
194     
195     if len(args) <=1 :
196         parser.print_help(file=stderr)
197         sys.exit(1)
198     keyword=args[0]
199     inputs=args[1:]
200     if not options.arch in known_arch:
201         print >> stderr, 'Unsupported arch',options.arch
202         parser.print_help(file=stderr)
203         sys.exit(1)
204     if not options.fcdistro in known_fcdistros:
205         print >> stderr, 'Unsupported fcdistro',options.fcdistro
206         parser.print_help(file=stderr)
207         sys.exit(1)
208
209     pkgs = PkgsParser (options.arch,options.fcdistro,options.pldistro,keyword,inputs,options)
210
211     if pkgs.run():
212         sys.exit(0)
213     else:
214         sys.exit(1)
215
216 if __name__ == '__main__':
217     if main():
218         sys.exit(0)
219     else:
220         sys.exit(1)