[cvs] / gkb / gkb.py  

cvs: gkb/gkb.py


1 : tmcnulty 1.1 #!/usr/bin/python
2 :     #
3 :     # GKB - GNU Kernel Builder
4 : tmcnulty 1.2 # Copyright (C) 2003-2004 Tobias McNulty, Mark Guertin
5 : tmcnulty 1.1 #
6 :     # This program is free software; you can redistribute it and/or modify
7 :     # it under the terms of the GNU General Public License as published by
8 :     # the Free Software Foundation; either version 2 of the License, or
9 :     # (at your option) any later version.
10 :     #
11 :     # This program is distributed in the hope that it will be useful,
12 :     # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 :     # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 :     # GNU General Public License for more details.
15 :     #
16 :     # You should have received a copy of the GNU General Public License
17 :     # along with this program; if not, write to the Free Software
18 :     # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 :     #
20 :     # Ported from PHP version Copyright (c) Tobias McNulty 2000-2003
21 :    
22 :     import sys, os, ConfigParser
23 :     import xml.parsers.expat
24 :     import md5
25 :     from ftplib import FTP
26 :     from commands import getoutput
27 :     from shutil import copy, move
28 :     from string import split
29 :     from urllib2 import urlopen
30 :     from ClientForm import ParseResponse
31 :    
32 :     debug=0
33 : tmcnulty 1.10 clean=0
34 :     dont_build=1
35 : tmcnulty 1.1 verbose=1
36 :    
37 :     # parse host-specific configuration information from gkb.cfg
38 :     config = ConfigParser.ConfigParser()
39 :     config.readfp(open('gkb-host.cfg'))
40 :     buildroot=config.get("options","buildroot")
41 :     host=config.get("options","host")
42 :     passwd=config.get("options","passwd")
43 :    
44 :     # this one is passed verbatim before the make command. might be useful for alternate toolchains, etc
45 :     # example: premake="HOST=powerpc-unknown-linux-gnu"
46 :     premake=config.get("options","premake")
47 :     # make options, passed verbatim after make command
48 :     makeopts=config.get("options","makeopts")
49 :    
50 :     # parse manager-related configuration from gkb-manager.cfg
51 :     config.readfp(open('gkb-manager.cfg'))
52 :     msite=config.get('options','msite')
53 :    
54 :     # these values will likely stay fairly static across different builds
55 :     patchdir="%s/patches" % buildroot # full path to patch file dir
56 :     configdir="%s/configs" % buildroot # path to where configs are to be stored
57 :     logdir="%s/logs" % buildroot # the logs will go here
58 :     bindir="%s/bin" % buildroot # the directory that binaries built will be sent for pkging
59 :     workdir="%s/work" % buildroot # work directory, where builds take place
60 :    
61 :     # setup the global dicts to hold build data, imported from xml
62 :     mastertrees={}
63 :     builds={}
64 :    
65 : tmcnulty 1.7 class Error(Exception):
66 :     """Base class for exceptions in this module."""
67 :     pass
68 :    
69 :     class BuildError(Error):
70 :     """Exception raised for build-level errors.
71 :    
72 :     Attributes:
73 :     message -- explanation of the error
74 :     """
75 :    
76 :     def __init__(self, message):
77 :     self.message = message
78 :    
79 :     class FatalError(Error):
80 :     """Exception for fatal errors that prevent program continuation.
81 :    
82 :     Attributes:
83 :     message -- explanation of why the specific transition is not allowed
84 :     """
85 :    
86 : tmcnulty 1.12 def __init__(self, message):
87 : tmcnulty 1.7 self.message = message
88 :    
89 : tmcnulty 1.1 def printverbose(myoutput):
90 :     """Supporting method to output info to stdout if verbosity level is set"""
91 :     if verbose==1:
92 :     print myoutput
93 :    
94 : tmcnulty 1.16 def linkpipes(input, output):
95 :     data = input.read(2048)
96 :     while data:
97 :     output.write(data)
98 : tmcnulty 1.18 data = input.read(2048)
99 :    
100 : tmcnulty 1.16 def runcmd(cmd, outfile=None, append=False, infile=None):
101 : tmcnulty 1.14 log("running " + cmd)
102 : tmcnulty 1.16
103 :     (pin, pouterr) = os.popen4(cmd)
104 :    
105 :     if infile != None:
106 :     inobj = open(infile, "r")
107 :     linkpipes(inobj, pin)
108 :     inobj.close()
109 :    
110 :     if outfile != None:
111 :     if append:
112 : tmcnulty 1.18 print "Alert: appending"
113 : tmcnulty 1.16 mode = "a"
114 :     else:
115 :     mode = "w"
116 :    
117 :     outobj = open(outfile, mode)
118 : tmcnulty 1.18 linkpipes(pouterr,outobj)
119 : tmcnulty 1.16 outobj.close()
120 :    
121 : tmcnulty 1.18 err = pouterr.close()
122 : tmcnulty 1.16
123 :     if err == None:
124 :     err = 0
125 : tmcnulty 1.17
126 : tmcnulty 1.15 log("result code: %i" % err)
127 : tmcnulty 1.14 return err
128 :    
129 : tmcnulty 1.1 def log(text,kinfo=0):
130 :     if kinfo:
131 :     printverbose(kinfo["name"] + " : " + text)
132 :     else:
133 :     printverbose("gkb : " + text)
134 :    
135 :     def verifydir(mydir,kinfo=0):
136 :     """Supporting method to verify a given exists, if not it will create it"""
137 :     log("verifying directory " + mydir,kinfo)
138 :     if not os.path.isdir(mydir):
139 :     log("%s doesn't exist, creating" % mydir,kinfo)
140 :     os.mkdir(mydir)
141 :     return mydir
142 :    
143 :     def chdir(mydir,kinfo):
144 :     verifydir(mydir,kinfo)
145 :     log("entering "+mydir,kinfo)
146 :     os.chdir(mydir)
147 :    
148 :     def verifyfile(myfile,kinfo):
149 :     """Supporting method to verify file exists, if not it will exit with error"""
150 :     if not os.path.isfile(myfile):
151 : tmcnulty 1.7 raise BuildError("cannot find file expected at %s, exiting" % myfile)
152 : tmcnulty 1.1 return myfile
153 :    
154 :     def md5sum(fileobj):
155 :     """calculates the md5sum for fileobj and returns it in hexadecimal"""
156 :     md = md5.new()
157 :     data = fileobj.read(8192)
158 :     while (data):
159 :     md.update(data)
160 :     data = fileobj.read(8192)
161 :    
162 :     digest = md.hexdigest()
163 :     return digest
164 :    
165 :     def krn_querymgr(command,kinfo):
166 :     """queries the build host manager with a variety of commands, such as checkout, checkin, etc."""
167 :     version=krn_localversion(kinfo)
168 :     log("querying distribution site manager with command '%s' (kernel version=%s)" % (command,version),kinfo)
169 :     result=getoutput("wget --quiet --output-document=- \"%s/manager.php?cmd=%s&host=%s&pass=%s&build=%s&version=%s\"" % (msite,command,host,passwd,kinfo["name"],version))
170 :     log("result: '"+result+"'",kinfo)
171 :     return result=="1"
172 :    
173 :     def krn_localversion(kinfo):
174 :     """Checks the version of the local source tree specified in kinfo"""
175 :     version=getoutput("""awk -F '=' '/^VERSION/{v=$2} /^PATCHLEVEL/{p=$2} /^SUBLEVEL/{s=$2} /^EXTRAVERSION/{e=$2} END { printf("%s.%s.%s%s\\n", v, p, s, e) }' """ + kinfo["workdir"] + """/Makefile | sed "s/ //g" """)
176 :     return version
177 :    
178 :     def gkb_build(root,kinfo):
179 :     """performs actual kernel builds given root, kinfo (tuple containing relevant data) , premake and makeopts"""
180 :    
181 :     # make sure needed directoriess (for this build) exist, if not create them
182 :    
183 :     verifydir("%s/%s" % (logdir, kinfo["name"]),kinfo)
184 :     verifydir("%s/%s" % (patchdir, kinfo["name"]),kinfo)
185 :    
186 :     # sync the source to make sure we are up to date ...
187 :     log("calling gkb_getsource",kinfo)
188 :     gkb_getsource(kinfo)
189 :    
190 : tmcnulty 1.10 # go into the work directory
191 : tmcnulty 1.12 chdir(workdir, kinfo)
192 : tmcnulty 1.10
193 :     # archive the clean source for later uploading
194 : tmcnulty 1.12 log("archiving source to " + kinfo["mastertree"] + ".tar.bz2", kinfo)
195 : tmcnulty 1.17 runcmd("tar cjf " + kinfo["mastertree"] + ".tar.bz2 " + kinfo["mastertree"])
196 : tmcnulty 1.10
197 :     # now we'll go into the tree's work dir
198 : tmcnulty 1.1 chdir(kinfo["workdir"],kinfo)
199 :    
200 :     # check for patches and apply if necessary
201 :     gkb_patch(kinfo)
202 :    
203 :     if kinfo["type"] == "kernel24":
204 :     krn_build24(kinfo)
205 :     elif kinfo["type"] == "kernel26":
206 :     krn_build24(kinfo) #run kernel24 for now, change later
207 :    
208 :     # Gerk comment:
209 :     # Do we need to change this with 24/26? we probably don't ... I'd rather see us define the list of make targets
210 :     # i.e. "dep","clean","vmlinux","modules","modules_install" or "clean","all","modules_install" for 2.6
211 :     # also of note, make all is not what we will always want with 2.6 kernels ... i.e. zImage.prep, zImage.chrp, etc
212 :     # also for backward compat they will continue to support calling things individual .. the makefile just does
213 :     # this for us with 'all'
214 :    
215 :    
216 :     def krn_build24(kinfo):
217 :     """build a kernel, version 2.4.x"""
218 :    
219 :     if krn_querymgr("checkout",kinfo):
220 : tmcnulty 1.7 try:
221 :     myversion=krn_localversion(kinfo)
222 :    
223 :     # fetch and cp the config file to work/.config
224 :     krn_config(kinfo)
225 :    
226 :     # **Note** : we set the preprocessing command to premake inline instead
227 :     # of globally as it is only needed in this target
228 :     if dont_build==0:
229 :     gkb_runmake("oldconfig",kinfo,premake+" /bin/cat %s/newlines | " % buildroot,makeopts)
230 :    
231 :     # give option to only repackage for testing purposes, comment out clean=1 at top of this file to use this feature
232 :     if (clean==1 and dont_build==0):
233 :     gkb_runmake("clean", kinfo, premake, makeopts)
234 :    
235 :     if dont_build==0:
236 :     gkb_runmake("dep", kinfo, premake, makeopts)
237 :     gkb_runmake(kinfo["binname"], kinfo, premake, makeopts)
238 :    
239 :     # We should check to see if binary built ok, if not bail out
240 :     mybindir=bindir+"/linux-"+kinfo["name"]+"-"+myversion
241 :     verifydir(mybindir,kinfo)
242 :     verifydir(mybindir+"/boot",kinfo)
243 :    
244 :     kbinloc=kinfo["workdir"]+"/"+kinfo["binpath"]+"/"+kinfo["binname"]
245 :     if verifyfile(kbinloc,kinfo):
246 :     # the binary exists, so let's cp it to bin...
247 :     copy(kbinloc,mybindir+"/boot/"+kinfo["binname"]+"-"+myversion)
248 :     else:
249 :     # the binary is not there, inform user and bail out with error
250 :     raise BuildError("%s is not present, assuming build failure and exiting. See log for details." % kbinloc)
251 :    
252 :     # now that we know he binary built, let's continue
253 :     if dont_build==0:
254 :     gkb_runmake("modules",kinfo, premake, makeopts)
255 :    
256 :     # **Note** : we prepend the INSTALL_MOD_PATH to the makeopts inline instead
257 :     # of globally as it is only needed in this target
258 :     gkb_runmake("modules_install", kinfo, premake, "INSTALL_MOD_PATH=%s %s" % (mybindir, makeopts))
259 :    
260 : tmcnulty 1.10 # compress and upload source archive
261 :     chdir(kinfo["workdir"]+"/..",kinfo)
262 :     archive_name = "src-%s.tar.bz2" % kinfo["mastertree"],
263 :    
264 :     # compress and upload kernel binary
265 : tmcnulty 1.7 chdir(bindir,kinfo)
266 :     archive_name = "linux-%s-%s.tar.bz2" % (kinfo["name"], myversion)
267 :    
268 :     log("compressing binary archive "+archive_name,kinfo)
269 :    
270 : tmcnulty 1.16 if runcmd("tar cjf "+archive_name+" "+os.path.basename(mybindir)):
271 : tmcnulty 1.7 raise BuildError("failed to `tar cjf %s`" % archive_name)
272 :    
273 : tmcnulty 1.16 runcmd("rm -rf %s" % mybindir)
274 : tmcnulty 1.7
275 : tmcnulty 1.10 krn_upload(archive_name,"kernel",myversion,kinfo)
276 :     krn_querymgr("checkin",kinfo)
277 :    
278 :     if (os.fork() == 0):
279 :     #in child
280 :     krn_upload(workdir + "/" + kinfo["mastertree"] + ".tar.bz2", "source", myversion, kinfo)
281 :    
282 : tmcnulty 1.7 except BuildError, e:
283 :     log(e.message, kinfo)
284 :     krn_querymgr("checkin",kinfo)
285 : tmcnulty 1.8 except:
286 :     krn_querymgr("checkin",kinfo)
287 :     raise
288 :    
289 : tmcnulty 1.1 def krn_build26(kinfo):
290 :     """build a kernel, version 2.6"""
291 :    
292 :     # source get routines
293 :     def gkb_getsource(kinfo):
294 :     """method to perform source sync on demand, currenty only supports rsync, but others can be added"""
295 :    
296 :     method=mastertrees[kinfo["mastertree"]]["method"]
297 :    
298 :     if method == "rsync":
299 :     get_rsync(kinfo)
300 :     elif method == "wget":
301 :     get_wget(kinfo)
302 :     elif method == "vanilla":
303 :     get_vanilla(kinfo)
304 :    
305 :     def get_rsync(kinfo):
306 :     """sync the source using rsync"""
307 :     args=mastertrees[kinfo["mastertree"]]["args"]
308 :    
309 :     if clean==1:
310 :     syncoptions="rsync -azv --delete"
311 :     else:
312 :     syncoptions="rsync -azv"
313 :    
314 :     syncline=args+" "+kinfo["workdir"]
315 :    
316 :     log("running rsync : %s %s" % (syncoptions, syncline),kinfo)
317 : tmcnulty 1.16 if runcmd("%s %s" % (syncoptions, syncline), "%s/%s/rsync.log" % (logdir, kinfo["name"])):
318 : tmcnulty 1.7 raise BuildError("sync failed, tried %s. See log for details." % syncline)
319 : tmcnulty 1.1
320 :     def get_wget(kinfo):
321 :     #needs work
322 :     """sync the source using wget and a tar.bz2"""
323 :     args=mastertrees[kinfo["mastertree"]]["args"]
324 :    
325 :     log("fetching source archive " % args,kinfo)
326 :     mysourcefile="%s/%s.tar.bz2" % (kinfo["workdir"],kinfo["name"])
327 :    
328 : tmcnulty 1.16 if runcmd("wget --quiet --output-document=%s %s/configs/%s" % (myconfigfile,msite,kinfo["name"])):
329 : tmcnulty 1.7 raise BuildError("unable to download configfile, aborting.")
330 : tmcnulty 1.1
331 :     log("decompressing source file",kinfo)
332 : tmcnulty 1.16 if runcmd("tar xjf " % (myconfigfile,msite,kinfo["name"])):
333 : tmcnulty 1.7 raise BuildError("unable to decompress source file, aborting.")
334 : tmcnulty 1.1
335 :     def get_vanilla(kinfo):
336 :     args=mastertrees[kinfo["mastertree"]]["args"]
337 :    
338 :     log("fetching source archive %s" % args)
339 :     mysourcefile="%s/%s.tar.bz2" % (workdir,kinfo["name"])
340 :    
341 : tmcnulty 1.16 if runcmd("wget -c --output-document=%s %s" % (mysourcefile,args)):
342 : tmcnulty 1.7 raise BuildError("unable to download source file %s, aborting." % args)
343 : tmcnulty 1.1
344 :     log("decompressing source file %s" % mysourcefile,kinfo)
345 :    
346 :     #save the current working ectory
347 :     cwd=getoutput("pwd")
348 :    
349 :     chdir(workdir,kinfo)
350 :     pfd=os.popen("tar vxjf %s" % mysourcefile,"r")
351 :     trash=data=pfd.read(255)
352 :    
353 :     while trash:
354 :     trash=pfd.read(4096)
355 :    
356 :     if pfd.close():
357 : tmcnulty 1.7 raise BuildError("unable to decompress source file, aborting.")
358 : tmcnulty 1.1
359 :     fnames=split(data)
360 :     name=fnames[0]
361 :    
362 : tmcnulty 1.16 runcmd("rm -rf %s" % kinfo["workdir"])
363 : tmcnulty 1.1 move(work + "/" + dirname, kinfo["workdir"])
364 :    
365 :     #restore the previous working ectory
366 :     chdir(cwd,kinfo)
367 :    
368 :     def gkb_patch(kinfo):
369 :     """method that applies patch found in kinfo config files for running kernel build"""
370 :     if kinfo["patches"]:
371 :     patches=split(kinfo["patches"],";")
372 :    
373 :     for patch in patches:
374 :     log("downloading patch %s" % patch,kinfo)
375 :    
376 :     #set a name for this patch file
377 :     mypatchfile="%s/%s/%s.patch" % (patchdir,kinfo["name"],patch)
378 :    
379 :     # download pathfile or bail
380 : tmcnulty 1.16 if runcmd("wget --quiet --output-document=%s %s/patches/%s/%s" % (mypatchfile,msite,kinfo["name"],patch)):
381 : tmcnulty 1.7 raise BuildError("unable to download patchfile %s, aborting." % patch)
382 : tmcnulty 1.1
383 :     # we have a patch file, apply it or bail
384 :     log("perfoming patch with %s" % patch,kinfo)
385 : tmcnulty 1.16
386 :     # former patchcommand was:
387 :     #patchcommand="patch -p1 < %s > %s/%s/patch-%s.log 2>&1" % (mypatchfile,logdir,kinfo["name"],patch)
388 :     #log("using %s from %s" % (patchcommand,kinfo["workdir"]),kinfo)
389 :    
390 : tmcnulty 1.1 chdir(kinfo["workdir"],kinfo)
391 : tmcnulty 1.16 if runcmd("patch -p1", "%s/%s/patch-%s.log" % (logdir,kinfo["name"],patch), False, mypatchfile):
392 : tmcnulty 1.7 raise BuildError("patchfile %s failed, aborting. See patch log for details." % patch)
393 : tmcnulty 1.1 else:
394 :     log("no patchfiles, continuing",kinfo)
395 :    
396 :     def krn_config(kinfo):
397 :     """method to fetch and place config file for running kernel build"""
398 :    
399 :     if kinfo["config"]==1:
400 :     log("fetching config file",kinfo)
401 :     myconfigfile="%s/%s.config" % (configdir,kinfo["name"])
402 :    
403 : tmcnulty 1.16 if runcmd("wget --quiet --output-document=%s %s/configs/%s" % (myconfigfile,msite,kinfo["name"])):
404 : tmcnulty 1.7 raise BuildError("unable to download configfile, aborting.")
405 : tmcnulty 1.1
406 :     log("copying config file to %s/.config" % kinfo["workdir"],kinfo)
407 :     copy(verifyfile(myconfigfile,kinfo),"%s/.config" % kinfo["workdir"])
408 :    
409 :     # Methods to support build()
410 :     def gkb_runmake(command, kinfo, premake, makeopts):
411 :     """Supporting method for build() ... a stub to run a make target and auto log it, given make target (command) and name (kernel name)"""
412 :     log("running make %s" % command,kinfo)
413 : tmcnulty 1.16 if runcmd("%s make %s %s" % (premake, makeopts, command), "%s/%s/make-%s.log" % (logdir, kinfo["name"], command)):
414 : tmcnulty 1.7 raise BuildError("unable to run make %s, aborting." % command)
415 : tmcnulty 1.1
416 :     def gkb_parsexml(name):
417 :     """parse the xml build file into mastertrees and builds"""
418 :     def start_element(name, attrs):
419 :     #print "In start_element: %s %s" % (name, attrs)
420 :     if name=="mastertree":
421 :     mastertrees[attrs["name"]]=attrs
422 :     elif name=="build":
423 :     builds[attrs["name"]]=attrs
424 :    
425 :     p = xml.parsers.expat.ParserCreate()
426 :     p.StartElementHandler = start_element
427 :     p.ParseFile(open(name))
428 : tmcnulty 1.3
429 : tmcnulty 1.10 def krn_upload(file, type, version, kinfo):
430 : tmcnulty 1.3 """upload the indicated file to the distribution site (kernel archives)"""
431 : tmcnulty 1.10
432 :     if type=="kernel":
433 :     log("uploading kernel version "+version+" to "+msite,kinfo)
434 :     elif type=="source":
435 :     log("uploading source "+kinfo["mastertree"]+" to "+msite,kinfo)
436 :     else:
437 :     raise BuildException, "invalid file upload type: " + type
438 :    
439 :     forms = ParseResponse(urlopen(msite+"/upload.html"))
440 : tmcnulty 1.3 form = forms[0]
441 :    
442 :     form["host"] = host
443 :     form["pass"] = passwd
444 : tmcnulty 1.10 form["type"] = type
445 :     form["tree"] = kinfo["mastertree"]
446 : tmcnulty 1.3 form["build"] = kinfo["name"]
447 :     form["version"] = version
448 :    
449 : tmcnulty 1.5 form.add_file(open(file), "application/x-bzip2", os.path.basename(file))
450 : tmcnulty 1.10
451 : tmcnulty 1.3 # form.click() returns a urllib2.Request object
452 :     # (see HTMLForm.click.__doc__ if you don't have urllib2)
453 : tmcnulty 1.10 response = urlopen(form.click("cmd"))
454 : tmcnulty 1.3
455 : tmcnulty 1.10 if debug:
456 :     print response.geturl()
457 :     print response.info() # headers
458 :     print response.read() # body
459 :    
460 :     log("response: " + response.read(),kinfo)
461 : tmcnulty 1.3
462 : tmcnulty 1.10 response.close()
463 :    
464 : tmcnulty 1.1 def main():
465 :     """ main program gets executed here """
466 :    
467 :     # get our working ectory
468 :     root=getoutput("pwd")
469 :    
470 :     today=getoutput("date +%D")
471 :     buildtime=getoutput("date +'%R:%S %Z'")
472 :    
473 :     print "GKB started %s %s" % (today,buildtime)
474 :    
475 :     # verify the existence important directories, and create if necessary
476 :     verifydir(logdir)
477 :     verifydir(configdir)
478 :     verifydir(patchdir)
479 :     verifydir(bindir)
480 :     verifydir("%s/work" % buildroot) # build dir, make sure it exists
481 :    
482 :     # download the build jobs from the master site
483 : tmcnulty 1.16 if runcmd("wget --quiet --output-document=gkb.xml \"%s/manager.php?cmd=getjobs&host=%s&pass=%s\"" % (msite,host,passwd)):
484 : tmcnulty 1.12 raise FatalError, "Unable to download build config from master site %s, aborting." % msite
485 : tmcnulty 1.1
486 :     # sets up 'mastertrees' and 'builds' dicts
487 :     gkb_parsexml('gkb.xml')
488 :    
489 :     for bdict in builds.values():
490 :     myworkdir="%s/%s" % (workdir,bdict["mastertree"])
491 :     bdict["workdir"]=myworkdir
492 :    
493 : tmcnulty 1.9 try:
494 :     # for now just call the build
495 :     gkb_build(root, bdict)
496 :     except BuildError, e:
497 :     log(e.message, bdict)
498 : tmcnulty 1.12 except KeyboardInterrupt, e:
499 :     log("Caught keyboard interrupt, exiting...")
500 :     break
501 : tmcnulty 1.13
502 : tmcnulty 1.1 endtime=getoutput("date +'%R:%S %Z'")
503 :    
504 :     print "GKB finished %s %s" % (today,endtime)
505 :    
506 :     # and finally, call the mainloop to execute
507 :     main()

Tobias McNulty

Powered by ViewCVS 1.0-dev
(Powered by ViewCVS)

ViewCVS and CVS Help