blob: 50ca86077c0ef81a8b2ff70ead8896d54b0ae97a [file] [log] [blame]
Brian Silverman44c68b12018-08-04 23:56:44 -07001#!/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 }
258
259class 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
266class 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
473class 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
492def set_arg(args, k, v = None):
493 if not args.get(k):
494 args[k] = v
495 return args[k]
496
497class 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
609class 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
661class 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
744class 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
816class 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
870def 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)