pycasso.py 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253
  1. #! /usr/bin/env python3
  2. """
  3. * PYCASSO - PYthon Compile And Setup Scripts Organizer *
  4. INSTALL FOR YOUR APPLICATION
  5. Assume you have an application named 'yourmodel'.
  6. To create a compile/setup script for this application,
  7. copy the template setup script to a name that you and other users
  8. will recoqnize as the main setup script:
  9. cp pycasso/py/pycasso_setup_template setup_yourmodel
  10. Edit the 'Settings' section in this new script to set the location
  11. of the PYCASSO scripts. Also specify the name of the module file
  12. with user scripts. If these are in a subdirectory './base/py', use fore example:
  13. # path to the PYCASSO modules:
  14. base_py = os.path.join('.','pycasso','py')
  15. # name of file with PYCASSO user scripts for this setup:
  16. pycasso_user_scripts = 'pycasso_user_scripts_tm5'
  17. Copy the template settings file 'pycasso__template.rc'
  18. to a suitable name:
  19. cp pycasso/rc/pycasso_template.rc yourmodel.rc
  20. Edit the settings if necessary, and start with:
  21. ./setup_yourmodel yourmodel.rc
  22. LOGGING
  23. Logging is implemented via the standard python 'logging' module.
  24. By default, one logger is defined that writes to the standard output;
  25. usually this is the terminal window, but in a batch environment this
  26. could be redirected to a file.
  27. In addition, a second logger could be setup if the 'logfile'
  28. option in the rc file is set.
  29. To add a new message to one of the logs, follow the examples in the code:
  30. logging.info( 'rc-file read successfully' )
  31. This would create a log-message of level INFO.
  32. Other options for log messages are:
  33. logging.debug (msg) # extra information to debug the scripts
  34. logging.warning (msg) # warnings about undesired behaviour
  35. logging.info (msg) # the standard messages
  36. logging.exception(msg) # something wrong, not fatal
  37. logging.error (msg) # something wrong, nearly fatal
  38. logging.critical (msg) # something wrong, probably fatal
  39. The threshold level for messages to be included is DEBUG for the file output.
  40. The threshold level for messages to be included is INFO for the screen,
  41. unless 'options.verbose' is True (set by '-v' or '--verbose' on the
  42. command line).
  43. """
  44. # ------------------------------------------------
  45. # begin
  46. # ------------------------------------------------
  47. def Main( args, pycasso_user_scripts ) :
  48. """
  49. Start the compilation and setup of the application.
  50. args
  51. List of unparsed arguments, probalby from 'sys.argv[1:]'.
  52. pycasso_user_scripts
  53. Name of file with user scripts for this particular setup.
  54. """
  55. import sys
  56. import os
  57. try:
  58. import exceptions
  59. except ImportError:
  60. import builtins as exceptions
  61. import subprocess
  62. import shutil
  63. import logging
  64. import traceback
  65. import datetime
  66. # tools:
  67. import rc
  68. import pycasso_tools
  69. #
  70. # * user scripts
  71. #
  72. # try to load user scripts and display proper error messages:
  73. try:
  74. pycus = __import__( pycasso_user_scripts )
  75. except ImportError :
  76. logging.error( 'could not import module : %s' % pycasso_user_scripts )
  77. logging.error( 'check definition of "pycasso_user_scripts" in "setup" script' )
  78. raise Exception
  79. except :
  80. logging.error( traceback.format_exc() )
  81. logging.error( 'error in user scripts module ?' )
  82. logging.error( 'check content of module : %s' % pycasso_user_scripts )
  83. raise Exception
  84. #
  85. # * arguments
  86. #
  87. # first parse the arguments to avoid that other messages
  88. # end up in the help text ..
  89. # parse the arguments;
  90. # returns a dictionary 'options' and a single name of the rcfile:
  91. # trap error status if help text is requested:
  92. try :
  93. options,rcfile = ParseArguments( args, pycasso_user_scripts )
  94. except exceptions.SystemExit :
  95. # help text was printed; return without exception:
  96. return
  97. except :
  98. # unknown exception ...
  99. logging.error( sys.exc_info()[1] )
  100. logging.error( 'exception from pycasso.Main' )
  101. raise Exception
  102. #
  103. # * setup logging to standard output
  104. #
  105. # initialise logging system:
  106. logger = Start_Logger()
  107. # initialise messages to standard output:
  108. stdout_handler = Start_StdOut_Log(logger)
  109. # info ...
  110. logging.info( '' )
  111. tnow = datetime.datetime.now().isoformat(' ').split('.',1)[0]
  112. logging.info( 'Started script at %s ...' % tnow )
  113. logging.info( '' )
  114. # info ...
  115. logging.info( 'options and arguments ...' )
  116. logging.info( ' parsed options : %s' % options )
  117. logging.info( ' parsed rcfile argument : %s' % rcfile )
  118. # archive original working directory
  119. owd = os.getcwd()
  120. #
  121. # * settings from rcfile
  122. #
  123. logging.info( 'read settings from %s ...' % rcfile )
  124. # read settings, raw first in order to evaluate
  125. # derived-but-required-before-expansion keys:
  126. rcf = rc.RcFile(rcfile, raw=False)
  127. #>>>>>>>>>>>>>>> KLUDGE >>>>>>>>>>>>>>>>>>>>>>>>>>>>
  128. # evaluate (to remain generic and usefull for any model, this should be
  129. # part of pycus. For now keep it here as a kludge). Here we add/overwrite
  130. # the total number of MPI tasks, according the longitudinal and
  131. # latitudinal values.
  132. # Note that keys in the included rc files are not seen in raw mode.
  133. nx=rcf.get('par.nx', 'int')
  134. ny=rcf.get('par.ny', 'int')
  135. if rcf.has_key('par.ntask') :
  136. rcf.replace('par.ntask',nx*ny)
  137. else:
  138. rcf.add('par.ntask',nx*ny)
  139. rcf.WriteFile( rcfile )
  140. #<<<<<<<<<<<<<<< KLUDGE <<<<<<<<<<<<<<<<<<<<<<<<<<<<
  141. # options provided at command line ?
  142. if len(options) == 0 :
  143. # info ...
  144. logging.info( 'no rcfile settings to be added or changed by options' )
  145. else :
  146. # info ...
  147. logging.info( 'change rcfile settings given options ...' )
  148. # add or replace settings from the arguments passed to the script:
  149. for key,val in options.items() :
  150. # add or replace ?
  151. if rcf.has_key(key) :
  152. # replace existing value:
  153. logging.info( ' reset "%s" to "%s" ...' % (key,str(val)) )
  154. rcf.replace( key, str(val) )
  155. else :
  156. # add new key:
  157. logging.info( ' set "%s" to "%s" ...' % (key,str(val)) )
  158. rcf.add( key, str(val), comment='new key added after options passed to setup script' )
  159. #
  160. # * apply settings for logging
  161. #
  162. logging.info( 'setup logging according to settings ...')
  163. # debug messages to standard output ?
  164. flag = rcf.get( 'verbose', 'bool', default=False )
  165. if flag :
  166. logging.info( ' verbose mode for standard output; print all messages ...' )
  167. stdout_handler.setLevel(logging.DEBUG)
  168. else :
  169. logging.info( ' quiet mode for standard output; print info messages only ...' )
  170. # setup logfile ?
  171. key = 'logfile'
  172. if rcf.has_key(key) :
  173. logfile = rcf.get(key)
  174. logging.info( ' open additional log file : %s' % logfile )
  175. logfile_handler = Start_File_Log( logger, logfile )
  176. else :
  177. logging.info( ' no log file; print to standard output only ...' )
  178. logfile_handler = None
  179. # test logging:
  180. logging.info (' test messages ...')
  181. logging.debug (' testing debug message ...')
  182. logging.info (' testing info message ...')
  183. logging.warning (' testing warning message ...')
  184. logging.error (' testing error message ...')
  185. logging.critical(' testing critical message ...')
  186. #
  187. # * display all settings
  188. #
  189. # display settings:
  190. logging.debug( '' )
  191. logging.debug( 'Content of rc dictionary:' )
  192. for key in rcf.keys() :
  193. logging.debug( ' %s : %s' % (key,rcf.get(key)) )
  194. logging.debug( '[done]' )
  195. #
  196. # * tasks
  197. #
  198. # create a source ?
  199. flag = rcf.get('build.copy','bool')
  200. if flag :
  201. logging.info( 'copy sources to build directory ...' )
  202. Build_Copy( rcf, pycasso_user_scripts )
  203. else :
  204. logging.info( 'no source to be copied ...' )
  205. # configure a source ?
  206. flag = rcf.get('build.configure','bool')
  207. if flag :
  208. logging.info( 'configure source ...' )
  209. Build_Configure( rcf, pycasso_user_scripts )
  210. else :
  211. logging.info( 'no source to be configured ...' )
  212. # make an executable ?
  213. flag = rcf.get('build.make','bool')
  214. if flag :
  215. logging.info( 'compile source ...' )
  216. Build_Make( rcf, pycasso_user_scripts )
  217. else :
  218. logging.info( 'no source to be made ...' )
  219. # check for compilation-only case
  220. if rcf.get('norunsetup','bool', default=False): return
  221. # change to destination directory ?
  222. rundir = rcf.get('rundir')
  223. if len(rundir) > 0 :
  224. # create target directory if necessary:
  225. pycasso_tools.CreateDirs( rundir )
  226. logging.info( 'change to run directory %s ...' % rundir )
  227. # goto this directory:
  228. os.chdir(rundir)
  229. else :
  230. logging.info( 'no run directory specified; stay here ...' )
  231. # copy files ?
  232. ifiles = rcf.get('install.copy')
  233. if len(ifiles) > 0 :
  234. logging.info( 'install files ...' )
  235. # loop over files to be installed:
  236. for ifile in ifiles.split() :
  237. # if the file contains a colon ':' ...
  238. if ':' in ifile :
  239. # ... the name after the colon is the target name
  240. sour,targ = ifile.split(':')
  241. else :
  242. # ... otherwise, copy to current directory:
  243. sour,targ = ifile,os.path.curdir
  244. logging.info( ' copy "%s" to "%s" ...' % (sour,targ) )
  245. # check ...
  246. if not os.path.exists(sour) :
  247. logging.error( 'source file not found : %s' % sour )
  248. raise IOError
  249. # use shell utility:
  250. shutil.copy( sour, targ )
  251. else :
  252. logging.info( 'no files to be installed ...' )
  253. # write rcfile ...
  254. newrc = rcf.get('install.rc')
  255. if len(newrc) > 0 :
  256. logging.info( ' install processed rcfile ...' )
  257. # write pre-processed rcfile:
  258. rcf.WriteFile( newrc )
  259. # write raw rcfile ...
  260. rawrc = rcf.get('install.raw.rc','bool',default=False)
  261. if rawrc :
  262. rawrc = os.path.join(owd,rcfile)
  263. targ = "%s.%s" % (rcfile, datetime.datetime.now().isoformat('-'))
  264. # info ...
  265. logging.info( ' install "%s" to "%s"...' % (rawrc, targ) )
  266. # write pre-processed rcfile:
  267. shutil.copy(rawrc,targ)
  268. #
  269. # * submit script / info / submitting
  270. #
  271. # settings for submit script:
  272. submit_script = rcf.get( 'submit.script' )
  273. submit_command = rcf.get( 'submit.command' )
  274. # where to search for scripts ?
  275. build_prefix = rcf.get('build.prefix')
  276. # use paths relative to run directory ?
  277. if rcf.get('submit.relpaths','bool') :
  278. # only available in recent versions ...
  279. if sys.version_info[0]+0.1*sys.version_info[1] >= 2.6 :
  280. build_prefix = os.path.relpath(build_prefix,start=rundir)
  281. else :
  282. logging.warning( "Option `submit.relpaths` requires at least python version 2.6 ; use absolute path instead" )
  283. # full path to submit script:
  284. submit_script_path = os.path.join( rundir, submit_script )
  285. # should exist ...
  286. if not os.path.exists(submit_script_path) :
  287. logging.error( 'submit script not found:' )
  288. logging.error( ' %s' % submit_script_path )
  289. logging.error( 'not included in "install.copy" list ?' )
  290. raise Exception
  291. # Insert path to submit modules. Default is 'build.prefix/bin'
  292. submit_bin_path = rcf.get( 'submit.bin.path', default=build_prefix)
  293. pycasso_tools.modify_text_file( submit_script_path,
  294. "pypath_default = os.path.join( os.pardir, 'build', 'bin' )",
  295. "pypath_default = os.path.join('%s','bin')" % submit_bin_path )
  296. # info ...
  297. logging.info( '' )
  298. logging.info( 'To submit a job:' )
  299. logging.info( '' )
  300. indent = ' '
  301. # need to change to run directory ?
  302. if len(rundir) > 0 :
  303. logging.info( indent+'# change to the run directory:' )
  304. logging.info( indent+'cd %s' % rundir )
  305. logging.info( '' )
  306. # disply usage text:
  307. logging.info( indent+'# submit a job (use --help for more information):' )
  308. logging.info( indent+'%s' % submit_command )
  309. # advertisement ...
  310. logging.info( '' )
  311. logging.info( 'For first glance on settings and results:' )
  312. logging.info( '' )
  313. logging.info( indent+'# run diadem postprocessor:' )
  314. logging.info( indent+'./tools/diadem/py/diadem %s' % rcfile )
  315. # submit automatically ?
  316. flag = rcf.get( 'submit.auto', 'bool' )
  317. if flag :
  318. # change to run directory:
  319. os.chdir( rundir )
  320. # info ...
  321. logging.info( '' )
  322. logging.info( 'submit automatically ...' )
  323. logging.info( ' %s' % submit_command )
  324. logging.info( '' )
  325. # call script:
  326. retcode = subprocess.call( submit_command.split() )
  327. if retcode != 0 :
  328. logging.error( 'from submission of : %s' % submit_command )
  329. raise Exception
  330. #
  331. # * end
  332. #
  333. logging.info( '' )
  334. tnow = datetime.datetime.now().isoformat(' ').split('.',1)[0]
  335. logging.info( 'End of script at %s .' % tnow )
  336. logging.info( '' )
  337. # close logs:
  338. if logfile_handler != None : logfile_handler.close()
  339. stdout_handler.close()
  340. return
  341. # ***
  342. def ParseArguments( args, pycasso_user_scripts ) :
  343. """
  344. Define the arguments accepted by a pycasso run script.
  345. Usage:
  346. options,rcfile = ParseArguments( args )
  347. Arguments:
  348. args
  349. Passed from the calling script, probably equal to : sys.argv[1:]
  350. pycasso_user_scripts
  351. Name of file with user scripts for this particular setup.
  352. Return values:
  353. options # object with following data fields:
  354. .verbose # (boolean) gives extra logging messages to the screen
  355. rcfile # name of settings file
  356. """
  357. # external:
  358. import logging
  359. import optparse
  360. # load user scripts:
  361. pycus = __import__( pycasso_user_scripts )
  362. # set text for 'usage' help line:
  363. usage = "\n %prog [options] rcfile\n %prog -h|--help"
  364. # descriptive text
  365. description = "Driver script to compile and setup a model application. "\
  366. "The 'rcfile' is a textfile with settings read by the scripts or "\
  367. "the application, a template should be available with this script."
  368. # initialise the option parser:
  369. parser = optparse.OptionParser( usage=usage, description=description )
  370. # define verbose option:
  371. parser.add_option( "-v", "--verbose",
  372. help="Print extra logging messages to standard output. "
  373. "This option will set rcfile key 'verbose' to 'True'.",
  374. dest="verbose", action="store_true", default=False )
  375. # only compilation ?
  376. parser.add_option( "-m", "--make",
  377. help="Only compilation. All build.* keys are True (except build.new). Run setup is skipped.",
  378. dest="force_comp", action="store_true", default=False )
  379. # skip compilation ?
  380. parser.add_option( "-r","--no-compile",
  381. help="Skip compilation. All build.* keys are False.",
  382. dest="no_build", action="store_true", default=False )
  383. # new build ?
  384. parser.add_option( "-n", "--new",
  385. help="Create new build; remove old build directory. "
  386. "This option will set rcfile key 'build.new' to 'True'.",
  387. dest="build_new", action="store_true", default=False )
  388. # how many jobs used for make etc ?
  389. parser.add_option( "-j", "--jobs",
  390. help="Number of jobs (commands) to run simultaneously. "
  391. "Empty value '' indicates unlimitted number. "
  392. "Now only used to (re)set the number of jobs used by the maker ('gmake -j JOBS')."
  393. "This flag will replace the value of 'build.jobs' in the rcfile.",
  394. dest="jobs", action="store", default=None )
  395. # submit job ?
  396. parser.add_option( "-s", "--submit",
  397. help="Submit the job after setup. "
  398. "See also the section on 'Submit options' below for "
  399. "options passed directly to the submit script. "
  400. "This option will set rcfile key 'submit.auto' to 'True'.",
  401. dest="submit_auto", action="store_true", default=False )
  402. # options for submitting the job:
  403. group = optparse.OptionGroup( parser, "Submit options",
  404. description="Options passed directly to the submit script, see its help text for details." )
  405. # where to submit to ?
  406. group.add_option( "-f", "--foreground",
  407. help="Run job in foreground.",
  408. dest="submit_to", action="store_const", const='foreground' )
  409. group.add_option( "-b", "--background",
  410. help="Run job in background.",
  411. dest="submit_to", action="store_const", const='background' )
  412. group.add_option( "-q", "--queue",
  413. help="Submit job to a queue system.",
  414. dest="submit_to", action="store_const", const='queue' )
  415. # when submitted, run in debugger ?
  416. group.add_option( "-d", "--debugger",
  417. help="Run executable in a debugger.",
  418. dest="submit_debugger", action="store_true" )
  419. # add group:
  420. parser.add_option_group( group )
  421. # add the user options:
  422. group = optparse.OptionGroup( parser, "User model options",
  423. description="These options are defined and handled in the 'pycasso_user_scripts_*' module." )
  424. pycus.DefineOptions( group )
  425. parser.add_option_group( group )
  426. # now parse the actual arguments:
  427. values,args = parser.parse_args( args=args )
  428. # at least rcfile should be specified as argument,
  429. # and no other arguments:
  430. if len(args) != 1 :
  431. parser.error("incorrect number of arguments\n")
  432. # translate options into a dictionairy
  433. # if they should replace rcfile values:
  434. opts = {}
  435. if values.verbose : opts['verbose' ] = True
  436. if values.build_new : opts['build.new' ] = True
  437. if values.jobs != None : opts['build.jobs' ] = values.jobs
  438. if values.submit_auto : opts['submit.auto' ] = True
  439. if values.submit_to != None : opts['submit.to' ] = values.submit_to
  440. if values.submit_debugger != None : opts['submit.debugger'] = values.submit_debugger
  441. if values.no_build :
  442. opts['build.copy' ] = False
  443. opts['build.configure' ] = False
  444. opts['build.new' ] = False
  445. opts['build.make' ] = False
  446. if values.force_comp :
  447. opts['build.copy' ] = True
  448. opts['build.configure' ] = True
  449. opts['build.make' ] = True
  450. opts['norunsetup' ] = True
  451. # add the parsed user options:
  452. pycus.StoreOptions( opts, values )
  453. # copy name of rcfile:
  454. rcfile = args[0]
  455. # return values:
  456. return opts,rcfile
  457. # ***
  458. def Start_Logger() :
  459. """
  460. logger = Start_Logger()
  461. """
  462. # external:
  463. import logging
  464. # create logger
  465. logger = logging.getLogger('')
  466. logger.setLevel(logging.DEBUG)
  467. # ok
  468. return logger
  469. # ***
  470. def Start_StdOut_Log(logger) :
  471. """
  472. stdout_handler = Start_StdOut_Log(logger)
  473. """
  474. # external:
  475. import sys
  476. import logging
  477. # set a format for screen use:
  478. logformat = '[%(levelname)-8s] %(message)s'
  479. # create formatter:
  480. formatter = logging.Formatter(logformat)
  481. # handler for standard output:
  482. stdout_handler = logging.StreamHandler(sys.stdout)
  483. stdout_handler.setLevel(logging.INFO)
  484. stdout_handler.setFormatter(formatter)
  485. logger.addHandler(stdout_handler)
  486. ## first messages:
  487. #logging.debug('testing debug message after start stdout log ...')
  488. #logging.info ('testing info message after start stdout log ...')
  489. # ok
  490. return stdout_handler
  491. # ***
  492. def Start_File_Log(logger,logfile) :
  493. """
  494. logfile_handler = Start_File_Log(logger,logfile)
  495. Create handler for log file.
  496. Initial level is 'DEBUG', thus all messages will be written to the file.
  497. """
  498. # external:
  499. import sys
  500. import logging
  501. # set format of lines written to logging:
  502. if sys.version_info < (2, 5):
  503. logformat = "%(asctime)s %(name)-10s, line %(lineno)4i [%(levelname)-10s] %(message)s"
  504. else:
  505. logformat = '%(lineno)4i %(filename)-30s -> %(funcName)-30s [%(levelname)-8s] %(message)s'
  506. #endif
  507. # create formatter:
  508. formatter = logging.Formatter(logformat)
  509. # now create a handler for the log file;
  510. # mode 'w' will cause the log file to be re-written:
  511. logfile_handler = logging.FileHandler(filename=logfile,mode='w')
  512. logfile_handler.setLevel(logging.DEBUG)
  513. logfile_handler.setFormatter(formatter)
  514. logger.addHandler(logfile_handler)
  515. ## first messages:
  516. #logging.debug('testing debug message after start file log ...')
  517. #logging.info ('testing info message after start file log ...')
  518. # ok
  519. return logfile_handler
  520. # ***
  521. def Build_Copy( rcf, pycasso_user_scripts ) :
  522. # external:
  523. import os
  524. import sys
  525. import shutil
  526. import logging
  527. import pycasso_tools
  528. import re
  529. # load user scripts:
  530. pycus = __import__( pycasso_user_scripts )
  531. # remove existing build ?
  532. remove_existing_build = rcf.get('build.new','bool')
  533. # target directory:
  534. prefix = rcf.get('build.prefix')
  535. logging.info( ' build prefix : %s ' % prefix )
  536. # extend name ?
  537. if rcf.get('build.prefix.extend','bool') :
  538. # start with original prefix:
  539. prefix_ext = prefix
  540. # get list with names of compiler flag groups to be used:
  541. flags = pycus.Build_FlagGroups( rcf )
  542. # add to prefix:
  543. if len(flags) > 0 :
  544. for flag in flags : prefix_ext = prefix_ext+'_'+flag
  545. else :
  546. prefix_ext = prefix_ext+'_'
  547. logging.info( ' build prefix extended : %s ' % prefix_ext )
  548. # create extended prefix directory if necessary:
  549. pycasso_tools.CreateDirs( prefix_ext, forceclean=remove_existing_build )
  550. # test if link already exists; use 'lexists' to trap broken link:
  551. if os.path.lexists(prefix) :
  552. # is a link ?
  553. if os.path.islink(prefix) :
  554. # remove current link:
  555. os.remove( prefix )
  556. else :
  557. logging.error( 'could not replace "'+prefix+'" by a symbolic link; remove first' )
  558. raise Exception
  559. # create link relative to current directory:
  560. os.symlink( os.path.basename(prefix_ext), prefix )
  561. else :
  562. # create prefix directory if necessary:
  563. pycasso_tools.CreateDirs( prefix, forceclean=remove_existing_build )
  564. # get list of sub directories to be scanned:
  565. subdirs = rcf.get('build.copy.subdirs').split()
  566. # eventually loop over sub directories:
  567. for subdir in subdirs :
  568. # full path:
  569. sdir = os.path.join(prefix,subdir)
  570. # create if necessary:
  571. pycasso_tools.CreateDirs( sdir )
  572. # loop over source directories:
  573. sourcedirs = rcf.get('build.copy.dirs')
  574. if len(sourcedirs) > 0 :
  575. # info ...
  576. logging.info( ' copy files from source directories...' )
  577. # some flags ...
  578. flag_remove__part = rcf.get('build.copy.remove.__part','bool')
  579. if flag_remove__part :
  580. logging.info( ' remove "__<name>" parts from sources files ...' )
  581. #endif
  582. # loop over source directories:
  583. for sourcedir in sourcedirs.split() :
  584. found_some_files = False
  585. # info ...
  586. logging.info( ' scanning %s ...' % sourcedir)
  587. # should be a directory ...
  588. if not os.path.isdir(sourcedir) :
  589. logging.error( 'specified source dir is not an existing directory : %s' % sourcedir )
  590. raise IOError
  591. #endif
  592. # empty ? then add something for current directory:
  593. if len(subdirs) == 0 : subdirs = os.path.curdir
  594. # loop over sub directories:
  595. for subdir in subdirs :
  596. # full path:
  597. if subdir == '.' :
  598. # just the directory:
  599. sourcepath = sourcedir
  600. else :
  601. # add sub directory:
  602. sourcepath = os.path.join(sourcedir,subdir)
  603. # info ...
  604. logging.debug( ' scanning %s ...' % sourcepath )
  605. #endif
  606. # list all files in this directroy:
  607. try :
  608. # list all files, might be empty:
  609. sourcefiles = os.listdir(sourcepath)
  610. except :
  611. # empty ? then next
  612. continue
  613. #endtry
  614. # loop over source files:
  615. for sfile in sourcefiles :
  616. found_some_files = True
  617. # full filename:
  618. sourcefile = os.path.join(sourcepath,sfile)
  619. # skip directories:
  620. if os.path.isdir(sourcefile) : continue
  621. # skip files with certain extensions (as a list of regex)
  622. name,ext = os.path.splitext(sfile)
  623. skipit = False
  624. for pattern in rcf.get('build.copy.skip.ext').split() :
  625. if re.search(pattern, ext) :
  626. logging.debug( ' %-50s %-30s [%-8s]' % (sourcefile,'','skip') )
  627. skipit = True
  628. if skipit :
  629. continue
  630. # default target file:
  631. outfile = sfile
  632. # remove '__name' part ?
  633. if flag_remove__part :
  634. # sourcefile contains '__' ?
  635. if '__' in sfile :
  636. # strip '__xxx' part from file name (used to identify versions):
  637. name,ext = os.path.splitext(sfile)
  638. outfile = name.split('__')[0]+ext
  639. # skip file ?
  640. if outfile in rcf.get('build.copy.skip.file').split() :
  641. logging.debug( ' %-50s %-30s [%-8s]' % (sourcefile,'','skip') )
  642. continue
  643. #endif
  644. # full target file:
  645. targetfile = os.path.join( prefix, subdir, outfile )
  646. # already present ?
  647. if os.path.exists(targetfile) :
  648. # different ?
  649. if pycasso_tools.diff_text_files(sourcefile,targetfile) :
  650. stat = 'differs'
  651. else:
  652. stat = '...'
  653. #endif
  654. else:
  655. stat = 'new'
  656. #endif
  657. # info ...
  658. logging.debug( ' %-50s -> %-30s [%-8s]' % (sourcefile,os.path.join(subdir,outfile),stat) )
  659. # copy source to target, preserve times etc:
  660. shutil.copy2( sourcefile, targetfile )
  661. if found_some_files == False :
  662. logging.warning(' found no source files in standard subdirs %s of %s. Mistake in source.dirs?' % (str(subdirs),sourcedir))
  663. #endfor # source directories
  664. else :
  665. logging.info( ' no source directories specified ...' )
  666. # add a new formed directory to the python path?
  667. flag = rcf.get('build.copy.pypath','bool')
  668. if flag :
  669. logging.info( ' add subdir <prefix>/py to python path ...' )
  670. # extra directory:
  671. newdir = os.path.join(prefix,'py')
  672. # add as first directory to search path:
  673. sys.path.insert(0,newdir)
  674. else :
  675. logging.info( ' no request for extension of the python path ...' )
  676. # ***
  677. def Build_Configure( rcf, pycasso_user_scripts ) :
  678. import os
  679. import sys
  680. import logging
  681. import subprocess
  682. import glob
  683. # tools:
  684. import go
  685. import pycasso_tools
  686. # load user scripts:
  687. pycus = __import__( pycasso_user_scripts )
  688. # change directory:
  689. current_dir = os.getcwd()
  690. configure_dir = rcf.get('build.sourcedir')
  691. logging.debug( ' change to %s ...' % configure_dir )
  692. os.chdir( configure_dir )
  693. #
  694. # * compiler and flags
  695. #
  696. # call user script to set compilers and linker:
  697. fc,f77,linker = pycus.Build_Compiler( rcf )
  698. # start without any flags:
  699. fflags = ''
  700. ldflags = ''
  701. libs = ''
  702. # very basic flags to compile the too large mdf module ...
  703. fflags_basic = ''
  704. # get list with names of compiler flag groups to be used:
  705. flaggroups = pycus.Build_FlagGroups( rcf )
  706. # loop over groups:
  707. for flaggroup in flaggroups :
  708. # add flags for this group:
  709. fflags = fflags.strip()+' '+rcf.get('compiler.flags.'+flaggroup+'.fflags' )
  710. ldflags = ldflags.strip()+' '+rcf.get('compiler.flags.'+flaggroup+'.ldflags')
  711. #endfor
  712. logging.info( ' fflags : %s' % fflags )
  713. logging.info( ' ldflags : %s' % ldflags )
  714. # idem for basic flags:
  715. flaggroups_basic = pycus.Build_FlagGroups( rcf, basic=True )
  716. # loop over groups:
  717. for flaggroup in flaggroups_basic :
  718. # add flags for this group:
  719. fflags_basic = fflags_basic.strip()+' '+rcf.get('compiler.flags.'+flaggroup+'.fflags' )
  720. logging.info( ' fflags basic : %s' % fflags_basic )
  721. #
  722. # * macro's
  723. #
  724. # defined macro's
  725. macros_def = rcf.get('build.configure.macro.define' ).split()
  726. # macro groups:
  727. groups = rcf.get('build.configure.macro.groups').split()
  728. # apply user changes to default list:
  729. for group in groups :
  730. # add (or remove!) macro defintions by user script:
  731. macros_def = pycus.Build_Define( rcf, group, macros_def )
  732. # create a 'clean' list of defined macro's without double definitions:
  733. macros_defined = []
  734. for m in macros_def :
  735. if m not in macros_defined : macros_defined = macros_defined + macros_def
  736. # initialize a list to store all supported macro's without double definitions:
  737. macros_supported = []
  738. # loop over groups:
  739. for group in groups :
  740. # start of the rc keys:
  741. keybase = 'build.configure.macro.'+group
  742. # values for this group:
  743. macros_all = rcf.get(keybase+'.all' ).split()
  744. macros_hfile = rcf.get(keybase+'.hfile' )
  745. # write header file ?
  746. if len(macros_hfile) > 0 :
  747. # info ...
  748. logging.info( ' update %s (include file with macro definitions) ...' % macros_hfile )
  749. # fill text for include file in list of strings,
  750. # each line should end with the newline expression '\n' :
  751. src = []
  752. src.append( '!\n' )
  753. src.append( '! Include file with macro definitions.\n' )
  754. src.append( '!\n' )
  755. # loop over macro's to be defined:
  756. for mdef in macros_def :
  757. # vallue assigned ?
  758. if '=' in mdef :
  759. # split in name and value:
  760. mname,mval = mdef.split('=')
  761. # in this group ?
  762. if mname in macros_all :
  763. # add line to file:
  764. src.append( '#define %s %s\n' % (mname,mval) )
  765. #endif
  766. else :
  767. # in this group ?
  768. if mdef in macros_all :
  769. # add line to file:
  770. src.append( '#define %s\n' % mdef )
  771. #endif
  772. #endif
  773. #endfor
  774. # write the source, replace existing file only if it was different:
  775. pycasso_tools.update_text_file( macros_hfile, src )
  776. #endif
  777. # extend list:
  778. for m in macros_all :
  779. if m not in macros_supported : macros_supported = macros_supported + macros_all
  780. # check for macro's that are not supported yet:
  781. any_error = False
  782. for macr in macros_defined :
  783. # not supported ?
  784. if macr not in macros_supported :
  785. # any unsupported macro's found yet ?
  786. if not any_error :
  787. # initial error message:
  788. logging.error( "one or more macro's have been defined that are not listed" )
  789. logging.error( "in any 'build.configure.macro.*.all' lists:" )
  790. #endif
  791. # display problematic macro:
  792. logging.error( " %s" % macr )
  793. # reset flag:
  794. any_error = True
  795. # any error ? then leave:
  796. if any_error : raise Exception
  797. # create list of macro definitions as command line arguments, e.g. -Dwith_this_flag etc:
  798. fc_defs = '' # for fortran compiler
  799. mk_defs = '' # for makedepf90
  800. # add macro definitions ?
  801. define_D = rcf.get( 'build.configure.define.D', 'bool' )
  802. if define_D :
  803. # compiler depended prefix for macro definition:
  804. fc_D = rcf.get('compiler.defineflag',default='-D')
  805. mk_D = '-D'
  806. # loop over macro's to be defined:
  807. for mdef in macros_defined :
  808. # add definition to command line argument list:
  809. fc_defs = fc_defs.strip()+(' %s%s' % (fc_D,mdef) )
  810. mk_defs = mk_defs.strip()+(' %s%s' % (mk_D,mdef) )
  811. #endfor
  812. #endif
  813. # add definitions to flags:
  814. fflags = fflags.strip()+' '+fc_defs
  815. # idem for basic flags:
  816. fflags_basic = fflags_basic.strip()+' '+fc_defs
  817. #
  818. # * libraries
  819. #
  820. # get list of default library names to be linked:
  821. libnames = rcf.get( 'build.configure.libs' )
  822. # info ...
  823. logging.debug( ' libraries to be used:' )
  824. if len(libnames) > 0 : logging.debug( ' %s (default)' % libnames )
  825. # convert to list:
  826. libnames = libnames.split()
  827. # loop over defined macro's:
  828. for mdef in macros_defined :
  829. # read list of libraries that should be added if this macro is defined:
  830. libnames_ifdef = rcf.get( 'build.configure.libs.ifdef.%s' % mdef, default='' )
  831. # defined ?
  832. if len(libnames_ifdef) > 0 :
  833. # add:
  834. libnames = libnames+libnames_ifdef.split()
  835. # info ...
  836. logging.debug( ' %s (macro `%s` defined)' % (libnames_ifdef,mdef) )
  837. # get list of all supported library names:
  838. libnames_all = rcf.get( 'build.configure.libs.all' ).split()
  839. # check if all libraries specified to be used are actually supported ...
  840. for libname in libnames :
  841. if libname not in libnames_all :
  842. logging.error( 'library name `%s` not in `build.configure.libs.all` list ...' % libname )
  843. raise Exception
  844. # info ...
  845. logging.debug( ' libraries linked (in this order):' )
  846. # now add compiler and linker flags ;
  847. # loop over all supported libraries (this specfifies the linking order!)
  848. for libname in libnames_all :
  849. # not in use ? then skip:
  850. if libname not in libnames : continue
  851. # info ...
  852. logging.debug( ' %s' % libname )
  853. # add include, and module flags (screen out degenerate cases):
  854. to_include = rcf.get('compiler.lib.'+libname+'.fflags')
  855. if to_include !='-I' and to_include !='-I/':
  856. logging.debug(' add Fflag for "%s": %s' % (libname,to_include))
  857. fflags = fflags.strip()+' '+to_include
  858. # add link flags:
  859. try:
  860. lib2add = rcf.get('compiler.lib.'+libname+'.libs')
  861. except KeyError:
  862. # 'libs' not found, so try combination of 'lib_dir' and 'lib_no_l'
  863. logging.info("try 'compiler.lib.%s.lib_dir instead" % libname)
  864. libdir = rcf.get('compiler.lib.'+libname+'.lib_dir')
  865. libnol = rcf.get('compiler.lib.'+libname+'.libs_no_l')
  866. if libdir =='-L' or libdir =='-L/':
  867. libdir=''
  868. if len(libnol)!=0:
  869. lib2add = libdir+' '.join([' -l'+f for f in libnol.split()])
  870. logging.debug(' add Lflag for "%s": %s' % (libname,lib2add))
  871. libs = libs.strip()+' '+lib2add
  872. # idem for basic flags:
  873. to_include = rcf.get('compiler.lib.'+libname+'.fflags')
  874. if to_include !='-I' and to_include !='-I/':
  875. logging.debug(' add FflagBasic for "%s": %s' % (libname,to_include))
  876. fflags_basic = fflags_basic.strip()+' '+to_include
  877. #
  878. # * write compiler flags to makefile
  879. #
  880. # name of include file with dependencies to be written:
  881. makefile_flags = rcf.get('build.configure.flags.includefile')
  882. # info ...
  883. logging.info( ' write %s (compiler and flags) ...' % makefile_flags )
  884. logging.info( ' compiler fflags : '+fflags )
  885. logging.info( ' ldflags : '+ldflags )
  886. logging.info( ' libs : '+libs )
  887. # fill content; each line should end with the newline expression '\n' :
  888. src = []
  889. src.append( '#\n' )
  890. src.append( '# include file with compiler flags for Makefile.\n' )
  891. src.append( '#\n' )
  892. src.append( '\n' )
  893. src.append( '# compiler and linker:\n' )
  894. src.append( 'FC = %s\n' % fc )
  895. src.append( 'F77 = %s\n' % f77 )
  896. src.append( 'LINKER = %s\n' % linker )
  897. src.append( '\n' )
  898. src.append( '# compile flags:\n' )
  899. src.append( 'FFLAGS = %s\n' % fflags )
  900. src.append( '\n' )
  901. src.append( '# compile flags without optim:\n' )
  902. src.append( 'FFLAGS_BASIC = %s\n' % fflags_basic )
  903. src.append( '\n' )
  904. src.append( '# linker flags:\n' )
  905. src.append( 'LDFLAGS = %s\n' % ldflags )
  906. src.append( '\n' )
  907. src.append( '# library flags:\n' )
  908. src.append( 'LIBS = %s\n' % libs )
  909. src.append( '\n' )
  910. # write:
  911. pycasso_tools.write_text_file( makefile_flags, src )
  912. #
  913. # * cleanup
  914. #
  915. # remove files ?
  916. logging.info( ' remove files ...' )
  917. for sfile in rcf.get('build.configure.remove').split() :
  918. # info ...
  919. logging.debug( ' %s ...' % sfile )
  920. # remove file if present:
  921. if os.path.exists(sfile) : os.remove( sfile )
  922. #endfor
  923. # clean for defined macro's (read list of pattern)
  924. logging.info( ' remove files for defined macro\'s ...' )
  925. for macr in macros_defined :
  926. # try to read list with files to be removed if macro is defined:
  927. patterns = rcf.get( 'build.configure.remove.ifdef.%s' % macr, default='' )
  928. # line with list of patterns was found ?
  929. if len(patterns) > 0:
  930. for pattern in patterns.split():
  931. for sfile in glob.glob(pattern) : # loop over files to be removed
  932. if os.path.exists(sfile) :
  933. logging.debug( ' %s ...' % sfile )
  934. os.remove(sfile)
  935. # clean for undefined macro's (read list of pattern)
  936. logging.info( ' remove files for undefined macro\'s ...' )
  937. for macr in macros_supported :
  938. # defined ? then next:
  939. if macr in macros_defined : continue
  940. # try to read list with files to be removed if macro is NOT defined:
  941. patterns = rcf.get( 'build.configure.remove.ifndef.%s' % macr, default='' )
  942. # line with list of patterns was found ?
  943. if len(patterns) > 0:
  944. for pattern in patterns.split():
  945. for sfile in glob.glob(pattern) : # loop over files to be removed
  946. if os.path.exists(sfile) :
  947. logging.debug( ' %s ...' % sfile )
  948. os.remove(sfile)
  949. #
  950. # * user script
  951. #
  952. # call user script:
  953. pycus.Build_Configure( rcf )
  954. #
  955. # * deps
  956. #
  957. # create deps ?
  958. flag = rcf.get('build.configure.makedep','bool')
  959. if flag :
  960. # name of include file with dependencies to be written:
  961. makefile_deps = rcf.get('build.configure.makedep.includefile')
  962. # info ...
  963. logging.info( ' create %s ...' % makefile_deps )
  964. # name of executable to be build:
  965. exe = rcf.get( 'build.make.exec' )
  966. # file filter:
  967. files = rcf.get('build.configure.makedep.files')
  968. #EC-EARTH # Check for the existence of makedepf90 on this machine
  969. #EC-EARTH try :
  970. #EC-EARTH p = go.subprocess.call( ['makedepf90','--version'] )
  971. #EC-EARTH except go.subprocess.CallingError as err :
  972. #EC-EARTH logging.error( err )
  973. #EC-EARTH logging.error( '')
  974. #EC-EARTH logging.error( '*******************************************************')
  975. #EC-EARTH logging.error( 'Cannot access a version of makedepf90')
  976. #EC-EARTH logging.error( '(or something wrong with call to subprocess)')
  977. #EC-EARTH logging.error( 'If it is installed, please check your $PATH ' )
  978. #EC-EARTH logging.error( 'If it is not installed, please install makedepf90 ' )
  979. #EC-EARTH logging.error( 'For the source code and instruction, see' )
  980. #EC-EARTH logging.error( ' http://personal.inet.fi/private/erikedelmann/makedepf90/' )
  981. #EC-EARTH logging.error( '*******************************************************')
  982. #EC-EARTH logging.error( '')
  983. #EC-EARTH raise IOError( 'makedepf90 not found')
  984. # EC-EARTH: uses the makedepf90 that ships with the distribution
  985. ecearth_makedepf90 = rcf.get( 'mkdepf90' )
  986. # Generate makefile with dependencies. Replace default rule for
  987. # linking executable: took off $(FFLAGS). So ensure that $(LDFLAGS)
  988. # has everything that is needed.
  989. # command = 'makedepf90 -l \'$(FC) -o $@ $(LDFLAGS) $(FOBJ) $(LIBS)\' %s -o %s %s' % (mk_defs,exe,files)
  990. command = '%s -l \'$(FC) -o $@ $(LDFLAGS) $(FOBJ) $(LIBS)\' %s -o %s %s' % (ecearth_makedepf90,mk_defs,exe,files)
  991. logging.debug( ' run command: %s' % command )
  992. try :
  993. # run as a shell command since the file list is probably '*.F90' :
  994. p = go.subprocess.call( command, shell=True )
  995. except go.subprocess.CallingError as err :
  996. logging.error( err )
  997. raise Exception
  998. except go.subprocess.StatusError as err :
  999. for line in err.stderr : logging.error(line)
  1000. logging.error( err )
  1001. raise Exception
  1002. # write result:
  1003. f = open( makefile_deps, 'w' )
  1004. for line in p.stdout : f.write(line+'\n')
  1005. f.close()
  1006. # add to log file:
  1007. logging.debug( '' )
  1008. logging.debug( '---[%s]------------------------------------------------------------' % makefile_deps )
  1009. for line in p.stdout : logging.debug(line)
  1010. logging.debug( '-------------------------------------------------------------------' )
  1011. logging.debug( '' )
  1012. #
  1013. # *
  1014. #
  1015. # back:
  1016. logging.debug( ' change back to %s ...' % current_dir )
  1017. os.chdir( current_dir )
  1018. return
  1019. # ***
  1020. def Build_Make( rcf, pycasso_user_scripts ) :
  1021. import os
  1022. import logging
  1023. # load user scripts:
  1024. pycus = __import__( pycasso_user_scripts )
  1025. # info ...
  1026. logging.info( 'make executable ...' )
  1027. # change directory:
  1028. Make_dir = rcf.get('build.make.dir')
  1029. logging.debug( ' change to %s ...' % Make_dir )
  1030. os.chdir( Make_dir )
  1031. # call user script:
  1032. pycus.Build_Make( rcf )
  1033. # ------------------------------------------------
  1034. # end
  1035. # ------------------------------------------------