# halld_lib.py # functions for Hall D online SCONS build system # ejw, 7-may-2013 import os import sys import re from glob import glob import SCons import commands from os import path from hdutil import cmloptions, loadoptions # The Hall D generic SConstruct file calls init_halld_env and build_halld or build_halld_rol # # The system builds doc, scripts, java, src, etc. # Note: recursively builds doc,scripts,binsrc,plugins,test all the way down the directory hierarchy. # # Intermediate build files for C and C++ stored in architecture-dependent hidden directories in src/libsrc, etc. # # Gets C/C++ compiler from PATH, uses -std=c++11 by default # Can override with c++11=N (or n or 0) on command line # # Gets global CXXFLAGS,CCFLAGS,CPPPATH,LINKFLAGS first from command line, then from environment variables # # Use "scons install" to install c and c++ into /arch/lib, bin, include dirs. # Use "scons ginstall" ("global" install) to install into dir parallel to the packages dir, if it exists. # This is typically used by system managers to install into production deployment dirs. # # Exception: test ALWAYS installs into /arch/bin # # # Notes: # must qualify all includes with the package name, e.g. #include # must have link in libsrc to itself with name = package name, e.g: (cd src/libsrc; ln -s . myPackage) # use "java" target to build java code ($scons java) (bug in SCONS?) # # # Usage: # scons [-c] [debug=(1,y,yes,Y,Yes,YES)] [prof=(1,y,yes,Y,Yes,YES)] [opt=level] [xxxFLAGS=yyy] [install] [ginstall] [java] # # where xxxFLAGS can be CXXFLAGS, CCFLAGS, CPPPATH and/or LINKFLAGS # # # Examples: # $ scons debug=1 # sets -Og # $ scons prof=yes # sets -pg # $ scons opt=g # sets -Og # $ scons opt=3 # sets -O3 # $ scons CXXFLAGS=-Wall # use -Wall flag when compiling C++ # $ scons C++11=0 # do not use -std=c++11 (can also use N and n) # # $ scons install # install into package root directory # $ scons ginstall # install into global installation/deployment directory # $ scons -c install # uninstall from package root directory # # $ scons java # build java instead of C and C++ # $ scons -c java # uninstall java # # # # Uses Maurizio's scons util library to implement command-line option stuff # # # ejw, 11-dec-2013 # # # # Still to do: # # not sure about java install scheme, need to test # get "$scons install java" to work, or get straight about defaults # improve help and cmloptions() # modify loadxxx() files for package dependent debug strategy def init_halld_env(opts): # handle command line options, help, etc. cmloptions(opts) # get package root package_root = SCons.Script.Dir('.').srcnode().abspath # if "ginstall" specified verify package parent dir exists, as this is target of global install if('ginstall' in SCons.Script.COMMAND_LINE_TARGETS): if(os.path.exists(package_root+"/../../packages")==False): print '\n ?error...ginstall target specified but package directory does not exist\n' sys.exit(1) # create environment, import all env vars, get g++ from path env = SCons.Environment.Environment(ENV = os.environ, options = opts) # import compile or link flags first from command line, then from env vars flagList = 'CXXFLAGS CCFLAGS CPPPATH LINKFLAGS' for x in flagList.split(' '): if SCons.Script.ARGUMENTS.get(x,0): exec("env.AppendUnique(" + x + " = SCons.Script.ARGUMENTS.get('" + x + "',0))") elif x in env['ENV']: exec("env.AppendUnique(" + x + " = (env['ENV'])['" + x + "'])") # set architecture arch = os.getenv('BMS_OSNAME', 'none') if arch == 'none': arch = commands.getoutput('osrelease.pl') env.Replace(arch = arch) print ' --> Building for architecture ' + arch + ' <--' # set package root env.Replace(package_root = package_root) # get package name packageName = package_root.split('/')[len(package_root.split('/'))-1] env.Replace(packageName = packageName) # set C++11 flag by default, can be overridden on command line: C++11=n # if (not SCons.Script.ARGUMENTS.get('C++11',0) or # ((SCons.Script.ARGUMENTS.get('C++11',0)=='1') or (SCons.Script.ARGUMENTS.get('C++11',0)[0]=='y') # or (SCons.Script.ARGUMENTS.get('C++11',0)[0]=='Y'))): # env.PrependUnique(CXXFLAGS = ['-std=c++11']) # always include symbol table env.PrependUnique(CXXFLAGS = ['-g']) env.PrependUnique(CCFLAGS = ['-g']) env.PrependUnique(CPPPATH = ['.']) # add DBG to env, check for debug command line arg dbg=False if (SCons.Script.ARGUMENTS.get('debug',0) and ((SCons.Script.ARGUMENTS.get('debug',0)=='1') or (SCons.Script.ARGUMENTS.get('debug',0)[0]=='y') or (SCons.Script.ARGUMENTS.get('debug',0)[0]=='Y'))): dbg=True env.AppendUnique(CXXFLAGS = '-Og') env.AppendUnique(CCFLAGS = '-Og') env.AppendUnique(LINKFLAGS = '-Og') env.Replace(dbg = dbg) # add PROF to env, check for prof command line arg prof=False if (SCons.Script.ARGUMENTS.get('prof',0) and ((SCons.Script.ARGUMENTS.get('prof',0)=='1') or (SCons.Script.ARGUMENTS.get('prof',0)[0]=='y') or (SCons.Script.ARGUMENTS.get('prof',0)[0]=='Y'))): prof=True env.AppendUnique(CXXFLAGS = '-pg') env.AppendUnique(CCFLAGS = '-pg') env.AppendUnique(LINKFLAGS = '-pg') env.Replace(prof = prof) # check for optimization flag # default optimization level is -O0 for g++, if debug on will use -Og if(SCons.Script.ARGUMENTS.get('opt',0)): level='-O'+SCons.Script.ARGUMENTS.get('opt',0) env.AppendUnique(CXXFLAGS = level) env.AppendUnique(CCFLAGS = level) env.AppendUnique(LINKFLAGS = level) # check for java flag, set special Java variables java=False if('java' in SCons.Script.COMMAND_LINE_TARGETS): java=True java_source = 'org' local_java_target = 'build/classes' local_jar_target = 'build/lib/' + packageName + '.jar' global_java_target = '../../build/classes' global_jar_target = '../../build/lib/' + packageName + '.jar' env.Replace(java_source = java_source) env.Replace(local_java_target = local_java_target, local_jar_target = local_jar_target, global_java_target = global_java_target, global_jar_target = global_jar_target) env.Replace(java = java) # not clear if more (or none) of these are needed, will find out when making new release...ejw, 12-dec-2013 # create build directories if needed # if((os.path.exists("./src/binsrc/")==True) and (os.path.exists("./src/binsrc/." + arch)==False)): # print "creating build dir ./src/binsrc/." + arch # os.makedirs("./src/binsrc/." + arch) if((os.path.exists("src/libsrc/")==True) and (os.path.exists("src/libsrc/." + arch)==False)): print "creating build dir src/libsrc/." + arch os.makedirs("src/libsrc/." + arch) # if((os.path.exists("./src/plugins/")==True) and (os.path.exists("./src/plugins/." + arch)==False)): # print "creating build dir ./src/plugins/." + arch # os.makedirs("./src/plugins/." + arch) # if((os.path.exists("./src/test/")==True) and (os.path.exists("./src/test/." + arch)==False)): # print "creating build dir ./src/test/." + arch # os.makedirs("./src/test/." + arch) # set installation dirs local_install_root = package_root local_install_dir = path.join(local_install_root,arch) global_install_root = package_root + '/../..' global_install_dir = path.join(global_install_root,arch) env.Replace(local_install_root = local_install_root, local_install_dir = local_install_dir, global_install_root = global_install_root, global_install_dir = global_install_dir) # setup standard options and help facility SCons.Script.Help(opts.GenerateHelpText(env)) loadoptions(env) # done return(env) # Performs build in subdirs # Only installs scripts for non-default install_dir # Package include and library must be first in CPPPATH and LIBPATH for binsrc and test # Note that binsrc and test both install into bin directory, but test always into local install dir # For some reason Java has to be built separately from other stuff def build_halld(env): # check if any files in lib dir libList = env.Glob('src/libsrc/*.cc')+env.Glob('src/libsrc/*.c') if(not env['java']): if(os.path.exists("doc")==True): walkDocDirs('docs',env.Clone()) if(os.path.exists("scripts")==True): walkScriptDirs('scripts',env.Clone()) if(os.path.exists("src/libsrc")==True): libEnv=env.Clone() SCons.Script.SConscript('src/libsrc/SConscript',exports=['libEnv'], variant_dir='src/libsrc/.' + env['arch'], duplicate=0) if(os.path.exists("src/plugins")==True): pluginEnv=env.Clone() pluginEnv.PrependUnique(CPPPATH = [env['package_root'] + '/src/libsrc']) pluginEnv.PrependUnique(LIBPATH = [env['package_root'] + '/src/libsrc/.' + env['arch']]) if(len(libList)>0): pluginEnv.PrependUnique(LIBS = [env['packageName']]) pluginEnv.Replace(pluginName=env['packageName']) walkPluginDirs('src/plugins',pluginEnv) if(os.path.exists("src/binsrc")==True): binEnv=env.Clone() binEnv.PrependUnique(CPPPATH = [env['package_root'] + '/src/libsrc']) binEnv.PrependUnique(LIBPATH = [env['package_root'] + '/src/libsrc/.' + env['arch']]) if(len(libList)>0): binEnv.PrependUnique(LIBS = [env['packageName']]) walkBinDirs('src/binsrc',binEnv) if(os.path.exists("src/test")==True): testEnv=env.Clone() testEnv.PrependUnique(CPPPATH = [env['package_root'] + '/src/libsrc']) testEnv.PrependUnique(LIBPATH = [env['package_root'] + '/src/libsrc/.' + env['arch']]) if(len(libList)>0): testEnv.PrependUnique(LIBS = [env['packageName']]) walkTestDirs('src/test',testEnv) else: if(os.path.exists("java")==True): javaEnv=env.Clone() SCons.Script.SConscript('java/SConscript',exports=['javaEnv'], duplicate=0) else: print '?no java directory found' # Builds rol dirs, which have a different organization than other dirs def build_halld_rol(env): if(os.path.exists("doc")==True): walkDocDirs('docs',env.Clone()) if(os.path.exists("src")==True): rolEnv=env.Clone() walkRolDirs('src',rolEnv) # recursively walks down doc dirs and executes all SConscript files it finds, ignores hidden dirs def walkDocDirs(dir,docEnv): for file in glob(dir+'/*'): if(os.path.isfile(file)): head,basename=os.path.split(file) if(basename=='SConscript'): SCons.Script.SConscript(file,exports=['docEnv'], duplicate=0) elif (os.path.isdir(file)): walkDocDirs(file,docEnv.Clone()) # recursively walks down script dirs and executes all SConscript files it finds, ignores hidden dirs def walkScriptDirs(dir,scriptEnv): for file in glob(dir+'/*'): if(os.path.isfile(file)): head,basename=os.path.split(file) if(basename=='SConscript'): SCons.Script.SConscript(file,exports=['scriptEnv'], duplicate=0) elif (os.path.isdir(file)): walkScriptDirs(file,scriptEnv.Clone()) # recursively walks down plugin dirs and executes all SConscript files it finds, ignores hidden dirs def walkPluginDirs(dir,pluginEnv): for file in glob(dir+'/*'): if(os.path.isfile(file)): head,basename=os.path.split(file) if(basename=='SConscript'): SCons.Script.SConscript(file,exports=['pluginEnv'],variant_dir=dir+'/.'+pluginEnv['arch'],duplicate=0) elif (os.path.isdir(file)): newEnv=pluginEnv.Clone() newEnv.Replace(pluginName=file) walkPluginDirs(file,newEnv) def walkBinDirs(dir,binEnv): for file in glob(dir+'/*'): if(os.path.isfile(file)): head,basename=os.path.split(file) if(basename=='SConscript'): SCons.Script.SConscript(file,exports=['binEnv'],variant_dir=dir+'/.'+binEnv['arch'],duplicate=0) elif (os.path.isdir(file)): walkBinDirs(file,binEnv.Clone()) # recursively walks down test dirs and executes all SConscript files it finds, ignores hidden dirs def walkTestDirs(dir,testEnv): for file in glob(dir+'/*'): if(os.path.isfile(file)): head,basename=os.path.split(file) if(basename=='SConscript'): SCons.Script.SConscript(file,exports=['testEnv'],variant_dir=dir+'/.'+testEnv['arch'],duplicate=0) elif (os.path.isdir(file)): walkTestDirs(file,testEnv.Clone()) # recursively walks down rol dirs and executes all SConscript files it finds, ignores hidden dirs def walkRolDirs(dir,rolEnv): for file in glob(dir+'/*'): if(os.path.isfile(file)): head,basename=os.path.split(file) if(basename=='SConscript'): SCons.Script.SConscript(file,exports=['rolEnv'],variant_dir=dir+'/.'+rolEnv['arch'],duplicate=0) elif (os.path.isdir(file)): newEnv=rolEnv.Clone() newEnv.Replace(rolName=file) walkRolDirs(file,newEnv) # calls loadxxx(env) in initxxx.py for each dependent package in reqlist def define_dependencies(env,reqlist) : list = reqlist.split() for l in list: exec("from load" + l + " import load" + l) exec("load" + l + "(env)") def buildBin(theEnv): # search for c and cc files that have main routines in them filename_regex = re.compile(r'[\w\.\- ]*\.(c|C)\w*$') find_main_regex = re.compile(r'^\s*int\s*main\s*\(.*$') mains = search_for_files(SCons.Script.Dir('.').srcnode().abspath, filename_regex, find_main_regex) # must have at least one main routine if(len(mains)>0): others = filter(lambda x: path.splitext(x)[-1] in ['.c', '.cc'], glob(SCons.Script.Dir('.').srcnode().abspath+'/*')) others = [os.path.basename(x) for x in others] others = [x for x in others if x not in mains] # if this binary has ROOT dictionaries, add them to the list if ('ROOTCINT_SOURCES' in theEnv) and len(theEnv['ROOTCINT_SOURCES'])>0: theEnv.AppendUnique(CPPPATH = '#') if len(others)==0: others = theEnv['ROOTCINT_SOURCES'] else: others+theEnv['ROOTCINT_SOURCES'] # define build (default) and install targets, check launch dir to see if install requested if (len(mains[0])>0): currentDir = theEnv.Dir('.').srcnode().abspath launchDir = theEnv.GetLaunchDir() for m in mains: tmp = [m] if len(others)!=0: tmp = [m]+others prog = theEnv.Program(m.split('.')[0],tmp) if currentDir.startswith(launchDir): theEnv.Alias("install", theEnv.Install(theEnv['local_install_dir']+"/bin",prog)) theEnv.Alias("ginstall", theEnv.Install(theEnv['global_install_dir']+"/bin",prog)) theEnv.Default(prog) def buildTest(theEnv): # search for c and cc files that have main routines in them filename_regex = re.compile(r'[\w\.\- ]*\.(c|C)\w*$') find_main_regex = re.compile(r'^\s*int\s*main\s*\(.*$') mains = search_for_files(SCons.Script.Dir('.').srcnode().abspath, filename_regex, find_main_regex) # must have at least one main routine if(len(mains)>0): others = filter(lambda x: path.splitext(x)[-1] in ['.c', '.cc'], glob(SCons.Script.Dir('.').srcnode().abspath+'/*')) others = [os.path.basename(x) for x in others] others = [x for x in others if x not in mains] scripts = theEnv.Glob('*.py')+theEnv.Glob('*.pl') # define build (default) and install targets if (len(mains[0])>0): currentDir = theEnv.Dir('.').srcnode().abspath launchDir = theEnv.GetLaunchDir() for m in mains: tmp = [m] if len(others)!=0: tmp = [m]+others prog = theEnv.Program(m.split('.')[0],tmp) if currentDir.startswith(launchDir): theEnv.Alias("install", theEnv.Install(theEnv['local_install_dir']+"/bin",prog)) theEnv.Alias("install", theEnv.Install(theEnv['local_install_dir']+"/scripts",scripts)) theEnv.Alias("ginstall", theEnv.Install(theEnv['local_install_dir']+"/bin",prog)) theEnv.Alias("ginstall", theEnv.Install(theEnv['local_install_dir']+"/scripts",scripts)) theEnv.Default(prog) def buildLib(theEnv): # get lists of include and source files includes = theEnv.Glob('*.hxx') + theEnv.Glob('*.h') source = theEnv.Glob('*.cc') + theEnv.Glob('*.c') currentDir = theEnv.Dir('.').srcnode().abspath launchDir = theEnv.GetLaunchDir() # must have at least one include file to install if(len(includes)>0): if currentDir.startswith(launchDir): theEnv.Alias("install", theEnv.Install(theEnv['local_install_dir']+"/include",includes)) theEnv.Alias("ginstall", theEnv.Install(theEnv['global_install_dir']+"/include",includes)) # must have at least one source file to make library if(len(source)>0): shlib = theEnv.SharedLibrary(theEnv['packageName'],source) theEnv.Default(shlib) if currentDir.startswith(launchDir): theEnv.Alias("install", theEnv.Install(theEnv['local_install_dir']+"/lib",shlib)) theEnv.Alias("ginstall", theEnv.Install(theEnv['global_install_dir']+"/lib",shlib)) def buildPlugin(theEnv): # get list of source files source = theEnv.Glob('*.cc') + theEnv.Glob('*.c') # must have at least one source file to make plugin if(len(source)>0): currentDir = theEnv.Dir('.').srcnode().abspath launchDir = theEnv.GetLaunchDir() plugin = theEnv.SharedLibrary(theEnv['pluginName'],source) theEnv.Default(plugin) if currentDir.startswith(launchDir): theEnv.Alias("install", theEnv.Install(theEnv['local_install_dir']+"/plugins",plugin)) theEnv.Alias("ginstall", theEnv.Install(theEnv['global_install_dir']+"/plugins",plugin)) def buildDoc(theEnv): # only if installing to different directory, don't copy SConscript file docs = [f for f in theEnv.Glob('*') if not str(f)=='SConscript'] currentDir = theEnv.Dir('.').srcnode().abspath launchDir = theEnv.GetLaunchDir() if(len(docs)>0): if currentDir.startswith(launchDir): theEnv.Alias("ginstall", theEnv.Install(theEnv['global_install_root']+"/doc",docs)) def buildScripts(theEnv): # only for ginstall scripts = [f for f in theEnv.Glob('*') if not str(f)=='SConscript'] currentDir = theEnv.Dir('.').srcnode().abspath launchDir = theEnv.GetLaunchDir() if(len(scripts)>0): if currentDir.startswith(launchDir): theEnv.Alias("ginstall", theEnv.Install(theEnv['global_install_root']+"/scripts",scripts)) def buildRol(theEnv): # get current dirname dir=theEnv.Dir('.').srcnode().abspath head,dirname=os.path.split(dir) # get list of source files source = theEnv.Glob('*.cc') + theEnv.Glob('*.c') # each source file has to be made into its own .so file # note: each file needs its own environment due to special file-dependent compilition flags currentDir = theEnv.Dir('.').srcnode().abspath launchDir = theEnv.GetLaunchDir() for rol in source: path,base=os.path.split(str(rol)) fname,ext=os.path.splitext(base) fileEnv = theEnv.Clone() rolSO = fileEnv.SharedLibrary(fname,rol) fileEnv.AppendUnique(CFLAGS = ['-DINIT_NAME='+fname+'__init']) fileEnv.Default(rolSO) if currentDir.startswith(launchDir): fileEnv.Alias("install", theEnv.Install(theEnv['local_install_dir']+"/rol/"+dirname,rolSO)) fileEnv.Alias("ginstall", theEnv.Install(theEnv['global_install_dir']+"/rol/"+dirname,rolSO)) def buildJava(theEnv): javaFiles = theEnv.Java(target = theEnv['java_target'], source = theEnv['java_source']) jarFile = theEnv.Jar(target = theEnv['jar_target'], source = theEnv['java_target']) theEnv.Default(jarFile) currentDir = theEnv.Dir('.').srcnode().abspath launchDir = theEnv.GetLaunchDir() if currentDir.startswith(launchDir): theEnv.Alias("install", theEnv.Install(theEnv['local_install_dir']+'/jar',jarFile)) theEnv.Alias("ginstall", theEnv.Install(theEnv['global_install_dir']+'/jar',jarFile)) # returns list of base filenames in directory whose name matches filename_regex and which have a line # that matches line_match_regex def search_for_files(directory, filename_regex, line_match_regex): result = [] for fullname in glob(directory+'/*'): head,basename=os.path.split(fullname) if filename_regex.match(basename): for line in open(fullname,'r'): if line_match_regex.match(line): result.append(basename) break return result