pycasso.py 44 KB

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