pycasso.py 44 KB

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