add forthcoming ubuntu (wily) and cleanup older fcdistros on all 3 accounts (fedora...
[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', 'i686', 'x86_64']
36 default_fcdistro = 'f22'
37 known_fcdistros = [
38     'centos5', 'centos6',
39     'f14', 'f18', 'f20', 'f21', 'f22',
40     'sl6', 
41     # debians
42     'wheezy','jessie',
43     # ubuntus
44     'precise', # 12.04 LTS
45     'trusty',  # 14.04 LTS
46     'utopic',  # 14.10
47     'vivid',   # 15.04
48     'wily',    # 15.10
49 ]
50 default_pldistro='onelab'
51
52 known_keywords = [
53     'group', 'groupname', 'groupdesc', 
54      'package', 'pip', 'gem', 
55     'nodeyumexclude', 'plcyumexclude', 'yumexclude',
56     'precious', 'junk', 'mirror',
57 ]
58
59
60 m_fcdistro_cutter = re.compile('([a-z]+)([0-9]+)')
61 re_ident='[a-z]+'
62
63 class PkgsParser:
64
65     def __init__ (self,arch,fcdistro,pldistro,keyword,inputs,options):
66         self.arch=arch
67         self.fcdistro=fcdistro
68         self.pldistro=pldistro
69         self.keyword=keyword
70         self.inputs=inputs
71         # for verbose, new_line, and the like
72         self.options=options
73         ok=False
74         for known in known_fcdistros:
75             if fcdistro == known:
76                 try:
77                     (distro,version)=m_fcdistro_cutter.match(fcdistro).groups()
78                 # debian-like names can't use numbering
79                 except:
80                     distro=fcdistro
81                     version=0
82                 ok=True
83         if ok:
84             self.distro=distro
85             self.version=int(version)
86         else:
87             print >> stderr, 'unrecognized fcdistro', fcdistro
88             sys.exit(1)
89
90     # qualifier is either '>=','<=', or '='
91     def match (self, qualifier, version):
92         if qualifier == '=':
93             return self.version == version
94         elif qualifier == '>=':
95             return self.version >= version
96         elif qualifier == '<=':
97             return self.version <= version
98         else:
99             raise Exception, 'Internal error - unexpected qualifier %r' % qualifier
100
101     m_comment=re.compile('\A\s*#')
102     m_blank=re.compile('\A\s*\Z')
103
104     m_ident=re.compile('\A'+re_ident+'\Z')
105     re_qualified = '\s*'
106     re_qualified += '(?P<plus_minus>[+-]?)'
107     re_qualified += '\s*'
108     re_qualified += '(?P<keyword>%s)'%re_ident
109     re_qualified += '\s*'
110     re_qualified += '(?P<qualifier>>=|<=|=)'
111     re_qualified += '\s*'
112     re_qualified += '(?P<fcdistro>%s[0-9]+)'%re_ident
113     re_qualified += '\s*'
114     m_qualified = re.compile('\A%s\Z'%re_qualified)
115
116     re_old = '[a-z]+[+-][a-z]+[0-9]+'
117     m_old = re.compile ('\A%s\Z'%re_old)
118     
119     # returns a tuple (included,excluded)
120     def parse (self,filename):
121         ok=True
122         included=[]
123         excluded=[]
124         lineno=0
125         try:
126             for line in file(filename).readlines():
127                 lineno += 1
128                 line=line.strip()
129                 if self.m_comment.match(line) or self.m_blank.match(line):
130                     continue
131                 try:
132                     [lefts,rights] = line.split(':',1)
133                     for left in lefts.split():
134                         ########## single ident
135                         if self.m_ident.match(left):
136                             if left not in known_keywords:
137                                 raise Exception,"Unknown keyword %r"%left
138                             elif left == self.keyword:
139                                 included += rights.split()
140                         else:
141                             m=self.m_qualified.match(left)
142                             if m:
143                                 (plus_minus,kw,qual,fcdistro) = m.groups()
144                                 if kw not in known_keywords:
145                                     raise Exception,"Unknown keyword in %r"%left
146                                 if fcdistro not in known_fcdistros:
147                                     raise Exception, 'Unknown fcdistro %r'%fcdistro
148                                 # skip if another keyword
149                                 if kw != self.keyword: continue
150                                 # does this fcdistro match ?
151                                 (distro,version)=m_fcdistro_cutter.match(fcdistro).groups()
152                                 version = int (version)
153                                 # skip if another distro family
154                                 if distro != self.distro: continue
155                                 # skip if the qualifier does not fit
156                                 if not self.match (qual, version): 
157                                     if self.options.verbose: print >> stderr,'%s:%d:qualifer %s does not apply'%(filename,lineno,left)
158                                     continue
159                                 # we're in, let's add (default) or remove (if plus_minus is minus)
160                                 if plus_minus == '-':
161                                     if self.options.verbose: print >> stderr,'%s:%d: from %s, excluding %r'%(filename,lineno,left,rights)
162                                     excluded += rights.split()
163                                 else:
164                                     if self.options.verbose: print >> stderr,'%s:%d: from %s, including %r'%(filename,lineno,left,rights)
165                                     included += rights.split()
166                             elif self.m_old.match(left):
167                                 raise Exception,'Old-fashioned syntax not supported anymore %r'%left
168                             else:
169                                 raise Exception,'error in left expression %r'%left
170                                 
171                 except Exception,e:
172                     ok=False
173                     print >> stderr, "%s:%d:syntax error: %r"%(filename,lineno,e)
174         except Exception,e:
175             ok=False
176             print >> stderr, 'Could not parse file',filename,e
177         return (ok,included,excluded)
178
179     def run (self):
180         ok=True
181         included=[]
182         excluded=[]
183         for input in self.inputs:
184             (o,i,e) = self.parse (input)
185             included += i
186             excluded += e
187             ok = ok and o
188         # avoid set operations that would not preserve order
189         results = [ x for x in included if x not in excluded ]
190         
191         results = [ x.replace('@arch@',self.arch).\
192                         replace('@fcdistro@',self.fcdistro).\
193                         replace('@pldistro@',self.pldistro) for x in results]
194         if self.options.sort_results:
195             results.sort()
196         # default is space-separated
197         if not self.options.new_line:
198             print " ".join(results)
199         # but for tests results are printed each on a line
200         else:
201             for result in results : print result
202         return ok
203
204 def main ():
205     usage="Usage: %prog [options] keyword input[...]"
206     parser=OptionParser (usage=usage)
207     parser.add_option ('-a','--arch',dest='arch',action='store',default=default_arch,
208                        help='target arch, e.g. i386 or x86_64, default=%s'%default_arch)
209     parser.add_option ('-f','--fcdistro',dest='fcdistro',action='store', default=default_fcdistro,
210                        help='fcdistro, e.g. f12 or centos5')
211     parser.add_option ('-d','--pldistro',dest='pldistro',action='store', default=default_pldistro,
212                        help='pldistro, e.g. onelab or planetlab')
213     parser.add_option ('-v', '--verbose',dest='verbose',action='store_true',default=False,
214                        help='verbose when using qualifiers')
215     parser.add_option ('-n', '--new-line',dest='new_line',action='store_true',default=False,
216                        help='print outputs separated with newlines rather than with a space')
217     parser.add_option ('-u', '--no-sort',dest='sort_results',default=True,action='store_false',
218                        help='keep results in the same order as in the inputs')
219     (options,args) = parser.parse_args()
220     
221     if len(args) <=1 :
222         parser.print_help(file=stderr)
223         sys.exit(1)
224     keyword=args[0]
225     inputs=args[1:]
226     if not options.arch in known_arch:
227         print >> stderr, 'Unsupported arch',options.arch
228         parser.print_help(file=stderr)
229         sys.exit(1)
230     if options.arch == 'i686': options.arch='i386'
231     if not options.fcdistro in known_fcdistros:
232         print >> stderr, 'Unsupported fcdistro',options.fcdistro
233         parser.print_help(file=stderr)
234         sys.exit(1)
235
236     pkgs = PkgsParser (options.arch,options.fcdistro,options.pldistro,keyword,inputs,options)
237
238     if pkgs.run():
239         sys.exit(0)
240     else:
241         sys.exit(1)
242
243 if __name__ == '__main__':
244     if main():
245         sys.exit(0)
246     else:
247         sys.exit(1)