#!/usr/bin/python # -*- coding: utf-8 -*- ################################################################################################### # SConstruct file - replacement for Makefile # # DESCRIPTION : # This file is used to compile, install and clean Cepam source code, libraries and binaries. # It can also manage other tasks, as archiving, committing to svn server, manage versions... # # USAGE : # scons [OPTIONS] [TARGETS] # # OPTIONS : # -h -- show complete help and options # # AUTHOR : # Mathieu Havel (mathieu.havel@oca.eu) # # CREATION DATE (dd/mm/yyyy) : # 23/01/2009 # # LAST CHANGES (dd/mm/yyyy) : # 05/03/2015 -- compiler options fixed [MH] # 03/06/2014 -- several bugs fixed # 10/06/2013 -- user-specified compiler is now better supported # 04/03/2011 -- fixed symbolic link issues, and put all programs in bin/ (clean root dir) [MH] # 24/09/2010 -- removed static, check and test now depend on optimise [TG] # 10/10/2009 -- added test of static model (static option) [TG] # 23/01/2009 -- creation # # LICENCE : # GPL ################################################################################################### EnsureSConsVersion(1, 0) ######################### #### Python module #### ######################### import os import sys import string import datetime import fnmatch import atexit ## aliases for python functions sep = os.sep # the seperator used in path ("\" under WINDOWS and "/" under LINUX) abspath = os.path.abspath # get the absolute path from a relative one ######################### ### Python function ### ######################### ## search and replace a string in "file" def search_and_replace(old_s, new_s, filename): f = open(filename, 'r') g = open(filename + '.ver.tmp', 'w') for line in f: g.write(line.replace(old_s, new_s)) f.close() g.close() os.remove(filename) os.rename(filename+'.ver.tmp', filename) ## Human version (opposed to svn revision number) class HumanVersion(object): """ CEPAM version manager """ def __init__(self, fVname="VERSION", fRname="README", fDname="DATE"): self.up_info = "[INFO] Upgrading CEPAM version and/or date..." self.old_info = " Old version number : %s" self.new_info = " New version number : %s" self.date_info = " New modification date is : %s" self.success_info = '[INFO] Done' self.cepam_ver = "CEPAM Version %s (%s)" self.Vname = fVname self.Rname = fRname self.Dname = fDname def read(self): """ Reads CEPAM version file. Version is supposed to be on the first line (no comments, blank line...) """ f = open(self.Vname, "r") line = f.readline() f.close() elems = line.split() self.version = elems[2] # version string tmp = elems[2].split(".") self.ver_maj = int(tmp[0]) # major version number self.ver_rel = int(tmp[1]) # release version number self.ver_min = int(tmp[2]) # minor version number self.date = elems[3].split("(")[1].split(")")[0] # date return self.version, self.date def _ver_str(self): """ Returns the string of version """ return "%d.%d.%d" % (self.ver_maj, self.ver_rel, self.ver_min) def upgrade(self, key="minor"): """ Upgrade CEPAM version number (default is minor one) """ success = True print self.up_info t = datetime.datetime.now() # retrieve current date key = key.lower() if key in ["major", "release", "minor"]: if key == "major": self.ver_maj += 1 self.ver_rel = 0 self.ver_min = 0 elif key == "release": self.ver_rel += 1 self.ver_min = 0 elif key == "minor": self.ver_min += 1 print self.old_info % self.version old_s = self.cepam_ver % (self.version, self.date) self.version = self._ver_str() self.date = t.strftime("%d/%m/%Y") print self.new_info % self.version print self.date_info % self.date new_s = self.cepam_ver % (self.version, self.date) search_and_replace(old_s, new_s, self.Vname) search_and_replace(old_s, new_s, self.Rname) print self.success_info elif key in ["date"]: old_s = self.cepam_ver % (self.version, self.date) self.date = t.strftime("%d/%m/%Y") print self.date_info % self.date new_s = self.cepam_ver % (self.version, self.date) search_and_replace(old_s, new_s, self.Vname) search_and_replace(old_s, new_s, self.Rname) print self.success_info else: success = False if success: f = open(self.Dname, "w") f.write(self.date + " [" + os.getenv("USER") + "]\n") f.close() return success ######################### ### CEPAM version ### ######################### HV = HumanVersion() ## human version (eg. 0.2.4) version, date = HV.read() #################################### ### Environment configuration #### #################################### ## paths definition (all relatives to root directory of Planets) cur_dir=os.getcwd() # directory where SConstruct file (this one) is src_dir=cur_dir+sep+'src' # main "sources" directory mod_dir=cur_dir+sep+'modules' # "modules" directory (.mod file created during compilation) lib_dir=cur_dir+sep+'lib' # "libraries" directory doc_dir=cur_dir+sep+'doc' # "documentation" directory bin_dir=cur_dir+sep+'bin' # "binaries" directory ("cepam", "cepasc2bin"...) dat_dir=cur_dir+sep+'data' # "data" directory (opacity tables...) inp_dir=cur_dir+sep+'input' # "input" directory (.don) gra_dir=cur_dir+sep+'graph' # "graph" directory com_dir=cur_dir+sep+'com' # "com" directory (tests, exploitation scripts...) test_dir=cur_dir+sep+'test_expls' # Directory of example cases for testing purposes test_cases=['evolutions','static','optimisations'] # List of different main test cases srcdiv_dir=cur_dir+sep+'src-div'+sep+'Tests' ## environment creation env = Environment(FORTRANPATH=['#', mod_dir, src_dir], # search path ('#' means home of Cepam) ENV=os.environ) # OS specific settings conf = Configure(env) conf.env.Tool('fortran') # configure some tools for Fortran conf.env.Tool('f90') conf.env.Tool('f95') conf.env.Tool('ifort') conf.env.Tool('tar') # configure the TAR archiver (even if not available) env.Replace(ARFLAGS='ruc') # 'ar' flags to create libraries ## signature methods (to check if a file must be rebuild or not) Decider('MD5-timestamp') # use both "MD5" and "timestamp" ################################ ### Compiler configuration ### ################################ ## automatic detection and configuration of compiler available_compilers = ['gfortran', 'ifort', 'lf95'] detect_fortran=conf.env.Detect(available_compilers) # automatic detection of Fortran compiler. #+ The first detected in the list will be used, #+ so the default compiler is 'gfortran' FC=detect_fortran # Fortran compiler is set automatically. #+ To force it, you can use 'compiler' option in the scons command line to set FC #+ to "gfortran", "ifort", or "lf95" (only these ones are currently supported, #+ although other compilers may work) FFLAGS = None LFLAGS = None FMODDIRPREFIX = None env = conf.Finish() UFC = ARGUMENTS.get('compiler', "") # user defined compiler if UFC in available_compilers: FC=UFC elif UFC != "": test = [UFC.startswith(name) for name in available_compilers] if any(test): # FC = available_compilers[test.index(True)] # force the use of default known compilers FC = UFC # use the user-supplied compiler name else: print "The given compiler name is not recognized... Do you want to use it anyway (yes/no) ?" choice = sys.stdin.readline().split("\n")[0] if choice in ['Y','y','Yes','yes','YES']: FC=UFC print 'what are the compiler flags? (eg. "-fmax-errors=5 -std=legacy"):' FFLAGS = ' ' + sys.stdin.readline().split("\n")[0] print 'what are the library flags? (eg. "-Wl,--subsystem,console -mwindows"):' LFLAGS = FFLAGS + ' ' + sys.stdin.readline().split("\n")[0] print 'what is the fortan module prefix? (eg. "-J"):' FMODDIRPREFIX = sys.stdin.readline().split("\n")[0] else: print "Using detected compiler : %s" % FC ## optimization flags optimization_flag=' -O2' # optimization level optimized = ARGUMENTS.get('optimized', 0) parallel = ARGUMENTS.get('parallelized', 0) # GNU Fortran (gfortran) -- normal gfc_flags = ' -fmax-errors=5 -ffree-line-length-132 -std=legacy -fd-lines-as-comments'# -march=i386 -ffpe-trap=invalid'#-fbounds-check #gfc_flags='-fmax-errors=5 -ffixed-line-length-132 -ffree-line-length-132 -std=legacy -fd-lines-as-comments' # GNU Fortran (gfortran) -- optimized ogfc_flags = gfc_flags + optimization_flag # Intel Fortran (ifort) -- normal ifc_flags = '-error-limit 5 -132 -free' #ifc_flags='-error-limit 5 -132' # Intel Fortran (ifort) -- optimized (the parallel flags are set in OS specific configurations) oifc_flags = ifc_flags + optimization_flag # Lahey / Fujitsu Fortran(lf95) -- normal lfc_flags = '--maxfatals 5 --wide --tp4 --warn' # Lahey / Fujitsu Fortran(lf95) -- optimized olfc_flags = lfc_flags + optimization_flag ########################################## ### Platform sepecific configuration ### ########################################## platforms = ['posix', 'win32', 'darwin'] systems = ['posix', 'nt'] thissystem = os.name thisplatform = env['PLATFORM'] if thisplatform == 'win32': # --------------------------------- WINDOWS configuration # define a cache dir (avoid rebuild of cached files that did not changed) win_cache_dir = cur_dir + sep + 'cache' + sep + 'windows' # this will be created if not existing cache_dir = win_cache_dir + sep + FC CacheDir(cache_dir) # paths install_dir = 'C:\\MinGW\\bin' # installation directory (should be in Windows PATH #+ and writable without root privilege) # WINDOWS libraries path (ADAPT TO YOUR CASE) win_lib_dir = 'C:\\MinGW\\lib' # adapt the MinGW lib directory to your case. lib_paths = [lib_dir, win_lib_dir] lib_list = [] # compiler specifics if (FC.startswith('gfortran')): env['INCPREFIX'] = '-B' if bool(int(optimized)): FFLAGS = ogfc_flags else: FFLAGS = gfc_flags if bool(int(parallel)): FFLAGS = gfc_flags + ' -O3 -ftree-vectorize -fopenmp' LFLAGS = FFLAGS + ' -Wl,--subsystem,console -mwindows' FMODDIRPREFIX = '-J' elif (FC.startswith('ifort')): if bool(int(optimized)): FFLAGS = oifc_flags else: FFLAGS = ifc_flags if bool(int(parallel)): FFLAGS += ' -parallel' # FFLAGS = FFLAGS + ' -Qopenmp -parallel' LFLAGS = FFLAGS + ' -mwindows' FMODDIRPREFIX = '-module ' elif (FC.startswith('lf95')): if bool(int(optimized)): FFLAGS = olfc_flags else: FFLAGS = lfc_flags if bool(int(parallel)): FFLAGS = FFLAGS + ' --parallel --ocl --threads 2' LFLAGS = FFLAGS + ' -mwindows' FMODDIRPREFIX = '--mod ' else: #if env['PLATFORM'] == 'posix': # --------------------------------- LINUX configuration is the default... # print a warning if the platform is not supported by this script... if not thissystem in systems: print "[WARNING] Your system (%s) is not supported (officially supported : %s), this script may not work properly !" % (thissystem, systems) if not thisplatform in platforms: print "[WARNING] Your platform (%s) is not supported (officially supported : %s), this script may not work properly !" % (thisplatform, platforms) # define a cache dir (avoid rebuild of cached file that did not changed) lin_cache_dir = cur_dir + '/cache/linux' # this will be created if not existing. cache_dir = lin_cache_dir + sep + FC CacheDir(cache_dir) # paths install_dir = '/usr/local/bin' # executables installation directory # LINUX libraries path (ADAPT TO YOUR CASE) lin_lib_dir = ['/usr/local/lib', '/usr/lib'] lib_paths = [lib_dir, lin_lib_dir] lib_list = [] # compiler specifics if (FC.startswith('gfortran')): if bool(int(optimized)): FFLAGS = ogfc_flags else: FFLAGS = gfc_flags if bool(int(parallel)): FFLAGS = gfc_flags + ' -O3 -ftree-vectorize -fopenmp' LFLAGS = FFLAGS FMODDIRPREFIX = '-J' elif (FC.startswith('ifort')): if bool(int(optimized)): FFLAGS = oifc_flags else: FFLAGS = ifc_flags if bool(int(parallel)): FFLAGS += ' -parallel' #FFLAGS = FFLAGS + ' -openmp -parallel' LFLAGS = FFLAGS FMODDIRPREFIX = '-module ' elif (FC.startswith('lf95')): if bool(int(optimized)): FFLAGS = olfc_flags else: FFLAGS = lfc_flags if bool(int(parallel)): FFLAGS = FFLAGS + ' --parallel --ocl --threads 2' LFLAGS = FFLAGS FMODDIRPREFIX = '--mod ' ## update environment definition env.Replace(CC=FC) # C compiler (fake ! CC should be equal to FC ) env.Replace(FORTRAN=FC) # Fortran Compiler env.Replace(F77=FC) # Fortran Compiler env.Replace(F90=FC) # Fortran Compiler env.Replace(F95=FC) # Fortran Compiler if FFLAGS is not None: env.Replace(F77FLAGS=FFLAGS) # Fortran flags for compilation env.Replace(F90FLAGS=FFLAGS) # Fortran flags for compilation env.Replace(F95FLAGS=FFLAGS) # Fortran flags for compilation env.Replace(FORTRANFLAGS=FFLAGS) # Fortran flags for compilation env.Replace(FORTRANMODDIR=mod_dir) # "modules" directory if FMODDIRPREFIX is not None: env.Replace(FORTRANMODDIRPREFIX=FMODDIRPREFIX) # "modules" prefix compilation if LFLAGS is not None: env.Replace(LINKFLAGS=LFLAGS) # Fortran flags for linking env.SetDefault(F90PATH=env['FORTRANPATH']) env.SetDefault(F95PATH=env['FORTRANPATH']) ################ ### Export ### ################ ## export compilation environment and paths Export(['cur_dir', 'src_dir', 'mod_dir', 'lib_dir', 'bin_dir', 'dat_dir', 'srcdiv_dir', 'com_dir']) Export('env') Export(['lib_list', 'lib_paths']) Export('sep') ################# ### Targets ### ################# ## Importing objects of Cepam code objs = SConscript([src_dir+sep+'SConscript']) ## Cepam executable objlaunch = env.Object(src_dir+sep+'cepamlaunch.f') cepam = env.Program(bin_dir + sep + 'cepam', objlaunch + objs, LIBS=lib_list, LIBPATH=lib_paths) Default(cepam) # Cepam will be installed under the 'bin' #+ directory with the name 'cepam' ; it is #+ the default target ## Optimized version of Cepam for Jupiter and Saturn (based on observed gravitationnal moments) objoptimise = env.Object(src_dir+sep+'optimise.f') optimise = env.Program(bin_dir + sep + 'optimise', objoptimise + objs, LIBS=lib_list, LIBPATH=lib_paths) ## Cepam : ASCII <-> BIN objasc2bin = env.Object([src_dir+sep+'cepasc2bin.f', src_dir+sep+'Listes'+sep+'rwbin.f', src_dir+sep+'Maths'+sep+'leng.f']) cepamasc2bin = env.Program(bin_dir + sep + 'cepasc2bin', objasc2bin, LIBS=lib_list, LIBPATH=lib_paths) ## Cepam : reconstruction of EOS data (a2b_EOS) a2bEOS = env.Program(bin_dir + sep + 'a2b_EOS', src_dir+sep+'a2b_EOS.f') ## Aliases install = env.Alias('install', [cepam, optimise, cepamasc2bin, a2bEOS]) # install all targets, building them if needed with #+ necessary libraries ## Executing installation tasks, if needed def exe_a2b(target, source, env): # this function convert .a and .a2 into .b and .b2 if needed a2bcmd = str(source[0]) a2blist = ['He_spleen_pt.b', 'inter_spleen_pt.b', 'ppt_spleen_pt.b', 'He_spleen_pt.b2', 'inter_spleen_pt.b2'] runa2b = 0 for ffile in os.listdir(Dir(dat_dir+sep+'EOS').srcnode().abspath): ffname, ffext = os.path.splitext(ffile) if ffext == '.a': if ((ffname+'.b' in a2blist) & (not os.path.exists(dat_dir+sep+'EOS'+sep+ffname+'.b'))): runa2b += 1 break if ffext == '.a2': if ((ffname+'.b2' in a2blist) & (not os.path.exists(dat_dir+sep+'EOS'+sep+ffname+'.b2'))): runa2b += 1 break if runa2b != 0: env.Execute(a2bcmd) runa2bEOS = env.Command('nonea2b', bin_dir+sep+'a2b_EOS', exe_a2b) env.Requires(runa2bEOS, a2bEOS) def create_link(target, source, env): """this functions create links if needed""" if thisplatform == 'win32': print "[WARNING] You cannot create link on windows... Thus Cepam may not work" else: lnkcmd = str(source[0]) lnsrc = os.path.realpath(cur_dir + sep + 'data') print "Creating 'data' links..." for tdir in test_cases: aptdir = os.path.abspath(test_dir+sep+tdir) for mydir in os.listdir(aptdir): thisdir = aptdir+sep+mydir if os.path.isdir(thisdir) and (not mydir.startswith('.')): lntarget = thisdir + sep + 'data' test1 = not os.path.exists(lntarget) test2 = os.path.islink(lntarget) and (not os.path.realpath(lntarget) == lnsrc) if test1 or test2: env.Execute(lnkcmd+" -s -f "+lnsrc+" "+lntarget) print 'Link directory: "' + lntarget + '" to "' + lnsrc + '"' return None makelnk = env.Command('nonelnk', '/bin/ln', create_link) env.Requires(cepam, [makelnk, runa2bEOS]) ############### ### Tests ### ############### Export('objs') ## this is built only when scons is called with "install", ".", or "tests" argument ## import tests tests = SConscript([srcdiv_dir+sep+'SConscript']) ## Executing tests, if scons called with "test" argument (without the endding "s") cmd = com_dir+sep+'test_cepam.bash' testop = '' print_warning = False warning = "[WARNING] Impossible to run tests on this platform" if thisplatform == 'win32': print_warning = True #testop = env.Execute('echo '+warning) testop = env.Command('none', 'none', "") if 'check' in COMMAND_LINE_TARGETS: if print_warning: print warning else: testop = env.Command('none', cmd, "/bin/bash $SOURCE -s") elif 'test' in COMMAND_LINE_TARGETS: if print_warning: print warning else: testop = env.Command('none', cmd, "/bin/bash $SOURCE") #NoCache('none') AlwaysBuild(testop) env.Requires(testop, [cepam, optimise]) ## Aliases env.Alias('test',[cepam, optimise, cepamasc2bin, testop]) env.Alias('check',[cepam, optimise, cepamasc2bin, testop]) env.Alias("all", [install, tests]) ################# ### Options ### ################# ## debug option debug = ARGUMENTS.get('debug',0) # if 'debug=1' is set at scons call time, then Cepam is built if int(debug): #+ in 'debug' mode _ff = ' -g' _lf = ' -g' if FC.startswith('ifort'): _ff += ' -debug -traceback -Wl,-no_pie' _lf += ' -debug -traceback -Wl,-no_pie' env.Append(FORTRANFLAGS=_ff) env.Append(LINKFLAGS=_lf) ## install option link = ARGUMENTS.get('link', 0) if int(link): if thisplatform == 'posix': for _file in os.listdir(Dir(bin_dir).srcnode().abspath): os.system('ln -s -f ' + bin_dir + sep + _file + ' ' + install_dir + sep + _file) print 'Link file: "' + install_dir + sep + _file + '" to "' + bin_dir + sep + _file + '"' Exit(0) elif thisplatform == 'win32': for _file in os.listdir(Dir(bin_dir).srcnode().abspath): # getting file name (taking off any extension) and add ".bat" extension filename = os.path.splitext(_file)[0]+".bat" # creating file (.bat file with path to executable) f = open(install_dir + sep + filename, 'w') f.write(bin_dir + sep + _file) f.close() print ('Create .bat file in: "' + install_dir + sep + _file + '" which will launch "' + bin_dir + sep + _file + '"') Exit(0) ################## ### Archives ### ################## ## Tar BZ2 archive archive = ARGUMENTS.get('archive',0) # if 'archive=1' is set at scons call time, #+ then an archive of Cepam project is built if int(archive): # update the date assert HV.upgrade('date') version, date = HV.read() date_tar = date.replace("/", "-") # directories and files to archive archive_list = [src_dir, doc_dir, dat_dir, 'VERSION', 'README', 'SConstruct', 'DATE', 'MODIFS', 'Makefile', 'graph', 'input', 'cepam_param.don', 'modifs_don', 'PERFORMANCES', com_dir] # create archive if thisplatform == 'win32': # WINDOWS : "libarchive" must be installed in order to get it working ! env.Replace(TAR='bsdtar') ext=".tar.bz2" env.Replace(TARFLAGS='-j -c --exclude=.* --exclude=*.o --exclude=cepam --exclude=*~ --exclude=*.mod') env.Replace(TARSUFFIX=ext) tarfile = env.Tar(cur_dir+sep+'cepam_v'+version+'_'+date_tar+ext, archive_list) Default(None) # clear all default targets Default(tarfile) ######################## ### Manual upgrade ### ######################## upver = ARGUMENTS.get('upver', 'none') if upver != 'none': up_success = HV.upgrade(upver) if up_success: Exit(0) else: print "Failed to upgrade version/date !" Exit(1) ############## ### Help ### ############## Help(""" ~~~~~~~~~~~~~~~~~~~~~ --- General help --- ~~~~~~~~~~~~~~~~~~~~~ Type: 'scons' -- to build only binaries 'scons .' -- to (re)build all (libraries, binaries..., if sources changed) 'scons [TARGETS]' -- to build only specified [TARGETS] 'scons tests' -- to build test programs 'scons test' -- to run all test programs 'scons check' -- to run one test program 'scons install' -- to build all binaries (not libraries if already built) 'scons debug=1' -- to build the 'debug' version program 'scons optimized=1' -- to build the optimized version program (%s) 'scons parallelized=1' -- to build the parallelized version program 'scons compiler=NAME' -- to use NAME compiler 'scons -c .' -- to clean non protected files (ie. not libraries) Supported compilers (you can specify another one to force using it) : %s """ % (optimization_flag, available_compilers)) ## Release specific help Help(""" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --- RELEASE specific help --- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Type: 'scons upver=ACTION' to change the version of Cepam (MajNum.RelNum.MinNum) EXIT right after Available ACTION are: * major - change version incrementing MajNum * release - change version incrementing RelNum * minor - change version incrementing MinNum * date - change only the date """) ## LINUX specific help if thisplatform == 'posix': Help(""" ~~~~~~~~~~~~~~~~~~~~~~~~~~~ --- LINUX specific help --- ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Type: 'scons -Q link=1' -- to create symbolic links in "%s" for Cepam's binaries (it may be necessary to run this with root privileges) EXIT right after 'scons -Q archive=1' -- to (re)build an archive of the current state of Cepam project EXIT right after """ % install_dir) ## WINDOWS specific help if thisplatform == 'win32': Help(""" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --- WINDOWS specific help --- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Type: 'scons -Q link=1' -- to create .bat file in "%s", and executing Cepam's binaries (it may be necessary to run this with root privileges) EXIT right after 'scons -Q archive=1' -- to (re)build an archive of the current state of Cepam project (you'll need "libarchive" package installed) EXIT right after """ % install_dir) ############## ### INFO ### ############## print '' print ' ===== Cepam : builder tool =====' print '' print '***************************************************' print 'System is : ',thissystem print 'Platform is : ',thisplatform print 'Current version of Cepam : ',version + ' (' + date + ')' print 'Fortran compiler is : ',env['FORTRAN'] print 'Fortran options are : ',env['FORTRANFLAGS'] print 'Linker flags are : ',env['LINKFLAGS'] print 'Libraries used are : ',lib_list print '***************************************************' print '' print 'The following targets are going to be built :', map(str, BUILD_TARGETS) print '' ################ ### ERRORS ### ################ def bf_to_str(bf): """Convert an element of GetBuildFailures() to a string in a useful way.""" import SCons.Errors if bf is None: # unknown targets product None in list return '(unknown tgt)' elif isinstance(bf, SCons.Errors.StopError): return str(bf) elif bf.node: return str(bf.node) + ': ' + bf.errstr elif bf.filename: return bf.filename + ': ' + bf.errstr return 'unknown failure: ' + bf.errstr def build_status(): """Convert the build status to a 2-tuple, (status, msg).""" from SCons.Script import GetBuildFailures bf = GetBuildFailures() if bf: # bf is normally a list of build failures; if an element is None, # it's because of a target that scons doesn't know anything about. status = 'failed' failures_message = "\n".join(["Failed building %s" % bf_to_str(x) for x in bf if x is not None]) else: # if bf is None, the build completed successfully. status = 'ok' failures_message = '' return (status, failures_message) def display_build_status(): """Display the build status. Called by atexit. Here you could do all kinds of complicated things.""" status, failures_message = build_status() if status == 'failed': print '***************************************************' print 'System is : ',thissystem print 'Platform is : ',thisplatform print 'Current version of Cepam : ',version + ' (' + date + ')' print '***************************************************' print "FAILED!!!!" # could display alert, ring bell, etc. elif status == 'ok': print "Build succeeded." print failures_message atexit.register(display_build_status)