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