sliceimage
[build.git] / run-nightlies.py
1 #!/usr/bin/python
2 # This script makes the declaration of builds declarative. In the past, our build system involved constructing a set of command lines
3 # that would get executed with parameters such as the name of the distribution, the kernel version and so on. Unfortunately, the code
4 # that went into creating these command lines was shared between people and often when someone modified his build, other builds would 
5 # break. With this script, each build is declared as a Python dict, such as in the following example:
6 #
7 # caglars_k32_build = {
8 #         'tags':'planetlab-k32-tags.mk',
9 #         'fcdistro':['centos5', 'f12','f8'],
10 #         'personality':['linux32','linux64'],
11 #         'test':0,
12 #         'release':'k32'
13 # }
14 #
15 # This declaration corresponds to 6 builds - with static values of 'tags', 'test' and 'release' and every combination of the values provided for
16 # 'fcdistro' and 'personality', i.e. 3x2. 
17 #
18 # More complex dependencies can be added, e.g. to build linux64 only for f12, you can set the values of the options to functions:
19 #
20 # caglars_k32_build = {
21 #         'tags':'planetlab-k32-tags.mk',
22 #         'fcdistro':['centos5', 'f12','f8'],
23 #         'personality': lambda build: if (build['fcdistro']=='f12') then return ['linux32', 'linux64'] else return ['linux32']
24 #         'test':0,
25 #         'release':'k32'
26 # }
27 #
28 # Naturally, you can achieve the same result by breaking the above declaration into two dicts, rather than using only one
29
30
31 import os
32 import re
33 import shlex
34 import subprocess
35 import time
36 from optparse import OptionParser
37
38 PARALLEL_BUILD = False
39
40 # Assemble a list of builds from a single build spec
41 def interpret_build(build, param_names, current_concrete_build={}, concrete_build_list=[]):
42     if (param_names==[]):
43         concrete_build_list.extend([current_concrete_build])
44     else:
45         (cur_param_name,remaining_param_names)=(param_names[0],param_names[1:])
46         cur_param = build[cur_param_name]
47
48         # If it's a list, produce a concrete build for each element of the list
49         if (type(cur_param)==type([])):
50             for value in cur_param:
51                 new_concrete_build = current_concrete_build.copy()
52                 new_concrete_build[cur_param_name] = value
53                 concrete_build_list = interpret_build(build, remaining_param_names, new_concrete_build, concrete_build_list)
54
55         # If not, just tack on the value and move on
56         else:
57             current_concrete_build[cur_param_name] = cur_param
58             concrete_build_list = interpret_build(build, remaining_param_names, current_concrete_build,concrete_build_list)
59
60     return concrete_build_list
61
62
63 # Fill in parameters that are not defined in __default_build__
64 def complete_build_spec_with_defaults (build, default_build):
65     for default_param in default_build.keys():
66         if (not build.has_key(default_param)):
67             build[default_param]=default_build[default_param]
68     return build
69
70
71 # Turn a concrete build into a commandline
72
73 def concrete_build_to_commandline(concrete_build):
74
75     cmdline = '''%(sh)s 
76         %(vbuildnightly)s
77         -b pl-%(fcdistro)s-%(arch)s-%(myplcversion)s-%(release)s-%(date)s
78         -f %(fcdistro)s 
79         -m %(mailto)s 
80         -p %(personality)s
81         -d %(pldistro)s
82         -r %(webpath)s
83         -s %(scmpath)s
84         -t %(tags)s
85         -w %(webpath)s/%(pldistro)s/%(fcdistro)s
86         %(runtests)s'''.replace('\n','')
87
88     cmdline = cmdline % concrete_build
89
90     purge_spaces = re.compile('\s+')
91
92     return purge_spaces.sub(' ', cmdline)
93
94
95 # reduce dependencies in a build 
96 def reduce_dependencies(concrete_build):
97     for b in concrete_build.keys():
98         val = concrete_build[b]
99         if (type(val)==type(lambda x:x)):
100             concrete_build[b] = val(concrete_build)
101     return concrete_build
102
103
104 # Turn build parameter dicts into commandlines and execute them
105 def process_builds (builds, build_names, default_build, options):
106     for build_name in build_names:
107         build = complete_build_spec_with_defaults (builds[build_name], default_build)
108         concrete_builds_without_deps = interpret_build (build, build.keys(), {}, [])
109         concrete_builds = map(reduce_dependencies, concrete_builds_without_deps)
110         commandlines = map(concrete_build_to_commandline, concrete_builds)
111         for commandline in commandlines:
112             if PARALLEL_BUILD == True:
113                 args = shlex.split(commandline)
114                 subprocess.Popen(args)
115                 # work around the vserver race
116                 time.sleep(60)
117             else:       
118                 if (build_name.startswith(options.prefix) and not options.pretend):
119                     os.system(commandline)
120                 else:
121                     print "### Skipping the following build###\n"
122                     print commandline
123
124 def main():
125     parser = OptionParser()
126     parser.add_option("-c", "--config-file", dest="config_file",
127                   help="Config file with build declarations", metavar="FILE", default = '/etc/build-conf-planetlab.py')
128     parser.add_option("-p", "--pretend",
129                   dest="pretend", default=False, action="store_true",
130                   help="don't run only print")
131
132     parser.add_option("-o", "--only-build", dest="prefix",
133                   help="Only build declarations starting with this prefix", metavar="PREFIX", default = '')
134
135     (options, args) = parser.parse_args ()
136
137     config_file = options.config_file
138
139     builds = {}
140     try:
141         execfile(config_file, builds)
142     except IOError, e:
143         raise IOError, "Could not open %s\n" % config_file
144
145
146     config_file_attributes = builds.keys()
147     build_names = [e for e in config_file_attributes if not e.startswith('__')]     
148
149     try:
150         default_build = builds['__default_build__']
151     except KeyError:
152         raise KeyError, "Please define the default build config in %s\n" % config_file
153
154     process_builds(builds, build_names, default_build, options)
155
156
157 if __name__ == "__main__":
158     main()
159