Brian Silverman | 44c68b1 | 2018-08-04 23:56:44 -0700 | [diff] [blame^] | 1 | #!/usr/bin/env python |
| 2 | |
| 3 | # Copyright Rene Rivera 2016 |
| 4 | # |
| 5 | # Distributed under the Boost Software License, Version 1.0. |
| 6 | # (See accompanying file LICENSE_1_0.txt or copy at |
| 7 | # http://www.boost.org/LICENSE_1_0.txt) |
| 8 | |
| 9 | import sys |
| 10 | import inspect |
| 11 | import optparse |
| 12 | import os.path |
| 13 | import string |
| 14 | import time |
| 15 | import subprocess |
| 16 | import codecs |
| 17 | import shutil |
| 18 | import threading |
| 19 | |
| 20 | toolset_info = { |
| 21 | 'clang-3.4' : { |
| 22 | 'ppa' : ["ppa:h-rayflood/llvm"], |
| 23 | 'package' : 'clang-3.4', |
| 24 | 'command' : 'clang++-3.4', |
| 25 | 'toolset' : 'clang', |
| 26 | 'version' : '' |
| 27 | }, |
| 28 | 'clang-3.5' : { |
| 29 | 'ppa' : ["ppa:h-rayflood/llvm"], |
| 30 | 'package' : 'clang-3.5', |
| 31 | 'command' : 'clang++-3.5', |
| 32 | 'toolset' : 'clang', |
| 33 | 'version' : '' |
| 34 | }, |
| 35 | 'clang-3.6' : { |
| 36 | 'ppa' : ["ppa:h-rayflood/llvm"], |
| 37 | 'package' : 'clang-3.6', |
| 38 | 'command' : 'clang++-3.6', |
| 39 | 'toolset' : 'clang', |
| 40 | 'version' : '' |
| 41 | }, |
| 42 | 'clang-3.7' : { |
| 43 | 'deb' : ["http://apt.llvm.org/trusty/","llvm-toolchain-trusty-3.7","main"], |
| 44 | 'apt-key' : ['http://apt.llvm.org/llvm-snapshot.gpg.key'], |
| 45 | 'package' : 'clang-3.7', |
| 46 | 'command' : 'clang++-3.7', |
| 47 | 'toolset' : 'clang', |
| 48 | 'version' : '' |
| 49 | }, |
| 50 | 'clang-3.8' : { |
| 51 | 'deb' : ["http://apt.llvm.org/trusty/","llvm-toolchain-trusty-3.8","main"], |
| 52 | 'apt-key' : ['http://apt.llvm.org/llvm-snapshot.gpg.key'], |
| 53 | 'package' : 'clang-3.8', |
| 54 | 'command' : 'clang++-3.8', |
| 55 | 'toolset' : 'clang', |
| 56 | 'version' : '' |
| 57 | }, |
| 58 | 'clang-3.9' : { |
| 59 | 'deb' : ["http://apt.llvm.org/trusty/","llvm-toolchain-trusty-3.9","main"], |
| 60 | 'apt-key' : ['http://apt.llvm.org/llvm-snapshot.gpg.key'], |
| 61 | 'package' : 'clang-3.9', |
| 62 | 'command' : 'clang++-3.9', |
| 63 | 'toolset' : 'clang', |
| 64 | 'version' : '' |
| 65 | }, |
| 66 | 'clang-4.0' : { |
| 67 | 'deb' : ["http://apt.llvm.org/trusty/","llvm-toolchain-trusty-4.0","main"], |
| 68 | 'apt-key' : ['http://apt.llvm.org/llvm-snapshot.gpg.key'], |
| 69 | 'package' : 'clang-4.0', |
| 70 | 'command' : 'clang++-4.0', |
| 71 | 'toolset' : 'clang', |
| 72 | 'version' : '' |
| 73 | }, |
| 74 | 'clang-5.0' : { |
| 75 | 'deb' : ["http://apt.llvm.org/trusty/","llvm-toolchain-trusty-5.0","main"], |
| 76 | 'apt-key' : ['http://apt.llvm.org/llvm-snapshot.gpg.key'], |
| 77 | 'package' : 'clang-5.0', |
| 78 | 'command' : 'clang++-5.0', |
| 79 | 'toolset' : 'clang', |
| 80 | 'version' : '' |
| 81 | }, |
| 82 | 'clang-6.0' : { |
| 83 | 'deb' : ["http://apt.llvm.org/trusty/","llvm-toolchain-trusty-6.0","main"], |
| 84 | 'apt-key' : ['http://apt.llvm.org/llvm-snapshot.gpg.key'], |
| 85 | 'package' : 'clang-6.0', |
| 86 | 'command' : 'clang++-6.0', |
| 87 | 'toolset' : 'clang', |
| 88 | 'version' : '' |
| 89 | }, |
| 90 | 'gcc-4.7' : { |
| 91 | 'ppa' : ["ppa:ubuntu-toolchain-r/test"], |
| 92 | 'package' : 'g++-4.7', |
| 93 | 'command' : 'g++-4.7', |
| 94 | 'toolset' : 'gcc', |
| 95 | 'version' : '' |
| 96 | }, |
| 97 | 'gcc-4.8' : { |
| 98 | 'bin' : 'gcc-4.8', |
| 99 | 'ppa' : ["ppa:ubuntu-toolchain-r/test"], |
| 100 | 'package' : 'g++-4.8', |
| 101 | 'command' : 'g++-4.8', |
| 102 | 'toolset' : 'gcc', |
| 103 | 'version' : '' |
| 104 | }, |
| 105 | 'gcc-4.9' : { |
| 106 | 'ppa' : ["ppa:ubuntu-toolchain-r/test"], |
| 107 | 'package' : 'g++-4.9', |
| 108 | 'command' : 'g++-4.9', |
| 109 | 'toolset' : 'gcc', |
| 110 | 'version' : '' |
| 111 | }, |
| 112 | 'gcc-5.1' : { |
| 113 | 'ppa' : ["ppa:ubuntu-toolchain-r/test"], |
| 114 | 'package' : 'g++-5', |
| 115 | 'command' : 'g++-5', |
| 116 | 'toolset' : 'gcc', |
| 117 | 'version' : '' |
| 118 | }, |
| 119 | 'gcc-5' : { |
| 120 | 'ppa' : ["ppa:ubuntu-toolchain-r/test"], |
| 121 | 'package' : 'g++-5', |
| 122 | 'command' : 'g++-5', |
| 123 | 'toolset' : 'gcc', |
| 124 | 'version' : '' |
| 125 | }, |
| 126 | 'gcc-6' : { |
| 127 | 'ppa' : ["ppa:ubuntu-toolchain-r/test"], |
| 128 | 'package' : 'g++-6', |
| 129 | 'command' : 'g++-6', |
| 130 | 'toolset' : 'gcc', |
| 131 | 'version' : '' |
| 132 | }, |
| 133 | 'gcc-7' : { |
| 134 | 'ppa' : ["ppa:ubuntu-toolchain-r/test"], |
| 135 | 'package' : 'g++-7', |
| 136 | 'command' : 'g++-7', |
| 137 | 'toolset' : 'gcc', |
| 138 | 'version' : '' |
| 139 | }, |
| 140 | 'gcc-8' : { |
| 141 | 'ppa' : ["ppa:ubuntu-toolchain-r/test"], |
| 142 | 'package' : 'g++-8', |
| 143 | 'command' : 'g++-8', |
| 144 | 'toolset' : 'gcc', |
| 145 | 'version' : '' |
| 146 | }, |
| 147 | 'mingw-5' : { |
| 148 | 'toolset' : 'gcc', |
| 149 | 'command' : 'C:\\\\MinGW\\\\bin\\\\g++.exe', |
| 150 | 'version' : '' |
| 151 | }, |
| 152 | 'mingw64-6' : { |
| 153 | 'toolset' : 'gcc', |
| 154 | 'command' : 'C:\\\\mingw-w64\\\\x86_64-6.3.0-posix-seh-rt_v5-rev1\\\\mingw64\\\\bin\\\\g++.exe', |
| 155 | 'version' : '' |
| 156 | }, |
| 157 | 'vs-2008' : { |
| 158 | 'toolset' : 'msvc', |
| 159 | 'command' : '', |
| 160 | 'version' : '9.0' |
| 161 | }, |
| 162 | 'vs-2010' : { |
| 163 | 'toolset' : 'msvc', |
| 164 | 'command' : '', |
| 165 | 'version' : '10.0' |
| 166 | }, |
| 167 | 'vs-2012' : { |
| 168 | 'toolset' : 'msvc', |
| 169 | 'command' : '', |
| 170 | 'version' : '11.0' |
| 171 | }, |
| 172 | 'vs-2013' : { |
| 173 | 'toolset' : 'msvc', |
| 174 | 'command' : '', |
| 175 | 'version' : '12.0' |
| 176 | }, |
| 177 | 'vs-2015' : { |
| 178 | 'toolset' : 'msvc', |
| 179 | 'command' : '', |
| 180 | 'version' : '14.0' |
| 181 | }, |
| 182 | 'vs-2017' : { |
| 183 | 'toolset' : 'msvc', |
| 184 | 'command' : '', |
| 185 | 'version' : '14.1' |
| 186 | }, |
| 187 | 'xcode-6.1' : { |
| 188 | 'command' : 'clang++', |
| 189 | 'toolset' : 'clang', |
| 190 | 'version' : '' |
| 191 | }, |
| 192 | 'xcode-6.2' : { |
| 193 | 'command' : 'clang++', |
| 194 | 'toolset' : 'clang', |
| 195 | 'version' : '' |
| 196 | }, |
| 197 | 'xcode-6.3' : { |
| 198 | 'command' : 'clang++', |
| 199 | 'toolset' : 'clang', |
| 200 | 'version' : '' |
| 201 | }, |
| 202 | 'xcode-6.4' : { |
| 203 | 'command' : 'clang++', |
| 204 | 'toolset' : 'clang', |
| 205 | 'version' : '' |
| 206 | }, |
| 207 | 'xcode-7.0' : { |
| 208 | 'command' : 'clang++', |
| 209 | 'toolset' : 'clang', |
| 210 | 'version' : '' |
| 211 | }, |
| 212 | 'xcode-7.1' : { |
| 213 | 'command' : 'clang++', |
| 214 | 'toolset' : 'clang', |
| 215 | 'version' : '' |
| 216 | }, |
| 217 | 'xcode-7.2' : { |
| 218 | 'command' : 'clang++', |
| 219 | 'toolset' : 'clang', |
| 220 | 'version' : '' |
| 221 | }, |
| 222 | 'xcode-7.3' : { |
| 223 | 'command' : 'clang++', |
| 224 | 'toolset' : 'clang', |
| 225 | 'version' : '' |
| 226 | }, |
| 227 | 'xcode-8.0' : { |
| 228 | 'command' : 'clang++', |
| 229 | 'toolset' : 'clang', |
| 230 | 'version' : '' |
| 231 | }, |
| 232 | 'xcode-8.1' : { |
| 233 | 'command' : 'clang++', |
| 234 | 'toolset' : 'clang', |
| 235 | 'version' : '' |
| 236 | }, |
| 237 | 'xcode-8.2' : { |
| 238 | 'command' : 'clang++', |
| 239 | 'toolset' : 'clang', |
| 240 | 'version' : '' |
| 241 | }, |
| 242 | 'xcode-8.3' : { |
| 243 | 'command' : 'clang++', |
| 244 | 'toolset' : 'clang', |
| 245 | 'version' : '' |
| 246 | }, |
| 247 | 'xcode-9.0' : { |
| 248 | 'command' : 'clang++', |
| 249 | 'toolset' : 'clang', |
| 250 | 'version' : '' |
| 251 | }, |
| 252 | 'xcode-9.1' : { |
| 253 | 'command' : 'clang++', |
| 254 | 'toolset' : 'clang', |
| 255 | 'version' : '' |
| 256 | }, |
| 257 | } |
| 258 | |
| 259 | class SystemCallError(Exception): |
| 260 | def __init__(self, command, result): |
| 261 | self.command = command |
| 262 | self.result = result |
| 263 | def __str__(self, *args, **kwargs): |
| 264 | return "'%s' ==> %s"%("' '".join(self.command), self.result) |
| 265 | |
| 266 | class utils: |
| 267 | |
| 268 | call_stats = [] |
| 269 | |
| 270 | @staticmethod |
| 271 | def call(*command, **kargs): |
| 272 | utils.log( "%s> '%s'"%(os.getcwd(), "' '".join(command)) ) |
| 273 | t = time.time() |
| 274 | result = subprocess.call(command, **kargs) |
| 275 | t = time.time()-t |
| 276 | if result != 0: |
| 277 | print "Failed: '%s' ERROR = %s"%("' '".join(command), result) |
| 278 | utils.call_stats.append((t,os.getcwd(),command,result)) |
| 279 | utils.log( "%s> '%s' execution time %s seconds"%(os.getcwd(), "' '".join(command), t) ) |
| 280 | return result |
| 281 | |
| 282 | @staticmethod |
| 283 | def print_call_stats(): |
| 284 | utils.log("================================================================================") |
| 285 | for j in sorted(utils.call_stats, reverse=True): |
| 286 | utils.log("{:>12.4f}\t{}> {} ==> {}".format(*j)) |
| 287 | utils.log("================================================================================") |
| 288 | |
| 289 | @staticmethod |
| 290 | def check_call(*command, **kargs): |
| 291 | cwd = os.getcwd() |
| 292 | result = utils.call(*command, **kargs) |
| 293 | if result != 0: |
| 294 | raise(SystemCallError([cwd].extend(command), result)) |
| 295 | |
| 296 | @staticmethod |
| 297 | def makedirs( path ): |
| 298 | if not os.path.exists( path ): |
| 299 | os.makedirs( path ) |
| 300 | |
| 301 | @staticmethod |
| 302 | def log_level(): |
| 303 | frames = inspect.stack() |
| 304 | level = 0 |
| 305 | for i in frames[ 3: ]: |
| 306 | if i[0].f_locals.has_key( '__log__' ): |
| 307 | level = level + i[0].f_locals[ '__log__' ] |
| 308 | return level |
| 309 | |
| 310 | @staticmethod |
| 311 | def log( message ): |
| 312 | sys.stdout.flush() |
| 313 | sys.stderr.flush() |
| 314 | sys.stderr.write( '# ' + ' ' * utils.log_level() + message + '\n' ) |
| 315 | sys.stderr.flush() |
| 316 | |
| 317 | @staticmethod |
| 318 | def rmtree(path): |
| 319 | if os.path.exists( path ): |
| 320 | #~ shutil.rmtree( unicode( path ) ) |
| 321 | if sys.platform == 'win32': |
| 322 | os.system( 'del /f /s /q "%s" >nul 2>&1' % path ) |
| 323 | shutil.rmtree( unicode( path ) ) |
| 324 | else: |
| 325 | os.system( 'rm -f -r "%s"' % path ) |
| 326 | |
| 327 | @staticmethod |
| 328 | def retry( f, max_attempts=5, sleep_secs=10 ): |
| 329 | for attempts in range( max_attempts, -1, -1 ): |
| 330 | try: |
| 331 | return f() |
| 332 | except Exception, msg: |
| 333 | utils.log( '%s failed with message "%s"' % ( f.__name__, msg ) ) |
| 334 | if attempts == 0: |
| 335 | utils.log( 'Giving up.' ) |
| 336 | raise |
| 337 | |
| 338 | utils.log( 'Retrying (%d more attempts).' % attempts ) |
| 339 | time.sleep( sleep_secs ) |
| 340 | |
| 341 | @staticmethod |
| 342 | def web_get( source_url, destination_file, proxy = None ): |
| 343 | import urllib |
| 344 | |
| 345 | proxies = None |
| 346 | if proxy is not None: |
| 347 | proxies = { |
| 348 | 'https' : proxy, |
| 349 | 'http' : proxy |
| 350 | } |
| 351 | |
| 352 | src = urllib.urlopen( source_url, proxies = proxies ) |
| 353 | |
| 354 | f = open( destination_file, 'wb' ) |
| 355 | while True: |
| 356 | data = src.read( 16*1024 ) |
| 357 | if len( data ) == 0: break |
| 358 | f.write( data ) |
| 359 | |
| 360 | f.close() |
| 361 | src.close() |
| 362 | |
| 363 | @staticmethod |
| 364 | def unpack_archive( archive_path ): |
| 365 | utils.log( 'Unpacking archive ("%s")...' % archive_path ) |
| 366 | |
| 367 | archive_name = os.path.basename( archive_path ) |
| 368 | extension = archive_name[ archive_name.find( '.' ) : ] |
| 369 | |
| 370 | if extension in ( ".tar.gz", ".tar.bz2" ): |
| 371 | import tarfile |
| 372 | import stat |
| 373 | |
| 374 | mode = os.path.splitext( extension )[1][1:] |
| 375 | tar = tarfile.open( archive_path, 'r:%s' % mode ) |
| 376 | for tarinfo in tar: |
| 377 | tar.extract( tarinfo ) |
| 378 | if sys.platform == 'win32' and not tarinfo.isdir(): |
| 379 | # workaround what appears to be a Win32-specific bug in 'tarfile' |
| 380 | # (modification times for extracted files are not set properly) |
| 381 | f = os.path.join( os.curdir, tarinfo.name ) |
| 382 | os.chmod( f, stat.S_IWRITE ) |
| 383 | os.utime( f, ( tarinfo.mtime, tarinfo.mtime ) ) |
| 384 | tar.close() |
| 385 | elif extension in ( ".zip" ): |
| 386 | import zipfile |
| 387 | |
| 388 | z = zipfile.ZipFile( archive_path, 'r', zipfile.ZIP_DEFLATED ) |
| 389 | for f in z.infolist(): |
| 390 | destination_file_path = os.path.join( os.curdir, f.filename ) |
| 391 | if destination_file_path[-1] == "/": # directory |
| 392 | if not os.path.exists( destination_file_path ): |
| 393 | os.makedirs( destination_file_path ) |
| 394 | else: # file |
| 395 | result = open( destination_file_path, 'wb' ) |
| 396 | result.write( z.read( f.filename ) ) |
| 397 | result.close() |
| 398 | z.close() |
| 399 | else: |
| 400 | raise 'Do not know how to unpack archives with extension \"%s\"' % extension |
| 401 | |
| 402 | @staticmethod |
| 403 | def make_file(filename, *text): |
| 404 | text = string.join( text, '\n' ) |
| 405 | with codecs.open( filename, 'w', 'utf-8' ) as f: |
| 406 | f.write( text ) |
| 407 | |
| 408 | @staticmethod |
| 409 | def append_file(filename, *text): |
| 410 | with codecs.open( filename, 'a', 'utf-8' ) as f: |
| 411 | f.write( string.join( text, '\n' ) ) |
| 412 | |
| 413 | @staticmethod |
| 414 | def mem_info(): |
| 415 | if sys.platform == "darwin": |
| 416 | utils.call("top","-l","1","-s","0","-n","0") |
| 417 | elif sys.platform.startswith("linux"): |
| 418 | utils.call("free","-m","-l") |
| 419 | |
| 420 | @staticmethod |
| 421 | def query_boost_version(boost_root): |
| 422 | ''' |
| 423 | Read in the Boost version from a given boost_root. |
| 424 | ''' |
| 425 | boost_version = None |
| 426 | if os.path.exists(os.path.join(boost_root,'Jamroot')): |
| 427 | with codecs.open(os.path.join(boost_root,'Jamroot'), 'r', 'utf-8') as f: |
| 428 | for line in f.readlines(): |
| 429 | parts = line.split() |
| 430 | if len(parts) >= 5 and parts[1] == 'BOOST_VERSION': |
| 431 | boost_version = parts[3] |
| 432 | break |
| 433 | if not boost_version: |
| 434 | boost_version = 'default' |
| 435 | return boost_version |
| 436 | |
| 437 | @staticmethod |
| 438 | def git_clone(sub_repo, branch, commit = None, cwd = None, no_submodules = False): |
| 439 | ''' |
| 440 | This clone mimicks the way Travis-CI clones a project's repo. So far |
| 441 | Travis-CI is the most limiting in the sense of only fetching partial |
| 442 | history of the repo. |
| 443 | ''' |
| 444 | if not cwd: |
| 445 | cwd = cwd = os.getcwd() |
| 446 | root_dir = os.path.join(cwd,'boostorg',sub_repo) |
| 447 | if not os.path.exists(os.path.join(root_dir,'.git')): |
| 448 | utils.check_call("git","clone", |
| 449 | "--depth=1", |
| 450 | "--branch=%s"%(branch), |
| 451 | "https://github.com/boostorg/%s.git"%(sub_repo), |
| 452 | root_dir) |
| 453 | os.chdir(root_dir) |
| 454 | else: |
| 455 | os.chdir(root_dir) |
| 456 | utils.check_call("git","pull", |
| 457 | # "--depth=1", # Can't do depth as we get merge errors. |
| 458 | "--quiet","--no-recurse-submodules") |
| 459 | if commit: |
| 460 | utils.check_call("git","checkout","-qf",commit) |
| 461 | if os.path.exists(os.path.join('.git','modules')): |
| 462 | if sys.platform == 'win32': |
| 463 | utils.check_call('dir',os.path.join('.git','modules')) |
| 464 | else: |
| 465 | utils.check_call('ls','-la',os.path.join('.git','modules')) |
| 466 | if not no_submodules: |
| 467 | utils.check_call("git","submodule","--quiet","update", |
| 468 | "--quiet","--init","--recursive", |
| 469 | ) |
| 470 | utils.check_call("git","submodule","--quiet","foreach","git","fetch") |
| 471 | return root_dir |
| 472 | |
| 473 | class parallel_call(threading.Thread): |
| 474 | ''' |
| 475 | Runs a synchronous command in a thread waiting for it to complete. |
| 476 | ''' |
| 477 | |
| 478 | def __init__(self, *command, **kargs): |
| 479 | super(parallel_call,self).__init__() |
| 480 | self.command = command |
| 481 | self.command_kargs = kargs |
| 482 | self.start() |
| 483 | |
| 484 | def run(self): |
| 485 | self.result = utils.call(*self.command, **self.command_kargs) |
| 486 | |
| 487 | def join(self): |
| 488 | super(parallel_call,self).join() |
| 489 | if self.result != 0: |
| 490 | raise(SystemCallError(self.command, self.result)) |
| 491 | |
| 492 | def set_arg(args, k, v = None): |
| 493 | if not args.get(k): |
| 494 | args[k] = v |
| 495 | return args[k] |
| 496 | |
| 497 | class script_common(object): |
| 498 | ''' |
| 499 | Main script to run Boost C++ Libraries continuous integration. |
| 500 | ''' |
| 501 | |
| 502 | def __init__(self, ci_klass, **kargs): |
| 503 | self.ci = ci_klass(self) |
| 504 | |
| 505 | opt = optparse.OptionParser( |
| 506 | usage="%prog [options] [commands]") |
| 507 | |
| 508 | #~ Debug Options: |
| 509 | opt.add_option( '--debug-level', |
| 510 | help="debugging level; controls the amount of debugging output printed", |
| 511 | type='int' ) |
| 512 | opt.add_option( '-j', |
| 513 | help="maximum number of parallel jobs to use for building with b2", |
| 514 | type='int', dest='jobs') |
| 515 | opt.add_option('--branch') |
| 516 | opt.add_option('--commit') |
| 517 | kargs = self.init(opt,kargs) |
| 518 | kargs = self.ci.init(opt, kargs) |
| 519 | set_arg(kargs,'debug_level',0) |
| 520 | set_arg(kargs,'jobs',2) |
| 521 | set_arg(kargs,'branch',None) |
| 522 | set_arg(kargs,'commit',None) |
| 523 | set_arg(kargs,'repo',None) |
| 524 | set_arg(kargs,'root_dir',None) |
| 525 | set_arg(kargs,'actions',None) |
| 526 | set_arg(kargs,'pull_request', None) |
| 527 | |
| 528 | #~ Defaults |
| 529 | for (k,v) in kargs.iteritems(): |
| 530 | setattr(self,k,v) |
| 531 | ( _opt_, self.actions ) = opt.parse_args(None,self) |
| 532 | if not self.actions or self.actions == []: |
| 533 | self.actions = kargs.get('actions',None) |
| 534 | if not self.actions or self.actions == []: |
| 535 | self.actions = [ 'info' ] |
| 536 | if not self.root_dir: |
| 537 | self.root_dir = os.getcwd() |
| 538 | self.build_dir = os.path.join(os.path.dirname(self.root_dir), "build") |
| 539 | |
| 540 | # API keys. |
| 541 | self.bintray_key = os.getenv('BINTRAY_KEY') |
| 542 | |
| 543 | try: |
| 544 | self.start() |
| 545 | self.command_info() |
| 546 | self.main() |
| 547 | utils.print_call_stats() |
| 548 | except: |
| 549 | utils.print_call_stats() |
| 550 | raise |
| 551 | |
| 552 | def init(self, opt, kargs): |
| 553 | return kargs |
| 554 | |
| 555 | def start(self): |
| 556 | pass |
| 557 | |
| 558 | def main(self): |
| 559 | for action in self.actions: |
| 560 | action_m = "command_"+action.replace('-','_') |
| 561 | ci_command = getattr(self.ci, action_m, None) |
| 562 | ci_script = getattr(self, action_m, None) |
| 563 | if ci_command or ci_script: |
| 564 | utils.log( "### %s.."%(action) ) |
| 565 | if os.path.exists(self.root_dir): |
| 566 | os.chdir(self.root_dir) |
| 567 | if ci_command: |
| 568 | ci_command() |
| 569 | elif ci_script: |
| 570 | ci_script() |
| 571 | |
| 572 | def b2( self, *args, **kargs ): |
| 573 | cmd = ['b2','--debug-configuration', '-j%s'%(self.jobs)] |
| 574 | cmd.extend(args) |
| 575 | |
| 576 | if 'toolset' in kargs: |
| 577 | cmd.append('toolset=' + kargs['toolset']) |
| 578 | |
| 579 | if 'parallel' in kargs: |
| 580 | return parallel_call(*cmd) |
| 581 | else: |
| 582 | return utils.check_call(*cmd) |
| 583 | |
| 584 | # Common test commands in the order they should be executed.. |
| 585 | |
| 586 | def command_info(self): |
| 587 | pass |
| 588 | |
| 589 | def command_install(self): |
| 590 | utils.makedirs(self.build_dir) |
| 591 | os.chdir(self.build_dir) |
| 592 | |
| 593 | def command_install_toolset(self, toolset): |
| 594 | if self.ci and hasattr(self.ci,'install_toolset'): |
| 595 | self.ci.install_toolset(toolset) |
| 596 | |
| 597 | def command_before_build(self): |
| 598 | pass |
| 599 | |
| 600 | def command_build(self): |
| 601 | pass |
| 602 | |
| 603 | def command_before_cache(self): |
| 604 | pass |
| 605 | |
| 606 | def command_after_success(self): |
| 607 | pass |
| 608 | |
| 609 | class ci_cli(object): |
| 610 | ''' |
| 611 | This version of the script provides a way to do manual building. It sets up |
| 612 | additional environment and adds fetching of the git repos that would |
| 613 | normally be done by the CI system. |
| 614 | |
| 615 | The common way to use this variant is to invoke something like: |
| 616 | |
| 617 | mkdir boost-ci |
| 618 | cd boost-ci |
| 619 | python path-to/ci_boost_<script>.py --branch=develop [--repo=mylib] ... |
| 620 | |
| 621 | Status: In working order. |
| 622 | ''' |
| 623 | |
| 624 | def __init__(self,script): |
| 625 | if sys.platform == 'darwin': |
| 626 | # Requirements for running on OSX: |
| 627 | # https://www.stack.nl/~dimitri/doxygen/download.html#srcbin |
| 628 | # https://tug.org/mactex/morepackages.html |
| 629 | doxygen_path = "/Applications/Doxygen.app/Contents/Resources" |
| 630 | if os.path.isdir(doxygen_path): |
| 631 | os.environ["PATH"] = doxygen_path+':'+os.environ['PATH'] |
| 632 | self.script = script |
| 633 | self.work_dir = os.getcwd() |
| 634 | self.exit_result = 0 |
| 635 | |
| 636 | def init(self, opt, kargs): |
| 637 | kargs['actions'] = [ |
| 638 | 'clone', |
| 639 | 'install', |
| 640 | 'before_build', |
| 641 | 'build', |
| 642 | 'before_cache', |
| 643 | 'finish' |
| 644 | ] |
| 645 | opt.add_option( '--repo', |
| 646 | help="Boost repo short name we are testing with, and hence the repo we clone.") |
| 647 | set_arg(kargs,'repo','boost') |
| 648 | return kargs |
| 649 | |
| 650 | def finish(self, result): |
| 651 | self.exit_result = result |
| 652 | |
| 653 | def command_clone(self): |
| 654 | self.script.root_dir = os.path.join(self.work_dir,'boostorg',self.script.repo) |
| 655 | self.script.build_dir = os.path.join(os.path.dirname(self.script.root_dir), "build") |
| 656 | utils.git_clone(self.script.repo, self.script.branch, self.script.commit, self.work_dir) |
| 657 | |
| 658 | def command_finish(self): |
| 659 | exit(self.exit_result) |
| 660 | |
| 661 | class ci_travis(object): |
| 662 | ''' |
| 663 | This variant build releases in the context of the Travis-CI service. |
| 664 | ''' |
| 665 | |
| 666 | def __init__(self,script): |
| 667 | self.script = script |
| 668 | self.work_dir = os.getenv("HOME") |
| 669 | |
| 670 | def init(self, opt, kargs): |
| 671 | set_arg(kargs,'root_dir', os.getenv("TRAVIS_BUILD_DIR")) |
| 672 | set_arg(kargs,'branch', os.getenv("TRAVIS_BRANCH")) |
| 673 | set_arg(kargs,'commit', os.getenv("TRAVIS_COMMIT")) |
| 674 | set_arg(kargs,'repo', os.getenv("TRAVIS_REPO_SLUG").split("/")[1]) |
| 675 | set_arg(kargs,'pull_request', |
| 676 | os.getenv('TRAVIS_PULL_REQUEST') \ |
| 677 | if os.getenv('TRAVIS_PULL_REQUEST') != 'false' else None) |
| 678 | return kargs |
| 679 | |
| 680 | def finish(self, result): |
| 681 | exit(result) |
| 682 | |
| 683 | def install_toolset(self, toolset): |
| 684 | ''' |
| 685 | Installs specific toolset on CI system. |
| 686 | ''' |
| 687 | info = toolset_info[toolset] |
| 688 | if sys.platform.startswith('linux'): |
| 689 | os.chdir(self.work_dir) |
| 690 | if 'ppa' in info: |
| 691 | for ppa in info['ppa']: |
| 692 | utils.check_call( |
| 693 | 'sudo','add-apt-repository','--yes',ppa) |
| 694 | if 'deb' in info: |
| 695 | utils.make_file('sources.list', |
| 696 | "deb %s"%(' '.join(info['deb'])), |
| 697 | "deb-src %s"%(' '.join(info['deb']))) |
| 698 | utils.check_call('sudo','bash','-c','cat sources.list >> /etc/apt/sources.list') |
| 699 | if 'apt-key' in info: |
| 700 | for key in info['apt-key']: |
| 701 | utils.check_call('wget',key,'-O','apt.key') |
| 702 | utils.check_call('sudo','apt-key','add','apt.key') |
| 703 | utils.check_call( |
| 704 | 'sudo','apt-get','update','-qq') |
| 705 | utils.check_call( |
| 706 | 'sudo','apt-get','install','-qq',info['package']) |
| 707 | if 'debugpackage' in info and info['debugpackage']: |
| 708 | utils.check_call( |
| 709 | 'sudo','apt-get','install','-qq',info['debugpackage']) |
| 710 | |
| 711 | # Travis-CI commands in the order they are executed. We need |
| 712 | # these to forward to our common commands, if they are different. |
| 713 | |
| 714 | def command_before_install(self): |
| 715 | pass |
| 716 | |
| 717 | def command_install(self): |
| 718 | self.script.command_install() |
| 719 | |
| 720 | def command_before_script(self): |
| 721 | self.script.command_before_build() |
| 722 | |
| 723 | def command_script(self): |
| 724 | self.script.command_build() |
| 725 | |
| 726 | def command_before_cache(self): |
| 727 | self.script.command_before_cache() |
| 728 | |
| 729 | def command_after_success(self): |
| 730 | self.script.command_after_success() |
| 731 | |
| 732 | def command_after_failure(self): |
| 733 | pass |
| 734 | |
| 735 | def command_before_deploy(self): |
| 736 | pass |
| 737 | |
| 738 | def command_after_deploy(self): |
| 739 | pass |
| 740 | |
| 741 | def command_after_script(self): |
| 742 | pass |
| 743 | |
| 744 | class ci_circleci(object): |
| 745 | ''' |
| 746 | This variant build releases in the context of the CircleCI service. |
| 747 | ''' |
| 748 | |
| 749 | def __init__(self,script): |
| 750 | self.script = script |
| 751 | self.work_dir = os.getenv("HOME") |
| 752 | |
| 753 | def init(self, opt, kargs): |
| 754 | set_arg(kargs,'root_dir', os.path.join(os.getenv("HOME"),os.getenv("CIRCLE_PROJECT_REPONAME"))) |
| 755 | set_arg(kargs,'branch', os.getenv("CIRCLE_BRANCH")) |
| 756 | set_arg(kargs,'commit', os.getenv("CIRCLE_SHA1")) |
| 757 | set_arg(kargs,'repo', os.getenv("CIRCLE_PROJECT_REPONAME").split("/")[1]) |
| 758 | set_arg(kargs,'pull_request', os.getenv('CIRCLE_PR_NUMBER')) |
| 759 | return kargs |
| 760 | |
| 761 | def finish(self, result): |
| 762 | exit(result) |
| 763 | |
| 764 | def command_machine_post(self): |
| 765 | # Apt update for the pckages installs we'll do later. |
| 766 | utils.check_call('sudo','apt-get','-qq','update') |
| 767 | # Need PyYAML to read Travis yaml in a later step. |
| 768 | utils.check_call("pip","install","--user","PyYAML") |
| 769 | |
| 770 | def command_checkout_post(self): |
| 771 | os.chdir(self.script.root_dir) |
| 772 | utils.check_call("git","submodule","update","--quiet","--init","--recursive") |
| 773 | |
| 774 | def command_dependencies_pre(self): |
| 775 | # Read in .travis.yml for list of packages to install |
| 776 | # as CircleCI doesn't have a convenient apt install method. |
| 777 | import yaml |
| 778 | utils.check_call('sudo','-E','apt-get','-yqq','update') |
| 779 | utils.check_call('sudo','apt-get','-yqq','purge','texlive*') |
| 780 | with open(os.path.join(self.script.root_dir,'.travis.yml')) as yml: |
| 781 | travis_yml = yaml.load(yml) |
| 782 | utils.check_call('sudo','apt-get','-yqq', |
| 783 | '--no-install-suggests','--no-install-recommends','--force-yes','install', |
| 784 | *travis_yml['addons']['apt']['packages']) |
| 785 | |
| 786 | def command_dependencies_override(self): |
| 787 | self.script.command_install() |
| 788 | |
| 789 | def command_dependencies_post(self): |
| 790 | pass |
| 791 | |
| 792 | def command_database_pre(self): |
| 793 | pass |
| 794 | |
| 795 | def command_database_override(self): |
| 796 | pass |
| 797 | |
| 798 | def command_database_post(self): |
| 799 | pass |
| 800 | |
| 801 | def command_test_pre(self): |
| 802 | self.script.command_install() |
| 803 | self.script.command_before_build() |
| 804 | |
| 805 | def command_test_override(self): |
| 806 | # CircleCI runs all the test subsets. So in order to avoid |
| 807 | # running the after_success we do it here as the build step |
| 808 | # will halt accordingly. |
| 809 | self.script.command_build() |
| 810 | self.script.command_before_cache() |
| 811 | self.script.command_after_success() |
| 812 | |
| 813 | def command_test_post(self): |
| 814 | pass |
| 815 | |
| 816 | class ci_appveyor(object): |
| 817 | |
| 818 | def __init__(self,script): |
| 819 | self.script = script |
| 820 | self.work_dir = os.path.dirname(os.getenv("APPVEYOR_BUILD_FOLDER")) |
| 821 | |
| 822 | def init(self, opt, kargs): |
| 823 | set_arg(kargs,'root_dir',os.getenv("APPVEYOR_BUILD_FOLDER")) |
| 824 | set_arg(kargs,'branch',os.getenv("APPVEYOR_REPO_BRANCH")) |
| 825 | set_arg(kargs,'commit',os.getenv("APPVEYOR_REPO_COMMIT")) |
| 826 | set_arg(kargs,'repo',os.getenv("APPVEYOR_REPO_NAME").split("/")[1]) |
| 827 | set_arg(kargs,'address_model',os.getenv("PLATFORM",None)) |
| 828 | set_arg(kargs,'variant',os.getenv("CONFIGURATION","debug")) |
| 829 | set_arg(kargs,'pull_request', os.getenv('APPVEYOR_PULL_REQUEST_NUMBER')) |
| 830 | return kargs |
| 831 | |
| 832 | def finish(self, result): |
| 833 | exit(result) |
| 834 | |
| 835 | # Appveyor commands in the order they are executed. We need |
| 836 | # these to forward to our common commands, if they are different. |
| 837 | |
| 838 | def command_install(self): |
| 839 | self.script.command_install() |
| 840 | |
| 841 | def command_before_build(self): |
| 842 | os.chdir(self.script.root_dir) |
| 843 | utils.check_call("git","submodule","update","--quiet","--init","--recursive") |
| 844 | self.script.command_before_build() |
| 845 | |
| 846 | def command_build_script(self): |
| 847 | self.script.command_build() |
| 848 | |
| 849 | def command_after_build(self): |
| 850 | self.script.command_before_cache() |
| 851 | |
| 852 | def command_before_test(self): |
| 853 | pass |
| 854 | |
| 855 | def command_test_script(self): |
| 856 | pass |
| 857 | |
| 858 | def command_after_test(self): |
| 859 | pass |
| 860 | |
| 861 | def command_on_success(self): |
| 862 | self.script.command_after_success() |
| 863 | |
| 864 | def command_on_failure(self): |
| 865 | pass |
| 866 | |
| 867 | def command_on_finish(self): |
| 868 | pass |
| 869 | |
| 870 | def main(script_klass): |
| 871 | if os.getenv('TRAVIS', False): |
| 872 | script_klass(ci_travis) |
| 873 | elif os.getenv('CIRCLECI', False): |
| 874 | script_klass(ci_circleci) |
| 875 | elif os.getenv('APPVEYOR', False): |
| 876 | script_klass(ci_appveyor) |
| 877 | else: |
| 878 | script_klass(ci_cli) |