Update master of plcapi.
[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         -r %(webpath)s
82         -s %(scmpath)s
83         -t %(tags)s
84         -w %(webpath)s/%(pldistro)s/%(fcdistro)s
85         %(runtests)s'''.replace('\n','')
86
87     cmdline = cmdline % concrete_build
88
89     purge_spaces = re.compile('\s+')
90
91     return purge_spaces.sub(' ', cmdline)
92
93
94 # reduce dependencies in a build 
95 def reduce_dependencies(concrete_build):
96     for b in concrete_build.keys():
97         val = concrete_build[b]
98         if (type(val)==type(lambda x:x)):
99             concrete_build[b] = val(concrete_build)
100     return concrete_build
101
102
103 # Turn build parameter dicts into commandlines and execute them
104 def process_builds (builds, build_names, default_build, options):
105     for build_name in build_names:
106         build = complete_build_spec_with_defaults (builds[build_name], default_build)
107         concrete_builds_without_deps = interpret_build (build, build.keys(), {}, [])
108         concrete_builds = map(reduce_dependencies, concrete_builds_without_deps)
109         commandlines = map(concrete_build_to_commandline, concrete_builds)
110         for commandline in commandlines:
111             if PARALLEL_BUILD == True:
112                 args = shlex.split(commandline)
113                 subprocess.Popen(args)
114                 # work around the vserver race
115                 time.sleep(60)
116             else:       
117                 if (build_name.startswith(options.prefix) and not options.pretend):
118                     os.system(commandline)
119                 else:
120                     print "### Skipping the following build###\n"
121                     print commandline
122
123 def main():
124     parser = OptionParser()
125     parser.add_option("-c", "--config-file", dest="config_file",
126                   help="Config file with build declarations", metavar="FILE", default = '/etc/build-conf-planetlab.py')
127     parser.add_option("-p", "--pretend",
128                   dest="pretend", default=False, action="store_true",
129                   help="don't run only print")
130
131     parser.add_option("-o", "--only-build", dest="prefix",
132                   help="Only build declarations starting with this prefix", metavar="PREFIX", default = '')
133
134     (options, args) = parser.parse_args ()
135
136     config_file = options.config_file
137
138     builds = {}
139     try:
140         execfile(config_file, builds)
141     except IOError, e:
142         raise IOError, "Could not open %s\n" % config_file
143
144
145     config_file_attributes = builds.keys()
146     build_names = [e for e in config_file_attributes if not e.startswith('__')]     
147
148     try:
149         default_build = builds['__default_build__']
150     except KeyError:
151         raise KeyError, "Please define the default build config in %s\n" % config_file
152
153     process_builds(builds, build_names, default_build, options)
154
155
156 if __name__ == "__main__":
157     main()
158