# -*- coding: iso-8859-1 -*-

# ==============================================================================
# COPYRIGHT (C) 1991 - 2003  EDF R&D                  WWW.CODE-ASTER.ORG
# THIS PROGRAM IS FREE SOFTWARE; YOU CAN REDISTRIBUTE IT AND/OR MODIFY
# IT UNDER THE TERMS OF THE GNU GENERAL PUBLIC LICENSE AS PUBLISHED BY
# THE FREE SOFTWARE FOUNDATION; EITHER VERSION 2 OF THE LICENSE, OR
# (AT YOUR OPTION) ANY LATER VERSION.
#
# THIS PROGRAM IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT
# WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF
# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. SEE THE GNU
# GENERAL PUBLIC LICENSE FOR MORE DETAILS.
#
# YOU SHOULD HAVE RECEIVED A COPY OF THE GNU GENERAL PUBLIC LICENSE
# ALONG WITH THIS PROGRAM; IF NOT, WRITE TO EDF R&D CODE_ASTER,
#    1 AVENUE DU GENERAL DE GAULLE, 92141 CLAMART CEDEX, FRANCE.
# ==============================================================================

"""
Tools to maintain a development version of Code_Aster, and useful for
the developper.
Methods are called by an ASTER_RUN object.
"""

__revision__ = "$Id: as_maintenance.py 2670 2006-12-22 13:55:20Z courtois $"


# ----- differ messages translation
def _(mesg):
   return mesg

import sys
import os
import re
import glob
import tarfile
import getpass
from optparse  import SUPPRESS_HELP
from sets      import Set
from types     import ListType, TupleType
EnumTypes = (ListType, TupleType)

from as_run           import RunAsterError
from as_config        import ASTER_CONFIG
from as_build         import ASTER_BUILD
from astk_maintenance import AstkUpdate, get_devel_param

#-------------------------------------------------------------------------------
def SetParser(run):
   """Configure the command-line parser, add options name to store to the list,
   set actions informations.
   run : ASTER_RUN object which manages the execution
   """
   acts_descr = {
      'getop'        : {
         'method' : GetOP,
         'syntax' : '[options] commande[.capy]',
         'help'   : 'return the main subroutine of a Code_Aster command'
      },
      'show'         : {
         'method' : Show,
         'syntax' : '[options] obj1 [obj2...]',
         'help'   : 'show a source file : fortran, C, python, capy, cata, histor or test'
      },
      'get'          : {
         'method' : Get,
         'syntax' : '[options] obj1 [obj2...]',
         'help'   : 'copy a source file in current directory'
      },
      'update'       : {
         'method' : Update,
         'syntax' : '[options] fich1.tar.gz [fich2.tar.gz...]',
         'help'   : 'perform one or several updates of a development version'
      },
      'make'         : {
         'method' : MakeAster,
         'syntax' : '[--version_dev=VERS] [target]',
         'help'   : 'build a Code_Aster version (executable, libraries, catalogues). `target` may be all or clean'
      },
      'auto_update'  : {
         'method' : AutoUpdate,
         'syntax' : '[options]',
         'help'   : 'download available updates from a server and apply them to the current development version'
      },
      'astk_update'  : {
         'method' : AstkUpdate,
         'syntax' : '[options]',
         'help'   : 'download available updates from a server and update astk/as_run itself'
      },
      'getversion'   : {
         'method' : GetVersion,
         'syntax' : '[options]',
         'help'   : 'return current release number of the development version'
      },
      'diag'         : {
         'method' : MakeDiag,
         'syntax' : '[--astest_dir=DIR1,[DIR2]] [--test_list=LIST] [--only_nook]',
         'help'   : 'build the diagnosis of Code_Aster testcases (from DIR or default astest directory)'
      },
      'ctags'         : {
         'method' : GenCtags,
         'syntax' : '[--version_dev=VERS]',
         'help'   : 'build ctags file'
      },
   }
   opts_descr = {
      'local' : {
         'args'   : ('-l', '--local'),
         'kwargs' : {
            'action'  : 'store_true',
            'default' : run['default_local'],
            'dest'    : 'local',
            'help'    : 'files will not been searched on a server but '+ \
                        'on the local machine'
         }
      },
      'nolocal' : {
         'args'   : ('--nolocal', ),
         'kwargs' : {
            'action'  : 'store_true',
            'default' : False,
            'dest'    : 'nolocal',
            'help'    : 'force remote files search (reverse of --local)'
         }
      },
      'vers' : {
         'args'   : ('--vers', ),
         'kwargs' : {
            'type'    : 'string',
            'default' : run['default_vers'],
            'action'  : 'store',
            'dest'    : 'aster_vers',
            'metavar' : 'VERS',
            'help'    : 'Code_Aster version to used (for get, show, getop)'
         }
      },
      'version_dev' : {
         'args'   : ('--version_dev', ),
         'kwargs' : {
            'type'    : 'string',
            'default' : run['version_dev'],
            'action'  : 'store',
            'dest'    : 'version_dev',
            'metavar' : 'VERS',
            'help'    : 'development version of Code_Aster (for make, ' \
                        'update, auto_update, getversion)'
         }
      },
      'all' : {
         'args'   : ('-a', '--all'),
         'kwargs' : {
            'default' : False,
            'action'  : 'store_true',
            'dest'    : 'all_test',
            'help'    : 'get all the files of the test'
         }
      },
      'astest_dir' : {
         'args'   : ('--astest_dir', ),
         'kwargs' : {
            'type'    : 'string',
            'action'  : 'store',
            'dest'    : 'astest_dir',
            'metavar' : 'DIR',
            'help'    : 'testcases directory to watch'
         }
      },
      'only_nook' : {
         'args'   : ('--only_nook', ),
         'kwargs' : {
            'default' : False,
            'action'  : 'store_true',
            'dest'    : 'only_nook',
            'help'    : 'report only errors (but time spent by passed testcases is included)'
         }
      },
      'test_list' : {
         'args'   : ('--test_list', ),
         'kwargs' : {
            'action'  : 'store',
            'dest'    : 'test_list',
            'metavar' : 'LIST',
            'help'    : 'limits the report to these testcases'
         }
      },
      'report_to' : {
         'args'   : ('--report_to', ),
         'kwargs' : {
            'type'    : 'string',
            'default' : '',
            'action'  : 'store',
            'dest'    : 'report_to',
            'metavar' : 'EMAIL',
            'help'    : 'email address to send the report of a execution (only used for --auto_update)'
         }
      },
      'config' : {
         'args'   : ('--config', ),
         'kwargs' : {
            'action'  : 'store',
            'dest'    : 'config',
            'metavar' : 'FILE',
            'help'    : 'use another "config.txt" file (only used for make, updaten auto_update).'
         }
      },
   }
   title = 'Options for maintenance operations'
   run.SetActions(
         actions_descr=acts_descr,
         actions_order=['getop', 'getversion', 'show', 'get', 'make', 'update', \
               'auto_update', 'astk_update', 'diag', 'ctags'],
         group_options=True, group_title=title, actions_group_title=False,
         options_descr=opts_descr
   )

#-------------------------------------------------------------------------------
def GetOP(run, *list_capy):
   """Return the main subroutine of a command.
   """
   REPREF = os.path.join(run.aster_root, run['aster_vers'])
   if len(list_capy) < 1:
      run.parser.error(
            _("'--%s' requires one or more arguments") % run.current_action)
   ficconf = os.path.join(REPREF, 'config.txt')
   conf = ASTER_CONFIG(ficconf, run)
   for capy in list_capy:
      capy = capy.replace('.capy', '')
      nf = os.path.join(REPREF, conf['SRCCAPY'][0], 'commande', capy+'.capy')
      if not os.path.exists(nf):
         run.Mess(_('file not found : %s') % nf, _('<A>_ALARM'))
         run.Mess(_('%s search capy files on local machine') \
               % run.current_action)
         break
      f = open(nf)
      nop = re.search('op *= *([-_0-9a-zA-Z]+)', f.read())
      if nop:
         name = nop.group(1)
         if re.search('[0-9]+', name):
            num = '%04d' % int(name)
            name = 'op'+num+'.f'
         else:
            name = name+'.py'
         Show(run, name)
      else:
         run.Mess(_('op statement not found in %s') % capy, _('<A>_ALARM'))

#-------------------------------------------------------------------------------
def Show(run, *args):
   """Show a source file : fortran, c, python, capy, cata, histor or test.
   """
   kwargs = { 'get' : False }
   Get(run, *args, **kwargs)

#-------------------------------------------------------------------------------
def Get(run, *args, **kwargs):
   """Copy a source file (as Show) in current directory
   """
   if not kwargs.has_key('get'):
      get = True
   else:
      get = kwargs['get']

   if len(args) < 1:
      run.parser.error(
            _("'--%s' requires one or more arguments") % run.current_action)

   copy = True
   if not run['local']:
      user, mach = get_devel_param(run)
      indv = run['aster_vers'][3:4]
      REPREF = os.path.join('/aster', 'v'+indv, run['aster_vers'])
      # répertoires (en distant on suppose une organisation standard)
      RC    = os.path.join(REPREF, 'bibc')
      RFORT = os.path.join(REPREF, 'bibfor')
      RF90  = os.path.join(REPREF, 'bibf90')
      RPY   = os.path.join(REPREF, 'bibpyt')
      RHIST = os.path.join(REPREF, 'histor')
      RCAPY = os.path.join(REPREF, 'catapy')
      RCATA = os.path.join(REPREF, 'catalo')
      RTEST = os.path.join(REPREF, 'astest')
   else:
      user = getpass.getuser()
      mach = os.uname()[1]
      REPREF = os.path.join(run.aster_root, run['aster_vers'])
      # répertoires
      ficconf = os.path.join(REPREF, 'config.txt')
      conf  = ASTER_CONFIG(ficconf, run)
      RC    = os.path.join(REPREF, conf['SRCC'][0])
      RFORT = os.path.join(REPREF, conf['SRCFOR'][0])
      RF90  = os.path.join(REPREF, conf['SRCF90'][0])
      RPY   = os.path.join(REPREF, conf['SRCPY'][0])
      RHIST = os.path.join(REPREF, conf['SRCHIST'][0])
      RCAPY = os.path.join(REPREF, conf['SRCCAPY'][0])
      RCATA = os.path.join(REPREF, conf['SRCCATA'][0])
      RTEST = os.path.join(REPREF, conf['SRCTEST'][0])
      # si 'local' et 'show', pas de copie
      if not get:
         copy = False

   # ----- répertoire temporaire
   if not get:
      rdest = run['cache_dir']
   else:
      rdest = os.getcwd()
   machdest = os.uname()[1]

   fmt = '-- %-44s [%+30s]'

   nberr = 0
   for obj in args:
      toedit = None
      # l'extension fournit le type sauf pour les histor
      ext = obj.split('.')[-1]
      baseobj = re.sub('.%s$' % ext, '', obj)
      if re.search('[0-9]+\.[0-9]+\.[0-9]+', obj):
         ext = 'hist'
      rep = ''
      srep = 0
      ct = 0
      if ext in ('c', 'h'):
         rep = RC
         srep = 1
      elif ext == 'f':
         rep = RFORT
         srep = 1
      elif ext == 'f90':    # but files are suffixed by '.f' !
         ext = 'f'
         obj = re.sub('.f90$', '.f', obj)
         rep = RF90
         srep = 1
      elif ext == 'py':
         rep = RPY
         srep = 1
      elif ext == 'hist':
         rep = RHIST
      elif ext == 'capy':
         rep = RCAPY
         srep = 1
      elif ext == 'cata':
         rep = RCATA
         srep = 1
      elif ext in ('comm', 'mail', 'mess', 'resu', 'para', 'code',
                   'datg', 'msup', 'msh', 'mgib') \
         or re.search('com[0-9]', ext) != None or ext.isdigit():
         rep = RTEST
         ct = 1
      
      if rep == '':
         print fmt % (obj, _('type unsupported'))
         nberr += 1
      else:
         print fmt % (obj, rep)
         jret = 0
         # le fichier existe ou on ne peut pas le vérifier
         if mach != machdest or srep == 1 or \
            (mach == machdest and os.path.isfile(os.path.join(rep, obj))):
         # 1. récupérer le(s) fichier(s)
            if run['all_test'] and ct == 1:
            # cas-test : all
               # est-il dans le cache ?
               if not run['force'] and \
                     os.path.isdir(os.path.join(rdest, baseobj)):
                  print '  |  '+_('from cache')
                  copy = True
               else:
                  src = os.path.join(rep, baseobj + '.*')
                  if copy:
                     if not os.path.isdir(os.path.join(rdest, baseobj)):
                        os.mkdir(os.path.join(rdest, baseobj))
                     if mach == machdest:
                        print '  |  '+_('copy all files of the test')
                     else:
                        print '  |  '+_('remote copy of the test files')
                        src = user+'@'+mach+':'+src
                     jret = run.Copy(os.path.join(rdest, baseobj), src,
                           niverr=_('<E>_COPY_ERROR'))
                  else:
                     toedit = glob.glob(src)
                     print '  |  '+_('just edit')
            else:
            # fichier individuel
               # est-il dans le cache ?
               if not run['force'] and \
                     os.path.isfile(os.path.join(rdest, obj)):
                  print '  |  '+_('from cache')
                  copy = True
               else:
                  if srep == 1:
                     src = os.path.join(rep, '*', obj)
                  else:
                     src = os.path.join(rep, obj)
                  if copy:
                     if mach == machdest:
                        print '  |  '+_('copy')
                     else:
                        print '  |  '+_('remote copy')
                        src = user+'@'+mach+':'+src
                     jret = run.Copy(rdest, src,
                           niverr=_('<E>_COPY_ERROR'))
                  else:
                     toedit = glob.glob(src)
                     print '  |  '+_('just edit')

         # 2. test / edition
            if jret != 0:
               print '  |  '+_('error occurs during copying')
               nberr += 1
            else:
               # si show
               if not get:
                  if not copy:
                     pass
                  elif run['all_test'] and ct == 1:
                  # cas-test : all
                     print '  |  '+_('edit files')
                     toedit = [os.path.join(rdest, baseobj, '*')]
                  else:
                  # fichier individuel
                     print '  |  '+_('edit file')
                     toedit = [os.path.join(rdest, obj)]
                  run.ASSERT(type(toedit) in EnumTypes)
                  if len(toedit) == 0:
                     run.Mess('', _('<E>_FILE_NOT_FOUND'))
                  if type(toedit) in EnumTypes:
                     toedit = ' '.join(toedit)
                  cmd = '%s %s' % (run['editor'], toedit)
                  jret, output = run.Shell(cmd, bg=True)
         else:
         # fichier inexistant
            print ' |   '+_('file not found')
            nberr += 1

   if nberr > 0:
      if nberr > 1:
         s = 's'
      else:
         s = ''
      run.Mess(_('%d error%s detected') % (nberr, s), _('<A>_ALARM'))

#-------------------------------------------------------------------------------
def GetVersion(run, *args, **kwargs):
   """Return release number of current development version :
      result[0:3] : version number
      result[3]   : date of release
      result[4]   : True if "exploitation", False if it's a "development" version
   """
   if len(args) > 0:
      run.parser.error(_("'--%s' requires no argument") % run.current_action)
   # ----- default keywords
   silent = False
   REPREF = os.path.join(run.aster_root, run['version_dev'])
   if kwargs.has_key('silent'):
      silent = kwargs['silent']
   if kwargs.has_key('REPREF'):
      REPREF = kwargs['REPREF']
   
   result = get_version(run, REPREF)
   iret = 0
   if len(result) != 5:
      iret = 4
   elif not silent:
      if result[4]:
         typv = 'exploitation'
      else:
         typv = 'development'
      run.Mess(_('Version %s %s - %s') % (typv, '.'.join(result[:3]), result[3]))
   return iret, result

#-------------------------------------------------------------------------------
def get_version(run, REPREF):
   """Retourne la version du répertoire REPREF.
   """
   ficconf = os.path.join(REPREF, 'config.txt')
   # if file don't exist, suppose default value
   if os.path.exists(ficconf):
      conf = ASTER_CONFIG(ficconf, run)
      bibpyt = conf['SRCPY'][0]
   else:
      bibpyt = 'bibpyt'
   # ----- properties.py
   f = os.path.join(REPREF, bibpyt, 'Accas', 'properties.py')
   if not os.path.isfile(f):
      run.Mess(_('file not found : %s') % f, _('<F>_FILE_NOT_FOUND'))
   mydict = {}
   execfile(f, mydict)
   result = mydict['version'].split('.')
   result.append(mydict['date'])
   result.append(mydict.get('exploit', False))
   return result

#-------------------------------------------------------------------------------
def MakeAster(run, *args):
   """Interface between Makefile and as_run.
   Build Code_Aster from sources, clean object files.
   """
   REPREF = os.path.join(run.aster_root, run['version_dev'])
   if not run.get('config'):
      fconf = os.path.join(REPREF, 'config.txt')
   else:
      fconf = os.path.abspath(run['config'])
   conf = ASTER_CONFIG(fconf, run)

   # check arguments
   target = 'all'
   if len(args) > 1:
      run.parser.error(_("'--%s' takes at most %d argument (%d given)") \
                         % (run.current_action, 1, len(args)))
   elif len(args) > 0:
      target = args[0]

   # set per version environment
   for f in conf.get_with_absolute_path('ENV_SH'):
      run.AddToEnv(f)

   run.print_timer = True

   # 0. ----- working directory during build
   reptrav = os.path.join(run['tmp_user'], 'build.'+run['num_job'])
   run.to_delete.append(reptrav)
   run.MkDir(reptrav)

   # 1. ----- Go !
   if   target == 'all':
      _build_aster(run, conf, False, REPREF, reptrav)
   
   elif target == 'clean':
      l_clean = ['BIN_NODBG', 'BIN_DBG', 'BINCMDE', 'BINELE', 'BINPICKLED',
                 'BINLIB_NODBG', 'BINLIB_DBG', 'BINSHLIB_NODBG', 'BINSHLIB_DBG',
                 'BINLIBF_NODBG', 'BINLIBF_DBG', 'BINSHLIBF_NODBG', 'BINSHLIBF_DBG',
                 'BINOBJ_NODBG', 'BINOBJF_NODBG', 'BINOBJ_DBG', 'BINOBJF_DBG']
      for key in l_clean:
         ficrep = conf[key][0]
         if ficrep != '':
            run.Delete(ficrep, verbose=True)
   
   else:
      run.Mess(_('unknown target : %s') % target, _('<F>_INVALID_ARGUMENT'))

#-------------------------------------------------------------------------------
def _build_aster(run, conf, iupdate, REPREF, reptrav, lardv=None):
   """Build or update Aster depending of iupdate value.
      iupdate : False for 'make', True for 'update'
      lardv   : list of object files to remove from lib_aster
   """
   if lardv == None:
      lardv = []
   build = ASTER_BUILD(run, conf)
   DbgPara = {
      'debug'     : { 'exe'    : conf['BIN_DBG'][0],
                      'suff'   : conf['BINOBJ_DBG'][0],
                      'suffer' : conf['BINOBJF_DBG'][0],
                      'libast' : conf['BINLIB_DBG'][0],
                      'libfer' : conf['BINLIBF_DBG'][0]},
      'nodebug'   : { 'exe'    : conf['BIN_NODBG'][0],
                      'suff'   : conf['BINOBJ_NODBG'][0],
                      'suffer' : conf['BINOBJF_NODBG'][0],
                      'libast' : conf['BINLIB_NODBG'][0],
                      'libfer' : conf['BINLIBF_NODBG'][0]},
   }
   tobuild = conf['MAKE'][0].split()

   # 1a. ----- perform update twice : debug and nodebug modes
   # first, remove object files from libaster
   if iupdate:
      for mode in tobuild:
         libaster = os.path.join(REPREF, DbgPara[mode]['libast'])
         # for backward compatibility
         if run.IsDir(libaster):
            libaster = os.path.join(libaster, 'lib_aster.lib')
         run.Mess(_('Start build in %s mode') % mode, 'TITLE')

         run.timer.Start('Deletion of old files')
         # 'ar -dv' not provided in config.txt
         cmd = [conf['LIB'][0].split()[0], '-dv', libaster]
         cmd.extend(lardv)
         run.VerbStart(_('remove object files from %s') % \
               os.path.basename(libaster), verbose=True)
         if lardv:
            if run['verbose']:
               print
            kret, out = run.Shell(' '.join(cmd))
            # ----- avoid to duplicate output
            if not run['verbose']:
               run.VerbEnd(kret, output=out, verbose=True)
            if kret != 0:
               run.Mess(_('error during deleting objects from archive'),
                     _('<A>_ARCHIVE_ERROR'))
         else:
            run.VerbIgnore(verbose=True)
         run.timer.Stop('Deletion of old files')
         run.CheckOK()
   
   # 1b. ----- perform update twice : debug and nodebug modes
   for mode in tobuild:
      libaster = os.path.join(REPREF, DbgPara[mode]['libast'])
      libferm  = os.path.join(REPREF, DbgPara[mode]['libfer'])
      # for backward compatibility
      if run.IsDir(libaster):
         libaster = os.path.join(libaster, 'lib_aster.lib')
      if run.IsDir(libferm):
         libferm = os.path.join(libaster, 'ferm.lib')
      run.Mess(_('Start build in %s mode') % mode, 'TITLE')      

      # 1.2. compile updated files
      run.timer.Start('Compilation in %s mode' % mode)
      kret = build.CompilAster(REPREF, dbg=mode)
      run.timer.Stop('Compilation in %s mode' % mode)
      run.CheckOK()

      # 1.3. archive '.o'
      # 1.3.1. obj or dbg
      run.timer.Start('Add object files to library')
      kret = build.Archive(repobj=os.path.join(REPREF, DbgPara[mode]['suff']),
            lib=libaster)
      run.CheckOK()

      # 1.3.2. obj_f or dbg_f
      kret = build.Archive(
            repobj=os.path.join(REPREF, DbgPara[mode]['suffer']),
            lib=libferm)
      run.CheckOK()
      run.timer.Stop('Add object files to library')

      # 1.4. update executable
      exec_name = os.path.join(REPREF, DbgPara[mode]['exe'])
      # 1.4.1. build
      run.timer.Start('Build executables')
      kret = build.Link(exec_name, [], libaster, libferm, reptrav)
      run.timer.Stop('Build executables')
      run.CheckOK()
      
      # 1.4.2. testing binary
      # - executable is callable (no shared library unreferenced)
      # - required Python modules are present
      run.timer.Start('Test executables')
      cmd = exec_name + ' -c '
      cpyt=['import traceback']
      cmd_import="""
print '%(cmd)s :',
try:
   %(cmd)s
   print 'OK'
except:
   print 'FAILED'
"""
      cpyt.append(cmd_import % {'cmd' : 'import os'})
      cpyt.append(cmd_import % {'cmd' : 'import Numeric'})
      #cpyt.append(cmd_import % {'cmd' : 'from Numeric import LinearAlgebra'})
      run.VerbStart(_('testing executable %s...') % exec_name, verbose=True)
      if run['verbose']:
         print
      kret, out = run.Shell(cmd + '"' + ''.join(cpyt)+ '"')
      # ----- avoid to duplicate output
      expr = re.compile('(^.*FAILED.*$)', re.MULTILINE)
      l_err = expr.findall(out)
      kret = max(kret, len(l_err))
      if not run['verbose']:
         run.VerbEnd(kret, output=out, verbose=True)
      if kret != 0:
         run.Mess(_('test of executables failed'), _('<F>_BUILD_ERROR'))
      run.timer.Stop('Test executables')

   # 2. ----- compile commands
   run.timer.Start('Compilation of commands catalogue')
   kret = build.CompilCapy(REPREF, reptrav, i18n=True)
   run.timer.Stop('Compilation of commands catalogue')
   run.CheckOK()

   # 3. ----- compile elements
   kargs = {
      'exe'    : os.path.join(REPREF, DbgPara['nodebug']['exe']),
      'cmde'   : os.path.join(REPREF, conf['BINCMDE'][0]),
   }
   run.timer.Start('Make pickled of elements')
   kret = build.MakePickled(REPREF, reptrav)
   run.timer.Stop('Make pickled of elements')
   run.CheckOK()

   run.timer.Start('Compilation of elements')
   kret = build.CompilEle(REPREF, reptrav, **kargs)
   run.timer.Stop('Compilation of elements')
   run.CheckOK()

   # 9. ----- end
   run.Mess(_('Code_Aster has been successfully built'), 'OK')

#-------------------------------------------------------------------------------
def Update(run, *args):
   """Extract "aster-maj" archives and make the update
   """
   REPREF = os.path.join(run.aster_root, run['version_dev'])
   if not run.get('config'):
      fconf = os.path.join(REPREF, 'config.txt')
   else:
      fconf = os.path.abspath(run['config'])
   conf   = ASTER_CONFIG(fconf, run)
   build  = ASTER_BUILD(run, conf)

   # check arguments
   if len(args) < 1:
      run.parser.error(
            _("'--%s' requires one or more arguments") % run.current_action)

   # set per version environment
   for f in conf.get_with_absolute_path('ENV_SH'):
      run.AddToEnv(f)

   run.print_timer = True
   larch = []
   for arch in args:
      larch.append(os.path.abspath(arch))

   # 0. ----- working directory during update(s)
   reptrav       = os.path.join(run['tmp_user'], 'update.'+run['num_job'])
   reptrav_built = os.path.join(run['tmp_user'], 'update_built.'+run['num_job'])
   run.to_delete.append(reptrav)
   run.to_delete.append(reptrav_built)
   run.MkDir(reptrav)
   run.MkDir(reptrav_built)
   prefix = 'maj'
   repmaj = os.path.join(reptrav, prefix)
   run.to_delete.append(repmaj)
   run.MkDir(repmaj)

   run.timer.Start('Extraction of archives')
   # 1. ----- for each archive
   os.chdir(reptrav)
   funig = os.path.join(repmaj, 'unigest')
   lupd  = []
   lunig = []
   for arch in larch:
      arch = os.path.abspath(arch)
      if tarfile.is_tarfile(arch):
         linfo = []

         # 1.1. ----- extraction
         run.VerbStart(_('extract archive %s') % arch, verbose=True)
         jret = 0
         try:
            tar = tarfile.open(arch, 'r')
            tar.errorlevel = 2
            for ti in tar:
               tar.extract(ti)
               # if name is in a previous unigest don't delete it
               name = re.sub('^'+prefix+'/', '', ti.name)
               if os.path.basename(name) != '' and name != 'unigest':
                  lupd.append(name)
               if name in lunig:
                  linfo.append(name)
                  lunig.remove(name)
         except tarfile.ExtractError:
            jret = 4
         run.VerbEnd(jret, verbose=True)
         tar.close()
         if jret != 0:
            run.Mess(_('error during extracting archive %s') % arch,
                  _('<F>_TAR_ERROR'))

         # 1.2. ----- give info about files not to delete
         if linfo:
            run.Mess(_('These files should have previously been deleted ' \
                  'and now they are updated :'))
            print ', '.join(linfo)

         # 1.3. ----- get directives from unigest
         linfo = []
         if os.path.exists(funig):
            for k, val in build.GetUnigest(funig).items():
               if k != 'filename':
                  lunig.extend(val)
            # delete twins
            lunig = dict([(it, 0) for it in lunig]).keys()
            os.remove(funig)
            # delete files from maj which have been modified since last update
            for f in lunig:
               if f in lupd:
                  lupd.remove(f)
               f = os.path.join(repmaj, f)
               if os.path.exists(f):
                  linfo.append(f)

         # 1.4. ----- give info about files to delete before update
         if linfo:
            run.Mess(_('These files have been modified by a previous update ' \
                  'but now they are deleted :'))
            for f in linfo:
               run.Delete(f, verbose=True)

      else:
         run.Mess(_('invalid tar (compressed) archive or ' \
               'file not found : %s') % arch, _('<F>_FILE_NOT_FOUND'))
   run.timer.Stop('Extraction of archives')

   # 2. ----- start update
   # current version
   i, vvv = GetVersion(run, silent=True, REPREF=REPREF)
   if i != 0:
      run.Mess(_('error occurs during getting release number'), _('<F>_ERROR'))
   old_vers = '.'.join(vvv[:3])
   # target version
   i, vnext = GetVersion(run, silent=True, REPREF=repmaj)
   if i != 0:
      run.Mess(_('error occurs during getting release number'), _('<F>_ERROR'))
   new_vers = '.'.join(vnext[:3])
   run.Mess(_('Update version %s to %s') % (old_vers, new_vers), 'TITLE')
   
   # 3. ----- copy files
   run.timer.Start('Copy of files')
   run.Mess(_('Copy updated files'), 'TITLE')
   os.chdir(repmaj)
   ddirs = {
      conf['SRCFOR'][0]  : ['bibfor',   ('*.f', '*.h')],
      conf['SRCF90'][0]  : ['bibf90',   ('*.f', '*.h')],
      conf['SRCC'][0]    : ['bibc',     ('*.c', '*.h')],
      conf['SRCPY'][0]   : ['bibpyt',   '*.py'],
      conf['SRCCAPY'][0] : ['catapy',   '*.capy'],
      conf['SRCCATA'][0] : ['catalo',   '*.cata'],
      conf['SRCFERM'][0] : ['fermetur', '*.f'],
      conf['SRCTEST'][0] : ['test',     '*'],
      conf['SRCHIST'][0] : ['histor',   '*'],
   }
   for repdest, param in ddirs.items():
      if repdest == '':
         run.VerbStart(_('updating %s') % (param[0]), verbose=True)
         run.VerbIgnore(verbose=True)
         continue
      rep  = param[0]
      l_suff = param[1]
      if not type(l_suff) in EnumTypes:
         l_suff = [l_suff,]
      for suff in l_suff:
         if os.path.isdir(rep):
            sub = True
            ldirs = glob.glob(os.path.join(rep, '*/'))
            # if there is no subdirectories (ex : test, fermetur...)
            if len(ldirs) == 0:
               ldirs = [rep, ]
               sub = False
            for entry in ldirs:
               src = os.path.join(entry, suff)
               if not sub:
                  dest = os.path.join(REPREF, repdest)
               else:
                  subdir = os.path.basename(os.path.normpath(entry))
                  dest = os.path.join(REPREF, repdest, subdir)
               run.VerbStart(_('updating %s (%s)') % (dest, suff), verbose=True)
               run.MkDir(dest, verbose=False)
               if len(glob.glob(src)) == 0:
                  run.VerbIgnore(verbose=True)
               else:
                  jret = run.Copy(dest, src)
                  run.VerbEnd(jret, verbose=True)
         elif os.path.isfile(rep):
            dest = os.path.join(REPREF, repdest)
            run.VerbStart(_('updating %s') % dest, verbose=True)
            run.MkDir(dest, verbose=False)
            if rep == 'histor':
               dest = os.path.join(REPREF, repdest, new_vers)
            jret = run.Copy(dest, rep)
            run.VerbEnd(jret, verbose=True)
            
   run.timer.Stop('Copy of files')

   # 4. ----- delete source files
   run.timer.Start('Deletion of old files')
   run.Mess(_('Apply unigest directives'), 'TITLE')
   lardv = []
   for f in lunig:
      run.Delete(os.path.join(REPREF, f), verbose=True)
      if re.search('^'+conf['SRCC'][0]+'/' + \
                  '|^'+conf['SRCFOR'][0]+'/' + \
                  '|^'+conf['SRCF90'][0]+'/', f):
         lardv.append(os.path.basename(re.sub('\.[cf]+$', '.o', f)))
   run.timer.Stop('Deletion of old files')

   # 5. ----- update executable, libs and catalogues
   _build_aster(run, conf, True, REPREF, reptrav_built, lardv=lardv)

#-------------------------------------------------------------------------------
def AutoUpdate(run, *args):
   """Try to download available updates and apply them.
   """
   if len(args) > 0:
      run.parser.error(_("'--%s' requires no argument") % run.current_action)

   run.print_timer = True
   if not run['local']:
      if run['update_protocol'] == 'HTTP':
         REPSRC = run['http_server_user']+'@'+run['http_server_ip']+ \
               ':'+run['http_rep_maj']
      else:
         user, mach = get_devel_param(run)
         REPSRC = '%s@%s:%s' % (user, mach, run['devel_rep_maj'])
   else:
      REPSRC = run['local_rep_maj']

   # version stabilisée courante (ex : 7.2)
   run.timer.Start('Download update archives')
   i, v = GetVersion(run, silent=True, \
         REPREF=os.path.join(run.aster_root, run['version_dev']))
   if i != 0:
      run.Mess(_('error occurs during getting release number'), _('<F>_ERROR'))
   sta   = '%s.%s'    % (v[0], v[1])
   vlast = '%s.%s.%s' % (v[0], v[1], v[2])
   run.Mess(_('Searching updates of %s following release %s...')\
         % (sta, vlast))

   reptrav = os.path.join(run['tmp_user'], 'auto_update.'+run['num_job'])
   run.to_delete.append(reptrav)
   run.MkDir(reptrav)
   os.chdir(reptrav)
   lmaj = []
   # ----- search for sub-releases of current version
   for i in [0, 1]:
      for j in range((1-i)*int(v[2])+1, 50):
         vch = v[0]+'.'+str(int(v[1])+i)+'.'+str(j)
         # nom du fichier archive
         src  = os.path.join(REPSRC, 'aster-maj-src-'+vch+'-1.tar.gz')
         dest = os.path.join(reptrav, os.path.basename(src))
         # use niverr='INFO' to ignore failure
         jret = run.Copy(dest, src, verbose=True, niverr='INFO', \
               protocol=run['update_protocol'])
         if jret != 0:
            if i == 1:
               run.Mess(_('Last release found : %s') % vlast + os.linesep)
            else:
               nextsta = v[0]+'.'+str(int(v[1])+1)
               run.Mess(_('Testing if STA %s was released...') % nextsta)
            break
         else:
            vlast = vch
            lmaj.append(dest)
   run.timer.Stop('Download update archives')

   # ----- start a multiple update
   if lmaj:
      mail = []
      run.ExitOnFatalError = False
      try:
         Update(run, *lmaj)
      except RunAsterError, msg:
         res = 'FAILED'
         run.ExitOnFatalError = True
         import traceback
         mail.extend([_('Exception raised by AutoUpdate:'),
            '-'*60, 'Traceback:'])
         mail.append(''.join(traceback.format_tb(sys.exc_traceback)))
         mail.append('-'*60)
         mail.append(_('Exit code : %s') % msg)
         run.Mess(os.linesep.join(mail), _('<F>_UPDATE_FAILED'))
      else:
         res = 'COMPLETED'
         run.ExitOnFatalError = True
         mail.append(_('Updates ended successfully on %s') % run.GetHostName())
         run.Mess(os.linesep.join(mail), 'OK')
      if run['report_to'] != '':
         run.SendMail(dest=run['report_to'],
            text = os.linesep.join(mail),
            subject = 'AutoUpdate %s %s on %s from %s to %s' % \
                  (run['version_dev'], res, run.GetHostName(), '.'.join(v[:3]),
                   vlast))
   else:
      run.Mess(_('no update found'), 'OK')
   # --- build tags file
   GenCtags(run, force=(len(lmaj) > 0))

#-------------------------------------------------------------------------------
def MakeDiag(run, *args):
   """Build the diagnosis of execution of testcases
   """
   REPREF = os.path.join(run.aster_root, run['aster_vers'])
   fconf = os.path.join(REPREF, 'config.txt')
   conf   = ASTER_CONFIG(fconf, run)
   build  = ASTER_BUILD(run, conf)

   # check arguments
   if len(args) > 0:
      run.parser.error(_("'--%s' requires no argument") % run.current_action)

   run.print_timer = False

   # 0. ----- initializations
   summary = _("""
--- Directory of testcases files    : %s
    Number of test-cases            : %s""")
   txt_vers = _("""    Version                         : %s
""")
   fmt_resu = '%-12s %-18s %10s %10s %10s'
   fmt_tot  = '-'*12 + ' ' + '-'*18 + ' ' + '-'*10 + ' ' + '-'*10 + ' ' + '-'*10
   # 1. ----- Go !
   l_dirs = run.get('astest_dir', os.path.join(REPREF, conf['SRCTEST'][0]))
   l_dirs = [os.path.join(REPREF, os.path.abspath(p.strip())) \
         for p in l_dirs.split(',')]
   # testcases list
   if not run.get('test_list'):
      list_tests = Set()
      for p in l_dirs:
         list_tests.update([os.path.splitext(f)[0] for f in glob.glob1(p, '*.comm')])
      list_tests = list(list_tests)
      list_tests.sort()
   else:
      flist = run['test_list']
      iret, list_tests = get_list(flist)
      if iret != 0:
         run.Mess(_('error during reading file : %s') % flist, _('<F>_ERROR'))
   list_tests.sort()
   nbtest = len(list_tests)
   
   print summary % (', '.join(l_dirs), nbtest)
   lresu  = []
   nbnook = 0
   noresu = 0
   lastv = ''
   i = 0
   for test in list_tests:
      i += 1
      # search the last resu file
      fresu = ''
      ferre = ''
      fmess = ''
      v_i = ''
      for p in l_dirs:
         vtmp = get_vers(os.path.join(p, test + '.resu'), run['verbose'])
         if vtmp > v_i:
            v_i = vtmp
            fresu = os.path.join(p, test + '.resu')
            ferre = os.path.join(p, test + '.erre')
            fmess = os.path.join(p, test + '.mess')
            v_erre = get_vers(ferre, run['verbose'])
            if v_erre != '' and v_erre != v_i:
               ferre = ''
      if lastv == '' or v_i > lastv:
         lastv = v_i
      diag, tcpu, tsys, ttot = build.GetDiag(err=ferre, resu=fresu, mess=fmess, \
                                             cas_test=True)
      lresu.append([os.path.basename(test), diag, tcpu, tsys, ttot, repr_vers(v_i)])
      # count nook
      if run.GetGrav(diag) >= run.GetGrav('NOOK'):
         nbnook += 1
      # count testcase without .resu
      if v_i == '':
         noresu += 1

   t = [0., 0., 0.]
   nbnook = 0
   vlast = repr_vers(lastv)
   print txt_vers % vlast
   for lin in lresu:
      test = lin[0]
      diag = lin[1]
      t[0] += float(lin[2])
      t[1] += float(lin[3])
      t[2] += float(lin[4])
      vers = lin[5]
      # count nook
      if run.GetGrav(diag) >= run.GetGrav('NOOK'):
         nbnook += 1
      if run.GetGrav(diag) >= run.GetGrav('NOOK') \
            or not run['only_nook'] or vers != vlast:
         print fmt_resu % tuple(lin[:-1]),
         if vers != vlast:
            print '%8s' % vers
         else:
            print
   print fmt_tot
   print fmt_resu % (_('%4d tests') % i, _('%d errors') % nbnook, t[0], t[1], t[2])
   print '%4d tests without .resu file' % noresu

#-------------------------------------------------------------------------------
def GenCtags(run, force=False):
   """Generate the ctags file.
   """
   run.Mess(_('Generation of ctags database'), 'TITLE')
   REPREF = os.path.join(run.aster_root, run['version_dev'])
   
   ctags = run.Which('ctags')
   ftags = 'tags'
   if ctags is None:
      run.Mess(_("'ctags' is not in your PATH. ctags not generated."))
      return
   
   style = run.get('ctags_style', '')
   if style == 'exuberant':
      # command line for Exuberant Ctags
      cmd = "find %(rep)s -name '%(suff)s' | %(ctags)s -a -o %(ftags)s -L -"
   elif style == 'emacs':
      # command line for GNU Emacs ctags
      cmd = "find %(rep)s -name '%(suff)s' | %(ctags)s -a -o %(ftags)s -"
   else:
      run.Mess(_("'Exuberant Ctags' and 'GNU Emacs ctags' styles are supported. "\
                 "You must define 'ctags_style' in the config file to generate ctags."))
      return
   
   dcmd = { 'ctags' : ctags, 'ftags' : ftags }
   if not os.access(REPREF, os.W_OK):
      run.Mess(_('no write access to %s') % REPREF, _('<F>_ERROR'))
   
   prev = os.getcwd()
   os.chdir(REPREF)
   
   force = run['force'] or force or (not os.path.isfile(ftags))
   if force:
      run.Delete(ftags, verbose=True)
      for rep, suff in (('bibc', '*.c'),
                        ('bibfor', '*.f'),
                        ('bibf90', '*.f'),
                        ('bibpyt', '*.py'),):
         dcmd['rep']  = rep
         dcmd['suff'] = suff
         run.VerbStart(_('build ctags file using source from %s') % rep, verbose=True)
         if run['verbose']:
            print
         iret, out = run.Shell(cmd % dcmd)
         if not run['verbose']:
            run.VerbEnd(iret, output=out, verbose=True)
         if iret != 0:
            run.Mess(_('error during generating ctags'),_('<E>_CTAGS_ERROR'))
      run.CheckOK()
   else:
      run.Mess(_("ctags file already exists, use --force to overwrite it."))
   
   os.chdir(prev)

#-------------------------------------------------------------------------------
def get_list(ftest):
   """Build list of the tests, ignore all after # or %  (characters comment).
   """
   iret = 0
   ltest = []
   try:
      line = open(ftest, 'r').read().splitlines()
   except (OSError, IOError):
      iret = 4
      return iret, ltest
   
   lfich = [t for t in [re.sub(' *[#%]+.*', '', l).strip() for l in line] if t != '']
   # delete twins (by keeping ordering)
   ltest = []
   for t in lfich:
      if not t in ltest:
         ltest.append(t)
   
   return iret, ltest

#-------------------------------------------------------------------------------
def get_vers(fich, debug=False):
   """Extract the version which produced 'resu'/'erre' file.
   """
   vers = ''
   exp = re.compile('CODE_ASTER.*VERSION.* ([0-9\.]+) ', re.MULTILINE)
   if os.path.isfile(fich):
      l_v = exp.findall(open(fich, 'r').read())
      if len(l_v) > 0:
         vers = l_v[0]
      if debug and vers == '':
         print _('unable to extract version from %s') % fich
   return vers

#-------------------------------------------------------------------------------
def repr_vers(vers, fmt='%d'):
   """Returns a representation of `vers` like '8.3.11' or '08.03.11' using `fmt`.
   """
   if vers == '':
      return vers
   l_v = vers.split('.')
   l_v = l_v + ['0'] * (3 - len(l_v))
   l_out = []
   for i in l_v:
      if not i.isdigit():
         l_out.append(i)
      else:
         l_out.append(fmt % int(i))
   return '.'.join(l_out)

