ia64/xen-unstable

view buildconfigs/ketchup @ 18836:ab0c1bdede53

Merge with ia64 tree.
author Keir Fraser <keir.fraser@citrix.com>
date Wed Nov 26 11:14:26 2008 +0000 (2008-11-26)
parents 8426e8a36290
children
line source
1 #!/usr/bin/python
2 #
3 # ketchup 0.9.8
4 # http://selenic.com/ketchup/wiki
5 #
6 # Copyright 2004 Matt Mackall <mpm@selenic.com>
7 #
8 # This software may be used and distributed according to the terms
9 # of the GNU General Public License, incorporated herein by reference.
10 #
11 # Usage:
12 #
13 # in an existing kernel directory, run:
14 #
15 # ketchup <version>
16 #
17 # where version is a complete kernel version, or a branch name to grab
18 # the latest version
19 #
20 # You can override some variables by creating a ~/.ketchuprc file.
21 # The ~/.ketchuprc is just a Python script, eg. it might look like this:
22 #
23 # kernel_url = 'http://kernel.localdomain/pub/linux/kernel'
24 # archive = os.environ["HOME"] + '/tmp/ketchup-archive'
25 # gpg = '/weird/path/to/gpg'
26 #
28 import re, sys, urllib, os, getopt, glob, shutil
30 def error(*args):
31 sys.stderr.write("ketchup: ")
32 for a in args:
33 sys.stderr.write(str(a))
34 sys.stderr.write("\n")
36 def qprint(*args):
37 if not options["quiet"]:
38 sys.stdout.write(" ".join(map(str, args)))
39 sys.stdout.write("\n")
41 def lprint(*args):
42 sys.stdout.write(" ".join(map(str, args)))
43 sys.stdout.write("\n")
46 def fancyopts(args, options, state, syntax=''):
47 long = []
48 short = ''
49 map = {}
50 dt = {}
52 def help(state, opt, arg, options = options, syntax = syntax):
53 lprint("Usage: ", syntax)
55 for s, l, d, c in options:
56 opt = ' '
57 if s: opt = opt + '-' + s + ' '
58 if l: opt = opt + '--' + l + ' '
59 if d: opt = opt + '(' + str(d) + ')'
60 lprint(opt)
61 if c: lprint(' %s' % c)
62 sys.exit(0)
64 options = [('h', 'help', help, 'Show usage info')] + options
66 for s, l, d, c in options:
67 map['-'+s] = map['--'+l]=l
68 state[l] = d
69 dt[l] = type(d)
70 if not d is None and not type(d) is type(help): s, l = s + ':', l + '='
71 if s: short = short + s
72 if l: long.append(l)
74 if os.environ.has_key("KETCHUP_OPTS"):
75 args = os.environ["KETCHUP_OPTS"].split() + args
77 try:
78 opts, args = getopt.getopt(args, short, long)
79 except getopt.GetoptError:
80 help(state, None, args)
81 sys.exit(-1)
83 for opt, arg in opts:
84 if dt[map[opt]] is type(help): state[map[opt]](state,map[opt],arg)
85 elif dt[map[opt]] is type(1): state[map[opt]] = int(arg)
86 elif dt[map[opt]] is type(''): state[map[opt]] = arg
87 elif dt[map[opt]] is type([]): state[map[opt]].append(arg)
88 elif dt[map[opt]] is type(None): state[map[opt]] = 1
90 return args
92 # Default values
93 kernel_url = 'http://www.kernel.org/pub/linux/kernel'
94 archive = os.environ["HOME"] + "/.ketchup"
95 rename_prefix = 'linux-'
96 rename_with_localversion = False
97 wget = "/usr/bin/wget"
98 gpg = "/usr/bin/gpg"
99 precommand = postcommand = None
100 default_tree = None
101 local_trees = {}
103 # Functions to parse version strings
105 def tree(ver):
106 return float(re.match(r'(\d+\.\d+)', ver).group(1))
108 def rev(ver):
109 p = pre(ver)
110 r = int(re.match(r'\d+\.\d+\.(\d+)', ver).group(1))
111 if p: r = r - 1
112 return r
114 def pre(ver):
115 try: return re.match(r'\d+\.\d+\.\d+(\.\d+)?-((rc|pre)\d+)', ver).group(2)
116 except: return None
118 def post(ver):
119 try: return re.match(r'\d+\.\d+\.\d+\.(\d+)', ver).group(1)
120 except: return None
122 def pretype(ver):
123 try: return re.match(r'\d+\.\d+\.\d+(\.\d+)?-((rc|pre)\d+)', ver).group(3)
124 except: return None
126 def prenum(ver):
127 try: return int(re.match(r'\d+\.\d+\.\d+-((rc|pre)(\d+))', ver).group(3))
128 except: return None
130 def prebase(ver):
131 return re.match(r'(\d+\.\d+\.\d+((-(rc|pre)|\.)\d+)?)', ver).group(1)
133 def revbase(ver):
134 return "%s.%s" % (tree(ver), rev(ver))
136 def base(ver):
137 v = revbase(ver)
138 if post(ver): v += "." + post(ver)
139 return v
141 def forkname(ver):
142 try: return re.match(r'\d+.\d+.\d+(\.\d+)?(-(rc|pre)\d+)?(-(\w+?)\d+)?',
143 ver).group(5)
144 except: return None
146 def forknum(ver):
147 try: return int(
148 re.match(r'\d+.\d+.\d+(\.\d+)?(-(rc|pre)\d+)?(-(\w+?)(\d+))?',
149 ver).group(6))
150 except: return None
152 def fork(ver):
153 try: return re.match(r'\d+.\d+.\d+(\.\d+)?(-(rc|pre)\d+)?(-(\w+))?', ver).group(4)
154 except: return None
156 def get_ver(makefile):
157 """ Read the version information from the specified makefile """
158 part = {}
159 parts = "VERSION PATCHLEVEL SUBLEVEL EXTRAVERSION".split(' ')
160 m = open(makefile)
161 for l in m.readlines():
162 for p in parts:
163 try: part[p] = re.match(r'%s\s*=\s*(\S+)' % p, l).group(1)
164 except: pass
166 version = "%s.%s.%s" % tuple([part[p] for p in parts[:3]])
167 version += part.get("EXTRAVERSION","")
168 return version
170 def get_localversion():
171 v = ''
173 for name in glob.glob('localversion*'):
174 try: v += open(name).readline().strip()
175 except: pass
177 try:
178 c = open('.config').read()
179 v += re.search(r'^CONFIG_LOCALVERSION="(.+)"', c, re.M).group(1)
180 except: pass
182 return v
184 def compare_ver(a, b):
185 """
186 Compare kernel versions a and b
188 Note that -pre and -rc versions sort before the version they modify,
189 -pre sorts before -rc, -bk, -git, and -mm, etc. sort alphabetically.
190 """
191 if a == b: return 0
193 c = cmp(float(tree(a)), float(tree(b)))
194 if c: return c
195 c = cmp(rev(a), rev(b))
196 if c: return c
197 c = cmp(int(post(a) or 0), int(post(b) or 0))
198 if c: return c
199 c = cmp(pretype(a), pretype(b)) # pre sorts before rc
200 if c: return c
201 c = cmp(prenum(a), prenum(b))
202 if c: return c
203 c = cmp(forkname(a), forkname(b))
204 if c: return c
205 return cmp(forknum(a), forknum(b))
207 def last(url, pat="(.*/)"):
208 for l in urllib.urlopen(url).readlines():
209 m = re.search('(?i)<a href="%s">' % pat, l)
210 if m: n = m.group(1)
211 return n
213 def latest_mm(url, pat):
214 url = kernel_url + '/people/akpm/patches/2.6/'
215 url += last(url)
216 part = last(url)
217 return part[:-1]
219 def latest_ck(url, pat):
220 url = "http://ck.kolivas.org/patches/2.6/pre-releases/"
221 url += last(url)
222 part = last(url)
223 pre = part[:-1]
225 url = "http://ck.kolivas.org/patches/2.6/"
226 url += last(url,"(2.6.*/)")
227 part = last(url)
228 rel = part[:-1]
230 l = [pre, rel]
231 l.sort(compare_ver)
232 return l[-1]
234 def latest_dir(url, pat):
235 """Find the latest link matching pat at url after sorting"""
236 p = []
237 for l in urllib.urlopen(url).readlines():
238 m = re.search('"%s"' % pat, l)
239 if m: p.append(m.group(1))
241 if not p: return None
243 p.sort(compare_ver)
244 return p[-1]
246 # mbligh is lazy and has a bunch of empty directories
247 def latest_mjb(url, pat):
248 url = kernel_url + '/people/mbligh/'
250 # find the last Linus release and search backwards
251 l = [find_ver('2.6'), find_ver("2.6-pre")]
252 l.sort(compare_ver)
253 linus = l[-1]
255 p = []
256 for l in urllib.urlopen(url).readlines():
257 m = re.search('"(2\.6\..*/)"', l)
258 if m:
259 v = m.group(1)
260 if compare_ver(v, linus) <= 0:
261 p.append(v)
263 p.sort(compare_ver)
264 p.reverse()
266 for ver in p:
267 mjb = latest_dir(url + ver, pat)
268 if mjb: return mjb
270 return None
272 def latest_26_tip(url, pat):
273 l = [find_ver('2.6'), find_ver('2.6-git'), find_ver('2.6-pre')]
274 l.sort(compare_ver)
275 return l[-1]
277 def find_info(ver):
278 b = "%.1f" % tree(ver)
279 f = forkname(ver)
280 p = pre(ver)
282 s = b
283 if f:
284 s = "%s-%s" % (b, f)
285 elif p:
286 s = "%s-pre" % b
288 return version_info[s]
290 def version_urls(ver):
291 """ Return the URL for the patch associated with the specified version """
292 i = find_info(ver)[1]
293 if type(i) != type([]):
294 i = [i]
296 v = {
297 'full': ver,
298 'tree': tree(ver),
299 'base': base(ver),
300 'prebase': prebase(ver)
301 }
303 l = []
304 for e in i:
305 l.append(e % v)
307 return l
309 def patch_path(ver):
310 return os.path.join(archive, os.path.basename(version_urls(ver)[0]))
312 def download(url, f):
313 qprint("Downloading %s" % os.path.basename(url))
314 if options["dry-run"]:
315 return 1
317 if not options["wget"]:
318 p = urllib.urlopen(url).read()
319 if p.find("<title>404") != -1:
320 return None
321 open(f, 'w').write(p)
322 else:
323 e = os.system("%s -c -O %s %s" %
324 (options["wget"], f + ".partial", url))
325 if e:
326 return None
327 os.rename(f + ".partial", f)
329 return 1
331 def verify(url, f, sign):
332 if options["no-gpg"] or options["dry-run"] or not options["gpg-path"]:
333 return 1
335 sf = f + sign
336 if not download(url + sign, sf):
337 error("signature download failed")
338 error("removing files...")
339 os.unlink(f)
340 return 0
342 qprint("Verifying signature...")
343 r = os.system("%s --verify %s %s" % (options["gpg-path"], sf, f))
344 if r:
345 error("gpg returned %d" % r)
346 error("removing files...")
347 os.unlink(f)
348 os.unlink(sf)
349 return 0
351 return 1
353 def trydownload(urls, f, sign):
354 for url in urls:
355 if download(url, f):
356 if not sign or verify(url, f, sign):
357 return f
358 if url[-4:] == ".bz2":
359 f2 = f[:-4] + ".gz"
360 url2 = url[:-4] + ".gz"
361 if download(url2, f2):
362 if not sign or verify(url2, f2, sign):
363 return f2
364 return None
366 def get_patch(ver):
367 """Return the path to patch for given ver, downloading if necessary"""
368 f = patch_path(ver)
369 if os.path.exists(f):
370 return f
371 if f[-4:] == ".bz2":
372 f2 = f[:-4] + ".gz"
373 if os.path.exists(f2):
374 return f2
376 urls = version_urls(ver)
377 sign = find_info(ver)[3]
378 if sign == 1: sign = ".sign"
379 f = trydownload(urls, f, sign)
380 if not f:
381 error("patch download failed")
382 sys.exit(-1)
384 return f
386 def apply_patch(ver, reverse = 0):
387 """Find the patch to upgrade from the predecessor of ver to ver and
388 apply or reverse it."""
389 p = get_patch(ver)
390 r = ""
391 if reverse:
392 r = " -R"
394 qprint("Applying %s%s" % (os.path.basename(p), r))
395 if options["dry-run"]:
396 return ver
398 def cmd(patch, reverse, dry):
399 base = "patch -l -p1%s" % reverse
400 if dry:
401 base += " --dry-run"
403 if p[-4:] == ".bz2":
404 pipe = "bzcat %s | %s" % (patch, base)
405 elif p[-3:] == ".gz":
406 pipe = "zcat %s | %s" % (patch, base)
407 else:
408 pipe = "%s < %s" % (base, patch)
410 err = os.system(pipe + " > .patchdiag")
411 if err:
412 sys.stderr.write(open(".patchdiag").read())
413 os.unlink(".patchdiag")
414 return err
416 err = cmd(p, r, 1)
417 if err:
418 error("patch %s failed: %d" % (p, err))
419 sys.exit(-1)
421 err = cmd(p, r, 0)
422 if err:
423 error("patch %s failed while it was supposed to apply: %d" % (p, err))
424 sys.exit(-1)
426 def untar(tarfile):
427 old = os.getcwd()
428 os.mkdir("ketchup-tmp")
429 os.chdir("ketchup-tmp")
431 err = os.system("bzcat %s | tar -xf -" % tarfile)
432 if err:
433 error("Unpacking failed: ", err)
434 sys.exit(-1)
436 err = os.system("mv linux*/* linux*/.[^.]* ..; rmdir linux*")
437 if err:
438 error("Unpacking failed: ", err)
439 sys.exit(-1)
441 os.chdir(old)
442 shutil.rmtree("ketchup-tmp")
444 def install_nearest(ver):
445 t = tree(ver)
446 tarballs = glob.glob(archive + "/linux-%s.*.tar.bz2" % t)
447 list = []
449 for f in tarballs:
450 m = re.match(r'.*/linux-(.*).tar.bz2$', f)
451 v = m.group(1)
452 d = abs(rev(v) - rev(ver))
453 list.append((d, f, v))
454 list.sort()
456 if not list or (options["full-tarball"] and list[0][0]):
457 f = "linux-%s.tar.bz2" % ver
458 url = "%s/v%s/%s" % (kernel_url, t, f)
459 f = archive + "/" + f
461 sign = find_info(ver)[3]
462 if sign == 1: sign = ".sign"
464 f = trydownload([url], f, sign)
465 if not f:
466 error("Tarball download failed")
467 sys.exit(-1)
469 else:
470 f = list[0][1]
471 ver = list[0][2]
473 qprint("Unpacking %s" % os.path.basename(f))
474 if options["dry-run"]: return ver
475 untar(f)
477 return ver
479 def find_ver(ver):
480 if ver in version_info.keys():
481 v = version_info[ver]
482 d = v[1]
483 if type(d) is type([]):
484 d = d[0]
485 for n in range(5):
486 return v[0](os.path.dirname(d), v[2])
487 error('retrying version lookup for %s' % ver)
488 else:
489 return ver
491 def transform(a, b):
492 if a == b:
493 qprint("Nothing to do!")
494 return
495 if not a:
496 a = install_nearest(base(b))
497 t = tree(a)
498 if t != tree(b):
499 error("Can't patch %s to %s" % (tree(a), tree(b)))
500 sys.exit(-1)
501 if fork(a):
502 apply_patch(a, 1)
503 a = prebase(a)
504 if prebase(a) != prebase(b):
505 if pre(a):
506 apply_patch(a, 1)
507 a = base(a)
509 if post(a) and post(a) != post(b):
510 apply_patch(prebase(a), 1)
512 ra, rb = rev(a), rev(b)
513 if ra > rb:
514 for r in range(ra, rb, -1):
515 apply_patch("%s.%s" % (t, r), -1)
516 if ra < rb:
517 for r in range(ra + 1, rb + 1):
518 apply_patch("%s.%s" % (t, r))
519 a = revbase(b)
521 if post(b) and post(a) != post(b):
522 apply_patch(prebase(b), 0)
523 a = base(b)
525 if pre(b):
526 apply_patch(prebase(b))
527 a = prebase(b)
529 if fork(b):
530 a = apply_patch(b)
532 def rename_dir(v):
533 """Rename the current directory to linux-v, where v is the function arg"""
534 if rename_with_localversion:
535 v += get_localversion()
536 cwd = os.getcwd()
537 basedir = os.path.dirname(cwd)
538 newdir = os.path.join(basedir, rename_prefix + v)
539 if newdir == cwd:
540 return
541 if os.access(newdir, os.F_OK):
542 error("Cannot rename directory, destination exists: %s", newdir);
543 return
544 os.rename(cwd, newdir)
545 qprint('Current directory renamed to %s' % newdir)
548 # latest lookup function, canonical urls, pattern for lookup function,
549 # signature flag, description
550 version_info = {
551 '2.4': (latest_dir,
552 kernel_url + "/v2.4" + "/patch-%(base)s.bz2",
553 r'patch-(.*?).bz2',
554 1, "old stable kernel series"),
555 '2.4-pre': (latest_dir,
556 kernel_url + "/v2.4" + "/testing/patch-%(prebase)s.bz2",
557 r'patch-(.*?).bz2',
558 1, "old stable kernel series prereleases"),
559 '2.6': (latest_dir,
560 kernel_url + "/v2.6" + "/patch-%(prebase)s.bz2",
561 r'patch-(.*?).bz2',
562 1, "current stable kernel series"),
563 '2.6-rc': (latest_dir,
564 kernel_url + "/v2.6" + "/testing/patch-%(prebase)s.bz2",
565 r'patch-(.*?).bz2',
566 1, "current stable kernel series prereleases"),
567 '2.6-pre': (latest_dir,
568 kernel_url + "/v2.6" + "/testing/patch-%(prebase)s.bz2",
569 r'patch-(.*?).bz2',
570 1, "current stable kernel series prereleases"),
571 '2.6-git': (latest_dir,
572 [kernel_url + "/v2.6" + "/snapshots/patch-%(full)s.bz2",
573 kernel_url + "/v2.6" + "/snapshots/old/patch-%(full)s.bz2"],
574 r'patch-(.*?).bz2',
575 1, "current stable kernel series snapshots"),
576 '2.6-bk': (latest_dir,
577 [kernel_url + "/v2.6" + "/snapshots/patch-%(full)s.bz2",
578 kernel_url + "/v2.6" + "/snapshots/old/patch-%(full)s.bz2"],
579 r'patch-(.*?).bz2',
580 1, "old stable kernel series snapshots"),
581 '2.6-tip': (latest_26_tip, "", "", 1,
582 "current stable kernel series tip"),
583 '2.6-mm': (latest_mm,
584 kernel_url + "/people/akpm/patches/" +
585 "%(tree)s/%(prebase)s/%(full)s/%(full)s.bz2", "",
586 1, "Andrew Morton's -mm development tree"),
587 '2.6-tiny': (latest_dir,
588 "http://www.selenic.com/tiny/%(full)s.patch.bz2",
589 r'(2.6.*?).patch.bz2',
590 1, "Matt Mackall's -tiny tree for small systems"),
591 '2.6-mjb': (latest_mjb,
592 kernel_url + "/people/mbligh/%(prebase)s/patch-%(full)s.bz2",
593 r'patch-(2.6.*?).bz2',
594 1, "Martin Bligh's random collection 'o crap"),
595 '2.6-rt': (latest_dir,
596 ["http://people.redhat.com/mingo/" +
597 "realtime-preempt/patch-%(full)s",
598 "http://people.redhat.com/mingo/" +
599 "realtime-preempt/older/patch-%(full)s"],
600 r'patch-(2.6.*?)',
601 0, "Ingo Molnar's realtime-preempt kernel"),
602 '2.6-ck': (latest_ck,
603 ["http://ck.kolivas.org/patches/2.6/" +
604 "%(prebase)s/%(full)s/patch-%(full)s.bz2",
605 "http://ck.kolivas.org/patches/2.6/pre-releases/" +
606 "%(prebase)s/%(full)s/patch-%(full)s.bz2"],
607 "", ".sig",
608 "Con Kolivas' patches for system responsiveness (desktop)"),
609 '2.6-cks': (latest_dir,
610 "http://ck.kolivas.org/patches/cks/patch-%(full)s.bz2",
611 r'patch-(2.6.*?).bz2', ".sig",
612 "Con Kolivas' patches for system responsiveness (server)")
613 }
615 # Override defaults with ~/.ketchuprc which is just a Python script
616 rcpath = os.path.expanduser('~/.ketchuprc')
617 if os.path.isfile(rcpath):
618 try:
619 execfile(rcpath)
620 except Exception, e:
621 sys.exit('Failed parsing %s\nError was: %s' % (rcpath, e))
623 # Add local trees
624 for k,v in local_trees.items():
625 version_info[k] = v
627 # Environment variables override defaults and ketchuprc
628 kernel_url = os.environ.get("KETCHUP_URL", kernel_url)
629 archive = os.environ.get("KETCHUP_ARCH", archive)
631 # And finally command line overrides everything
632 if not os.path.exists(wget): wget = ""
633 if not os.path.exists(gpg): gpg = ""
635 options = {}
636 opts = [
637 ('a', 'archive', archive, 'cache directory'),
638 ('d', 'directory', '.', 'directory to update'),
639 ('f', 'full-tarball', None, 'if unpacking a tarball, download the latest'),
640 ('g', 'gpg-path', gpg, 'path for GnuPG'),
641 ('G', 'no-gpg', None, 'disable GPG signature verification'),
642 ('k', 'kernel-url', kernel_url, 'base url for kernel.org mirror'),
643 ('l', 'list-trees', None, 'list supported trees'),
644 ('m', 'show-makefile', None, 'output version in makefile <arg>'),
645 ('n', 'dry-run', None, 'don\'t download or apply patches'),
646 ('p', 'show-previous', None, 'output version previous to <arg>'),
647 ('q', 'quiet', None, 'reduce output'),
648 ('r', 'rename-directory', None, 'rename updated directory to %s<v>'
649 % rename_prefix),
650 ('s', 'show-latest', None, 'output the latest version of <arg>'),
651 ('u', 'show-url', None, 'output URL for <arg>'),
652 ('w', 'wget', wget, 'command to use for wget'),
653 ]
655 args = fancyopts(sys.argv[1:], opts, options,
656 'ketchup [options] [ver]')
658 archive = options["archive"]
659 kernel_url = options["kernel-url"]
660 if options["no-gpg"]: options["gpg-path"] = ''
662 # Process args
664 if not os.path.exists(options["directory"]):
665 qprint("Creating target directory", options["directory"])
666 os.mkdir(options["directory"])
667 os.chdir(options["directory"])
669 if os.path.isfile(".ketchuprc"):
670 try:
671 execfile(".ketchuprc")
672 except Exception, e:
673 sys.exit('Failed parsing .ketchuprc\nError was: %s' % (e))
675 if options["list-trees"]:
676 l = version_info.keys()
677 l.sort()
678 for tree in l:
679 if version_info[tree][3] == 0:
680 lprint(tree, "(unsigned)")
681 else:
682 lprint(tree, "(signed)")
683 lprint(" " + version_info[tree][4])
684 sys.exit(0)
686 if options["show-makefile"] and len(args) < 2:
687 if not args:
688 lprint(get_ver("Makefile"))
689 else:
690 lprint(get_ver(args[0]))
691 sys.exit(0)
693 if len(args) == 0 and default_tree:
694 qprint("Using default tree \"%s\"" % (default_tree))
695 args.append(default_tree)
697 if len(args) != 1:
698 error("No version given on command line and no default in configuration")
699 sys.exit(-1)
701 if options["show-latest"]:
702 lprint(find_ver(args[0]))
703 sys.exit(0)
705 if options["show-url"]:
706 lprint(version_urls(find_ver(args[0]))[0])
707 sys.exit(0)
709 if options["show-previous"]:
710 v = find_ver(args[0])
711 p = prebase(v)
712 if p == v: p = base(v)
713 if p == v:
714 if rev(v) > 0: p = "%.1f.%s" % (tree(v), rev(v) -1)
715 else: p = "unknown"
716 lprint(p)
717 sys.exit(0)
719 if not os.path.exists(options["archive"]):
720 qprint("Creating cache directory", options["archive"])
721 os.mkdir(options["archive"])
723 if precommand and os.system(precommand):
724 sys.exit('Precommand "%s" failed!' % precommand)
726 try:
727 a = get_ver('Makefile')
728 except:
729 a = None
731 if not a and os.listdir("."):
732 error("Can't find kernel version for non-empty directory")
733 sys.exit(-1)
735 b = find_ver(args[0])
736 qprint("%s -> %s" % (a, b))
737 transform(a, b)
738 if options["rename-directory"] and not options["dry-run"]:
739 rename_dir(b)
741 if postcommand and os.system(postcommand):
742 sys.exit('Postcommand "%s" failed!' % postcommand)