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 9import sys 10import inspect 11import optparse 12import os.path 13import string 14import time 15import subprocess 16import codecs 17import shutil 18import threading 19 20toolset_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 'xcode-9.2' : { 258 'command' : 'clang++', 259 'toolset' : 'clang', 260 'version' : '' 261 }, 262 'xcode-9.3' : { 263 'command' : 'clang++', 264 'toolset' : 'clang', 265 'version' : '' 266 }, 267 'xcode-9.4' : { 268 'command' : 'clang++', 269 'toolset' : 'clang', 270 'version' : '' 271 }, 272 'xcode-10.0' : { 273 'command' : 'clang++', 274 'toolset' : 'clang', 275 'version' : '' 276 }, 277 } 278 279class SystemCallError(Exception): 280 def __init__(self, command, result): 281 self.command = command 282 self.result = result 283 def __str__(self, *args, **kwargs): 284 return "'%s' ==> %s"%("' '".join(self.command), self.result) 285 286class utils: 287 288 call_stats = [] 289 290 @staticmethod 291 def call(*command, **kargs): 292 utils.log( "%s> '%s'"%(os.getcwd(), "' '".join(command)) ) 293 t = time.time() 294 result = subprocess.call(command, **kargs) 295 t = time.time()-t 296 if result != 0: 297 print "Failed: '%s' ERROR = %s"%("' '".join(command), result) 298 utils.call_stats.append((t,os.getcwd(),command,result)) 299 utils.log( "%s> '%s' execution time %s seconds"%(os.getcwd(), "' '".join(command), t) ) 300 return result 301 302 @staticmethod 303 def print_call_stats(): 304 utils.log("================================================================================") 305 for j in sorted(utils.call_stats, reverse=True): 306 utils.log("{:>12.4f}\t{}> {} ==> {}".format(*j)) 307 utils.log("================================================================================") 308 309 @staticmethod 310 def check_call(*command, **kargs): 311 cwd = os.getcwd() 312 result = utils.call(*command, **kargs) 313 if result != 0: 314 raise(SystemCallError([cwd].extend(command), result)) 315 316 @staticmethod 317 def makedirs( path ): 318 if not os.path.exists( path ): 319 os.makedirs( path ) 320 321 @staticmethod 322 def log_level(): 323 frames = inspect.stack() 324 level = 0 325 for i in frames[ 3: ]: 326 if i[0].f_locals.has_key( '__log__' ): 327 level = level + i[0].f_locals[ '__log__' ] 328 return level 329 330 @staticmethod 331 def log( message ): 332 sys.stdout.flush() 333 sys.stderr.flush() 334 sys.stderr.write( '# ' + ' ' * utils.log_level() + message + '\n' ) 335 sys.stderr.flush() 336 337 @staticmethod 338 def rmtree(path): 339 if os.path.exists( path ): 340 #~ shutil.rmtree( unicode( path ) ) 341 if sys.platform == 'win32': 342 os.system( 'del /f /s /q "%s" >nul 2>&1' % path ) 343 shutil.rmtree( unicode( path ) ) 344 else: 345 os.system( 'rm -f -r "%s"' % path ) 346 347 @staticmethod 348 def retry( f, max_attempts=5, sleep_secs=10 ): 349 for attempts in range( max_attempts, -1, -1 ): 350 try: 351 return f() 352 except Exception, msg: 353 utils.log( '%s failed with message "%s"' % ( f.__name__, msg ) ) 354 if attempts == 0: 355 utils.log( 'Giving up.' ) 356 raise 357 358 utils.log( 'Retrying (%d more attempts).' % attempts ) 359 time.sleep( sleep_secs ) 360 361 @staticmethod 362 def web_get( source_url, destination_file, proxy = None ): 363 import urllib 364 365 proxies = None 366 if proxy is not None: 367 proxies = { 368 'https' : proxy, 369 'http' : proxy 370 } 371 372 src = urllib.urlopen( source_url, proxies = proxies ) 373 374 f = open( destination_file, 'wb' ) 375 while True: 376 data = src.read( 16*1024 ) 377 if len( data ) == 0: break 378 f.write( data ) 379 380 f.close() 381 src.close() 382 383 @staticmethod 384 def unpack_archive( archive_path ): 385 utils.log( 'Unpacking archive ("%s")...' % archive_path ) 386 387 archive_name = os.path.basename( archive_path ) 388 extension = archive_name[ archive_name.find( '.' ) : ] 389 390 if extension in ( ".tar.gz", ".tar.bz2" ): 391 import tarfile 392 import stat 393 394 mode = os.path.splitext( extension )[1][1:] 395 tar = tarfile.open( archive_path, 'r:%s' % mode ) 396 for tarinfo in tar: 397 tar.extract( tarinfo ) 398 if sys.platform == 'win32' and not tarinfo.isdir(): 399 # workaround what appears to be a Win32-specific bug in 'tarfile' 400 # (modification times for extracted files are not set properly) 401 f = os.path.join( os.curdir, tarinfo.name ) 402 os.chmod( f, stat.S_IWRITE ) 403 os.utime( f, ( tarinfo.mtime, tarinfo.mtime ) ) 404 tar.close() 405 elif extension in ( ".zip" ): 406 import zipfile 407 408 z = zipfile.ZipFile( archive_path, 'r', zipfile.ZIP_DEFLATED ) 409 for f in z.infolist(): 410 destination_file_path = os.path.join( os.curdir, f.filename ) 411 if destination_file_path[-1] == "/": # directory 412 if not os.path.exists( destination_file_path ): 413 os.makedirs( destination_file_path ) 414 else: # file 415 result = open( destination_file_path, 'wb' ) 416 result.write( z.read( f.filename ) ) 417 result.close() 418 z.close() 419 else: 420 raise 'Do not know how to unpack archives with extension \"%s\"' % extension 421 422 @staticmethod 423 def make_file(filename, *text): 424 text = string.join( text, '\n' ) 425 with codecs.open( filename, 'w', 'utf-8' ) as f: 426 f.write( text ) 427 428 @staticmethod 429 def append_file(filename, *text): 430 with codecs.open( filename, 'a', 'utf-8' ) as f: 431 f.write( string.join( text, '\n' ) ) 432 433 @staticmethod 434 def mem_info(): 435 if sys.platform == "darwin": 436 utils.call("top","-l","1","-s","0","-n","0") 437 elif sys.platform.startswith("linux"): 438 utils.call("free","-m","-l") 439 440 @staticmethod 441 def query_boost_version(boost_root): 442 ''' 443 Read in the Boost version from a given boost_root. 444 ''' 445 boost_version = None 446 if os.path.exists(os.path.join(boost_root,'Jamroot')): 447 with codecs.open(os.path.join(boost_root,'Jamroot'), 'r', 'utf-8') as f: 448 for line in f.readlines(): 449 parts = line.split() 450 if len(parts) >= 5 and parts[1] == 'BOOST_VERSION': 451 boost_version = parts[3] 452 break 453 if not boost_version: 454 boost_version = 'default' 455 return boost_version 456 457 @staticmethod 458 def git_clone(owner, repo, branch, commit = None, repo_dir = None, submodules = False, url_format = "https://github.com/%(owner)s/%(repo)s.git"): 459 ''' 460 This clone mimicks the way Travis-CI clones a project's repo. So far 461 Travis-CI is the most limiting in the sense of only fetching partial 462 history of the repo. 463 ''' 464 if not repo_dir: 465 repo_dir = os.path.join(os.getcwd(), owner+','+repo) 466 utils.makedirs(os.path.dirname(repo_dir)) 467 if not os.path.exists(os.path.join(repo_dir,'.git')): 468 utils.check_call("git","clone", 469 "--depth=1", 470 "--branch=%s"%(branch), 471 url_format%{'owner':owner,'repo':repo}, 472 repo_dir) 473 os.chdir(repo_dir) 474 else: 475 os.chdir(repo_dir) 476 utils.check_call("git","pull", 477 # "--depth=1", # Can't do depth as we get merge errors. 478 "--quiet","--no-recurse-submodules") 479 if commit: 480 utils.check_call("git","checkout","-qf",commit) 481 if os.path.exists(os.path.join('.git','modules')): 482 if sys.platform == 'win32': 483 utils.check_call('dir',os.path.join('.git','modules')) 484 else: 485 utils.check_call('ls','-la',os.path.join('.git','modules')) 486 if submodules: 487 utils.check_call("git","submodule","--quiet","update", 488 "--quiet","--init","--recursive", 489 ) 490 utils.check_call("git","submodule","--quiet","foreach","git","fetch") 491 return repo_dir 492 493class parallel_call(threading.Thread): 494 ''' 495 Runs a synchronous command in a thread waiting for it to complete. 496 ''' 497 498 def __init__(self, *command, **kargs): 499 super(parallel_call,self).__init__() 500 self.command = command 501 self.command_kargs = kargs 502 self.start() 503 504 def run(self): 505 self.result = utils.call(*self.command, **self.command_kargs) 506 507 def join(self): 508 super(parallel_call,self).join() 509 if self.result != 0: 510 raise(SystemCallError(self.command, self.result)) 511 512def set_arg(args, k, v = None): 513 if not args.get(k): 514 args[k] = v 515 return args[k] 516 517class script_common(object): 518 ''' 519 Main script to run continuous integration. 520 ''' 521 522 def __init__(self, ci_klass, **kargs): 523 self.ci = ci_klass(self) 524 525 opt = optparse.OptionParser( 526 usage="%prog [options] [commands]") 527 528 #~ Debug Options: 529 opt.add_option( '--debug-level', 530 help="debugging level; controls the amount of debugging output printed", 531 type='int' ) 532 opt.add_option( '-j', 533 help="maximum number of parallel jobs to use for building with b2", 534 type='int', dest='jobs') 535 opt.add_option('--branch') 536 opt.add_option('--commit') 537 kargs = self.init(opt,kargs) 538 kargs = self.ci.init(opt, kargs) 539 set_arg(kargs,'debug_level',0) 540 set_arg(kargs,'jobs',2) 541 set_arg(kargs,'branch',None) 542 set_arg(kargs,'commit',None) 543 set_arg(kargs,'repo',None) 544 set_arg(kargs,'repo_dir',None) 545 set_arg(kargs,'actions',None) 546 set_arg(kargs,'pull_request', None) 547 548 #~ Defaults 549 for (k,v) in kargs.iteritems(): 550 setattr(self,k,v) 551 ( _opt_, self.actions ) = opt.parse_args(None,self) 552 if not self.actions or self.actions == []: 553 self.actions = kargs.get('actions',None) 554 if not self.actions or self.actions == []: 555 self.actions = [ 'info' ] 556 if not self.repo_dir: 557 self.repo_dir = os.getcwd() 558 self.build_dir = os.path.join(os.path.dirname(self.repo_dir), "build") 559 560 # API keys. 561 self.bintray_key = os.getenv('BINTRAY_KEY') 562 563 try: 564 self.start() 565 self.command_info() 566 self.main() 567 utils.print_call_stats() 568 except: 569 utils.print_call_stats() 570 raise 571 572 def init(self, opt, kargs): 573 return kargs 574 575 def start(self): 576 pass 577 578 def main(self): 579 for action in self.actions: 580 action_m = "command_"+action.replace('-','_') 581 ci_command = getattr(self.ci, action_m, None) 582 ci_script = getattr(self, action_m, None) 583 if ci_command or ci_script: 584 utils.log( "### %s.."%(action) ) 585 if os.path.exists(self.repo_dir): 586 os.chdir(self.repo_dir) 587 if ci_command: 588 ci_command() 589 elif ci_script: 590 ci_script() 591 592 def b2( self, *args, **kargs ): 593 cmd = ['b2','--debug-configuration', '-j%s'%(self.jobs)] 594 cmd.extend(args) 595 596 if 'toolset' in kargs: 597 cmd.append('toolset=' + kargs['toolset']) 598 599 if 'parallel' in kargs: 600 return parallel_call(*cmd) 601 else: 602 return utils.check_call(*cmd) 603 604 # Common test commands in the order they should be executed.. 605 606 def command_info(self): 607 pass 608 609 def command_install(self): 610 utils.makedirs(self.build_dir) 611 os.chdir(self.build_dir) 612 613 def command_install_toolset(self, toolset): 614 if self.ci and hasattr(self.ci,'install_toolset'): 615 self.ci.install_toolset(toolset) 616 617 def command_before_build(self): 618 pass 619 620 def command_build(self): 621 pass 622 623 def command_before_cache(self): 624 pass 625 626 def command_after_success(self): 627 pass 628 629class ci_cli(object): 630 ''' 631 This version of the script provides a way to do manual building. It sets up 632 additional environment and adds fetching of the git repos that would 633 normally be done by the CI system. 634 635 The common way to use this variant is to invoke something like: 636 637 mkdir ci 638 cd ci 639 python path-to/library_test.py --branch=develop [--repo=mylib] ... 640 641 Status: In working order. 642 ''' 643 644 def __init__(self,script): 645 if sys.platform == 'darwin': 646 # Requirements for running on OSX: 647 # https://www.stack.nl/~dimitri/doxygen/download.html#srcbin 648 # https://tug.org/mactex/morepackages.html 649 doxygen_path = "/Applications/Doxygen.app/Contents/Resources" 650 if os.path.isdir(doxygen_path): 651 os.environ["PATH"] = doxygen_path+':'+os.environ['PATH'] 652 self.script = script 653 self.repo_dir = os.getcwd() 654 self.exit_result = 0 655 656 def init(self, opt, kargs): 657 kargs['actions'] = [ 658 # 'clone', 659 'install', 660 'before_build', 661 'build', 662 'before_cache', 663 'finish' 664 ] 665 return kargs 666 667 def finish(self, result): 668 self.exit_result = result 669 670 def command_finish(self): 671 exit(self.exit_result) 672 673class ci_travis(object): 674 ''' 675 This variant build releases in the context of the Travis-CI service. 676 ''' 677 678 def __init__(self,script): 679 self.script = script 680 681 def init(self, opt, kargs): 682 set_arg(kargs,'repo_dir', os.getenv("TRAVIS_BUILD_DIR")) 683 set_arg(kargs,'branch', os.getenv("TRAVIS_BRANCH")) 684 set_arg(kargs,'commit', os.getenv("TRAVIS_COMMIT")) 685 set_arg(kargs,'repo', os.getenv("TRAVIS_REPO_SLUG").split("/")[1]) 686 set_arg(kargs,'pull_request', 687 os.getenv('TRAVIS_PULL_REQUEST') \ 688 if os.getenv('TRAVIS_PULL_REQUEST') != 'false' else None) 689 return kargs 690 691 def finish(self, result): 692 exit(result) 693 694 def install_toolset(self, toolset): 695 ''' 696 Installs specific toolset on CI system. 697 ''' 698 info = toolset_info[toolset] 699 if sys.platform.startswith('linux'): 700 os.chdir(self.script.build_dir) 701 if 'ppa' in info: 702 for ppa in info['ppa']: 703 utils.check_call( 704 'sudo','add-apt-repository','--yes',ppa) 705 if 'deb' in info: 706 utils.make_file('sources.list', 707 "deb %s"%(' '.join(info['deb'])), 708 "deb-src %s"%(' '.join(info['deb']))) 709 utils.check_call('sudo','bash','-c','cat sources.list >> /etc/apt/sources.list') 710 if 'apt-key' in info: 711 for key in info['apt-key']: 712 utils.check_call('wget',key,'-O','apt.key') 713 utils.check_call('sudo','apt-key','add','apt.key') 714 utils.check_call( 715 'sudo','apt-get','update','-qq') 716 utils.check_call( 717 'sudo','apt-get','install','-qq',info['package']) 718 if 'debugpackage' in info and info['debugpackage']: 719 utils.check_call( 720 'sudo','apt-get','install','-qq',info['debugpackage']) 721 722 # Travis-CI commands in the order they are executed. We need 723 # these to forward to our common commands, if they are different. 724 725 def command_before_install(self): 726 pass 727 728 def command_install(self): 729 self.script.command_install() 730 731 def command_before_script(self): 732 self.script.command_before_build() 733 734 def command_script(self): 735 self.script.command_build() 736 737 def command_before_cache(self): 738 self.script.command_before_cache() 739 740 def command_after_success(self): 741 self.script.command_after_success() 742 743 def command_after_failure(self): 744 pass 745 746 def command_before_deploy(self): 747 pass 748 749 def command_after_deploy(self): 750 pass 751 752 def command_after_script(self): 753 pass 754 755class ci_circleci(object): 756 ''' 757 This variant build releases in the context of the CircleCI service. 758 ''' 759 760 def __init__(self,script): 761 self.script = script 762 763 def init(self, opt, kargs): 764 set_arg(kargs,'repo_dir', os.path.join(os.getenv("HOME"),os.getenv("CIRCLE_PROJECT_REPONAME"))) 765 set_arg(kargs,'branch', os.getenv("CIRCLE_BRANCH")) 766 set_arg(kargs,'commit', os.getenv("CIRCLE_SHA1")) 767 set_arg(kargs,'repo', os.getenv("CIRCLE_PROJECT_REPONAME").split("/")[1]) 768 set_arg(kargs,'pull_request', os.getenv('CIRCLE_PR_NUMBER')) 769 return kargs 770 771 def finish(self, result): 772 exit(result) 773 774 def command_machine_post(self): 775 # Apt update for the pckages installs we'll do later. 776 utils.check_call('sudo','apt-get','-qq','update') 777 # Need PyYAML to read Travis yaml in a later step. 778 utils.check_call("pip","install","--user","PyYAML") 779 780 def command_checkout_post(self): 781 os.chdir(self.script.repo_dir) 782 utils.check_call("git","submodule","update","--quiet","--init","--recursive") 783 784 def command_dependencies_pre(self): 785 # Read in .travis.yml for list of packages to install 786 # as CircleCI doesn't have a convenient apt install method. 787 import yaml 788 utils.check_call('sudo','-E','apt-get','-yqq','update') 789 utils.check_call('sudo','apt-get','-yqq','purge','texlive*') 790 with open(os.path.join(self.script.repo_dir,'.travis.yml')) as yml: 791 travis_yml = yaml.load(yml) 792 utils.check_call('sudo','apt-get','-yqq', 793 '--no-install-suggests','--no-install-recommends','--force-yes','install', 794 *travis_yml['addons']['apt']['packages']) 795 796 def command_dependencies_override(self): 797 self.script.command_install() 798 799 def command_dependencies_post(self): 800 pass 801 802 def command_database_pre(self): 803 pass 804 805 def command_database_override(self): 806 pass 807 808 def command_database_post(self): 809 pass 810 811 def command_test_pre(self): 812 self.script.command_install() 813 self.script.command_before_build() 814 815 def command_test_override(self): 816 # CircleCI runs all the test subsets. So in order to avoid 817 # running the after_success we do it here as the build step 818 # will halt accordingly. 819 self.script.command_build() 820 self.script.command_before_cache() 821 self.script.command_after_success() 822 823 def command_test_post(self): 824 pass 825 826class ci_appveyor(object): 827 828 def __init__(self,script): 829 self.script = script 830 831 def init(self, opt, kargs): 832 set_arg(kargs,'repo_dir',os.getenv("APPVEYOR_BUILD_FOLDER")) 833 set_arg(kargs,'branch',os.getenv("APPVEYOR_REPO_BRANCH")) 834 set_arg(kargs,'commit',os.getenv("APPVEYOR_REPO_COMMIT")) 835 set_arg(kargs,'repo',os.getenv("APPVEYOR_REPO_NAME").split("/")[1]) 836 set_arg(kargs,'address_model',os.getenv("PLATFORM",None)) 837 set_arg(kargs,'variant',os.getenv("CONFIGURATION","debug")) 838 set_arg(kargs,'pull_request', os.getenv('APPVEYOR_PULL_REQUEST_NUMBER')) 839 return kargs 840 841 def finish(self, result): 842 exit(result) 843 844 # Appveyor commands in the order they are executed. We need 845 # these to forward to our common commands, if they are different. 846 847 def command_install(self): 848 self.script.command_install() 849 850 def command_before_build(self): 851 os.chdir(self.script.repo_dir) 852 utils.check_call("git","submodule","update","--quiet","--init","--recursive") 853 self.script.command_before_build() 854 855 def command_build_script(self): 856 self.script.command_build() 857 858 def command_after_build(self): 859 self.script.command_before_cache() 860 861 def command_before_test(self): 862 pass 863 864 def command_test_script(self): 865 pass 866 867 def command_after_test(self): 868 pass 869 870 def command_on_success(self): 871 self.script.command_after_success() 872 873 def command_on_failure(self): 874 pass 875 876 def command_on_finish(self): 877 pass 878 879def main(script_klass): 880 if os.getenv('TRAVIS', False): 881 script_klass(ci_travis) 882 elif os.getenv('CIRCLECI', False): 883 script_klass(ci_circleci) 884 elif os.getenv('APPVEYOR', False): 885 script_klass(ci_appveyor) 886 else: 887 script_klass(ci_cli) 888