1# -*- coding: utf-8 -*-
2
3# ==============================================================================
4# COPYRIGHT (C) 1991 - 2015  EDF R&D                  WWW.CODE-ASTER.ORG
5# THIS PROGRAM IS FREE SOFTWARE; YOU CAN REDISTRIBUTE IT AND/OR MODIFY
6# IT UNDER THE TERMS OF THE GNU GENERAL PUBLIC LICENSE AS PUBLISHED BY
7# THE FREE SOFTWARE FOUNDATION; EITHER VERSION 2 OF THE LICENSE, OR
8# (AT YOUR OPTION) ANY LATER VERSION.
9#
10# THIS PROGRAM IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT
11# WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF
12# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. SEE THE GNU
13# GENERAL PUBLIC LICENSE FOR MORE DETAILS.
14#
15# YOU SHOULD HAVE RECEIVED A COPY OF THE GNU GENERAL PUBLIC LICENSE
16# ALONG WITH THIS PROGRAM; IF NOT, WRITE TO EDF R&D CODE_ASTER,
17#    1 AVENUE DU GENERAL DE GAULLE, 92141 CLAMART CEDEX, FRANCE.
18# ==============================================================================
19
20"""AsterBuild class.
21"""
22
23
24import os
25import os.path as osp
26import sys
27import re
28import copy
29import time
30from glob import glob
31from zipfile import ZipFile
32from warnings import warn
33
34from asrun.core         import magic
35from asrun.common.i18n  import _
36from asrun.mystring     import ufmt, to_unicode
37from asrun.thread       import Task, TaskAbort, Dispatcher
38from asrun.runner       import Runner
39from asrun.common_func  import get_tmpname
40from asrun.common.utils import re_search, YES_VALUES, unique_basename_remove
41from asrun.common.utils import make_writable
42from asrun.common.sysutils import is_newer, is_newer_mtime
43from asrun.system       import split_path
44
45from asrun.backward_compatibility import bwc_deprecate_class
46
47fmt_catapy = 'cata%s.py%s'
48fcapy  = fmt_catapy % ('', '')
49fcapy_ = fmt_catapy % ('', '*')
50
51repcatapy = ('entete', 'commun', 'commande')
52repcatalo = ('compelem', 'typelem', 'options')
53
54
55class CompilTask(Task):
56    """Compilation task.
57    """
58    # declare attrs
59    run = fmsg = dep_h = None
60    force = verbose = debug = False
61    nbnook = nskip = 0
62    cmd = ""
63    def _mess(self, msg, cod='', store=False):
64        """pass"""
65
66    def execute(self, f, **kwargs):
67        """Function called for each item of the stack.
68        Warning : 'execute' should not modify attributes.
69        """
70        if self.nbnook >= 3:
71            raise TaskAbort(_('Maximum number of errors reached : %d') % self.nbnook)
72        jret  = 2
73        skip  = not self.force
74        out   = ''
75        lenmax = 50
76        tail = f
77        if len(f) > lenmax + 3:
78            tail = '...' + f[len(f) - lenmax:]
79        obj = osp.splitext(osp.basename(f))[0]+'.o'
80        # ----- skip file if .o more recent than source file
81        too_old = True
82        if osp.exists(obj):
83            too_old = False
84            dep_src = [f,]
85            for inc in get_include(f):
86                deps = self.dep_h.get(inc, [])
87                dep_src.extend(deps)
88            for ref in dep_src:
89                too_old = not is_newer(obj, ref)
90                if too_old:
91                    break
92        if too_old:
93            skip = False
94        if not skip:
95            jret, out = self.run.Shell(self.cmd + ' ' + f)
96        return jret, skip, out, f, tail
97
98    def result(self, jret, skip, out, f, tail, **kwargs):
99        """Function called after each task to treat results of execute.
100        Arguments are 'execute' results + keywords args.
101        'result' is called thread-safely, so can store results in attributes.
102        """
103        if skip:
104            self.nskip += 1
105            if self.verbose or self.debug:
106                self.run.VerbStart(ufmt(_('compiling %s'), tail), verbose=True)
107                self.run.VerbIgnore(verbose=True)
108        else:
109            self.run.VerbStart(ufmt(_('compiling %s'), tail), verbose=True)
110            if self.verbose:
111                print()
112            # ----- avoid to duplicate output
113            if not self.verbose:
114                if jret == 0 and out.strip() != '' \
115                and self.run.get('print_compiler_output', "") in YES_VALUES:
116                    print()
117                    print(out)
118                    self.run.VerbStart(ufmt(_('compiling %s'), tail), verbose=True)
119                self.run.VerbEnd(jret, output=out, verbose=True)
120            if jret != 0:
121                print(out, file=self.fmsg)
122                self._mess(ufmt(_('error during compiling %s (see %s)'), f,
123                                osp.abspath(self.fmsg.name)),
124                        '<E>_COMPIL_ERROR', store=True)
125                self.nbnook += 1
126            self.codret = max(self.codret, jret)
127
128
129class AsterBuild_Classic:
130    """This class provides functions to build a version of Code_Aster.
131    """
132    attr = ()
133
134    def __init__(self, run, conf):
135        """run   : AsterRun object
136        conf : AsterConfig object
137        """
138        # initialisations
139        self.verbose = False
140        self.debug = False
141        # ----- reference to AsterRun object which manages the execution
142        self.run = run
143        if run != None and run.__class__.__name__ == 'AsterRun':
144            self.verbose = run['verbose']
145            self.debug = run['debug']
146        # ----- Check if 'conf' is a valid AsterConfig object
147        self.conf = conf
148        if conf == None or conf.__class__.__name__ != 'AsterConfig':
149            self._mess(_('no configuration object provided !'), '<F>_ERROR')
150        else:
151            # ----- check that AsterConfig fields are correct (not nul !)
152            pass
153        # ----- Initialize Runner object
154        self.runner = Runner(self.conf.get_defines())
155        self.runner.set_cpuinfo(1, 1)
156
157    def _mess(self, msg, cod='', store=False):
158        """Just print a message
159        """
160        if hasattr(self.run, 'Mess'):
161            self.run.Mess(msg, cod, store)
162        else:
163            print('%-18s %s' % (cod, msg))
164
165    def support(self, feature):
166        """Tell if the feature is supported"""
167        return feature in self.attr
168
169    def addFeature(self, feature):
170        """Add a supported feature"""
171        self.attr = tuple( set(self.attr).union([feature]) )
172
173    def Compil(self, typ, rep, repobj, dbg, rep_trav='',
174              error_if_empty=False, numthread=1, dep_h=None):
175        """Compile 'rep/*.suffix' files and put '.o' files in 'repobj'
176            dbg : debug or nodebug
177            (rep can also be a file)
178        rep can be remote.
179        """
180        prev = os.getcwd()
181        self.run.DBG('type : %s' % typ, 'rep : %s' % rep, 'repobj : %s' % repobj)
182
183        # ----- how many threads ?
184        if numthread == 'auto':
185            numthread = self.run.GetCpuInfo('numthread')
186
187        # ----- modified or added includes
188        if self.run.IsRemote(rep):
189            opt_incl = '-I.'
190        elif self.run.IsDir(rep):
191            opt_incl = '-I%s' % rep
192        else:
193            opt_incl = ''
194
195        # ----- get command line from conf object
196        defines = []
197        for defs in self.conf['DEFS']:
198            defines.extend(re.split('[ ,]', defs))
199        add_defines = ' '.join(['-D%s' % define for define in defines if define != ''])
200        self.run.DBG('Add defines : %s' % add_defines)
201        if typ == 'C':
202            l_mask = ['*.c']
203            if self.conf['CC'][0] == '':
204                self._mess(_("C compiler not defined in 'config.txt' (CC)"), \
205                        '<F>_CMD_NOT_FOUND')
206            cmd = self.conf['CC'][:]
207            if dbg == 'debug':
208                cmd.extend(self.conf['OPTC_D'])
209            else:
210                cmd.extend(self.conf['OPTC_O'])
211            cmd.append(add_defines)
212            cmd.append(opt_incl)
213            cmd.extend(self.conf['INCL'])
214        elif typ == 'F':
215            l_mask = ['*.f']
216            if self.conf['F77'][0] == '':
217                self._mess(_("Fortran 77 compiler not defined in 'config.txt' (F77)"), \
218                        '<F>_CMD_NOT_FOUND')
219            cmd = self.conf['F77'][:]
220            if dbg == 'debug':
221                cmd.extend(self.conf['OPTF_D'])
222            else:
223                cmd.extend(self.conf['OPTF_O'])
224            cmd.append(add_defines)
225            cmd.append(opt_incl)
226            cmd.extend(self.conf['INCLF'])
227        elif typ == 'F90':
228            l_mask = ['*.f', '*.F']
229            if self.conf['F90'][0] == '':
230                self._mess(_("Fortran 90 compiler not defined in 'config.txt' (F90)"), \
231                        '<F>_CMD_NOT_FOUND')
232            cmd = self.conf['F90'][:]
233            if dbg == 'debug':
234                cmd.extend(self.conf['OPTF90_D'])
235            else:
236                cmd.extend(self.conf['OPTF90_O'])
237            cmd.append(add_defines)
238            cmd.append(opt_incl)
239            cmd.extend(self.conf['INCLF90'])
240        else:
241            self._mess(ufmt(_('unknown type : %s'), typ), '<F>_PROGRAM_ERROR')
242        cmd = ' '.join(cmd)
243
244        # ----- if force is True, don't use ctime to skip a file
245        force = self.run['force']
246
247        # ----- copy source files if remote
248        if self.run.IsRemote(rep):
249            if rep_trav == '':
250                self._mess(_('rep_trav must be defined'), '<F>_PROGRAM_ERROR')
251            obj = osp.join(rep_trav, '__tmp__'+osp.basename(rep))
252            iret = self.run.MkDir(obj)
253            if self.run.IsDir(rep):
254                src = [osp.join(rep, mask) for mask in l_mask + ['*.h']]
255            else:
256                src = [rep,]
257            iret = self.run.Copy(obj, niverr='SILENT', *src)
258            rep = obj
259        else:
260            rep = self.run.PathOnly(rep)
261
262        # ----- check that directories exist
263        if not osp.isdir(repobj):
264            iret = self.run.MkDir(repobj, verbose=True)
265
266        # ----- work in repobj
267        os.chdir(repobj)
268
269        # ----- log file
270        msg = osp.basename(rep)+'.msg'
271        if osp.exists(msg):
272            os.remove(msg)
273        fmsg = open(msg, 'w')
274        fmsg.write(os.linesep + \
275                 'Messages de compilation' + os.linesep + \
276                 '=======================' + os.linesep)
277
278        # ----- list of source files
279        files = []
280        if self.run.IsDir(rep):
281            for suffix in l_mask:
282                files.extend(glob(osp.join(rep, suffix)))
283        elif osp.splitext(rep)[-1] != '.h':
284            files.append(rep)
285        if len(files) == 0:
286            iret = 0
287            niverr = ''
288            if error_if_empty:
289                iret = 2
290                niverr = '<A>_NO_SOURCE_FILE'
291            self._mess(ufmt(_('no source file in %s'), rep), niverr)
292            return iret, []
293
294        # ----- Compile all files in parallel using a Dispatcher object
295        task = CompilTask(cmd=cmd, run=self.run, force=force,       # IN
296                                verbose=self.verbose, debug=self.debug,   # IN
297                                _mess=self._mess, fmsg=fmsg,              # IN
298                                dep_h=dep_h,                              # IN
299                                codret=0, nskip=0, nbnook=0)              # OUT
300        compilation = Dispatcher(files, task, numthread)
301        self.run.DBG(compilation.report())
302
303        self._mess(ufmt(_('%4d files compiled from %s'), len(files), rep))
304        if task.nskip > 0:
305            self._mess(_('%4d files skipped (object more recent than source)') \
306                % task.nskip)
307
308        fmsg.close()
309        os.chdir(prev)
310        return task.codret, files
311
312    def CompilAster(self, REPREF, repdest='', dbg='nodebug', ignore_ferm=False,
313                   numthread='auto'):
314        """Compile all Aster source files, put '.o' files in 'repdest'/DIR
315        and DIR_f with DIR=obj or dbg depends on 'dbg'
316        (DIR for "real" source files and DIR_f for fermetur).
317        """
318        self._mess(_('Compilation of source files'), 'TITLE')
319        dest_o = {  'nodebug' : self.conf['BINOBJ_NODBG'][0],
320                        'debug'   : self.conf['BINOBJ_DBG'][0]    }
321        dest_f_o = {'nodebug' : self.conf['BINOBJF_NODBG'][0],
322                        'debug'   : self.conf['BINOBJF_DBG'][0]   }
323
324        # ----- define type and destination of '*.o' files for each directory
325        if repdest == '':
326            repdest = REPREF
327        dest = osp.join(repdest, dest_o[dbg])
328        dest_f = osp.join(repdest, dest_f_o[dbg])
329        para = {
330            self.conf['SRCC'][0]     : ['C', dest],
331            self.conf['SRCFOR'][0]   : ['F', dest],
332            self.conf['SRCF90'][0]   : ['F90', dest],
333        }
334        if not ignore_ferm:
335            para[self.conf['SRCFERM'][0]] = ['F', dest_f]
336        lmods = [d for d in list(para.keys()) if d != '']
337        lfull = [osp.join(REPREF, rep) for rep in lmods]
338
339        # ----- check that directories exist
340        for obj in lfull:
341            if not osp.exists(obj):
342                self._mess(ufmt(_('directory does not exist : %s'), obj),
343                        '<E>_FILE_NOT_FOUND')
344        self.run.CheckOK()
345
346        # ----- nobuild list
347        nobuild = []
348        for d in self.conf['NOBUILD']:
349            nobuild.extend([osp.join(REPREF, d) for d in d.split()])
350        # bibf90 dirs automatically added if there is no F90 compiler
351        if self.conf['F90'][0] == '' and self.conf['SRCF90'][0] != '':
352            nobuild.extend([d for d in \
353                glob(osp.join(REPREF, self.conf['SRCF90'][0], '*'))
354                if not d in nobuild])
355            self._mess(ufmt(_("There is no Fortran 90 compiler in 'config.txt'. " \
356                            "All source files from '%s' were ignored."),
357                            self.conf['SRCF90'][0]),
358                      '<A>_IGNORE_F90', store=True)
359        if len(nobuild)>0:
360            self._mess(_('These directories will not be built :'))
361            for d in nobuild:
362                print(' '*10+'>>> %s' % d)
363
364        # ----- compilation
365        dep_h = build_include_depend(*lfull)
366        iret = 0
367        for module in lmods:
368            if module != self.conf['SRCFERM'][0]:
369                # ----- glob subdirectories
370                lrep = glob(osp.join(REPREF, module, '*'))
371            else:
372                lrep = (osp.join(REPREF, module), )
373            for rep in lrep:
374                if osp.isdir(rep):
375                    tail = '...'+re.sub('^'+REPREF, '', rep)
376                    print()
377                    jret = 0
378                    if not rep in nobuild:
379                        self._mess(ufmt(_('Compilation from %s directory'), tail))
380                        jret, bid = self.Compil(para[module][0], rep, para[module][1],
381                                                dbg, numthread=numthread, dep_h=dep_h)
382                    else:
383                        self.run.VerbStart(ufmt(_('Directory %s is in NOBUILD list'), tail),
384                                           verbose=True)
385                        self.run.VerbIgnore(verbose=True)
386                    iret = max(iret, jret)
387
388        return iret
389
390    def Archive(self, repobj, lib, force=False):
391        """Archive all .o files from 'repobj' into 'lib'
392        All args must be local.
393        """
394        self._mess(_('Archive object files'), 'TITLE')
395        prev = os.getcwd()
396        # ----- get command line from conf object
397        cmd0 = [self.conf['LIB'][0]+' '+lib]
398
399        # ----- check that directories exist
400        if not osp.isdir(repobj):
401            self._mess(ufmt(_('directory does not exist : %s'), repobj), '<E>_FILE_NOT_FOUND')
402        rep = osp.dirname(lib)
403        if not osp.isdir(rep):
404            iret = self.run.MkDir(rep, verbose=True)
405        self.run.CheckOK()
406
407        # ----- if force is True, don't use ctime to skip a file
408        force = force or self.run['force']
409        if osp.exists(lib):
410            mtlib = os.stat(lib).st_mtime
411        else:
412            mtlib = 0
413            force = True
414
415        # ----- work in repobj
416        os.chdir(repobj)
417
418        # ----- list of source files
419        files = glob('*.o')
420        ntot = len(files)
421        if ntot == 0:
422            self._mess(ufmt(_('no object file in %s'), rep))
423            return 0
424
425        # ----- sleep 1 s to be sure than last .o will be older than lib !
426        time.sleep(1)
427
428        # ----- use MaxCmdLen to limit command length
429        iret = 0
430        nskip = 0
431        while len(files) > 0:
432            cmd = copy.copy(cmd0)
433            clen = len(cmd[0])+1
434            nadd = 0
435            while len(files) > 0 and clen+len(files[0]) < self.run.system.MaxCmdLen-15:
436                if not force and is_newer_mtime(mtlib, files[0]):
437                    nskip += 1
438                    bid = files.pop(0)
439                else:
440                    clen = clen+len(files[0])+1
441                    cmd.append(files.pop(0))
442                    nadd += 1
443            self.run.VerbStart(_('%4d / %4d objects added to archive...') % \
444                    (nadd, ntot), verbose=True)
445            if nadd > 0:
446                if self.verbose:
447                    print()
448                jret, out = self.run.Shell(' '.join(cmd))
449                # ----- avoid to duplicate output
450                if not self.verbose:
451                    self.run.VerbEnd(jret, output=out, verbose=True)
452                if jret != 0:
453                    self._mess(_('error during archiving'), '<E>_ARCHIVE_ERROR')
454                iret = max(iret, jret)
455            else:
456                self.run.VerbIgnore(verbose=True)
457
458        self._mess(_('%4d files archived') % ntot)
459        if nskip > 0:
460            self._mess(_('%4d files skipped (objects older than library)') \
461                % nskip)
462        os.chdir(prev)
463        return iret
464
465    def Link(self, exe, lobj, libaster, libferm, reptrav):
466        """Link a number of object and archive files into an executable.
467            exe      : name of the executable to build
468            lobj     : list of object files
469            libaster : Code_Aster main library
470            libferm  : Code_Aster fermetur library
471            reptrav  : working directory
472        If "python.o" is not in 'lobj' it will be extracted from 'libaster'.
473        Other libs are given by AsterConfig object.
474        None argument must not be remote !
475        """
476        self._mess(_('Build Code_Aster executable'), 'TITLE')
477        prev = os.getcwd()
478        # ----- check that directories exist
479        for obj in lobj+[libaster, libferm]:
480            if not osp.exists(obj):
481                self._mess(ufmt(_('file not found : %s'), obj), '<E>_FILE_NOT_FOUND')
482        rep = osp.dirname(exe)
483        if not osp.isdir(rep):
484            iret = self.run.MkDir(rep, verbose=True)
485        self.run.CheckOK()
486
487        # ----- if force is True, don't use ctime to skip a file
488        force = self.run['force']
489        if not osp.exists(exe):
490            force = True
491        else:
492            for f in lobj + [libaster, libferm]:
493                if not is_newer(exe, f):
494                    force = True
495                    self.run.DBG('%s more recent than %s' % (f, exe))
496                    break
497
498        # ----- work in reptrav
499        os.chdir(reptrav)
500
501        # ----- python.o in lobj ?
502        mainobj = self.conf['BINOBJ_MAIN'][0]
503        if mainobj not in [osp.basename(o) for o in lobj] and force:
504            # get 'ar' command without arguments, if it's not 'ar' &$#@!
505            cmd = [self.conf['LIB'][0].split()[0]]
506            cmd.extend(['-xv', libaster, mainobj])
507            cmd = ' '.join(cmd)
508            self.run.VerbStart(ufmt(_('extracting %s from %s...'), repr(mainobj),
509                                    osp.basename(libaster)), verbose=True)
510            if self.verbose:
511                print()
512            iret, out = self.run.Shell(cmd)
513            # ----- avoid to duplicate output
514            if not self.verbose:
515                self.run.VerbEnd(iret, output=out, verbose=True)
516            if iret != 0:
517                self._mess(ufmt(_('error during extracting %s'), repr(mainobj)),
518                        '<F>_LIBRARY_ERROR')
519            lobj.insert(0, mainobj)
520        else:
521            lobj = [o for o in lobj if osp.basename(o) == mainobj] +\
522                [o for o in lobj if osp.basename(o) != mainobj]
523
524        # ----- get command line from conf object and args
525        cmd = copy.copy(self.conf['LINK'])
526        cmd.extend(self.conf['OPTL'])
527        cmd.append('-o')
528        cmd.append(exe)
529        cmd.extend(lobj)
530        cmd.append(libaster)
531        cmd.extend(self.conf['BIBL'])
532        cmd.append(libferm)
533        cmd = ' '.join(cmd)
534
535        # ----- run ld or skip this stage
536        iret = 0
537        tail = osp.join('...', osp.basename(rep), osp.basename(exe))
538        self.run.VerbStart(ufmt(_('creating %s...'), tail), verbose=True)
539        if force:
540            if self.verbose:
541                print()
542            iret, out = self.run.Shell(cmd)
543            self.run.DBG(out, all=True)
544            # ----- avoid to duplicate output
545            if not self.verbose:
546                self.run.VerbEnd(iret, output=out, verbose=True)
547            if iret != 0:
548                self._mess(_('error during linking'), '<E>_LINK_ERROR')
549        else:
550            self.run.VerbIgnore(verbose=True)
551            self._mess(_('executable more recent than objects and libs ' \
552                    'in arguments'))
553
554        os.chdir(prev)
555        return iret
556
557    def PrepEnv(self, REPREF, repdest, dbg='nodebug', lang='', **kargs):
558        """Prepare 'repdest' with Code_Aster environment from REPREF and
559        given arguments in kargs[k] (k in 'exe', 'cmde', 'ele', 'py', 'unigest').
560        Note : - REPREF/elements and kargs['ele'] don't exist when building
561            elements for the first time.
562             - only PYSUPPR entries from 'unigest' are considered here.
563        All elements of kargs can be remote.
564        """
565        reptrav = osp.join(repdest, self.conf['REPPY'][0], '__tmp__')
566        prev = os.getcwd()
567        # ----- check that files and directories in kargs exist
568        self._check_file_args(list(kargs.values()))
569        if not osp.isdir(repdest):
570            iret = self.run.MkDir(repdest, verbose=True)
571        self.run.CheckOK()
572
573        # ----- language
574        if lang in ('', 'fr'):
575            lang = 'fr'
576            suff = ''
577        else:
578            suff = '_' + lang
579        mask_catapy = fmt_catapy % (suff, '*')
580        mask_catapy_all = fmt_catapy % ('*', '*')
581
582        # ----- copy REPREF/aster?.exe or kargs['exe']
583        iret = 0
584        if 'exe' in kargs:
585            src = kargs['exe']
586        else:
587            if dbg == 'debug':
588                src = osp.join(REPREF, self.conf['BIN_DBG'][0])
589            else:
590                src = osp.join(REPREF, self.conf['BIN_NODBG'][0])
591        # do not make symlinks if we only prepare the environment because
592        # the copy of remote files will be deleted from proxy_dir before the
593        # manual execution.
594        use_symlink = not kargs.get('only_env', False) and self.run['symlink']
595        dest = repdest
596        if src != osp.join(repdest, osp.basename(src)):
597            if use_symlink and not self.run.IsRemote(src):
598                src = self.run.PathOnly(src)
599                lien = osp.join(repdest, osp.basename(src))
600                iret = self.run.Symlink(src, lien, verbose=True)
601            else:
602                iret = self.run.Copy(dest, src, verbose=True)
603        else:
604            self.run.VerbStart(ufmt(_('copying %s...'), src), verbose=True)
605            self.run.VerbIgnore(verbose=True)
606
607        # ----- copy REPREF/bibpyt
608        self.run.MkDir(osp.join(repdest, self.conf['REPPY'][0], 'Cata'))
609        iret = 0
610        src  = osp.join(REPREF, self.conf['SRCPY'][0], '*')
611        dest = osp.join(repdest, self.conf['REPPY'][0])
612        iret = self.run.Copy(dest, src)
613
614        # if 'py' is in kargs
615        if 'py' in kargs:
616            self._mess(_("copy user's python source files"))
617            # ----- python source given by user
618            lobj = kargs['py']
619            if not type(lobj) in (list, tuple):
620                lobj = [lobj, ]
621            for occ in lobj:
622                obj = occ
623                if self.run.IsRemote(occ):
624                    obj = reptrav
625                    iret = self.run.MkDir(obj)
626                    iret = self.run.Copy(obj, occ, verbose=True)
627                files = self.run.FindPattern(obj, '*.py', maxdepth=10)
628
629                for f in files:
630                    mod, rep = self.GetCModif('py', f)
631                    reppydest = osp.join(repdest, self.conf['REPPY'][0], rep)
632                    if not self.run.Exists(reppydest):
633                        self.run.MkDir(reppydest)
634                        self._mess(ufmt(_("unknown package '%s' in %s"),
635                            rep, osp.join(REPREF, self.conf['SRCPY'][0])), '<A>_ALARM')
636                    dest = osp.join(reppydest, mod+'.py')
637                    iret = self.run.Copy(dest, f, verbose=True)
638
639        # ----- apply PYSUPPR directives from unigest
640        if 'unigest' in kargs:
641            for f in kargs['unigest']['py']:
642                self.run.Delete(osp.join(repdest, \
643                        re.sub('^'+self.conf['SRCPY'][0], self.conf['REPPY'][0], f)),
644                        verbose=True)
645
646        # ----- copy REPREF/commande or kargs['cmde']
647        iret = 0
648        if 'cmde' in kargs:
649            src = kargs['cmde']
650        else:
651            src = osp.join(REPREF, self.conf['BINCMDE'][0])
652        dest = osp.join(repdest, self.conf['REPPY'][0], 'Cata')
653        if use_symlink and not self.run.IsRemote(src):
654            src = self.run.PathOnly(src)
655            # checks catalogue exists
656            l_capy = glob(osp.join(src, mask_catapy))
657            if len(l_capy) == 0 and lang != '':
658                self._mess(ufmt(_("no catalogue found for language '%s', " \
659                         "use standard (fr) one..."), lang))
660                mask_catapy = fmt_catapy % ('', '*')
661                l_capy = glob(osp.join(src, mask_catapy))
662            if len(l_capy) == 0:
663                self._mess(ufmt(_('no catalogue found : %s'), osp.join(src, mask_catapy)),
664                        '<F>_FILE_NOT_FOUND')
665            # add symlink
666            for f in l_capy:
667                root = osp.splitext(fcapy)[0]
668                ext  = osp.splitext(f)[-1]
669                lien = osp.join(dest, root + ext)
670                iret = self.run.Symlink(f, lien, verbose=True)
671        else:
672            iret = self.run.Copy(dest, osp.join(src, mask_catapy_all), verbose=True)
673            # checks catalogue exists
674            l_capy = glob(osp.join(dest, mask_catapy))
675            if len(l_capy) == 0 and lang != '':
676                self._mess(ufmt(_("no catalogue found for language '%s', " \
677                         "use standard (fr) one..."), lang))
678                mask_catapy = fmt_catapy % ('', '*')
679                l_capy = glob(osp.join(dest, mask_catapy))
680            for f in l_capy:
681                root = osp.splitext(fcapy)[0]
682                ext  = osp.splitext(f)[-1]
683                self.run.Rename(f, osp.join(dest, root + ext))
684
685        # ----- copy REPREF/elements or kargs['ele']
686        iret = 0
687        if 'ele' in kargs:
688            src = kargs['ele']
689        else:
690            src = osp.join(REPREF, self.conf['BINELE'][0])
691        if self.run.Exists(src) and src != osp.join(repdest, 'elem.1'):
692            # symlink not allowed for elem.1 (rw)
693            iret = self.run.Copy(osp.join(repdest, 'elem.1'), src, verbose=True)
694        else:
695            self.run.VerbStart(ufmt(_('copying %s...'), src), verbose=True)
696            self.run.VerbIgnore(verbose=True)
697
698        # ----- result directories
699        os.chdir(repdest)
700        self.run.MkDir('REPE_OUT')
701
702        os.chdir(prev)
703        self.run.Delete(reptrav)
704        return iret
705
706    def CompilCapy(self, REPREF, reptrav, exe='', cmde='', i18n=False, **kargs):
707        """Compile commands catalogue from REPREF/commande using 'exe'
708        and puts result in 'cmde'. kargs can contain :
709            capy    : list of user's capy files
710            unigest : GetUnigest dict with "CATSUPPR catapy module" entries
711        All args can be remote.
712        If `i18n` is True, translates cata.py.
713        """
714        self._mess(_('Compilation of commands catalogue'), 'TITLE')
715        prev = os.getcwd()
716        if exe == '':
717            exe = osp.join(REPREF, self.conf['BIN_NODBG'][0])
718            if not self.run.Exists(exe):
719                exe = osp.join(REPREF, self.conf['BIN_DBG'][0])
720        if cmde == '':
721            cmde = osp.join(REPREF, self.conf['BINCMDE'][0])
722        # ----- check that files and directories in kargs exist
723        self._check_file_args(list(kargs.values()))
724        if not osp.isdir(cmde):
725            iret = self.run.MkDir(cmde, verbose=True)
726        if not osp.isdir(reptrav):
727            iret = self.run.MkDir(reptrav, verbose=True)
728        self.run.CheckOK()
729
730        # ----- work in reptrav
731        reptrav = self.runner.set_rep_trav(reptrav)
732        os.chdir(reptrav)
733
734        # ----- if force is True, don't use ctime to skip a file
735        force = self.run['force']
736
737        # ----- copy capy from REPREF
738        bascpy = osp.basename(self.conf['SRCCAPY'][0])
739        if osp.exists(bascpy):
740            self.run.Delete(bascpy)
741        iret = self.run.Copy(bascpy,
742                osp.join(REPREF, self.conf['SRCCAPY'][0]), verbose=True)
743
744        # if 'capy' is in kargs
745        if 'capy' in kargs:
746            force = True
747            self._mess(_("copy user's catalogues"))
748            # ----- catapy given by user
749            lobj = kargs['capy']
750            if not type(lobj) in (list, tuple):
751                lobj = [lobj, ]
752            for occ in lobj:
753                obj = occ
754                if self.run.IsRemote(occ):
755                    obj = osp.join(bascpy,
756                                  '__tmp__'+osp.basename(occ))
757                    iret = self.run.MkDir(obj)
758                    if self.run.IsDir(occ):
759                        src = osp.join(occ, '*.capy')
760                    else:
761                        src = occ
762                    iret = self.run.Copy(obj, src)
763                    files = glob(osp.join(obj, '*.capy'))
764                else:
765                    occ = self.run.PathOnly(occ)
766                    if self.run.IsDir(occ):
767                        files = glob(osp.join(occ, '*.capy'))
768                    else:
769                        files = [occ, ]
770                for f in files:
771                    mod, rep = self.GetCModif('capy', f)
772                    # because filename is important, remove sha1 digest
773                    mod = unique_basename_remove(mod)
774                    dest = osp.join(bascpy, rep, mod + '.capy')
775                    iret = self.run.Copy(dest, f, verbose=True)
776                    if not rep in repcatapy:
777                        self._mess(ufmt(_('unknown module name : %s'), rep), '<A>_ALARM')
778
779        # ----- apply CATSUPPR directives from unigest
780        if 'unigest' in kargs:
781            force = True
782            for f in kargs['unigest']['capy']:
783                self.run.Delete(osp.join(reptrav, f), verbose=True)
784
785        # ----- build cata.py
786        if osp.exists(fcapy):
787            self.run.Delete(fcapy)
788
789        # ----- test if compilation can be skipped
790        if force or not osp.exists(osp.join(cmde, fcapy)):
791            force = True
792        else:
793            ltest = glob(osp.join(bascpy, '*', '*.capy'))
794            for f in ltest:
795                if not is_newer(osp.join(cmde, fcapy), f):
796                    force = True
797                    break
798
799        if not force:
800            self.run.VerbStart(_('compilation of commands'), verbose=True)
801            self.run.VerbIgnore(verbose=True)
802        else:
803            fo = open(fcapy, 'w')
804            for rep in repcatapy:
805                lcapy = glob(osp.join(bascpy, rep, '*.capy'))
806                lcapy.sort()
807                for capy in lcapy:
808                    fo2 = open(capy, 'r')
809                    fo.write(fo2.read())
810                    fo2.close()
811            fo.close()
812
813            # ----- compile cata.py
814            dtmp = kargs.copy()
815            dtmp['exe'] = exe
816            dtmp['cmde'] = reptrav
817            if 'py' in kargs:
818                dtmp['py'] = kargs['py']
819            self.PrepEnv(REPREF, reptrav, **dtmp)
820
821            self.run.VerbStart(_('compilation of commands'), verbose=True)
822            cmd_import = """
823import sys
824sys.path.insert(0, "%s")
825iret = 0
826try:
827    from Cata import cata
828    from Cata.cata import JdC
829    cr = JdC.report()
830    if not cr.estvide() :
831        iret = 4
832        print ">> Catalogue de commandes : DEBUT RAPPORT"
833        print cr
834        print ">> Catalogue de commandes : FIN RAPPORT"
835except:
836    iret = 4
837    import traceback
838    traceback.print_exc(file=sys.stdout)
839sys.exit(iret)
840""" % self.conf['REPPY'][0]
841            cmd_import_file = osp.join(reptrav, 'cmd_import.py')
842            with open(cmd_import_file, 'w') as f:
843                f.write(cmd_import)
844
845            if self.verbose:
846                print()
847            cmd = [osp.join('.', osp.basename(exe))]
848            cmd.append(cmd_import_file)
849            cmd_exec = self.runner.get_exec_command(' '.join(cmd),
850                env=self.conf.get_with_absolute_path('ENV_SH'))
851            iret, out = self.run.Shell(cmd_exec)
852            # ----- avoid to duplicate output
853            if not self.verbose:
854                self.run.VerbEnd(iret, output=out, verbose=True)
855            if iret != 0:
856                self._mess(ufmt(_('error during compiling %s'), fcapy), '<E>_CATAPY_ERROR')
857            else:
858                kret = self.run.Copy(cmde,
859                   osp.join(self.conf['REPPY'][0], 'Cata', fcapy_), verbose=True)
860                self._mess(_('Commands catalogue successfully compiled'), 'OK')
861
862
863            # --- translations...
864            if i18n:
865                pattern = '#:LANG:%s:LANG_END:'
866                old     = pattern % '.*'
867                exp     = re.compile(old, re.MULTILINE | re.DOTALL)
868                for lang in [l for l in self.conf['I18N'] if not l in ('', 'fr')]:
869                    self.run.VerbStart(ufmt(_('working for i18n (language %s)...'), lang),
870                                       verbose=True)
871                    print()
872                    new  = pattern % (os.linesep + "lang = '%s'" % lang + os.linesep + '#')
873                    cata_out  = fmt_catapy % ('_' + lang, '')
874                    cata_out_ = fmt_catapy % ('_' + lang, '*')
875                    tmpl_dict = get_tmpname(self.run, basename='template_dict_%s.py' % lang)
876                    # fake aster module
877                    with open(osp.join(self.conf['REPPY'][0], 'aster.py'), 'w') as f:
878                        f.write("""
879# fake aster module
880""")
881                    sys.path.insert(0, self.conf['REPPY'][0])
882                    iret = 0
883                    try:
884                        from Cata.i18n_cata import i18n_make_cata_fich
885                        txt_cata = i18n_make_cata_fich(fichier_dtrans=tmpl_dict, lang=lang)
886                        txt_orig = []
887                        fo = open(cata_out, 'w')
888                        # prendre l'entete
889                        for rep in repcatapy[:1]:
890                            for capy in glob(osp.join(bascpy, rep, '*.capy')):
891                                with open(capy, 'r') as f:
892                                    txt0 = f.read()
893                                txt2 = exp.sub(new, txt0)
894                                fo.write(txt2)
895                        # reprendre les imports
896                        for rep in repcatapy[1:]:
897                            for capy in glob(osp.join(bascpy, rep, '*.capy')):
898                                fo.write(get_txt_capy(capy))
899                                with open(capy, 'r') as f:
900                                    txt_orig.append(f.read())
901                        fo.write(os.linesep)
902                        fo.write(txt_cata)
903                        fo.write(os.linesep)
904                        fo.write(os.linesep.join(txt_orig))
905                        fo.close()
906                    except:
907                        iret = 4
908                        import traceback
909                        traceback.print_exc(file=magic.get_stdout())
910
911                    # ----- avoid to duplicate output
912                    if not self.verbose:
913                        self.run.VerbEnd(iret, output=out, verbose=True)
914
915                    if iret != 0:
916                        self._mess(ufmt(_("error during building %s"), cata_out), '<E>_I18N_ERROR')
917                    else:
918                        kret = self.run.Copy(cmde, cata_out_, verbose=True)
919                        self._mess(ufmt(_('Commands catalogue successfully translated ' \
920                                           '(language %s)'), lang), 'OK')
921
922        os.chdir(prev)
923        if not kargs.get('keep_reptrav'):
924            self.run.Delete(reptrav)
925        return iret
926
927    def CompilEle(self, REPREF, reptrav, ele='', **kargs):
928        """Compile elements catalogue as 'ele' from cata_ele.pickled.
929        kargs must contain arguments for a Code_Aster execution (i.e. for PrepEnv)
930        and optionaly :
931            cata    : a list of user's elements files
932            unigest : GetUnigest dict with "CATSUPPR catalo module" entries
933            pickled : use a different cata_ele.pickled (not in REPREF)
934            (make_surch_offi needs unigest filename)
935        All args can be remote.
936        """
937        self._mess(_('Compilation of elements catalogue'), 'TITLE')
938        required = ('exe', 'cmde')
939        if ele == '':
940            ele = osp.join(REPREF, self.conf['BINELE'][0])
941        pickled = kargs.get("pickled", osp.join(REPREF, self.conf['BINPICKLED'][0]))
942        prev = os.getcwd()
943        # ----- check for required arguments
944        for a in required:
945            if a not in kargs:
946                self._mess('(CompilEle) '+_('argument %s is required') % a,
947                        '<E>_PROGRAM_ERROR')
948
949        # ----- check that files and directories in kargs exist
950        self._check_file_args(list(kargs.values()))
951        if not osp.isdir(reptrav):
952            iret = self.run.MkDir(reptrav, verbose=True)
953        self.run.CheckOK()
954
955        # ----- work in repobj
956        reptrav = self.runner.set_rep_trav(reptrav)
957        os.chdir(reptrav)
958
959        # ----- if force is True, don't use ctime to skip a file
960        force = self.run['force']
961
962        # 1. ----- if 'cata' is in kargs
963        if 'cata' in kargs:
964            force = True
965            self._mess(_("copy user's catalogues"))
966            fo = open('surch.cata', 'w')
967            # ----- catapy given by user
968            lobj = kargs['cata']
969            if not type(lobj) in (list, tuple):
970                lobj = [kargs['cata'], ]
971            for occ in lobj:
972                obj = occ
973                if self.run.IsRemote(occ):
974                    obj = osp.join(reptrav, '__tmp__'+osp.basename(occ))
975                    iret = self.run.MkDir(obj)
976                    if self.run.IsDir(occ):
977                        src = osp.join(occ, '*.cata')
978                    else:
979                        src = occ
980                    iret = self.run.Copy(obj, src)
981                    files = glob(osp.join(obj, '*.cata'))
982                else:
983                    occ = self.run.PathOnly(occ)
984                    if self.run.IsDir(occ):
985                        files = glob(osp.join(occ, '*.cata'))
986                    else:
987                        files = [occ, ]
988                for f in files:
989                    fo2 = open(f, 'r')
990                    self.run.VerbStart(ufmt(_('adding %s'), f), verbose=True)
991                    fo.write(fo2.read())
992                    self.run.VerbEnd(iret=0, output='', verbose=True)
993                    fo2.close()
994            fo.close()
995
996        # 2. ----- compile elements with MAKE_SURCH_OFFI
997        self.PrepEnv(REPREF, reptrav, **kargs)
998        iret = 0
999        cmd = [osp.join('.', osp.basename(kargs['exe']))]
1000        cmd.append(osp.join(self.conf['REPPY'][0], self.conf['MAKE_SURCH_OFFI'][0]))
1001        cmd.extend(self.conf['REPPY'])
1002        cmd.append('MAKE_SURCH')
1003        cmd.append('surch.cata')
1004        funig = 'unigest_bidon'
1005        if 'unigest' in kargs:
1006            force = True
1007            funig = kargs['unigest']['filename']
1008            if self.run.IsRemote(funig):
1009                funig = 'unigest'
1010                kret = self.run.Copy(funig, kargs['unigest']['filename'])
1011            else:
1012                funig = self.run.PathOnly(funig)
1013        cmd.append(funig)
1014        cmd.append(pickled)
1015        cmd.append('fort.4')
1016        self.run.VerbStart(ufmt(_('pre-compilation of elements with %s'),
1017                osp.basename(self.conf['MAKE_SURCH_OFFI'][0])), verbose=True)
1018
1019        # 2.1. ----- test if compilation can be skipped
1020        if force or not osp.exists(ele):
1021            force = True
1022        else:
1023            ltest = [pickled, ]
1024            ltest.extend(glob(osp.join(self.conf['REPPY'][0], '*', '*.py')))
1025            for f in ltest:
1026                if not is_newer(ele, f):
1027                    force = True
1028                    self.run.DBG('%s more recent than %s' % (f, ele))
1029                    break
1030
1031        if not force:
1032            self.run.VerbIgnore(verbose=True)
1033        else:
1034            if self.verbose:
1035                print()
1036            cmd_exec = self.runner.get_exec_command(' '.join(cmd),
1037                env=self.conf.get_with_absolute_path('ENV_SH'))
1038            jret, out = self.run.Shell(cmd_exec)
1039            # ----- avoid to duplicate output
1040            if not self.verbose:
1041                self.run.VerbEnd(jret, output=out, verbose=True)
1042            if jret != 0:
1043                self._mess(_('error during pre-compilation of elements'),
1044                        '<F>_CATAELE_ERROR')
1045
1046        # 3. ----- build elem.1 with kargs['exe']
1047        cmd = [osp.join('.', osp.basename(kargs['exe']))]
1048        cmd.append(osp.join(self.conf['REPPY'][0], self.conf['ARGPYT'][0]))
1049        cmd.extend(self.conf['ARGEXE'])
1050        # <512 for 32 bits builds
1051        cmd.append('-commandes fort.1 -memjeveux 500 -tpmax 120')
1052        fo = open('fort.1', 'w')
1053        fo.write("""
1054# COMPILATION DU CATALOGUE D'ELEMENT
1055DEBUT ( CATALOGUE = _F( FICHIER = 'CATAELEM' , UNITE = 4 ))
1056MAJ_CATA ( ELEMENT = _F())
1057FIN()
1058""")
1059        fo.close()
1060        self.run.VerbStart(ufmt(_('compilation of elements with %s'), kargs['exe']), verbose=True)
1061
1062        jret = 0
1063        if not force:
1064            self.run.VerbIgnore(verbose=True)
1065        else:
1066            if self.verbose:
1067                print()
1068            cmd_exec = self.runner.get_exec_command(' '.join(cmd),
1069                env=self.conf.get_with_absolute_path('ENV_SH'))
1070            jret, out = self.run.Shell(cmd_exec)
1071            # ----- diagnostic of Code_Aster execution
1072            diag = self.getDiag()[0]
1073            if self.run.GetGrav(diag) < self.run.GetGrav('<S>') \
1074             and osp.exists('elem.1'):
1075                pass
1076            else:
1077                jret = 4
1078            # ----- avoid to duplicate output
1079            if not self.verbose:
1080                self.run.VerbEnd(jret, output=out, verbose=True)
1081            if jret == 0:
1082                self._mess(_('Elements catalogue successfully compiled'), diag)
1083            else:
1084                self.run.DoNotDelete(reptrav)
1085                print(_(' To re-run compilation manually :'))
1086                print(' cd %s' % reptrav)
1087                print(' ', cmd_exec)
1088                self._mess(_('error during compilation of elements'),
1089                        '<F>_CATAELE_ERROR')
1090
1091            # 4. ----- copy elements to 'ele'
1092            iret = self.run.Copy(ele, 'elem.1', verbose=True)
1093
1094        os.chdir(prev)
1095        self.run.Delete(reptrav)
1096        return iret
1097
1098    def MakePickled(self, REPREF, reptrav, repdest='', **kargs):
1099        """Make 'repdest'/cata_ele.pickled.
1100        kargs can contain :
1101            exe     : Code_Aster executable or Python interpreter (can be remote)
1102        """
1103        self._mess(_('Prepare cata_ele.pickled'), 'TITLE')
1104        if repdest == '':
1105            repdest = REPREF
1106        prev = os.getcwd()
1107        # ----- check if reptrav exists
1108        if not osp.isdir(reptrav):
1109            iret = self.run.MkDir(reptrav, verbose=True)
1110
1111        # ----- work in repobj
1112        reptrav = self.runner.set_rep_trav(reptrav)
1113        os.chdir(reptrav)
1114
1115        # ----- if force is True, don't use ctime to skip a file
1116        force = self.run['force']
1117
1118        # ----- use REPREF/aster?.exe or kargs['exe']
1119        iret = 0
1120        if 'exe' in kargs:
1121            exe = kargs['exe']
1122            if self.run.IsRemote(exe):
1123                kret = self.run.Copy(reptrav, exe)
1124                exe = osp.join(reptrav, osp.basename(exe))
1125            else:
1126                exe = self.run.PathOnly(exe)
1127        else:
1128            exe = osp.join(REPREF, self.conf['BIN_NODBG'][0])
1129            if not self.run.Exists(exe):
1130                exe = osp.join(REPREF, self.conf['BIN_DBG'][0])
1131
1132        # ----- call MAKE_CAPY_OFFI
1133        cmd = [exe, ]
1134        cmd.append(osp.join(REPREF, self.conf['SRCPY'][0], self.conf['MAKE_CAPY_OFFI'][0]))
1135        cmd.append(osp.join(REPREF, self.conf['SRCPY'][0]))
1136        cmd.append('TRAV_PICKLED')
1137        cmd.append(osp.join(REPREF, self.conf['SRCCATA'][0]))
1138        cata_pickled = osp.basename(self.conf['BINPICKLED'][0])
1139        cmd.append(cata_pickled)
1140
1141        # ----- test if compilation can be skipped
1142        if not osp.exists(osp.join(repdest, self.conf['BINPICKLED'][0])):
1143            force = True
1144        else:
1145            ltest = glob(osp.join(REPREF, self.conf['SRCCATA'][0], '*', '*.cata'))
1146            ltest.extend(glob(osp.join(REPREF, self.conf['SRCPY'][0], '*', '*.py')))
1147            for f in ltest:
1148                if not is_newer(osp.join(repdest, self.conf['BINPICKLED'][0]), f):
1149                    force = True
1150                    break
1151
1152        self.run.VerbStart(ufmt(_('build cata_ele.pickled with %s'),
1153                osp.basename(self.conf['MAKE_CAPY_OFFI'][0])), verbose=True)
1154
1155        if not force:
1156            self.run.VerbIgnore(verbose=True)
1157        else:
1158            if self.verbose:
1159                print()
1160            cmd_exec = self.runner.get_exec_command(' '.join(cmd),
1161                env=self.conf.get_with_absolute_path('ENV_SH'))
1162            jret, out = self.run.Shell(cmd_exec)
1163            # ----- avoid to duplicate output
1164            if not self.verbose:
1165                self.run.VerbEnd(jret, output=out, verbose=True)
1166            if jret == 0 and osp.exists(cata_pickled):
1167                self._mess(_('cata_ele.pickled successfully created'), 'OK')
1168            else:
1169                self._mess(_('error during making cata_ele.pickled'),
1170                        '<F>_PICKLED_ERROR')
1171
1172            # ----- copy cata_ele.pickled to 'repdest'
1173            iret = self.run.Copy(osp.join(repdest, self.conf['BINPICKLED'][0]),
1174                                        cata_pickled, verbose=True)
1175
1176        os.chdir(prev)
1177        self.run.Delete(reptrav)
1178        return iret
1179
1180    def GetUnigest(self, funig):
1181        """Build a dict of the file names by parsing unigest file 'funig' where
1182        the keys are 'f', 'f90', 'c', 'py', 'capy', 'cata', 'test' and 'fdepl'.
1183        funig can be remote.
1184        """
1185        dico = {}
1186        # ----- check that file exists
1187        if not self.run.Exists(funig):
1188            self._mess(ufmt(_('file not found : %s'), funig), '<A>_ALARM')
1189            return dico
1190        # ----- copy funig if it's a remote file
1191        if self.run.IsRemote(funig):
1192            name = '__tmp__.unigest'
1193            kret = self.run.Copy(name, funig)
1194        else:
1195            name = self.run.PathOnly(funig)
1196
1197        dico = unigest2dict(name, self.conf)
1198        return dico
1199
1200    def getDiag(self, err='fort.9', resu='fort.8', mess='fort.6',
1201                cas_test=False):
1202        """Return the diagnostic after a Code_Aster execution
1203        as a list : [diag, tcpu, tsys, ttotal, telapsed]
1204            err   : error file
1205            resu  : result file (None = ignored)
1206            mess  : messages file (None = ignored)
1207        """
1208        run = self.run
1209        diag, tcpu, tsys, ttot, telap = '<F>_ABNORMAL_ABORT', 0., 0., 0., 0.
1210
1211        # 1. ----- parse error file
1212        diag2, stop = _diag_erre(err)
1213        run.DBG('Fichier : %s' % err, '_diag_erre : %s' % diag2)
1214        diag = diag2 or diag
1215        if stop:
1216            run.ASSERT(diag != None)
1217        else:
1218            # if .erre doesn't exist, do the same analysis with .resu
1219            if diag2 is None:
1220                diag2, stop = _diag_erre(resu)
1221                run.DBG('Fichier : %s' % resu, '_diag_erre : %s' % diag2)
1222                diag = diag2 or diag
1223
1224            if stop:
1225                run.ASSERT(diag != None)
1226            else:
1227                # 2. ----- parse result and mess files
1228                diag, tcpu, tsys, ttot, telap = _diag_resu(resu, cas_test=cas_test)
1229                run.DBG('Fichier : %s' % resu, '_diag_resu : %s' % diag)
1230                diag2, stop2 = _diag_erre(mess, mess=True)
1231                run.DBG('Fichier : %s' % mess, '_diag_erre : %s' % diag2)
1232                if diag and diag2 and run.GetGrav(diag2) > run.GetGrav(diag):
1233                    diag = diag2
1234                # if resu doesn't exist, trying with .mess
1235                if diag is None or diag.startswith('<S>'):
1236                    diag = _diag_mess(mess) or 'NO_RESU_FILE'
1237                    run.DBG('Fichier : %s' % mess, '_diag_mess : %s' % diag)
1238
1239        return diag, tcpu, tsys, ttot, telap
1240
1241    def GetCModif(self, typ, fich):
1242        """Retourne, pour `typ` = 'capy'/'py', le nom du module et du package.
1243        `fich` est local.
1244        """
1245        para = {
1246            'capy'   : { 'comment' : '#&', 'nmin' : 3 },
1247            'py'     : { 'comment' : '#@', 'nmin' : 4 },
1248        }
1249        if typ not in list(para.keys()):
1250            self._mess(ufmt(_('invalid type : %s'), typ), '<F>_PROGRAM_ERROR')
1251
1252        with open(fich, 'r') as f:
1253            lig = f.readline().split()
1254        if len(lig) < para[typ]['nmin'] or \
1255        (len(lig) > 0 and lig[0] != para[typ]['comment']):
1256            self._mess(ufmt(_("invalid first line of file : %s\n" \
1257                      "      Should start with '%s' and have at least %s fields"),
1258                            osp.basename(fich), para[typ]['comment'], para[typ]['nmin']),
1259                       '<F>_INVALID_FILE')
1260
1261        if typ == 'py':
1262            mod = lig[2]
1263            rep = lig[3]
1264        elif typ == 'capy':
1265            mod = osp.splitext(osp.basename(fich))[0]
1266            rep = lig[2].lower()
1267
1268        return mod, rep
1269
1270
1271    def _check_file_args(self, args):
1272        """Check that files and directories in arguments exist."""
1273        if type(args) not in (list, tuple):
1274            args = [args, ]
1275        for occ in args:
1276            obj = occ
1277            if not type(occ) in (list, tuple):
1278                obj = [occ,]
1279            for rep in obj:
1280                if type(rep) not in (str, str):
1281                    continue
1282                if not self.run.Exists(rep):
1283                    self._mess(ufmt(_('path does not exist : %s'), rep),
1284                        '<E>_FILE_NOT_FOUND')
1285
1286class AsterBuild_CataZip(AsterBuild_Classic):
1287
1288    def CompilCapy(self, REPREF, reptrav, exe='', cmde='', i18n=False, **kargs):
1289        """Also build the zipped catalogue."""
1290        kargs['keep_reptrav'] = True
1291        iret = AsterBuild_Classic.CompilCapy(self, REPREF, reptrav, exe, cmde, i18n, **kargs)
1292        if iret != 0:
1293            return iret
1294        prev = os.getcwd()
1295        reptrav = self.runner.set_rep_trav(reptrav)
1296        os.chdir(osp.join(reptrav, self.conf['REPPY'][0]))
1297        name = osp.join(osp.dirname(cmde), self.conf['BINCMDE_ZIP'][0])
1298        zip = ZipFile(name, 'w')
1299        for fname in ('__init__.py', 'cata.py', 'ops.py'):
1300            zip.write(osp.join('Cata', fname))
1301        zip.close()
1302        os.chdir(prev)
1303        self.run.Delete(reptrav)
1304        return iret
1305
1306class AsterBuildInstalled(AsterBuild_Classic):
1307    """For a version installed by waf:
1308    - do not copy the executable,
1309    - do not copy bibpyt, Cata is already available in PYTHONPATH
1310    """
1311    attr = ('waf', )
1312
1313    def PrepEnv(self, REPREF, repdest, dbg='nodebug', lang='', **kargs):
1314        """Prepare 'repdest' with Code_Aster environment from REPREF and
1315        given arguments in kargs[k] (k in 'exe', 'cmde', 'ele', 'py', 'unigest').
1316        Note : - REPREF/elements and kargs['ele'] don't exist when building
1317            elements for the first time.
1318        All elements of kargs can be remote.
1319        """
1320        prev = os.getcwd()
1321        if not osp.isdir(repdest):
1322            iret = self.run.MkDir(repdest, verbose=True)
1323        self.run.CheckOK()
1324
1325        # ----- copy REPREF/elements or kargs['ele']
1326        iret = 0
1327        if 'ele' in kargs:
1328            src = kargs['ele']
1329        else:
1330            src = osp.join(REPREF, self.conf['BINELE'][0])
1331        if self.run.Exists(src) and src != osp.join(repdest, 'elem.1'):
1332            # symlink not allowed for elem.1 (rw)
1333            iret = self.run.Copy(osp.join(repdest, 'elem.1'), src, verbose=True)
1334            make_writable(osp.join(repdest, 'elem.1'))
1335        else:
1336            self.run.VerbStart(ufmt(_('copying %s...'), src), verbose=True)
1337            self.run.VerbIgnore(verbose=True)
1338
1339        # ----- result directories
1340        os.chdir(repdest)
1341        self.run.MkDir('REPE_OUT')
1342
1343        os.chdir(prev)
1344        return iret
1345
1346class AsterBuildInstalledNoCopy(AsterBuild_Classic):
1347    """For a version installed by waf and with enhancements to directly use the
1348    installation directory with no copy.
1349    The elements catalog is found using the ASTER_ELEMENTSDIR environment
1350    variable, Cata is already available in PYTHONPATH.
1351    """
1352    attr = ('waf', 'nocopy')
1353
1354    def PrepEnv(self, REPREF, repdest, dbg='nodebug', lang='', **kargs):
1355        """Prepare 'repdest' with Code_Aster environment from REPREF"""
1356        iret = 0
1357        prev = os.getcwd()
1358        if not osp.isdir(repdest):
1359            iret = self.run.MkDir(repdest, verbose=True)
1360        self.run.CheckOK()
1361
1362        # ----- result directories
1363        os.chdir(repdest)
1364        self.run.MkDir('REPE_OUT')
1365
1366        os.chdir(prev)
1367        return iret
1368
1369class AsterBuildInstalledNoResu(AsterBuildInstalledNoCopy):
1370    """For a version installed by waf, with enhancements to directly use the
1371    installation directory with no copy.
1372    This version does not create '.resu' file.
1373    """
1374    attr = ('waf', 'nocopy', 'noresu')
1375
1376    def getDiag(self, mess='fort.6', cas_test=False, **kwargs):
1377        """Return the diagnostic after a Code_Aster execution
1378        as a list : [diag, tcpu, tsys, ttotal, telapsed]
1379            mess  : messages file (None = ignored)
1380        """
1381        return AsterBuildInstalledNoCopy.getDiag(self, mess, mess, mess,
1382                                                 cas_test)
1383
1384class AsterBuildInstalledNoSuperv(AsterBuildInstalledNoCopy):
1385    """For a version installed by waf, with enhancements to directly use the
1386    installation directory with no copy, and without the Python supervisor.
1387    """
1388    attr = ('waf', 'nocopy', 'nosuperv')
1389
1390
1391class AsterBuildInstalledNoSupervNoResu(AsterBuildInstalledNoResu):
1392    """For a version installed by waf, with enhancements to directly use the
1393    installation directory with no copy, and without the Python supervisor.
1394    This version does not create '.resu' file.
1395    """
1396    attr = ('waf', 'nocopy', 'noresu', 'nosuperv')
1397
1398
1399def AsterBuild(run, conf):
1400    """Return the relevant AsterBuild_xxx implementation depending on
1401    parameters of the AsterConfig object."""
1402    klass = AsterBuild_Classic
1403    if 'nosuperv' in conf['BUILD_TYPE'][0]:
1404        if 'noresu' in conf['BUILD_TYPE'][0]:
1405            klass = AsterBuildInstalledNoSupervNoResu
1406        else:
1407            klass = AsterBuildInstalledNoSuperv
1408    elif 'noresu' in conf['BUILD_TYPE'][0]:
1409        klass = AsterBuildInstalledNoResu
1410    elif 'nocopy' in conf['BUILD_TYPE'][0]:
1411        klass = AsterBuildInstalledNoCopy
1412    elif 'waf' in conf['BUILD_TYPE'][0]:
1413        klass = AsterBuildInstalled
1414    elif conf['BINCMDE_ZIP'][0] != '':
1415        klass = AsterBuild_CataZip
1416    builder = klass(run, conf)
1417    # add extras features (better than check the version)
1418    for feature in ('use_numthreads', 'orbinitref', 'container'):
1419        if feature in conf['BUILD_TYPE'][0]:
1420            builder.addFeature(feature)
1421    run.DBG("version supports: {0}".format(builder.attr))
1422    return builder
1423
1424
1425def get_txt_capy(capy):
1426    """On utilise le fait qu'il n'y a rien après la définition de l'OPER, PROC ou MACRO.
1427    """
1428    with open(capy, 'r') as f:
1429        txt = f.read()
1430    expr = re.compile('^[A-Za-z_0-9]+ *= *[OPERPROCMACROFORM]+ *\(.*',
1431                            re.MULTILINE | re.DOTALL)
1432    sans_op = expr.sub('', txt)
1433    keep = ['']
1434    for line in sans_op.splitlines():
1435        if not re.search('^ *#', line):
1436            keep.append(line)
1437    keep.append('')
1438    return os.linesep.join(keep)
1439
1440_no_convergence_exceptions = (
1441    'NonConvergenceError', 'EchecComportementError', 'PilotageError',
1442)
1443_contact_exceptions = (
1444    'TraitementContactError', 'MatriceContactSinguliereError',
1445    'BoucleGeometrieError', 'BoucleFrottementError', 'BoucleContactError',
1446    'CollisionError', 'InterpenetrationError',
1447)
1448
1449def _diag_erre(err, mess=False):
1450    """Diagnostic à partir du fichier '.erre'.
1451    Si le fichier n'existe pas, on retourne (None, False).
1452    Pour un fichier de message, 'ARRET NORMAL DANS FIN' est absent, donc on ne
1453    peut pas déterminer 'ok'.
1454    """
1455    run = magic.run
1456    diag = None
1457    stop = False
1458    if err is None or not os.access(err, os.R_OK):
1459        pass
1460    else:
1461        with open(err, 'rb') as f:
1462            txt = to_unicode(f.read())
1463
1464        n_debut = re_search(txt, string="-- CODE_ASTER -- VERSION", result='number')
1465        n_fin   = re_search(txt,
1466                string="""<I> <FIN> ARRET NORMAL DANS "FIN" PAR APPEL A "JEFINI".""",
1467                result='number', flag=re.IGNORECASE)
1468
1469        alarm = re_search(txt, pattern='^ *. *%s', string='<A>')
1470        fatal = re_search(txt, pattern='^ *. *%s', string='<F>')
1471        exitcode = re_search(txt, pattern='^ *%s_EXIT_CODE = ([0-9]+)',
1472                             string='<I>', result='value')
1473        exitcode = exitcode and int(exitcode[0]) or 0
1474
1475        if fatal:
1476            expir = re_search(txt, pattern='^ *. *%s',
1477                    string='<F> <INIAST> VERSION EXPIREE', flag=re.IGNORECASE)
1478            superv = re_search(txt, pattern='^ *. *%s',
1479                    string='<F> <SUPERVISEUR> ARRET SUR ERREUR(S) UTILISATEUR',
1480                    flag=re.IGNORECASE)
1481        else:
1482            expir = superv = False
1483
1484        errs = re_search(txt, pattern='^ *. *%s', string='<S>')
1485
1486        if errs:
1487            mem = re_search(txt, string='MEMOIRE INSUFFISANTE POUR ALLOUER', flag=re.IGNORECASE)
1488            arretcpu = re_search(txt, string='ARRET PAR MANQUE DE TEMPS', flag=re.IGNORECASE) \
1489                 or re_search(txt, pattern='<%s>', string='ArretCPUError') \
1490                 or re_search(txt, string='timelimit expired', flag=re.IGNORECASE)
1491            nonconverg = max([re_search(txt, pattern='<%s>', string=exc) \
1492                              for exc in _no_convergence_exceptions])
1493            contact = max([re_search(txt, pattern='<%s>', string=exc) \
1494                              for exc in _contact_exceptions])
1495        else:
1496            mem = arretcpu = nonconverg = contact = False
1497
1498        ok = (n_debut == n_fin and n_fin > 0) or mess
1499        run.DBG('n_debut=%s, n_fin=%s, mess=%s : ok=%s' % (n_debut, n_fin, mess, ok))
1500        run.DBG('alarm=%s, errs=%s, fatal=%s, exitcode=%s' % (alarm, errs, fatal, exitcode))
1501        if ok:
1502            diag = 'OK'
1503            if alarm:
1504                diag = '<A>_ALARM'
1505            if exitcode != 0:
1506                errs = True
1507        # "S" (recoverable) error
1508        if errs:
1509            diag = '<S>_ERROR'
1510            if arretcpu:
1511                diag = '<S>_CPU_LIMIT'
1512            elif nonconverg:
1513                diag = '<S>_NO_CONVERGENCE'
1514            elif contact:
1515                diag = '<S>_CONTACT_ERROR'
1516            elif mem:
1517                diag = '<S>_MEMORY_ERROR'
1518        # fatal error
1519        if fatal:
1520            diag = '<F>_ERROR'
1521            if superv:
1522                diag = '<F>_SUPERVISOR'
1523            elif expir:
1524                diag = '<F>_EXPIRED_VERS'
1525        if not ok or fatal or errs:
1526            stop = True
1527
1528    return diag, stop
1529
1530
1531def _diag_resu(resu, cas_test):
1532    """Diagnostic à partir du fichier '.resu'.
1533    """
1534    diag, tcpu, tsys, ttot, telap = None, 0., 0., 0., 0.
1535    # 2. ----- parse result file
1536    if resu is None or not os.access(resu, os.R_OK):
1537        pass
1538    else:
1539        # OK / NOOK
1540        ok = False
1541        nook = False
1542        # <A>_ALARME
1543        alarm = False
1544        diag0 = diag
1545
1546        with open(resu, 'rb') as f:
1547            txt = to_unicode(f.read())
1548        ok = re_search(txt, pattern='^ *OK +')
1549        nook = re_search(txt, pattern='^ *NOOK +')
1550        alarm = re_search(txt, pattern='^ *. *%s', string='<A>')
1551
1552        value = re_search(txt, result='value',
1553                pattern='TOTAL_JOB +: +([0-9\.]+) +: +([0-9\.]+) +: +([0-9\.]+) +: +([0-9\.]+)')
1554        if len(value) > 0:
1555            tcpu, tsys, ttot, telap = [float(v) for v in value[0]]
1556
1557        if nook:
1558            diag0 = 'NOOK_TEST_RESU'
1559        else:
1560            if cas_test and not ok:
1561                diag0 = 'NO_TEST_RESU'
1562            else:
1563                diag0 = 'OK'
1564                if alarm:
1565                    diag0 = '<A>_ALARM'
1566        diag = diag0
1567
1568    return diag, tcpu, tsys, ttot, telap
1569
1570
1571def _diag_mess(mess):
1572    """Essaie de trouver des pistes supplémentaires si le .mess existe.
1573    """
1574    diag = None
1575    if mess is None or not osp.isfile(mess):
1576        pass
1577    else:
1578        with open(mess, 'rb') as f:
1579            txt = to_unicode(f.read())
1580        syntax = re_search(txt, string="ERREUR A LA VERIFICATION SYNTAXIQUE") or \
1581                    re_search(txt, pattern="ERREUR .*ACCAS")
1582        if syntax:
1583            diag = '<F>_SYNTAX_ERROR'
1584
1585        else:
1586            # recherche de la levée non récupérée d'exception
1587            arretcpu = re_search(txt, pattern='<%s>', string='ArretCPUError') \
1588                 or re_search(txt, string='timelimit expired', flag=re.IGNORECASE)
1589            if arretcpu:
1590                diag = '<S>_CPU_LIMIT'
1591
1592    return diag
1593
1594
1595def unigest2dict(funig, conf, with_fordepla=False):
1596    """Build a dict of the file names by parsing unigest file 'funig' where
1597    the keys are 'f', 'f90', 'c', 'py', 'capy', 'cata', 'test' and 'fdepl'.
1598    funig can be remote.
1599    """
1600    repref = { 'bibfor' : 'f', 'bibf90' : 'f90', 'bibc' : 'c', 'bibpyt' : 'py',
1601              'catapy' : 'capy', 'catalo' : 'cata', 'astest' : 'test' }
1602    dico = {}
1603    for cat in ('f', 'f90', 'c', 'py', 'capy', 'cata', 'test', 'fdepl'):
1604        dico[cat] = []
1605
1606    # ----- keep a reference to the file for CompilEle
1607    dico['filename'] = funig
1608    assert osp.exists(funig), 'file not found : %s' % funig
1609
1610    # parse unigest
1611    fo = open(funig, 'r')
1612    for line in fo:
1613        # new syntax : SUPPR bibfor/algeline/zzzzzz.f
1614        mat = re.search('^ *SUPPR +([^\s]+)', line)
1615        if mat:
1616            path = mat.group(1).strip()
1617            mod = split_path(path)[0]
1618            key = repref.get(mod)
1619            if key:
1620                if key == 'test' and osp.splitext(path)[-1] in ('', '.comm'):
1621                    path = osp.splitext(path)[0] + '.*'
1622                # force use of native separator
1623                path = osp.join(*split_path(path))
1624                dico[key].append(path)
1625
1626        mat = re.search('^ *FORSUPPR +([-_a-zA-Z0-9]+) +([-_a-zA-Z0-9]+)', line)
1627        if mat:
1628            nam = mat.group(1).lower()+'.f'
1629            mod = mat.group(2).lower()
1630            dico['f'].append(osp.join(conf['SRCFOR'][0], mod, nam))
1631
1632        mat = re.search('^ *F90SUPPR +([-_a-zA-Z0-9]+) +([-_a-zA-Z0-9]+)', line)
1633        if mat:
1634            nam = mat.group(1).lower()+'.F'
1635            mod = mat.group(2).lower()
1636            dico['f90'].append(osp.join(conf['SRCF90'][0], mod, nam))
1637
1638        mat = re.search('^ *CSUPPR +([-_a-zA-Z0-9]+) +([-_a-zA-Z0-9]+)', line)
1639        if mat:
1640            nam = mat.group(1).lower()+'.c'
1641            mod = mat.group(2).lower()
1642            dico['c'].append(osp.join(conf['SRCC'][0], mod, nam))
1643
1644        mat = re.search('^ *PYSUPPR +([-_a-zA-Z0-9]+) +([-_a-zA-Z0-9]+)', line)
1645        if mat:
1646            nam = mat.group(1)+'.py'
1647            mod = mat.group(2)
1648            dico['py'].append(osp.join(conf['SRCPY'][0], mod, nam))
1649
1650        mat = re.search('^ *CATSUPPR +([-_a-zA-Z0-9]+) +([-_a-zA-Z0-9]+)', line)
1651        if mat:
1652            nam = mat.group(1).lower()
1653            mod = mat.group(2).lower()
1654            if mod in repcatapy:
1655                dico['capy'].append(osp.join(conf['SRCCAPY'][0], mod, nam+'.capy'))
1656            elif mod in repcatalo:
1657                dico['cata'].append(osp.join(conf['SRCCATA'][0], mod, nam+'.cata'))
1658            else:
1659                print('<A>_ALARM', ufmt(_('unknown type %s in unigest file'), mod), end=' ')
1660
1661        mat = re.search('^ *TESSUPPR +([-_a-zA-Z0-9\.]+)', line)
1662        if mat:
1663            nam = mat.group(1).lower()
1664            ext = osp.splitext(nam)[-1]
1665            if ext in ('', '.comm'):
1666                nam = osp.splitext(nam)[0] + '.*'
1667            dico['test'].extend([osp.join(dtest, nam) for dtest in conf['SRCTEST']])
1668
1669        mat = re.search('^ *FORDEPLA +([-_a-zA-Z0-9]+) +([-_a-zA-Z0-9]+)' \
1670                ' +([-_a-zA-Z0-9]+)', line)
1671        if mat:
1672            nam = mat.group(1).lower()+'.f'
1673            old = mat.group(2).lower()
1674            new = mat.group(3).lower()
1675            if with_fordepla:
1676                dico['fdepl'].append([osp.join(conf['SRCFOR'][0], old, nam),
1677                        osp.join(conf['SRCFOR'][0], new, nam)])
1678            else:
1679                dico['f'].append(osp.join(conf['SRCFOR'][0], old, nam))
1680    fo.close()
1681    return dico
1682
1683def glob_unigest(dico, repref):
1684    """Expand paths relatively to `repref`."""
1685    prev = os.getcwd()
1686    os.chdir(repref)
1687    dico = dico.copy()
1688    for key, values in list(dico.items()):
1689        if key == 'filename':
1690            continue
1691        new = []
1692        for path in values:
1693            new.extend(glob(path))
1694        dico[key] = new
1695    os.chdir(prev)
1696    return dico
1697
1698def get_include(src):
1699    """Returns include files in the given source."""
1700    code = open(src, 'r').read()
1701    linc = re.findall('^\#include *[\"\<]{1}(.*)[\"\>]{1}', code, re.MULTILINE)
1702    return linc
1703
1704
1705def build_include_depend(*dirs):
1706    """Build a tree of dependencies between include files."""
1707    linc = []
1708    for dirsrc in dirs:
1709        for base, l_dirs, l_nondirs in os.walk(dirsrc):
1710            linc.extend(glob(osp.join(base, "*.h")))
1711    wrk = {}
1712    for inc in linc:
1713        wrk[inc] = set()
1714        code = open(inc, 'r').read()
1715        for other in linc:
1716            if inc == other:
1717                continue
1718            mat = re.search('^\#include.*%s' % osp.basename(other), code, re.MULTILINE)
1719            if mat:
1720                wrk[inc].add(other)
1721    new = -1
1722    while new != 0:
1723        new = 0
1724        for inc in linc:
1725            n0 = len(wrk[inc])
1726            for other in linc:
1727                if other in wrk[inc]:
1728                    wrk[inc].update(wrk[other])
1729            new = new + len(wrk[inc]) - n0
1730    dep = {}
1731    for key, val in list(wrk.items()):
1732        dep[osp.basename(key)] = list(val)
1733    return dep
1734
1735ASTER_BUILD = bwc_deprecate_class('ASTER_BUILD', AsterBuild)
1736