1 | #!/usr/bin/python |
---|
2 | |
---|
3 | |
---|
4 | import datetime |
---|
5 | import optparse |
---|
6 | import os |
---|
7 | import stat |
---|
8 | import re |
---|
9 | import shutil |
---|
10 | |
---|
11 | |
---|
12 | from Notify import notify |
---|
13 | |
---|
14 | |
---|
15 | class Logger: # in more recent versions, Python features a logger |
---|
16 | def __init__(self,name): |
---|
17 | self.name = name |
---|
18 | if os.path.exists(self.name): |
---|
19 | os.remove(self.name) |
---|
20 | |
---|
21 | def log(self,msg): |
---|
22 | f = open(self.name,"a") # append |
---|
23 | f.write(msg+'\n') |
---|
24 | f.close() # close after each log message |
---|
25 | |
---|
26 | website = "/afs/cern.ch/user/n/nougaret/www/mad" |
---|
27 | reportDir = "/afs/cern.ch/user/n/nougaret/scratch1/mad-automation" |
---|
28 | # 12 april 2009 fix the directory in which to extract MAD-X |
---|
29 | #currentDir = os.getcwd() |
---|
30 | currentDir = reportDir |
---|
31 | extractDir = currentDir+'/MadSvnExtract' |
---|
32 | |
---|
33 | notify('jean-luc','MadSvnExtract','MadSvnExtract will be created as ' + extractDir) |
---|
34 | |
---|
35 | class Repository: |
---|
36 | def __init__(self): |
---|
37 | pass |
---|
38 | def checkout(self,releaseTag): |
---|
39 | # command = "cvs -d " + Repository.repoDir + " checkout -r" + releaseTag + " madX" |
---|
40 | command = "svn co svn+ssh://svn.cern.ch/reps/madx/tags/" + releaseTag + " " + extractDir |
---|
41 | os.system(command) |
---|
42 | |
---|
43 | class Build: |
---|
44 | builds = [] |
---|
45 | def __init__(self,name,command,outcome): |
---|
46 | self.name = name # 'Makefile', 'Makefile_dev' or 'Makefile_nag' |
---|
47 | self.command = command # the build command |
---|
48 | self.outcome = outcome |
---|
49 | Build.builds.append(self) |
---|
50 | |
---|
51 | # first set environment variables for lf95 and NAG compilers |
---|
52 | # this is necessary for the acron job which does not perform a login |
---|
53 | # that would set the variables transparently. |
---|
54 | |
---|
55 | # 27 september: this script is no longer called from an acron job, therefore |
---|
56 | # no longer need to reset the environment variables within the scripts. |
---|
57 | # instead we inherit everything from ~/.cshrc on pcslux99 |
---|
58 | |
---|
59 | def main(): |
---|
60 | |
---|
61 | # temporary: for the Intel compiler, lines split after the 80th character |
---|
62 | # unless we set up a specific environment variable to set the line length |
---|
63 | # note that this variable will be transmitted to the child processes |
---|
64 | # launched through os.sytem() calls. |
---|
65 | # all 'madx < input > output' calls where madx is compiled with the ifort |
---|
66 | # Intel compiler will take FORT_FMT_RECL into account. |
---|
67 | # os.environ['FORT_FMT_RECL']='256' # lines split after 256 chars instead of |
---|
68 | # THIS DOES NOT SEEM TO WORK 13 april 2010 - force the variable when invoking the makefile instead |
---|
69 | # the default 80 |
---|
70 | |
---|
71 | LOG_FILENAME = reportDir + "/MadBuild_Report.txt" |
---|
72 | logger = Logger(LOG_FILENAME) |
---|
73 | logger.log('first logging message') |
---|
74 | logger.log('start logging at '+(datetime.datetime.now()).ctime()) |
---|
75 | |
---|
76 | compilationOK = True # default (assumes all the compilations proceed till completion) |
---|
77 | |
---|
78 | globalStartTime = (datetime.datetime.now()).ctime() |
---|
79 | usage = "%prog [options]" |
---|
80 | parser = optparse.OptionParser(usage) |
---|
81 | parser.add_option('--release','-r',help="release tag",dest="releaseTag") |
---|
82 | parser.add_option('--dev','-d',help="construct executable with Makefile_develop",action="store_true") |
---|
83 | parser.add_option('--nag','-n',help="construct executable with Makefile_nag",action="store_true") |
---|
84 | (options,args) = parser.parse_args() |
---|
85 | |
---|
86 | if options.dev and not options.nag: |
---|
87 | logger.log("option --dev selected") |
---|
88 | elif options.nag and not options.dev: |
---|
89 | logger.log("option --nag selected") |
---|
90 | elif options.dev and options.nag: |
---|
91 | logger.log("options --dev and --nag selected") |
---|
92 | else: |
---|
93 | logger.log("only compile madx_Makefile") |
---|
94 | |
---|
95 | if not options.releaseTag: |
---|
96 | raise("except a release tag to be specified") |
---|
97 | else: # is the release tag well formed? |
---|
98 | releasePattern = re.compile(r'^madX\-\d+_\d{2}_\d{2}(\_dev)?$') |
---|
99 | if not releasePattern.match(options.releaseTag): |
---|
100 | raise("release tag is ill-formed: it should be like 'madX-4_01_01' instead of '"+options.releaseTag+"'") |
---|
101 | |
---|
102 | # 23 juin 2010 |
---|
103 | #os.environ['PATH'] = os.environ['PATH'] +\ |
---|
104 | # ":/afs/cern.ch/sw/fortran/nag/f95.5.361/bin:/afs/cern.ch/sw/fortran/lahey/lf9562/bin" |
---|
105 | # |
---|
106 | #if os.getenv('LD_LIBRARY_PATH')==None: # would cause a key error at runtime |
---|
107 | # os.environ['LD_LIBRARY_PATH'] = ":/afs/cern.ch/sw/fortran/lahey/lf9562/lib" |
---|
108 | #else: |
---|
109 | # os.environ['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH'] + ":/afs/cern.ch/sw/fortran/lahey/lf9562/lib" |
---|
110 | |
---|
111 | makefiles = ['Makefile'] |
---|
112 | |
---|
113 | if options.nag: |
---|
114 | # 23 juin 2010 |
---|
115 | # os.environ['NAG95_ROOT'] = "/afs/cern.ch/sw/fortran/nag/f95.5.361" # flexlm license manager for NAG compiler |
---|
116 | #os.environ['LM_LICENSE_FILE'] = "/afs/cern.ch/sw/fortran/nag/f95.5.361/license.dat" |
---|
117 | # 27 september 2010: rely on the path and env set in ~/.cshrc instead of setting environment variables |
---|
118 | #os.environ['NAG95_ROOT'] = "/usr/local/lib/NAG_Fortran" |
---|
119 | #os.environ['NAG_KUSARI_FILE'] = os.environ['NAG95_ROOT']+"/license.dat" |
---|
120 | makefiles.append('Makefile_nag') |
---|
121 | |
---|
122 | #os.environ['PATH'] = ".:/usr/local/lib/NAG_FORTRAN:"+os.environ['PATH'] |
---|
123 | |
---|
124 | if options.dev: |
---|
125 | makefiles.append('Makefile_develop') |
---|
126 | |
---|
127 | # if the directory in which to extract the CVS already exists, then delete it |
---|
128 | if os.path.exists(extractDir): |
---|
129 | shutil.rmtree(extractDir) |
---|
130 | # now create the directory in which to extract the CVS |
---|
131 | #os.mkdir(extractDir) |
---|
132 | #os.chdir(extractDir) |
---|
133 | repo = Repository() |
---|
134 | repo.checkout(options.releaseTag) # creates or overwrites extractDir |
---|
135 | os.chdir(extractDir) |
---|
136 | os.chdir('./madX') |
---|
137 | |
---|
138 | for m in makefiles: |
---|
139 | |
---|
140 | # do a make clean and remove any preexisting target |
---|
141 | if os.path.exists('./madx'): |
---|
142 | os.remove('./madx') |
---|
143 | logger.log('remove preexisting ./madx executable') |
---|
144 | else: |
---|
145 | logger.log('no preexisting ./madx executable') |
---|
146 | os.system('make clean') |
---|
147 | logger.log("invoked 'make clean' at time "+(datetime.datetime.now()).ctime()) |
---|
148 | |
---|
149 | #print("now to compile "+m+" in " + os.getcwd()) |
---|
150 | if m == 'Makefile': |
---|
151 | #invocation = "make f95=/opt/intel/Compiler/11.0/081/bin/ia32/ifort DEBUG=NO FORT_FMT_RECL=256 madx" |
---|
152 | invocation = "make f95=ifort DEBUG=NO FORT_FMT_RECL=256 madx" |
---|
153 | elif m == 'Makefile_develop': |
---|
154 | invocation = "make f95=lf95 DEBUG=YES madx" # the Lahey Fortran compiler |
---|
155 | elif m == 'Makefile_nag': |
---|
156 | invocation = "make f95=f95 DEBUG=YES madx" # the NAG compiler |
---|
157 | else: |
---|
158 | raise("should never reach this point") |
---|
159 | print "make invocation=",invocation |
---|
160 | #notify('jean-luc','Point 3.2', 'makefile='+m) |
---|
161 | output = "./makeResult" |
---|
162 | #os.system(invocation) |
---|
163 | startTime = (datetime.datetime.now()).ctime() |
---|
164 | #notify('jean-luc','now in directory', 'os.getcwd()='+os.getcwd()+" at time"+(datetime.datetime.now()).ctime()) |
---|
165 | #notify('jean-luc','Point 3.3', 'now to issue command='+invocation+">& "+output+" at time"+(datetime.datetime.now()).ctime()) |
---|
166 | logger.log('now located in '+os.getcwd()) |
---|
167 | logger.log('now to invoke command='+invocation+">& "+output+" at time "+(datetime.datetime.now()).ctime()) |
---|
168 | |
---|
169 | os.system(invocation+">& "+output) |
---|
170 | |
---|
171 | endTime = (datetime.datetime.now()).ctime() |
---|
172 | |
---|
173 | logger.log("compilation command completed at time : " + endTime) |
---|
174 | |
---|
175 | f = open(output,'r') |
---|
176 | lines = f.readlines() |
---|
177 | f.close() |
---|
178 | #notify('jean-luc','Point 4', '') |
---|
179 | # if the target madx is absent, compilation failed |
---|
180 | if os.path.exists('./madx'): |
---|
181 | # success or warning |
---|
182 | outcome = 'success' # for the time-being |
---|
183 | outcome = lookForWarnings(lines) |
---|
184 | executable = './madx_'+m # new name of the executable |
---|
185 | shutil.copyfile('./madx',executable) |
---|
186 | if os.path.exists(executable): |
---|
187 | logger.log('.madx has been copied into '+executable+" under "+os.getcwd()) |
---|
188 | else: |
---|
189 | logger.log("can't find executable "+executable+" which must have just been created") |
---|
190 | os.chmod(executable,stat.S_IRWXU) # read, write, and execute permission by owner |
---|
191 | logger.log("a ./madx executable was succesfully created at "+(datetime.datetime.now()).ctime()) |
---|
192 | # very important: grant execution rights to the script |
---|
193 | #print("succesfully compiled for "+m) |
---|
194 | else: # failure |
---|
195 | outcome = 'failure' |
---|
196 | compilationOK = False # overwrite |
---|
197 | logger.log("missing ./madx executable") |
---|
198 | #print("failed to compile for "+m) |
---|
199 | |
---|
200 | b = Build(m,invocation,outcome) |
---|
201 | #notify('jean-luc','Point 5', 'makefile='+m) |
---|
202 | page = BuildWebPage(website+'/build_'+m+'_madx.htm',m,lines,outcome,startTime,endTime) |
---|
203 | page.output() |
---|
204 | |
---|
205 | #notify('jean-luc','Point 6', '') |
---|
206 | globalEndTime = (datetime.datetime.now()).ctime() |
---|
207 | # create the main HTML page from which the three above HTML pages can be reached |
---|
208 | page = MainWebPage(website+'/build.htm',globalStartTime,globalEndTime,Build.builds) |
---|
209 | page.output() |
---|
210 | |
---|
211 | # Finally output the return status on stdout to be processed by the calling MadBuildAndTest.py |
---|
212 | if not compilationOK: |
---|
213 | notify('jean-luc','compilation failed','failed to compile release = '+options.releaseTag) |
---|
214 | else: |
---|
215 | notify('jean-luc','compilation succeeded','managed to compile last release = '+options.releaseTag) |
---|
216 | print compilationOK |
---|
217 | |
---|
218 | notify('jean-luc','reached the end of MadBuild.py','compilationOK=' + str(compilationOK)) |
---|
219 | |
---|
220 | |
---|
221 | |
---|
222 | def lookForWarnings(lines): |
---|
223 | warningPattern = re.compile(r'[\s\t]*[\w\d_\-:]+[\s\t]+[Ww]arning(s?)') |
---|
224 | for line in lines: |
---|
225 | m = warningPattern.search(line) |
---|
226 | if m: |
---|
227 | if m.lastindex == 1: |
---|
228 | return('warning') |
---|
229 | return('success') |
---|
230 | |
---|
231 | |
---|
232 | |
---|
233 | class MainWebPage: # the main web page that lists all the outcomes for the successive builds |
---|
234 | |
---|
235 | def __init__(self,name,startTime,endTime,builds): |
---|
236 | self.name = name |
---|
237 | self.startTime = startTime |
---|
238 | self.endTime = endTime |
---|
239 | self.builds = builds # a list of Build objects |
---|
240 | |
---|
241 | def header(self): |
---|
242 | self.contents += '<DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">\n' |
---|
243 | self.contents += '<html>\n' |
---|
244 | self.contents += '<head>\n' |
---|
245 | self.contents += '<title>MAD build result page</title>\n' |
---|
246 | self.contents += '<link rel=stylesheet href="./MadTestWebStyle.css" type="text/css">\n' |
---|
247 | self.contents += '</head>\n' |
---|
248 | |
---|
249 | def body(self): |
---|
250 | self.contents += '<body>\n' |
---|
251 | self.contents += 'Build started '+ self.startTime + ', ended ' + self.endTime |
---|
252 | self.contents += '<table>\n' |
---|
253 | for b in self.builds: |
---|
254 | weblink = "build_"+b.name+"_madx.htm" |
---|
255 | self.contents += '<tr class="'+b.outcome+'"><td>'+b.name+'</td><td>'+b.command+'</td><td><a href="'+weblink+'">'+b.outcome+'</a></td></tr>\n' |
---|
256 | self.contents += '<table>\n' |
---|
257 | self.contents += '<body>\n' |
---|
258 | |
---|
259 | def footer(self): |
---|
260 | self.contents += '</html>\n' |
---|
261 | |
---|
262 | def output(self): |
---|
263 | self.contents = "" |
---|
264 | self.header() |
---|
265 | self.body() |
---|
266 | self.footer() |
---|
267 | f = open(self.name,'w') |
---|
268 | for c in self.contents: |
---|
269 | f.write(c) |
---|
270 | f.close() |
---|
271 | |
---|
272 | def display(self): |
---|
273 | os.system('firefox ' + self.name+ '&') |
---|
274 | |
---|
275 | class BuildWebPage(MainWebPage): |
---|
276 | def __init__(self,name,makefile,lines,returnStatus,startTime,endTime): |
---|
277 | self.name = name |
---|
278 | self.returnStatus = returnStatus |
---|
279 | self.buildResult = lines |
---|
280 | self.startTime = startTime |
---|
281 | self.endTime = endTime |
---|
282 | self.makefile = makefile |
---|
283 | |
---|
284 | def header(self): |
---|
285 | self.contents += '<DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">\n' |
---|
286 | self.contents += '<html>\n' |
---|
287 | self.contents += '<head>\n' |
---|
288 | self.contents += '<title>MAD build main page</title>\n' |
---|
289 | self.contents += '<link rel=stylesheet href="./MadTestWebStyle.css" type="text/css">\n' |
---|
290 | self.contents += '</head>\n' |
---|
291 | |
---|
292 | def body(self): |
---|
293 | self.contents += '<body>\n' |
---|
294 | self.contents += '<p>Build started '+self.startTime+', ended '+self.endTime+'</p>\n' |
---|
295 | self.contents += '<table width="75%" border="0">\n' |
---|
296 | # top coloured banner |
---|
297 | self.contents += '<tr class='+self.returnStatus+'><td width="80%">Outcome of build process for \''+self.makefile+'\'</td><td width="20%">'+\ |
---|
298 | self.returnStatus+"</td></tr>\n" |
---|
299 | for l in self.buildResult: |
---|
300 | self.contents += '<tr><td colspan="2">'+l.rstrip("\n")+'</td></tr>\n' |
---|
301 | self.contents += "</table>\n" |
---|
302 | self.contents += '</body>\n' |
---|
303 | |
---|
304 | if __name__ == "__main__": |
---|
305 | main() |
---|
306 | |
---|