1*44844408SAndroid Build Coastguard Workerfrom __future__ import print_function 2*44844408SAndroid Build Coastguard Workerimport collections 3*44844408SAndroid Build Coastguard Workerimport itertools 4*44844408SAndroid Build Coastguard Workerimport json 5*44844408SAndroid Build Coastguard Workerimport os 6*44844408SAndroid Build Coastguard Workerimport os.path 7*44844408SAndroid Build Coastguard Workerimport re 8*44844408SAndroid Build Coastguard Workerimport shutil 9*44844408SAndroid Build Coastguard Workerimport string 10*44844408SAndroid Build Coastguard Workerimport subprocess 11*44844408SAndroid Build Coastguard Workerimport sys 12*44844408SAndroid Build Coastguard Workerimport cgi 13*44844408SAndroid Build Coastguard Worker 14*44844408SAndroid Build Coastguard Workerclass BuildDesc: 15*44844408SAndroid Build Coastguard Worker def __init__(self, prepend_envs=None, variables=None, build_type=None, generator=None): 16*44844408SAndroid Build Coastguard Worker self.prepend_envs = prepend_envs or [] # [ { "var": "value" } ] 17*44844408SAndroid Build Coastguard Worker self.variables = variables or [] 18*44844408SAndroid Build Coastguard Worker self.build_type = build_type 19*44844408SAndroid Build Coastguard Worker self.generator = generator 20*44844408SAndroid Build Coastguard Worker 21*44844408SAndroid Build Coastguard Worker def merged_with(self, build_desc): 22*44844408SAndroid Build Coastguard Worker """Returns a new BuildDesc by merging field content. 23*44844408SAndroid Build Coastguard Worker Prefer build_desc fields to self fields for single valued field. 24*44844408SAndroid Build Coastguard Worker """ 25*44844408SAndroid Build Coastguard Worker return BuildDesc(self.prepend_envs + build_desc.prepend_envs, 26*44844408SAndroid Build Coastguard Worker self.variables + build_desc.variables, 27*44844408SAndroid Build Coastguard Worker build_desc.build_type or self.build_type, 28*44844408SAndroid Build Coastguard Worker build_desc.generator or self.generator) 29*44844408SAndroid Build Coastguard Worker 30*44844408SAndroid Build Coastguard Worker def env(self): 31*44844408SAndroid Build Coastguard Worker environ = os.environ.copy() 32*44844408SAndroid Build Coastguard Worker for values_by_name in self.prepend_envs: 33*44844408SAndroid Build Coastguard Worker for var, value in list(values_by_name.items()): 34*44844408SAndroid Build Coastguard Worker var = var.upper() 35*44844408SAndroid Build Coastguard Worker if type(value) is unicode: 36*44844408SAndroid Build Coastguard Worker value = value.encode(sys.getdefaultencoding()) 37*44844408SAndroid Build Coastguard Worker if var in environ: 38*44844408SAndroid Build Coastguard Worker environ[var] = value + os.pathsep + environ[var] 39*44844408SAndroid Build Coastguard Worker else: 40*44844408SAndroid Build Coastguard Worker environ[var] = value 41*44844408SAndroid Build Coastguard Worker return environ 42*44844408SAndroid Build Coastguard Worker 43*44844408SAndroid Build Coastguard Worker def cmake_args(self): 44*44844408SAndroid Build Coastguard Worker args = ["-D%s" % var for var in self.variables] 45*44844408SAndroid Build Coastguard Worker # skip build type for Visual Studio solution as it cause warning 46*44844408SAndroid Build Coastguard Worker if self.build_type and 'Visual' not in self.generator: 47*44844408SAndroid Build Coastguard Worker args.append("-DCMAKE_BUILD_TYPE=%s" % self.build_type) 48*44844408SAndroid Build Coastguard Worker if self.generator: 49*44844408SAndroid Build Coastguard Worker args.extend(['-G', self.generator]) 50*44844408SAndroid Build Coastguard Worker return args 51*44844408SAndroid Build Coastguard Worker 52*44844408SAndroid Build Coastguard Worker def __repr__(self): 53*44844408SAndroid Build Coastguard Worker return "BuildDesc(%s, build_type=%s)" % (" ".join(self.cmake_args()), self.build_type) 54*44844408SAndroid Build Coastguard Worker 55*44844408SAndroid Build Coastguard Workerclass BuildData: 56*44844408SAndroid Build Coastguard Worker def __init__(self, desc, work_dir, source_dir): 57*44844408SAndroid Build Coastguard Worker self.desc = desc 58*44844408SAndroid Build Coastguard Worker self.work_dir = work_dir 59*44844408SAndroid Build Coastguard Worker self.source_dir = source_dir 60*44844408SAndroid Build Coastguard Worker self.cmake_log_path = os.path.join(work_dir, 'batchbuild_cmake.log') 61*44844408SAndroid Build Coastguard Worker self.build_log_path = os.path.join(work_dir, 'batchbuild_build.log') 62*44844408SAndroid Build Coastguard Worker self.cmake_succeeded = False 63*44844408SAndroid Build Coastguard Worker self.build_succeeded = False 64*44844408SAndroid Build Coastguard Worker 65*44844408SAndroid Build Coastguard Worker def execute_build(self): 66*44844408SAndroid Build Coastguard Worker print('Build %s' % self.desc) 67*44844408SAndroid Build Coastguard Worker self._make_new_work_dir() 68*44844408SAndroid Build Coastguard Worker self.cmake_succeeded = self._generate_makefiles() 69*44844408SAndroid Build Coastguard Worker if self.cmake_succeeded: 70*44844408SAndroid Build Coastguard Worker self.build_succeeded = self._build_using_makefiles() 71*44844408SAndroid Build Coastguard Worker return self.build_succeeded 72*44844408SAndroid Build Coastguard Worker 73*44844408SAndroid Build Coastguard Worker def _generate_makefiles(self): 74*44844408SAndroid Build Coastguard Worker print(' Generating makefiles: ', end=' ') 75*44844408SAndroid Build Coastguard Worker cmd = ['cmake'] + self.desc.cmake_args() + [os.path.abspath(self.source_dir)] 76*44844408SAndroid Build Coastguard Worker succeeded = self._execute_build_subprocess(cmd, self.desc.env(), self.cmake_log_path) 77*44844408SAndroid Build Coastguard Worker print('done' if succeeded else 'FAILED') 78*44844408SAndroid Build Coastguard Worker return succeeded 79*44844408SAndroid Build Coastguard Worker 80*44844408SAndroid Build Coastguard Worker def _build_using_makefiles(self): 81*44844408SAndroid Build Coastguard Worker print(' Building:', end=' ') 82*44844408SAndroid Build Coastguard Worker cmd = ['cmake', '--build', self.work_dir] 83*44844408SAndroid Build Coastguard Worker if self.desc.build_type: 84*44844408SAndroid Build Coastguard Worker cmd += ['--config', self.desc.build_type] 85*44844408SAndroid Build Coastguard Worker succeeded = self._execute_build_subprocess(cmd, self.desc.env(), self.build_log_path) 86*44844408SAndroid Build Coastguard Worker print('done' if succeeded else 'FAILED') 87*44844408SAndroid Build Coastguard Worker return succeeded 88*44844408SAndroid Build Coastguard Worker 89*44844408SAndroid Build Coastguard Worker def _execute_build_subprocess(self, cmd, env, log_path): 90*44844408SAndroid Build Coastguard Worker process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=self.work_dir, 91*44844408SAndroid Build Coastguard Worker env=env) 92*44844408SAndroid Build Coastguard Worker stdout, _ = process.communicate() 93*44844408SAndroid Build Coastguard Worker succeeded = (process.returncode == 0) 94*44844408SAndroid Build Coastguard Worker with open(log_path, 'wb') as flog: 95*44844408SAndroid Build Coastguard Worker log = ' '.join(cmd) + '\n' + stdout + '\nExit code: %r\n' % process.returncode 96*44844408SAndroid Build Coastguard Worker flog.write(fix_eol(log)) 97*44844408SAndroid Build Coastguard Worker return succeeded 98*44844408SAndroid Build Coastguard Worker 99*44844408SAndroid Build Coastguard Worker def _make_new_work_dir(self): 100*44844408SAndroid Build Coastguard Worker if os.path.isdir(self.work_dir): 101*44844408SAndroid Build Coastguard Worker print(' Removing work directory', self.work_dir) 102*44844408SAndroid Build Coastguard Worker shutil.rmtree(self.work_dir, ignore_errors=True) 103*44844408SAndroid Build Coastguard Worker if not os.path.isdir(self.work_dir): 104*44844408SAndroid Build Coastguard Worker os.makedirs(self.work_dir) 105*44844408SAndroid Build Coastguard Worker 106*44844408SAndroid Build Coastguard Workerdef fix_eol(stdout): 107*44844408SAndroid Build Coastguard Worker """Fixes wrong EOL produced by cmake --build on Windows (\r\r\n instead of \r\n). 108*44844408SAndroid Build Coastguard Worker """ 109*44844408SAndroid Build Coastguard Worker return re.sub('\r*\n', os.linesep, stdout) 110*44844408SAndroid Build Coastguard Worker 111*44844408SAndroid Build Coastguard Workerdef load_build_variants_from_config(config_path): 112*44844408SAndroid Build Coastguard Worker with open(config_path, 'rb') as fconfig: 113*44844408SAndroid Build Coastguard Worker data = json.load(fconfig) 114*44844408SAndroid Build Coastguard Worker variants = data[ 'cmake_variants' ] 115*44844408SAndroid Build Coastguard Worker build_descs_by_axis = collections.defaultdict(list) 116*44844408SAndroid Build Coastguard Worker for axis in variants: 117*44844408SAndroid Build Coastguard Worker axis_name = axis["name"] 118*44844408SAndroid Build Coastguard Worker build_descs = [] 119*44844408SAndroid Build Coastguard Worker if "generators" in axis: 120*44844408SAndroid Build Coastguard Worker for generator_data in axis["generators"]: 121*44844408SAndroid Build Coastguard Worker for generator in generator_data["generator"]: 122*44844408SAndroid Build Coastguard Worker build_desc = BuildDesc(generator=generator, 123*44844408SAndroid Build Coastguard Worker prepend_envs=generator_data.get("env_prepend")) 124*44844408SAndroid Build Coastguard Worker build_descs.append(build_desc) 125*44844408SAndroid Build Coastguard Worker elif "variables" in axis: 126*44844408SAndroid Build Coastguard Worker for variables in axis["variables"]: 127*44844408SAndroid Build Coastguard Worker build_desc = BuildDesc(variables=variables) 128*44844408SAndroid Build Coastguard Worker build_descs.append(build_desc) 129*44844408SAndroid Build Coastguard Worker elif "build_types" in axis: 130*44844408SAndroid Build Coastguard Worker for build_type in axis["build_types"]: 131*44844408SAndroid Build Coastguard Worker build_desc = BuildDesc(build_type=build_type) 132*44844408SAndroid Build Coastguard Worker build_descs.append(build_desc) 133*44844408SAndroid Build Coastguard Worker build_descs_by_axis[axis_name].extend(build_descs) 134*44844408SAndroid Build Coastguard Worker return build_descs_by_axis 135*44844408SAndroid Build Coastguard Worker 136*44844408SAndroid Build Coastguard Workerdef generate_build_variants(build_descs_by_axis): 137*44844408SAndroid Build Coastguard Worker """Returns a list of BuildDesc generated for the partial BuildDesc for each axis.""" 138*44844408SAndroid Build Coastguard Worker axis_names = list(build_descs_by_axis.keys()) 139*44844408SAndroid Build Coastguard Worker build_descs = [] 140*44844408SAndroid Build Coastguard Worker for axis_name, axis_build_descs in list(build_descs_by_axis.items()): 141*44844408SAndroid Build Coastguard Worker if len(build_descs): 142*44844408SAndroid Build Coastguard Worker # for each existing build_desc and each axis build desc, create a new build_desc 143*44844408SAndroid Build Coastguard Worker new_build_descs = [] 144*44844408SAndroid Build Coastguard Worker for prototype_build_desc, axis_build_desc in itertools.product(build_descs, axis_build_descs): 145*44844408SAndroid Build Coastguard Worker new_build_descs.append(prototype_build_desc.merged_with(axis_build_desc)) 146*44844408SAndroid Build Coastguard Worker build_descs = new_build_descs 147*44844408SAndroid Build Coastguard Worker else: 148*44844408SAndroid Build Coastguard Worker build_descs = axis_build_descs 149*44844408SAndroid Build Coastguard Worker return build_descs 150*44844408SAndroid Build Coastguard Worker 151*44844408SAndroid Build Coastguard WorkerHTML_TEMPLATE = string.Template('''<html> 152*44844408SAndroid Build Coastguard Worker<head> 153*44844408SAndroid Build Coastguard Worker <title>$title</title> 154*44844408SAndroid Build Coastguard Worker <style type="text/css"> 155*44844408SAndroid Build Coastguard Worker td.failed {background-color:#f08080;} 156*44844408SAndroid Build Coastguard Worker td.ok {background-color:#c0eec0;} 157*44844408SAndroid Build Coastguard Worker </style> 158*44844408SAndroid Build Coastguard Worker</head> 159*44844408SAndroid Build Coastguard Worker<body> 160*44844408SAndroid Build Coastguard Worker<table border="1"> 161*44844408SAndroid Build Coastguard Worker<thead> 162*44844408SAndroid Build Coastguard Worker <tr> 163*44844408SAndroid Build Coastguard Worker <th>Variables</th> 164*44844408SAndroid Build Coastguard Worker $th_vars 165*44844408SAndroid Build Coastguard Worker </tr> 166*44844408SAndroid Build Coastguard Worker <tr> 167*44844408SAndroid Build Coastguard Worker <th>Build type</th> 168*44844408SAndroid Build Coastguard Worker $th_build_types 169*44844408SAndroid Build Coastguard Worker </tr> 170*44844408SAndroid Build Coastguard Worker</thead> 171*44844408SAndroid Build Coastguard Worker<tbody> 172*44844408SAndroid Build Coastguard Worker$tr_builds 173*44844408SAndroid Build Coastguard Worker</tbody> 174*44844408SAndroid Build Coastguard Worker</table> 175*44844408SAndroid Build Coastguard Worker</body></html>''') 176*44844408SAndroid Build Coastguard Worker 177*44844408SAndroid Build Coastguard Workerdef generate_html_report(html_report_path, builds): 178*44844408SAndroid Build Coastguard Worker report_dir = os.path.dirname(html_report_path) 179*44844408SAndroid Build Coastguard Worker # Vertical axis: generator 180*44844408SAndroid Build Coastguard Worker # Horizontal: variables, then build_type 181*44844408SAndroid Build Coastguard Worker builds_by_generator = collections.defaultdict(list) 182*44844408SAndroid Build Coastguard Worker variables = set() 183*44844408SAndroid Build Coastguard Worker build_types_by_variable = collections.defaultdict(set) 184*44844408SAndroid Build Coastguard Worker build_by_pos_key = {} # { (generator, var_key, build_type): build } 185*44844408SAndroid Build Coastguard Worker for build in builds: 186*44844408SAndroid Build Coastguard Worker builds_by_generator[build.desc.generator].append(build) 187*44844408SAndroid Build Coastguard Worker var_key = tuple(sorted(build.desc.variables)) 188*44844408SAndroid Build Coastguard Worker variables.add(var_key) 189*44844408SAndroid Build Coastguard Worker build_types_by_variable[var_key].add(build.desc.build_type) 190*44844408SAndroid Build Coastguard Worker pos_key = (build.desc.generator, var_key, build.desc.build_type) 191*44844408SAndroid Build Coastguard Worker build_by_pos_key[pos_key] = build 192*44844408SAndroid Build Coastguard Worker variables = sorted(variables) 193*44844408SAndroid Build Coastguard Worker th_vars = [] 194*44844408SAndroid Build Coastguard Worker th_build_types = [] 195*44844408SAndroid Build Coastguard Worker for variable in variables: 196*44844408SAndroid Build Coastguard Worker build_types = sorted(build_types_by_variable[variable]) 197*44844408SAndroid Build Coastguard Worker nb_build_type = len(build_types_by_variable[variable]) 198*44844408SAndroid Build Coastguard Worker th_vars.append('<th colspan="%d">%s</th>' % (nb_build_type, cgi.escape(' '.join(variable)))) 199*44844408SAndroid Build Coastguard Worker for build_type in build_types: 200*44844408SAndroid Build Coastguard Worker th_build_types.append('<th>%s</th>' % cgi.escape(build_type)) 201*44844408SAndroid Build Coastguard Worker tr_builds = [] 202*44844408SAndroid Build Coastguard Worker for generator in sorted(builds_by_generator): 203*44844408SAndroid Build Coastguard Worker tds = [ '<td>%s</td>\n' % cgi.escape(generator) ] 204*44844408SAndroid Build Coastguard Worker for variable in variables: 205*44844408SAndroid Build Coastguard Worker build_types = sorted(build_types_by_variable[variable]) 206*44844408SAndroid Build Coastguard Worker for build_type in build_types: 207*44844408SAndroid Build Coastguard Worker pos_key = (generator, variable, build_type) 208*44844408SAndroid Build Coastguard Worker build = build_by_pos_key.get(pos_key) 209*44844408SAndroid Build Coastguard Worker if build: 210*44844408SAndroid Build Coastguard Worker cmake_status = 'ok' if build.cmake_succeeded else 'FAILED' 211*44844408SAndroid Build Coastguard Worker build_status = 'ok' if build.build_succeeded else 'FAILED' 212*44844408SAndroid Build Coastguard Worker cmake_log_url = os.path.relpath(build.cmake_log_path, report_dir) 213*44844408SAndroid Build Coastguard Worker build_log_url = os.path.relpath(build.build_log_path, report_dir) 214*44844408SAndroid Build Coastguard Worker td = '<td class="%s"><a href="%s" class="%s">CMake: %s</a>' % ( build_status.lower(), cmake_log_url, cmake_status.lower(), cmake_status) 215*44844408SAndroid Build Coastguard Worker if build.cmake_succeeded: 216*44844408SAndroid Build Coastguard Worker td += '<br><a href="%s" class="%s">Build: %s</a>' % ( build_log_url, build_status.lower(), build_status) 217*44844408SAndroid Build Coastguard Worker td += '</td>' 218*44844408SAndroid Build Coastguard Worker else: 219*44844408SAndroid Build Coastguard Worker td = '<td></td>' 220*44844408SAndroid Build Coastguard Worker tds.append(td) 221*44844408SAndroid Build Coastguard Worker tr_builds.append('<tr>%s</tr>' % '\n'.join(tds)) 222*44844408SAndroid Build Coastguard Worker html = HTML_TEMPLATE.substitute( title='Batch build report', 223*44844408SAndroid Build Coastguard Worker th_vars=' '.join(th_vars), 224*44844408SAndroid Build Coastguard Worker th_build_types=' '.join(th_build_types), 225*44844408SAndroid Build Coastguard Worker tr_builds='\n'.join(tr_builds)) 226*44844408SAndroid Build Coastguard Worker with open(html_report_path, 'wt') as fhtml: 227*44844408SAndroid Build Coastguard Worker fhtml.write(html) 228*44844408SAndroid Build Coastguard Worker print('HTML report generated in:', html_report_path) 229*44844408SAndroid Build Coastguard Worker 230*44844408SAndroid Build Coastguard Workerdef main(): 231*44844408SAndroid Build Coastguard Worker usage = r"""%prog WORK_DIR SOURCE_DIR CONFIG_JSON_PATH [CONFIG2_JSON_PATH...] 232*44844408SAndroid Build Coastguard WorkerBuild a given CMake based project located in SOURCE_DIR with multiple generators/options.dry_run 233*44844408SAndroid Build Coastguard Workeras described in CONFIG_JSON_PATH building in WORK_DIR. 234*44844408SAndroid Build Coastguard Worker 235*44844408SAndroid Build Coastguard WorkerExample of call: 236*44844408SAndroid Build Coastguard Workerpython devtools\batchbuild.py e:\buildbots\jsoncpp\build . devtools\agent_vmw7.json 237*44844408SAndroid Build Coastguard Worker""" 238*44844408SAndroid Build Coastguard Worker from optparse import OptionParser 239*44844408SAndroid Build Coastguard Worker parser = OptionParser(usage=usage) 240*44844408SAndroid Build Coastguard Worker parser.allow_interspersed_args = True 241*44844408SAndroid Build Coastguard Worker# parser.add_option('-v', '--verbose', dest="verbose", action='store_true', 242*44844408SAndroid Build Coastguard Worker# help="""Be verbose.""") 243*44844408SAndroid Build Coastguard Worker parser.enable_interspersed_args() 244*44844408SAndroid Build Coastguard Worker options, args = parser.parse_args() 245*44844408SAndroid Build Coastguard Worker if len(args) < 3: 246*44844408SAndroid Build Coastguard Worker parser.error("Missing one of WORK_DIR SOURCE_DIR CONFIG_JSON_PATH.") 247*44844408SAndroid Build Coastguard Worker work_dir = args[0] 248*44844408SAndroid Build Coastguard Worker source_dir = args[1].rstrip('/\\') 249*44844408SAndroid Build Coastguard Worker config_paths = args[2:] 250*44844408SAndroid Build Coastguard Worker for config_path in config_paths: 251*44844408SAndroid Build Coastguard Worker if not os.path.isfile(config_path): 252*44844408SAndroid Build Coastguard Worker parser.error("Can not read: %r" % config_path) 253*44844408SAndroid Build Coastguard Worker 254*44844408SAndroid Build Coastguard Worker # generate build variants 255*44844408SAndroid Build Coastguard Worker build_descs = [] 256*44844408SAndroid Build Coastguard Worker for config_path in config_paths: 257*44844408SAndroid Build Coastguard Worker build_descs_by_axis = load_build_variants_from_config(config_path) 258*44844408SAndroid Build Coastguard Worker build_descs.extend(generate_build_variants(build_descs_by_axis)) 259*44844408SAndroid Build Coastguard Worker print('Build variants (%d):' % len(build_descs)) 260*44844408SAndroid Build Coastguard Worker # assign build directory for each variant 261*44844408SAndroid Build Coastguard Worker if not os.path.isdir(work_dir): 262*44844408SAndroid Build Coastguard Worker os.makedirs(work_dir) 263*44844408SAndroid Build Coastguard Worker builds = [] 264*44844408SAndroid Build Coastguard Worker with open(os.path.join(work_dir, 'matrix-dir-map.txt'), 'wt') as fmatrixmap: 265*44844408SAndroid Build Coastguard Worker for index, build_desc in enumerate(build_descs): 266*44844408SAndroid Build Coastguard Worker build_desc_work_dir = os.path.join(work_dir, '%03d' % (index+1)) 267*44844408SAndroid Build Coastguard Worker builds.append(BuildData(build_desc, build_desc_work_dir, source_dir)) 268*44844408SAndroid Build Coastguard Worker fmatrixmap.write('%s: %s\n' % (build_desc_work_dir, build_desc)) 269*44844408SAndroid Build Coastguard Worker for build in builds: 270*44844408SAndroid Build Coastguard Worker build.execute_build() 271*44844408SAndroid Build Coastguard Worker html_report_path = os.path.join(work_dir, 'batchbuild-report.html') 272*44844408SAndroid Build Coastguard Worker generate_html_report(html_report_path, builds) 273*44844408SAndroid Build Coastguard Worker print('Done') 274*44844408SAndroid Build Coastguard Worker 275*44844408SAndroid Build Coastguard Worker 276*44844408SAndroid Build Coastguard Workerif __name__ == '__main__': 277*44844408SAndroid Build Coastguard Worker main() 278*44844408SAndroid Build Coastguard Worker 279