#!/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) : # 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,file): f = open(file,'r') g = open(file + '.ver.tmp','w') for line in f: g.write(line.replace(old_s,new_s)) f.close() g.close() os.remove(file) os.rename(file+'.ver.tmp',file) ## Human version (opposed to svn revision number) def human_version(action='read',up_kind='minor'): global version, date if action == 'read': f = open('VERSION','r') # open 'VERSION' file a = f.readline() # read it f.close() # close it b = string.split(a) # split informations version = b[2] # stock version number date_loc = b[3] # stock date date = date_loc.split("(")[1].split(")")[0] elif action =='upgrade': # here we suppose that we have already read the file t = datetime.datetime.now() # retrieve current date new_date = t.strftime("%d/%m/%Y %H:%M:%S") # format new date new_date2 = t.strftime("%d/%m/%Y") new_ver = version # initialization version = version.split(".") maj_vn = int(version[0]) # get major version number rel_vn = int(version[1]) # get release version number min_vn = int(version[2]) # get minor version number old_ver = str(maj_vn)+'.'+str(rel_vn)+'.'+str(min_vn) if up_kind == "major": maj_vn += 1 # increment major version number new_ver = str(maj_vn)+'.0.0' # new version number print "[INFO] Old version number : " + old_ver print "[INFO] New version number : " + new_ver print "[INFO] New modification date is : " + new_date old_s = "CEPAM Version %s (%s)" % (old_ver,date) new_s = "CEPAM Version %s (%s)" % (new_ver,new_date2) search_and_replace(old_s,new_s,"VERSION") search_and_replace(old_s,new_s,"README") print '[INFO] Version and date have been updated' elif up_kind == "release": rel_vn += 1 # increment release version number new_ver = str(maj_vn)+'.'+str(rel_vn)+'.0' # new version number print "[INFO] Old version number : " + old_ver print "[INFO] New version number : " + new_ver print "[INFO] New modification date is : " + new_date old_s = "CEPAM Version %s (%s)" % (old_ver,date) new_s = "CEPAM Version %s (%s)" % (new_ver,new_date2) search_and_replace(old_s,new_s,"VERSION") search_and_replace(old_s,new_s,"README") print '[INFO] Version and date have been updated' elif up_kind == "minor": min_vn += 1 # increment minor version number new_ver = str(maj_vn)+'.'+str(rel_vn)+'.'+str(min_vn) # new version number print "[INFO] Old version number : " + old_ver print "[INFO] New version number : " + new_ver print "[INFO] New modification date is : " + new_date old_s = "CEPAM Version %s (%s)" % (old_ver,date) new_s = "CEPAM Version %s (%s)" % (new_ver,new_date2) search_and_replace(old_s,new_s,"VERSION") search_and_replace(old_s,new_s,"README") print '[INFO] Version and date have been updated' elif up_kind == "date": print "[INFO] New modification date is : " + new_date old_s = "CEPAM Version %s (%s)" % (old_ver,date) new_s = "CEPAM Version %s (%s)" % (old_ver,new_date2) search_and_replace(old_s,new_s,"VERSION") search_and_replace(old_s,new_s,"README") f = open('DATE','w') f.write(new_date+'; '+ os.getenv('USER')) f.close() print '[INFO] Date has been updated' version = new_ver return version, date ######################### ### Planets version ### ######################### ## human version (eg. 0.2.4) version,date = human_version(action='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...) sdi_dir=cur_dir+sep+'src-div' # "src-div" directory (for test programs) exp_dir=cur_dir+sep+'test_expls' # "test_expls" directory (reference test results) ## some other paths... evo_dir=exp_dir+sep+'evolutions' tes_dir=sdi_dir+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) env = conf.Finish() UFC = ARGUMENTS.get('compiler',"") # user defined compiler if UFC in available_compilers: FC=UFC elif UFC != "": 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 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 -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' # 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 == '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 == 'ifort'): if bool(int(optimized)): FFLAGS=oifc_flags else: FFLAGS=ifc_flags if bool(int(parallel)): FFLAGS=FFLAGS + ' -Qopenmp -parallel' LFLAGS=FFLAGS + ' -mwindows' FMODDIRPREFIX='-module ' elif (FC == '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 == '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 == 'ifort'): if bool(int(optimized)): FFLAGS=oifc_flags else: FFLAGS=ifc_flags if bool(int(parallel)): FFLAGS=FFLAGS + ' -openmp -parallel' LFLAGS=FFLAGS FMODDIRPREFIX='-module ' elif (FC == '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 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 env.Replace(FORTRANMODDIRPREFIX=FMODDIRPREFIX) # "modules" prefix compilation 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','tes_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') cepamlaunch = env.Program('cepam',objlaunch + objs, LIBS=lib_list, LIBPATH=lib_paths) cepam = env.Install(bin_dir,cepamlaunch) 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') cepamoptimise = env.Program('optimise',objoptimise + objs, LIBS=lib_list, LIBPATH=lib_paths) optimise = env.Install(bin_dir,cepamoptimise) ## 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('cepasc2bin',objasc2bin, LIBS=lib_list, LIBPATH=lib_paths) cepasc2bin = env.Install(bin_dir,cepamasc2bin) ## Cepam : reconstruction of EOS data (a2b_EOS) a2bEOS = env.Program(src_dir+sep+'a2b_EOS.f') a2b_EOS = env.Install(bin_dir,a2bEOS) ## Aliases env.Alias('install',bin_dir) # 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,a2b_EOS) 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]) msg = "Creating 'data' links..." createlnk = 0 for mydir in os.listdir(Dir(evo_dir).srcnode().abspath): if ((os.path.isdir(evo_dir+sep+mydir)) & (not mydir.startswith('.'))): lnsrc = cur_dir + sep + 'data' lntarget = evo_dir + sep + mydir + sep + 'data' if not os.path.exists(lntarget): createlnk += 1 if createlnk == 1: print msg 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 SConscript([tes_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) ## Aliases env.Alias('test',[cepam,cepasc2bin,testop]) env.Alias('check',[cepam,cepasc2bin,testop]) ################# ### 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 env.Append(FORTRANFLAGS = ' -g') env.Append(LINKFLAGS = ' -g') ## 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 version,date = human_version(action="upgrade",up_kind="date") date_tar = date.replace("/","-") # directories and files to archive archive_list = [src_dir,doc_dir,dat_dir,'VERSION','README','SConstruct','DATE','MODIFS','Makefile', gra_dir, 'cepam_param.don','modifs_don','PERFORMANCES',com_dir,inp_dir, exp_dir,sdi_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': if upver in ["major","release"]: version,date = human_version(action='upgrade',up_kind=upver) Exit(0) elif upver == "minor": version,date = human_version(action='upgrade',up_kind=upver) Exit(0) elif upver == "date": version,date = human_version(action='upgrade',up_kind=upver) Exit(0) else: print '[ERROR] This action does not exist' 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 : ',os.name print 'Platform is : ',env['PLATFORM'] 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)