1# -*- coding: utf-8 -*-
2# ==============================================================================
3# COPYRIGHT (C) 1991 - 2003  EDF R&D                  WWW.CODE-ASTER.ORG
4# THIS PROGRAM IS FREE SOFTWARE; YOU CAN REDISTRIBUTE IT AND/OR MODIFY
5# IT UNDER THE TERMS OF THE GNU GENERAL PUBLIC LICENSE AS PUBLISHED BY
6# THE FREE SOFTWARE FOUNDATION; EITHER VERSION 2 OF THE LICENSE, OR
7# (AT YOUR OPTION) ANY LATER VERSION.
8#
9# THIS PROGRAM IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT
10# WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF
11# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. SEE THE GNU
12# GENERAL PUBLIC LICENSE FOR MORE DETAILS.
13#
14# YOU SHOULD HAVE RECEIVED A COPY OF THE GNU GENERAL PUBLIC LICENSE
15# ALONG WITH THIS PROGRAM; IF NOT, WRITE TO EDF R&D CODE_ASTER,
16#    1 AVENUE DU GENERAL DE GAULLE, 92141 CLAMART CEDEX, FRANCE.
17# ==============================================================================
18
19"""This module defines following classes :
20- SETUP        main class to configure, make, install Code_Aster products,
21- DEPENDENCIES stores dependencies between products,
22- SUMMARY      stores setup informations,
23- SYSTEM       encapsulates calls to system commands,
24- FIND_TOOLS   package of utilities to find files, libraries...
25
26+ exceptions to control errors occurences.
27"""
28
29
30__all__ = ['SETUP', 'SUMMARY', 'SYSTEM', 'DEPENDENCIES', 'FIND_TOOLS',
31   'should_continue', 'GetPrefix', 'GetSitePackages', 'AddToPostInstallDir',
32   'less_than_version', 'export_parameters', 'get_install_message',
33   'SetupCheckError', 'SetupChgFilesError', 'SetupChmodError', 'SetupCleanError',
34   'SetupConfigureError', 'SetupError', 'SetupExtractError', 'SetupInstallError',
35   'SetupMakeError']
36
37import sys
38import os
39import os.path as osp
40import glob
41import re
42import time
43import traceback
44import tarfile
45import compileall
46import imp
47import pprint
48import distutils.sysconfig as SC
49from functools import partial
50from subprocess import Popen, PIPE
51
52StringTypes = (str, str)
53EnumTypes=(list, tuple)
54
55# ----- differ messages translation
56def _(mesg): return mesg
57
58#-------------------------------------------------------------------------------
59class SetupError(Exception):
60    def __repr__(self):
61        txt = [str(arg) for arg in self.args]
62        return ''.join(txt).strip()
63
64class SetupExtractError(SetupError):   pass
65class SetupConfigureError(SetupError): pass
66class SetupChgFilesError(SetupError):  pass
67class SetupChmodError(SetupError):     pass
68class SetupMakeError(SetupError):      pass
69class SetupInstallError(SetupError):   pass
70class SetupCheckError(SetupError):     pass
71class SetupCleanError(SetupError):     pass
72
73
74def get_install_message(package, missed):
75    """Return a message recommending to install a package"""
76    txt = ""
77    if missed:
78        txt += """
79Unable to find %s
80""" % missed
81    if package:
82        txt += """
83A package named "%(pkg)s" is required (the package name may differ
84depending on your system). You should install it using your package
85manager or by a command line similar to :
86
87on debian/ubuntu:
88    apt-get install %(pkg)s
89
90on centos, rhel, fedora:
91    yum install %(pkg)s
92
93on mandriva:
94    urpmi %(pkg)s
95""" % { 'pkg' : package }
96    return txt
97
98def _clean_path(lpath, returns='str'):
99   """Clean a path variable as PATH, PYTHONPATH, LD_LIBRARY_PATH...
100   """
101   dvu = {}
102   lnew = []
103   lpath = (':'.join(lpath)).split(':')
104   for p in lpath:
105      p = osp.abspath(p)
106      if p != '' and not dvu.get(p, False):
107         lnew.append(p)
108         dvu[p] = True
109   if returns == 'str':
110      val = ':'.join(lnew)
111   else:
112      val = lnew
113   return val
114
115def _chgline(line, dtrans, delimiter=re.escape('?')):
116   """Change all strings by their new value provided by 'dtrans' dictionnary
117   in the string 'line'.
118   """
119   for old, new in list(dtrans.items()):
120      line=re.sub(delimiter+old+delimiter, str(new), line)
121   return line
122
123_noprompt = False
124def should_continue_reg(noprompt):
125    """Register behavior for `should_continue`."""
126    global _noprompt
127    _noprompt = noprompt
128
129def should_continue(default='n', stop=True, question=None):
130   """Ask if the user want to stop or continue.
131   The response is case insensitive.
132   If 'stop' is True, ends the execution with exit code 'UserInterruption',
133   else returns the response.
134   """
135   if _noprompt:
136       return 'yes'
137   yes = ['yes', 'y', 'oui', 'o']   # only lowercase
138   no  = ['no',  'n', 'non',]
139   question = question or "Do you want to continue (y/n, default %s) ? " % default.lower()
140   valid=False
141   while not valid:
142      try:
143         resp=input(question)
144      except EOFError:
145         resp=None
146      except KeyboardInterrupt:
147         sys.exit('KeyboardInterrupt')
148      if resp=='':
149         resp=default
150      valid = resp!=None and resp.lower() in yes+no
151   if resp.lower() in no:
152      resp = 'no'
153      if stop:
154         sys.exit('UserInterruption')
155   else:
156      resp = 'yes'
157   return resp.lower()
158
159def GetSitePackages(prefix):
160   return SC.get_python_lib(prefix=prefix)
161
162def GetPrefix(site_packages):
163   suff = osp.join('lib', 'python'+sys.version[:3], 'site-packages')
164   if not site_packages.endswith(suff):
165      suff = osp.join('lib64', 'python'+sys.version[:3], 'site-packages')
166      if not site_packages.endswith(suff):
167          raise SetupError(_('invalid `site_packages` path : %s') % site_packages)
168   return site_packages.replace(suff, '')
169
170def AddToPostInstallDir(filename, postinst, dest):
171   """Add filename to post-installation directory.
172   """
173   fbas = osp.basename(filename)
174   fdir = osp.dirname(filename)
175   ibckp = 0
176   fnum = osp.join(postinst, '%06d_numfile' % 0)
177   if osp.exists(fnum):
178      try:
179         ibckp = int(open(fnum, 'r').read().strip())
180      except:
181         pass
182   ibckp += 1
183   #bckpf = osp.join(postinst, '%06d_file_%s'    % (ibckp, fbas))
184   bdest = osp.join(postinst, '%06d_dest_%s'    % (ibckp, fbas))
185   open(fnum, 'w').write(str(ibckp))
186   open(bdest, 'w').write(osp.join(dest, filename))
187
188#-------------------------------------------------------------------------------
189#-------------------------------------------------------------------------------
190#-------------------------------------------------------------------------------
191class SETUP:
192   """
193   Attributes :
194    .product      : product name,
195    .version      : product version,
196    .description  : description of the product,
197    .archive      : archive filename (gzip and bzip2 compressed archives support
198      is provided by tarfile module, default is product-version),
199    .content      : name of the directory contained in archive
200      (default is product-version),
201    .verbose      : True for verbose mode,
202    .debug        : True for debug mode,
203    .installdir   : installation directory,
204    .sourcedir    : directory where is 'archive',
205    .workdir      : directory used for extraction, compilation...
206    .delimiter    : ChgFiles search strings enclosed by this delimiter
207      (note with special character in regexp, default is '?'),
208    .manual       : if False, Go() is automatically called by __init__
209      (NOTE : if True and error occurs SETUP instance won't be created because
210       __init__ wasn't finish),
211    .actions      : give the list of actions run by 'Go' method and arguments,
212    .clean_actions : give the list of actions run if 'Go' failed,
213    ._tmpdir : setup working directory,
214    .exit_code    : None during process, exit code after,
215
216    .depend       : DEPENDENCIES object.
217
218     + Shell, VerbStart, VerbIgnore, VerbEnd : shortcuts to SYSTEM methods
219
220   NB : Configure, Make, Install and Check methods can be provided by 'user'
221      through 'external' argument for particular applications.
222   """
223   _separ = '\n' + '-'*80 + '\n'
224   _fmt_info = _separ+_(""" Installation of   : %s %s
225   %s
226 Archive filename  : %s
227 Destination       : %s
228 Working directory : %s""")+_separ
229   _fmt_enter = _("entering directory '%s'")
230   _fmt_leave = _("leaving directory '%s'")
231   _fmt_title = '\n >>> %s <<<\n'
232   _fmt_inst  = _(' %s has already been installed.\n' \
233                  ' Remove %s to force re-installation.')
234   _fmt_ended = _separ+_(' Installation of %s %s successfully completed')+_separ
235#-------------------------------------------------------------------------------
236   def __init__(self, **kargs):
237      """Initializes an instance of an SETUP object and calls Info to print
238      informations.
239      """
240      self.exit_code = None
241      self._tmpdir = kargs.get('tmpdir', os.environ.get('TMPDIR', '/tmp'))
242      # product parameters
243      required_args=('product', 'version', 'description', 'installdir', 'sourcedir')
244      for arg in required_args:
245         try:
246            setattr(self, arg, kargs[arg])
247         except KeyError as msg:
248            raise SetupError(_('missing argument %s') % str(msg))
249      optional_args={
250         'depend'    : None,
251         'archive'   : '%s-%s' % (self.product, self.version),
252         'content'   : '%s-%s' % (self.product, self.version),
253         'workdir'   : osp.join(self._tmpdir,
254               'install_'+self.product+'.'+str(os.getpid())),
255         'delimiter' : re.escape('?'),
256         'manual'    : True,
257         'reinstall' : 'ask',
258      }
259      for arg, default in list(optional_args.items()):
260         setattr(self, arg, kargs.get(arg, default))
261      self.success_key = 'successfully_installed_%s' % self.product
262      self.success_key = self.success_key.replace('-', '')
263      self.success_key = self.success_key.replace('.', '')
264
265      # default actions can only be set after initialisation
266      default_actions = (
267         ('Extract'   , {}),
268         ('Configure' , {}),
269         ('Make'      , {}),
270         ('Install'   , {}),
271         ('Check'     , {}),
272         ('Clean'     , {}),
273      )
274      self.actions = kargs.get('actions', default_actions)
275      self.clean_actions = kargs.get('clean_actions', [])
276      for d in ('installdir', 'sourcedir', 'workdir'):
277         setattr(self, d, osp.abspath(getattr(self, d)))
278
279      # external methods
280      system=kargs.get('system')
281      if system==None:
282         raise SetupError(_("Argument not found 'system'"))
283      self.verbose    = system.verbose
284      self.debug      = system.debug
285      self.Shell      = system.local_shell
286      self.VerbStart  = system.VerbStart
287      self.VerbIgnore = system.VerbIgnore
288      self.VerbEnd    = system.VerbEnd
289      self._print = kargs['log']._print
290
291      # print product informations
292      self.Info()
293
294      # Go if not manual
295      if not self.manual:
296         self.Go()
297
298#-------------------------------------------------------------------------------
299   def _call_external(self, **kargs):
300      """Call an external user's method to perform an operation.
301      """
302      try:
303         kargs.get('external')(self, **kargs)
304      except:
305         self.exit_code=4
306         self._print(self._separ, term='')
307         self._print(self._fmt_title % _('External method traceback'))
308         traceback.print_exc()
309         self._print(self._separ)
310         raise SetupConfigureError(_("external method failed (see traceback above)."))
311
312#-------------------------------------------------------------------------------
313   def Go(self):
314      """Run successively each actions defined in 'self.actions',
315      call PreCheck() first.
316      """
317      if self.depend.cfg.get(self.success_key) == 'YES' \
318         and self.reinstall == 'ignore':
319         self._print(self._fmt_inst % (self.product, self.depend.cache))
320      else:
321         self.PreCheck()
322         self.depend.LastCheck()
323         for act, kargs in self.actions:
324            if act is None:
325               continue
326            elif not hasattr(self, act):
327               raise SetupError(_("unknown action '%s'") % act)
328            else:
329               getattr(self, act)(**kargs)
330         self.exit_code = 0
331         # trace of success in cache file
332         self.depend.cfg[self.success_key] = 'YES'
333         self.depend.FillCache(self.product, [self.success_key, ])
334      self._print(self._fmt_ended % (self.product, self.version))
335
336#-------------------------------------------------------------------------------
337   def IfFailed(self):
338      """Run successively each actions defined in 'self.clean_actions',
339      if installation failed.
340      """
341      if self.exit_code == 0:
342         return
343
344      for act, kargs in self.clean_actions:
345         if not hasattr(self, act):
346            raise SetupError(_("unknown action '%s'") % act)
347         else:
348            getattr(self, act)(**kargs)
349
350#-------------------------------------------------------------------------------
351   def IsInstalled(self, filename):
352      """Raise SetupError if the product is already installed."""
353      if self.reinstall == 'force':
354          return
355      all_inst = True
356      for path in filename:
357          path = self.special_vars(path)
358          all_inst = all_inst and osp.exists(path)
359          if not all_inst:
360              break
361      if all_inst:
362          msg = _("Product '%s' is already installed.") % self.product
363          if self.reinstall == 'ask':
364              self._print(msg)
365              question = _("Choose 'no' to keep the current installation, "
366                           "'yes' to force its re-installation (y/n, default no): ")
367              resp = should_continue(stop=False, question=question)
368              if resp == 'no':
369                  self.reinstall = 'ignore'
370          if self.reinstall == 'ignore':
371              self.exit_code = 0
372              raise SetupError(msg)
373
374   def PreCheck(self, check_values=True):
375      """Check for permission and variables settings.
376      """
377      # fill cache file
378      self.depend.FillCache(self.product)
379
380      # check for permissions and create directories
381      self.VerbStart(_('Checking permissions...'))
382      iret=0
383      ldirs=[self.installdir, self.workdir]
384      for d in ldirs:
385         try:
386            if not osp.exists(d):
387               os.makedirs(d)
388            elif os.access(d, os.W_OK)==0:
389               raise OSError(_('no permission to write in %s') % d)
390         except OSError:
391            iret+=1
392            if iret==1: self._print()
393            self._print(_(' Unsufficient permission to create or write in %s') % repr(d))
394      if iret!=0: self.VerbStart('')   # just for pretty print if fails
395      self.VerbEnd(iret)
396      if iret!=0:
397         raise SetupError(_('permission denied'))
398
399#-------------------------------------------------------------------------------
400   def Info(self):
401      """Print informations about the installation of current product
402      """
403      self._print(self._fmt_info % (self.product, self.version, self.description,
404                              self.archive, self.installdir, self.workdir))
405
406#-------------------------------------------------------------------------------
407   def Extract(self, **kargs):
408      """Extract the archive of the product into 'workdir'.
409         archive : full pathname of archive,
410         command : alternative command line to extract archive (MUST include
411            archive filename !),
412         extract_as : rename content.
413      """
414      self._print(self._fmt_title % _('Extraction'))
415      if kargs.get('external')!=None:
416         self._call_external(**kargs)
417         return
418      archive = kargs.get('archive', osp.join(self.sourcedir,self.archive))
419      command = kargs.get('command')
420      newname = kargs.get('extract_as', None)
421      path=self.workdir
422      iextr_as=newname!=None and self.content!=newname and self.content!='.'
423      if iextr_as:
424         path=osp.join(self.workdir,'tmp_extract')
425         if not osp.exists(path):
426            os.makedirs(path)
427
428      if not osp.isfile(archive):
429         l_fic = []
430         for opt in ('', '-*'):
431            for ext in ('.tar', '.tar.gz', '.tgz', '.tar.bz2'):
432               l_fic.extend(glob.glob(archive + opt + ext))
433         if len(l_fic) > 0:
434            archive = l_fic[0]
435      if not osp.isfile(archive):
436         self.exit_code=1
437         raise SetupExtractError(_("file not found : %s") % archive)
438
439      prev=os.getcwd()
440      self._print(self._fmt_enter % path)
441      os.chdir(path)
442
443      if command != None:
444         iret,output = self.Shell(command,
445               alt_comment=_('Extracting %s...') % osp.basename(archive))
446         if iret!=0:
447            self.exit_code=4
448            raise SetupExtractError(_('error during extracting archive %s') % archive)
449      else:
450         self.VerbStart(_('Extracting %s...') % osp.basename(archive))
451         # open archive using tarfile module
452         try:
453            tar = tarfile.open(archive, 'r')
454         except (tarfile.CompressionError, tarfile.ReadError):
455            # try with gzip or bzip2
456            self.VerbIgnore()
457            iret = -1
458            if archive.endswith('gz'):
459               iret, output = self.Shell('gzip -dc %s | tar -xf -' % archive,
460                     alt_comment=_('Trying GZIP decompression...'))
461            elif archive.endswith('bz2'):
462               iret, output = self.Shell('bzip2 -dc %s | tar -xf -' % archive,
463                     alt_comment=_('Trying BZIP2 decompression...'))
464            else:
465               iret, output = self.Shell('tar -xf %s' % archive,
466                     alt_comment=_('Trying raw tar extraction...'))
467            if iret != 0:
468               self.exit_code = 2
469               raise SetupExtractError(_('unsupported archive format for %s') % archive)
470         # extract archive
471         else:
472            iret=0
473            n=0
474            tar.errorlevel=2
475            try:
476               for ti in tar:
477                  n+=1
478                  if ti.issym():             # pourquoi supprime-t-on les symlink ?
479                     try:
480                        os.remove(ti.name)
481                     except OSError:
482                        pass                 # la cible n'existe pas encore
483                  tar.extract(ti)
484            except tarfile.ExtractError:
485               iret=4
486            except (IOError,OSError) as msg:
487               iret=8
488            except:
489               iret=16
490            self.VerbEnd(iret)
491            tar.close()
492            self._print(_(' --- %d files extracted') % n)
493            if iret!=0:
494               traceback.print_exc()
495               self.exit_code=iret
496               raise SetupExtractError(_('error during extracting archive %s') % archive)
497
498      if iextr_as:
499         iret=0
500         self.VerbStart(_('Renaming %s to %s...') % (self.content, newname))
501         newname=osp.join(self.workdir,newname)
502         try:
503            if osp.exists(newname):
504               self._print()
505               iret,output=self.Shell('rm -rf '+newname,
506                     alt_comment=_('Deleting previous content of %s...') % newname)
507               if iret!=0:
508                  raise OSError
509            os.rename(self.content, newname)
510         except (OSError, IOError) as msg:
511            iret=4
512            self._print()
513            raise SetupExtractError(_('error renaming %s to %s') \
514                  % (self.content, newname))
515         self.VerbEnd(iret)
516
517      self._print(self._fmt_leave % path)
518      os.chdir(prev)
519      if iextr_as:
520         self.Clean(to_delete=path)
521
522#-------------------------------------------------------------------------------
523   def Configure(self, **kargs):
524      """Configuration of the product.
525         command : alternative command line
526         path    : directory to build
527      """
528      self._print(self._fmt_title % _('Configuration'))
529      command = kargs.get('command')
530      path    = kargs.get('path', osp.join(self.workdir,self.content))
531      path = self.special_vars(path)
532      if command==None:
533         command='./configure --prefix='+self.installdir
534
535      if not osp.isdir(path):
536         self.exit_code=4
537         raise SetupConfigureError(_('directory not exists %s') % path)
538
539      prev=os.getcwd()
540      self._print(self._fmt_enter % path)
541      os.chdir(path)
542
543      if kargs.get('external')!=None:
544         self._call_external(**kargs)
545
546      else:
547         self._print(_('Command line :'), command)
548         iret,output=self.Shell(command, follow_output=self.verbose,
549               alt_comment=_('configure %s installation...') % self.product)
550         if iret!=0:
551            if not self.verbose: self._print(output)
552            self.exit_code=4
553            raise SetupConfigureError(_('error during configure'))
554
555      self._print(self._fmt_leave % path)
556      os.chdir(prev)
557
558#-------------------------------------------------------------------------------
559   def ChgFiles(self, **kargs):
560      """Modify files from 'files' according a dictionnary 'dtrans'
561         defined as { 'string_to_replace' : 'new_value' }.
562      Pathnames are relative to workdir/content or to 'path' argument.
563      !!! Do only post-install tasks if 'only_postinst' is True.
564      optional args : 'delimiter', 'keep', 'ext', 'postinst', 'postdest'
565         postinst : directory to backup file for post-installation
566         postdest : if different of self.installdir
567      """
568      self._print(self._fmt_title % _('Modifying pre-configured files'))
569      chglist   = kargs.get('files', [])
570      dtrans    = kargs.get('dtrans',{})
571      delimiter = kargs.get('delimiter', self.delimiter)
572      keep      = kargs.get('keep', False)
573      ext       = kargs.get('ext')
574      postinst  = kargs.get('postinst')
575      postdest  = kargs.get('postdest', self.installdir)
576      postdest  = self.special_vars(postdest)
577      path      = kargs.get('path', osp.join(self.workdir,self.content))
578      path      = self.special_vars(path)
579      only_postinst = kargs.get('only_post', False)
580
581      if not osp.isdir(path):
582         self.exit_code=4
583         raise SetupChgFilesError(_('directory not exists %s') % path)
584
585      prev=os.getcwd()
586      self._print(self._fmt_enter % path)
587      os.chdir(path)
588
589      if kargs.get('external') != None:
590         self._call_external(**kargs)
591
592      else:
593         for f0 in chglist:
594            for f in glob.glob(f0):
595               iret=0
596               self.VerbStart(_('modifying %s') % f)
597               if osp.isfile(f):
598                  if not only_postinst:
599                     iret=self._chgone(f, dtrans, delimiter, keep, ext)
600                  if postinst != None:
601                     AddToPostInstallDir(f, postinst, postdest)
602                  self.VerbEnd(iret)
603               else:
604                  self.VerbIgnore()
605
606      self._print(self._fmt_leave % path)
607      os.chdir(prev)
608
609   def _chgone(self, filename, dtrans, delimiter=None, keep=False, ext=None):
610      """Change all strings by their new value provided by 'dtrans' dictionnary
611      in the existing file 'filename'.
612      """
613      if delimiter == None:
614         delimiter = self.delimiter
615      if ext == None:
616         ext = '.orig'
617      iret = 0
618      try:
619         os.rename(filename, filename+ext)
620      except OSError:
621         return 4
622      fsrc = open(filename+ext, 'r')
623      fnew = open(filename, 'w')
624      for line in fsrc:
625         fnew.write(_chgline(line, dtrans, delimiter))
626      fnew.close()
627      fsrc.close()
628      if not keep:
629         os.remove(filename+ext)
630      return iret
631
632#-------------------------------------------------------------------------------
633   def Make(self, **kargs):
634      """Compilation of product.
635         command : alternative command line
636         path    : directory to build (or list of directories)
637      """
638      self._print(self._fmt_title % _('Building the product'))
639      if kargs.get('external')!=None:
640         self._call_external(**kargs)
641         return
642      command = kargs.get('command')
643      lpath   = kargs.get('path', osp.join(self.workdir,self.content))
644      nbcpu   = kargs.get('nbcpu', 1)
645      capturestderr = kargs.get('capturestderr', True)
646      if not type(lpath) in EnumTypes:
647         lpath=[lpath,]
648      if command == None:
649         command = 'make'
650      if nbcpu != 1:
651         command += ' -j %d' % nbcpu
652
653      for path in lpath:
654         path = self.special_vars(path)
655         if not osp.isdir(path):
656            self.exit_code=4
657            raise SetupConfigureError(_('directory not exists %s') % path)
658
659         prev=os.getcwd()
660         self._print(self._fmt_enter % path)
661         os.chdir(path)
662
663         self._print(_('Command line :'), command)
664         iret,output=self.Shell(command, follow_output=self.verbose,
665               capturestderr=capturestderr,
666               alt_comment=_('compiling %s...') % self.product)
667         if iret!=0:
668            if not self.verbose: self._print(output)
669            self.exit_code=4
670            raise SetupMakeError(_('error during compilation'))
671
672         self._print(self._fmt_leave % path)
673         os.chdir(prev)
674
675#-------------------------------------------------------------------------------
676   def Install(self, **kargs):
677      """Perform installation of the product.
678         command : alternative command line
679         path    : directory to build
680      """
681      self._print(self._fmt_title % _('Installation'))
682      command = kargs.get('command')
683      path    = kargs.get('path', osp.join(self.workdir,self.content))
684      path = self.special_vars(path)
685      if command==None:
686         command='make install'
687
688      if not osp.isdir(path):
689         self.exit_code=4
690         raise SetupInstallError(_('directory not exists %s') % path)
691
692      prev=os.getcwd()
693      self._print(self._fmt_enter % path)
694      os.chdir(path)
695
696      if kargs.get('external')!=None:
697         self._call_external(**kargs)
698
699      else:
700         self._print(_('Command line :'), command)
701         iret,output=self.Shell(command, follow_output=self.verbose,
702               alt_comment=_('installing %s to %s...') % (self.product, self.installdir))
703         if iret!=0:
704            if not self.verbose: self._print(output)
705            self.exit_code=4
706            raise SetupInstallError(_('error during installation'))
707
708      self._print(self._fmt_leave % path)
709      os.chdir(prev)
710
711#-------------------------------------------------------------------------------
712   def PyInstall(self, **kargs):
713      """Perform installation of a Python module.
714         command  : alternative command line
715         path     : initial directory
716         prefix   : installation prefix
717         cmd_opts : options
718      """
719      format = '%(python)s %(script)s %(global_opts)s %(cmd)s --prefix=%(prefix)s %(cmd_opts)s'
720      d_cmd = {}
721      d_cmd['python']      = self.depend.cfg.get('PYTHON_EXE', sys.executable)
722      d_cmd['script']      = kargs.get('script', 'setup.py')
723      d_cmd['global_opts'] = kargs.get('global_opts', '')
724      d_cmd['cmd']         = kargs.get('cmd', 'install')
725      d_cmd['cmd_opts']    = kargs.get('cmd_opts', '')
726      d_cmd['prefix']      = kargs.get('prefix', GetPrefix(self.installdir))
727
728      kargs['command'] = kargs.get('command', format % d_cmd)
729      self.Install(**kargs)
730
731#-------------------------------------------------------------------------------
732   def PyCompile(self, **kargs):
733      """Recursively descend the directory tree named by 'path', compiling
734      all .py files along the way.
735         path    : one or more directories
736      """
737      self._print(self._fmt_title % _('Compiling Python source files'))
738      lpath    = kargs.get('path', osp.join(self.workdir,self.content))
739      if not type(lpath) in EnumTypes:
740         lpath=[lpath,]
741
742      iret=0
743      for path in lpath:
744         path = self.special_vars(path)
745         if not osp.isdir(path):
746            self.exit_code=4
747            raise SetupInstallError(_('directory not exists %s') % path)
748
749         prev=os.getcwd()
750         self._print(self._fmt_enter % path)
751         os.chdir(path)
752
753         ierr=0
754         self.VerbStart(_('Building byte-code files from %s...') % path)
755         self._print()
756         try:
757            compileall.compile_dir(path, quiet=(self.verbose==False))
758         except Exception as msg:
759            ierr=iret=4
760            self._print(msg)
761         if self.verbose: self.VerbStart('')   # just for pretty print
762         self.VerbEnd(ierr)
763      if iret!=0:
764         raise SetupInstallError(_('error during byte-code built'))
765
766      self._print(self._fmt_leave % path)
767      os.chdir(prev)
768
769#-------------------------------------------------------------------------------
770   def Chmod(self, **kargs):
771      """Change mode of the 'files' to 'mode'.
772      Pathnames are relative to installdir or to 'path' argument.
773      """
774      self._print(self._fmt_title % _('Set files permission'))
775      chglist = kargs.get('files', [])
776      try:
777         mode    = int(kargs.get('mode', 0o644))
778      except ValueError:
779         self.exit_code=4
780         raise SetupChmodError(_("an integer is required for 'mode'"))
781      path=kargs.get('path', self.installdir)
782      path = self.special_vars(path)
783
784      if not osp.isdir(path):
785         self.exit_code=4
786         raise SetupChgFilesError(_('directory not exists %s') % path)
787
788      prev=os.getcwd()
789      self._print(self._fmt_enter % path)
790      os.chdir(path)
791
792      if kargs.get('external')!=None:
793         self._call_external(**kargs)
794
795      else:
796         for f0 in chglist:
797            for f in glob.glob(f0):
798               iret=0
799               self.VerbStart(_('applying chmod %04o to %s') % (mode,f))
800               if osp.isfile(f):
801                  try:
802                     os.chmod(f, mode)
803                  except OSError:
804                     iret=4
805                  self.VerbEnd(iret)
806               else:
807                  self.VerbIgnore()
808
809      self._print(self._fmt_leave % path)
810      os.chdir(prev)
811
812#-------------------------------------------------------------------------------
813   def Check(self, **kargs):
814      """Check if installation was successfull.
815         path    : directory to build
816      """
817      self._print(self._fmt_title % _('Check for installation'))
818      command = kargs.get('command')
819      path    = kargs.get('path', osp.join(self.workdir,self.content))
820      path = self.special_vars(path)
821      if command==None:
822         command='make check'
823
824      if not osp.isdir(path):
825         self.exit_code=4
826         raise SetupConfigureError(_('directory not exists %s') % path)
827
828      prev=os.getcwd()
829      self._print(self._fmt_enter % path)
830      os.chdir(path)
831
832      if kargs.get('external')!=None:
833         self._call_external(**kargs)
834
835      else:
836         self._print(_('Command line :'), command)
837         iret,output=self.Shell(command, follow_output=self.verbose,
838               alt_comment=_('checking %s installation...') % self.product)
839         if iret!=0:
840            if not self.verbose: self._print(output)
841            self.exit_code=4
842            raise SetupCheckError(_('error during checking installation'))
843
844      self._print(self._fmt_leave % path)
845      os.chdir(prev)
846
847#-------------------------------------------------------------------------------
848   def Clean(self, **kargs):
849      """Clean working directory.
850         'to_delete' : list of objects to delete
851      Pathnames are relative to workdir or to 'path' argument.
852      """
853      self._print(self._fmt_title % _('Clean temporary objects'))
854      to_del = kargs.get('to_delete', [self.content])
855      path   = kargs.get('path', self.workdir)
856      if not type(to_del) in EnumTypes:
857         to_del=[to_del]
858
859      prev=os.getcwd()
860      self._print(self._fmt_enter % path)
861      os.chdir(path)
862
863      for obj in [osp.abspath(o) for o in to_del]:
864         iret,output=self.Shell(cmd='rm -rf '+obj,
865               alt_comment=_('deleting %s...') % obj)
866      try:
867         os.rmdir(self.workdir)
868         self._print(_('deleting %s...') % self.workdir)
869      except:
870         pass
871
872      self._print(self._fmt_leave % path)
873      os.chdir(prev)
874
875#-------------------------------------------------------------------------------
876   def special_vars(self, ch):
877      """Insert in `ch` the content of "special vars" (attributes).
878      """
879      auth_vars = ['product', 'version', 'content',
880         'workdir', 'installdir', 'sourcedir']
881      for var in auth_vars:
882         spv = '__setup.%s__' % var
883         if ch.find(spv) > -1:
884            ch = ch.replace(spv, getattr(self, var))
885      return ch
886
887#-------------------------------------------------------------------------------
888#-------------------------------------------------------------------------------
889#-------------------------------------------------------------------------------
890class DEPENDENCIES:
891   """Class to store dependencies between products.
892   Attributes :
893      prod_req : dict where keys are products names and values the list of
894         variables required by them,
895      req_prod : reverse of prod_req,
896      prod_set : dict where keys are products names and values the list of
897         variables set by them,
898      set_prod : reverse of prod_set,
899      req_obj  : dict product : required objects (only tested by LastCheck),
900      cfg      : dictionnary containing parameters values,
901      cache    : cache filename,
902      debug    : debug mode.
903   """
904   _separ='\n' + '-'*80 + '\n'
905#-------------------------------------------------------------------------------
906   def __init__(self, **kargs):
907      """Constructor
908      """
909      self.cfg=kargs.get('cfg', {})
910      self.cache=kargs.get('cache', {})
911      self._print = kargs['log']._print
912
913      # external methods
914      system=kargs.get('system')
915      if system==None:
916         raise SetupError(_("Argument not found 'system'"))
917      self.debug      = system.debug
918      self.Shell      = system.local_shell
919      self.VerbStart  = system.VerbStart
920      self.VerbIgnore = system.VerbIgnore
921      self.VerbEnd    = system.VerbEnd
922
923      self.prod_req = {}
924      self.req_prod = {}
925      self.prod_set = {}
926      self.set_prod = {}
927      self.req_obj  = {}
928      if kargs.get('req')!=None or kargs.get('set')!=None:
929         self.Add('__main__', kargs.get('req', []), kargs.get('set', []))
930
931#-------------------------------------------------------------------------------
932   def PrintContent(self, **kargs):
933      """Print content
934      """
935      self._print(self._separ, ('Content of cfg :'))
936      self._print(self.cfg)
937      self._print(_('\nProducts prerequisites :'))
938      self._print(self.prod_req)
939      self._print(_('\nProducts which require these variables :'))
940      self._print(self.req_prod)
941      self._print(_('\nVariables set by products :'))
942      self._print(self.prod_set)
943      self._print(_('\nProducts which set these variables :'))
944      self._print(self.set_prod)
945
946#-------------------------------------------------------------------------------
947   def Add(self, product, req=None, set=None, reqobj=None):
948      """Add dependencies of 'product'.
949      'req' should contains all variables set before this product setup.
950      Required variables that could be deduced from others should only be
951      in 'set'.
952      'reqobj' contains names of objects (actually only files) required.
953      """
954      if req is None:
955         req = []
956      if set is None:
957         set = []
958      if reqobj is None:
959         reqobj = []
960
961      if product not in self.prod_req: self.prod_req[product]=[]
962      if product not in self.prod_set: self.prod_set[product]=[]
963      if product not in self.req_obj:  self.req_obj[product]=[]
964
965      # takes from req only variables not set by product
966      for r in [r for r in req if not r in set]:
967         check_varname(r)
968         self.prod_req[product].append(r)
969         if r not in self.req_prod: self.req_prod[r]=[]
970         self.req_prod[r].append(product)
971      for s in set:
972         check_varname(s)
973         self.prod_set[product].append(s)
974         if s not in self.set_prod: self.set_prod[s]=[]
975         self.set_prod[s].append(product)
976
977      for o in reqobj:
978         self.req_obj[product].append(o)
979
980      # check
981      self.CheckDepVal(product)
982
983      # set variables are considered as required
984      for r in set:
985         check_varname(r)
986         self.prod_req[product].append(r)
987         if r not in self.req_prod: self.req_prod[r]=[]
988         self.req_prod[r].append(product)
989
990#-------------------------------------------------------------------------------
991   def CheckDepVal(self, product='all'):
992      """Check for dependencies, values setting and permission.
993      """
994      if self.debug:
995         self._print('PASSAGE CHECKDEPVAL product='+product, self._separ, term='')
996         self.PrintContent()
997
998      self._print(self._separ, term='')
999      self.VerbStart(_('Checking for dependencies and required ' \
1000            'variables for %s...') % repr(product))
1001      iret, lerr = self.Check(product)
1002      if iret!=0:
1003         self.VerbStart('')   # just for pretty print if fails
1004      self.VerbEnd(iret)
1005      if iret!=0:
1006        raise SetupError(_('inconsistent dependencies or missing variables'\
1007               +'\n     Problem with : %s') % ', '.join(lerr))
1008
1009#-------------------------------------------------------------------------------
1010   def Check(self, product='all', check_deps=True, check_values=True):
1011      """Check for dependencies.
1012      """
1013      iret=0
1014      lerr=[]
1015      if product=='all':
1016         product=list(self.prod_req.keys())
1017      if not type(product) in EnumTypes:
1018         product=[product,]
1019      for p in product:
1020         if p not in self.prod_req:
1021            iret=-1
1022            self._print()
1023            self._print(_(' No dependencies found for %s') % repr(p))
1024         else:
1025            for v in self.prod_req[p]:
1026               err=0
1027               if check_values and v not in self.cfg:
1028                  iret+=1
1029                  err=1
1030                  lerr.append(v)
1031                  if iret==1: self._print()
1032                  self._print(_(' %15s is required by %s but not set.') % (v, repr(p)))
1033               if check_deps and not v in list(self.set_prod.keys()) and err==0:
1034                  iret+=1
1035                  lerr.append(v)
1036                  if iret==1: self._print()
1037                  self._print(_(' %15s is required by %s') % (v, repr(p)))
1038      return iret, lerr
1039
1040#-------------------------------------------------------------------------------
1041   def LastCheck(self, product='all'):
1042      """Check for required objects are present.
1043      """
1044      iret=0
1045      lerr=[]
1046      if product=='all':
1047         product=list(self.req_obj.keys())
1048      if not type(product) in EnumTypes:
1049         product=[product,]
1050      for p in product:
1051         if p not in self.req_obj:
1052            iret=-1
1053            self._print()
1054            self._print(_(' No objects dependencies found for %s') % repr(p))
1055         else:
1056            for v in self.req_obj[p]:
1057               typ=v.split(':')[0]
1058               val=''.join(v.split(':')[1:])
1059               if typ=='file':
1060                  vf=_chgline(val, self.cfg)
1061                  if not osp.exists(vf):
1062                     iret+=1
1063                     lerr.append(vf)
1064                     self._print(_(' %s requires this file : %s') % (repr(p), vf))
1065               else:
1066                  raise SetupError(_('unknown type of object : %s') % typ)
1067      if iret>0:
1068        raise SetupError(_('missing objects'\
1069               +'\n     Problem with : %s') % ', '.join(lerr))
1070
1071#-------------------------------------------------------------------------------
1072   def FillCache(self, product='all', only=None):
1073      """Fill cache file with all values set by product if `only` is None
1074      or only these of `only` list.
1075      NOTE : call it only if you are sure variables are set !
1076      """
1077      lerr=[]
1078      if product=='all':
1079         product=list(self.prod_req.keys())
1080      if not type(product) in EnumTypes:
1081         product=[product,]
1082
1083      # fill cache file with values set by product(s)
1084      self.VerbStart(_('Filling cache...'))
1085      iret=0
1086      f=open(self.cache, 'a')
1087      for p in product:
1088         f.write('\n# Variables set by %s at %s\n' % \
1089               (repr(p),time.strftime('%c')))
1090         if type(only) in EnumTypes:
1091            l_vars = only[:]
1092         else:
1093            l_vars = self.prod_set[p]
1094         # alphabetical sort (easier to read)
1095         l_vars.sort()
1096         for v in l_vars:
1097            if v in self.cfg:
1098               f.write('%s = %s\n' % (v, repr(self.cfg[v])))
1099            else:
1100               iret=255
1101               self._print()
1102               lerr.append(v)
1103               self._print(_(' %15s not yet defined' % repr(v)))
1104      f.close()
1105      if iret!=0: self.VerbStart('')   # just for pretty print if fails
1106      self.VerbEnd(iret)
1107      if iret!=0:
1108         raise SetupError(_('unavailable variables' \
1109               +'\n     Problem with : %s') % ', '.join(lerr))
1110
1111#-------------------------------------------------------------------------------
1112#-------------------------------------------------------------------------------
1113#-------------------------------------------------------------------------------
1114class SUMMARY:
1115   """This class collects informations about product installations as
1116   diagnostic, exception raised if any...
1117   """
1118   _separ='\n' + '-'*80 + '\n'
1119   _fmt_title=_separ + '      %s' + _separ
1120   _fmt_sum=_(""" Installation of   : %(product)s %(version)s
1121 Destination       : %(installdir)s
1122 Elapsed time      : %(time).2f s""")
1123   _fmt_except=_('\n *** Exception %s raised : %s\nSee detailed traceback in' \
1124         ' the logfile')
1125#-------------------------------------------------------------------------------
1126   def __init__(self, list_of_products, **kargs):
1127      self.products=list_of_products
1128      self.diag={}
1129
1130      # external methods
1131      system=kargs.get('system')
1132      self._print = kargs['log']._print
1133      if system==None:
1134         raise SetupError(_("Argument not found 'system'"))
1135      self.Shell      = system.local_shell
1136      self.VerbStart  = system.VerbStart
1137      self.VerbIgnore = system.VerbIgnore
1138      self.VerbEnd    = system.VerbEnd
1139
1140      self._glob_title = "Code_Aster + %d of its prerequisites" % len(self.products)
1141      self._t_ini = kargs.get('t_ini') or time.time()
1142      for p in self.products:
1143         self.diag[p]={
1144            'product'      : p,
1145            'version'      : '(version unavailable)',
1146            'installdir'   : 'unknown',
1147            'exit_code'    : None,
1148            'time'         : 0.,
1149            'tb_info'      : None,
1150         }
1151
1152
1153   def SetGlobal(self, aster_root, version):
1154      """Set informations for aster-full
1155      """
1156      self.diag[self._glob_title] = {
1157            'product'      : self._glob_title,
1158            'version'      : version,
1159            'installdir'   : aster_root,
1160            'exit_code'    : 0,
1161            'time'         : 0.,
1162            'tb_info'      : None,
1163      }
1164
1165#-------------------------------------------------------------------------------
1166   def Set(self, product, setup, dt, traceback_info=None):
1167      """Set informations about a product installation.
1168      """
1169      if isinstance(setup, SETUP):
1170         self.diag[product].update({
1171            'version'      : setup.version,
1172            'installdir'   : setup.installdir,
1173            'exit_code'    : setup.exit_code,
1174         })
1175      else:
1176         self.diag[product]['exit_code']=4
1177      self.diag[product]['time']=dt
1178      if traceback_info!=None:
1179         self.diag[product]['tb_info']=traceback_info
1180
1181#-------------------------------------------------------------------------------
1182   def Print(self):
1183      """Return a representation of the SUMMARY object
1184      """
1185      self.diag[self._glob_title]['time'] = time.time() - self._t_ini
1186
1187      self._print(self._fmt_title % _('SUMMARY OF INSTALLATION'))
1188      for p in self.products + [self._glob_title,]:
1189         self.VerbStart(self._fmt_sum % self.diag[p])
1190         if self.diag[p]['exit_code']==None:
1191            self.VerbIgnore()
1192         else:
1193            if self.diag[p]['exit_code']!=0:
1194               self._print(self._fmt_except % self.diag[p]['tb_info'][:2])
1195#                traceback.print_tb(self.diag[p]['tb_info'][2])
1196               self.VerbStart('')   # just for pretty print if fails
1197            self.VerbEnd(self.diag[p]['exit_code'])
1198
1199#-------------------------------------------------------------------------------
1200#-------------------------------------------------------------------------------
1201#-------------------------------------------------------------------------------
1202def _exitcode(status, default=99):
1203   """Extrait le code retour du status. Retourne `default` si le process
1204   n'a pas fini par exit.
1205   """
1206   if os.WIFEXITED(status):
1207      iret = os.WEXITSTATUS(status)
1208   elif os.WIFSIGNALED(status):
1209      iret = os.WTERMSIG(status)
1210   elif os.WIFSTOPPED(status):
1211      iret = os.WSTOPSIG(status)
1212   else:
1213      iret = default
1214   return iret
1215
1216#-------------------------------------------------------------------------------
1217_command_id = 0
1218def get_command_id():
1219    """Return a unique identifier for command started by 'local_shell'.
1220    """
1221    global _command_id
1222    _command_id += 1
1223    return '%d_%08d' % (os.getpid(), _command_id)
1224
1225def get_command_line(cmd_in, bg, follow_output, separated_stderr,
1226                     output_filename, error_filename, var_exitcode):
1227    """Returns the command to run to redirect output/error, retreive exit code
1228    """
1229    command = {
1230        'foreground' : '( %(cmd)s ) > /dev/null 2>&1',
1231        'background' : '( %(cmd)s ) > /dev/null 2>&1 &',
1232        'follow_with_stderr' : '( %(cmd)s ; echo %(var)s=$? ) 2>&1 | tee %(output)s',
1233        'follow_separ_stderr' : '( %(cmd)s ; echo %(var)s=$? ) 2> %(error)s | tee %(output)s',
1234        'not_follow_with_stderr' : '( %(cmd)s ) > %(output)s 2>&1',
1235        'not_follow_separ_stderr' : '( %(cmd)s ) > %(output)s 2> %(error)s',
1236        'rm_file' : '\\rm -f %(args)s',
1237        'rm_dirs' : '\\rm -rf %(args)s',
1238        'copy' : 'cp -L -r %(args)s',
1239        'ping' : 'ping -c 1 -W %(timeout)s %(host)s',
1240        'shell_cmd' : "bash -c",
1241        'file' : "file %(args)s",
1242        'hostid' : 'ifconfig',
1243    }
1244    values = {
1245        'cmd' : cmd_in.replace(os.linesep, ''),
1246        'output' : output_filename,
1247        'error' : error_filename,
1248        'var' : var_exitcode,
1249    }
1250    if bg:
1251        # new_cmd = cmd + ' &' => NO : stdout/stderr must be closed not to block
1252        new_cmd = command['background'] % values
1253    elif follow_output:
1254        if not separated_stderr:
1255            new_cmd = command['follow_with_stderr'] % values
1256        else:
1257            new_cmd = command['follow_separ_stderr'] % values
1258    else:
1259        if not separated_stderr:
1260            new_cmd = command['not_follow_with_stderr'] % values
1261        else:
1262            new_cmd = command['not_follow_separ_stderr'] % values
1263    #print3(u"Command :", new_cmd)
1264    # may happen if tmpdir has been deleted before another one, just before exit.
1265    if not osp.isdir(osp.dirname(output_filename)):
1266        new_cmd = command['foreground'] % values
1267    return new_cmd
1268
1269def get_tmpname_base(dirname=None, basename=None):
1270    """Return a name for a temporary directory (*not created*)
1271    of the form : 'dirname'/user@machine-'basename'.'pid'
1272    *Only* basename is not compulsory in this variant.
1273    """
1274    basename = basename or 'tmpname-%.6f' % time.time()
1275    pid = "pid-%.6f" % time.time()
1276    root, ext = osp.splitext(basename)
1277    name = root + '.' + str(pid) + ext
1278    return osp.join(dirname, name)
1279
1280
1281class SYSTEM:
1282   """Class to encapsultate "system" commands (this a simplified version of
1283   ASTER_SYSTEM class defined in ASTK_SERV part).
1284   """
1285   # this value should be set during installation step.
1286   MaxCmdLen=1024
1287   # line length -9
1288   _LineLen=80-9
1289#-------------------------------------------------------------------------------
1290   def __init__(self, run, **kargs):
1291      """run : dictionnary to define 'verbose', 'debug'
1292      """
1293      self.verbose   = run['verbose']
1294      self.debug     = run['debug']
1295      self._print = kargs['log']._print
1296      self._tmpdir = osp.join(kargs.get('tmpdir', '/tmp'),
1297                                           'system.%s' % os.getpid())
1298      if not osp.exists(self._tmpdir):
1299         os.makedirs(self._tmpdir)
1300      if 'maxcmdlen' in kargs:
1301         self.MaxCmdLen = kargs['maxcmdlen']
1302
1303#-------------------------------------------------------------------------------
1304   def _mess(self,msg,cod=''):
1305      """Just print a message
1306      """
1307      self._print('%-18s %s' % (cod,msg))
1308
1309#-------------------------------------------------------------------------------
1310   def VerbStart(self,cmd,verbose=None):
1311      """Start message in verbose mode
1312      """
1313      Lm=self._LineLen
1314      if verbose==None:
1315         verbose=self.verbose
1316      if verbose:
1317         pcmd=cmd
1318         if len(cmd)>Lm-2 or cmd.count('\n')>0:
1319            pcmd=pcmd+'\n'+' '*Lm
1320         self._print(('%-'+str(Lm)+'s') % (pcmd,), term='')
1321         #sys.stdout.flush()  done by _print
1322
1323#-------------------------------------------------------------------------------
1324   def VerbEnd(self,iret,output='',verbose=None):
1325      """Ends message in verbose mode
1326      """
1327      if verbose==None:
1328         verbose=self.verbose
1329      if verbose:
1330         if iret==0:
1331            self._print('[  OK  ]')
1332         else:
1333            self._print(_('[FAILED]'))
1334            self._print(_('Exit code : %d') % iret)
1335         if (iret!=0 or self.debug) and output:
1336            self._print(output)
1337
1338#-------------------------------------------------------------------------------
1339   def VerbIgnore(self,verbose=None):
1340      """Ends message in verbose mode
1341      """
1342      if verbose==None:
1343         verbose=self.verbose
1344      if verbose:
1345         self._print('[ SKIP ]')
1346
1347#-------------------------------------------------------------------------------
1348   def local_shell(self, cmd, bg=False, verbose=None, follow_output=False,
1349                   alt_comment=None, interact=False, capturestderr=True,
1350                   **ignore_args):
1351        """Execute a command shell
1352            cmd           : command
1353            bg            : put command in background if True
1354            verbose       : print status messages during execution if True
1355            follow_output : follow interactively output of command
1356            alt_comment   : print this "alternative comment" instead of "cmd"
1357        Return :
1358            iret     : exit code if bg = False,
1359                    0 if bg = True
1360            output   : output lines (as string)
1361        """
1362        separated_stderr = not capturestderr
1363        if not alt_comment:
1364            alt_comment = cmd
1365        if verbose==None:
1366            verbose=self.verbose
1367        if bg:
1368            interact=False
1369        if len(cmd) > self.MaxCmdLen:
1370            self._mess((_('length of command shell greater '\
1371                    'than %d characters.') % self.MaxCmdLen), '<A>_ALARM')
1372        if self.debug:
1373            self._print('<local_shell>', cmd, DBG=True)
1374        self.VerbStart(alt_comment, verbose=verbose)
1375        if follow_output and verbose:
1376            self._print(_('\nCommand output :'))
1377
1378        var_id = "EXIT_COMMAND_%s" % get_command_id()
1379        fout_name = get_tmpname_base(self._tmpdir, 'local_shell_output')
1380        ferr_name = get_tmpname_base(self._tmpdir, 'local_shell_error')
1381        new_cmd = get_command_line(cmd, bg, follow_output, separated_stderr,
1382                                   fout_name, ferr_name, var_id)
1383        # execution
1384        iret = os.system(new_cmd)
1385        iret = _exitcode(iret)
1386        output, error = "", ""
1387        try:
1388            output = open(fout_name, "r").read()
1389            os.remove(fout_name)
1390        except:
1391            pass
1392        try:
1393            error = open(ferr_name, "r").read()
1394            os.remove(ferr_name)
1395        except:
1396            pass
1397
1398        if follow_output:
1399            # repeat header message
1400            self.VerbStart(alt_comment, verbose=verbose)
1401        mat = re.search('EXIT_CODE=([0-9]+)', output)
1402        if mat:
1403            iret = int(mat.group(1))
1404        elif follow_output:
1405            # os.system returns exit code of tee
1406            mat = re.search("%s=([0-9]+)" % var_id, output)
1407            if mat:
1408                iret = int(mat.group(1))
1409        self.VerbEnd(iret, output, verbose=verbose)
1410        self._print('ERROR : iret = %s' % iret, '+++ STANDARD OUTPUT:', output,
1411                    '+++ STANDARD ERROR:', error, '+++ END', DBG=True)
1412        if bg:
1413            iret = 0
1414        return iret, output
1415
1416#-------------------------------------------------------------------------------
1417   def GetHostName(self, host=None):
1418      """Return hostname of the machine 'host' or current machine if None.
1419      """
1420      from socket import gethostname, gethostbyaddr
1421      if host==None:
1422         host = gethostname()
1423      try:
1424         fqn, alias, ip = gethostbyaddr(host)
1425      except:
1426         fqn, alias, ip = host, [], None
1427      if fqn.find('localhost')>-1:
1428         alias=[a for a in alias if a.find('localhost')<0]
1429         if len(alias)>0:
1430            fqn=alias[0]
1431         for a in alias:
1432            if a.find('.')>-1:
1433               fqn=a
1434               break
1435      return fqn
1436
1437#-------------------------------------------------------------------------------
1438   def AddToEnv(self, profile, verbose=None):
1439      """Read 'profile' file (with sh/bash/ksh syntax) and add updated
1440      variables to os.environ.
1441      """
1442      def env2dict(s):
1443         """Convert output to a dictionnary."""
1444         l = s.split(os.linesep)
1445         d = {}
1446         for line in l:
1447            mat = re.search('^([-a-zA-Z_0-9@\+]+)=(.*$)', line)
1448            if mat != None:
1449               d[mat.group(1)] = mat.group(2)
1450         return d
1451      if verbose==None:
1452         verbose=self.verbose
1453
1454      if not profile:
1455         return
1456      if type(profile) is str:
1457         ftmp = osp.join(self._tmpdir, 'temp.opt_env')
1458         open(ftmp, 'w').write(profile)
1459         os.chmod(ftmp, 0o755)
1460         profile = ftmp
1461
1462      if not osp.isfile(profile):
1463         self._mess(_('file not found : %s') % profile, '<A>_FILE_NOT_FOUND')
1464         return
1465      # read initial environment
1466      iret, out = self.local_shell('sh -c env', verbose=verbose)
1467      env_init = env2dict(out)
1468      if iret != 0:
1469         self._mess(_('error getting environment'), '<E>_ABNORMAL_ABORT')
1470         return
1471      # read profile and dump modified environment
1472      iret, out = self.local_shell('sh -c ". %s ; env"' % profile, verbose=verbose)
1473      env_prof = env2dict(out)
1474      if iret != 0:
1475         self._mess(_('error reading profile : %s') % profile,
1476               '<E>_ABNORMAL_ABORT')
1477         return
1478      # "diff"
1479      for k, v in list(env_prof.items()):
1480         if env_init.get(k, None) != v:
1481            if self.debug:
1482               self._print('AddToEnv adding : %s=%s' % (k, v), DBG=True)
1483            os.environ[k] = v
1484      for k in [k for k in list(env_init.keys()) if env_prof.get(k) is None]:
1485         self._print('Unset %s ' % k, DBG=True)
1486         try:
1487            del os.environ[k]
1488         except:
1489            pass
1490
1491#-------------------------------------------------------------------------------
1492def _getsubdirs(prefdirs, others, maxdepth=5):
1493   """Returns the list of subdirectories of 'prefdirs' and 'others' up to 'maxdepth'.
1494   Note that 'prefdirs' appear at the beginning of the returned list,
1495   followed by their subdirectories, then 'others', and their subdirectories.
1496   """
1497   new, dnew = [], {}   # dnew exists only for performance (order must be kept in new)
1498   for dirs in (prefdirs, others):
1499      if not type(dirs) in EnumTypes:
1500         dirs=[dirs]
1501      dirs=[osp.realpath(i) for i in dirs if i!='']
1502      for d in dirs:
1503         if dnew.get(d) is None:
1504            new.append(d)
1505            dnew[d] = 1
1506      if maxdepth > 0:
1507         for d in dirs:
1508            level=len(d.split(osp.sep))
1509            for root, l_dirs, l_nondirs in os.walk(d):
1510               lev=len(root.split(osp.sep))
1511               if lev <= (level + maxdepth):
1512                  if dnew.get(root) is None:
1513                     new.append(root)
1514                     dnew[root] = 1
1515               else:
1516                  del l_dirs[:] # empty dirs list so we don't walk needlessly
1517   return new
1518
1519#-------------------------------------------------------------------------------
1520#-------------------------------------------------------------------------------
1521#-------------------------------------------------------------------------------
1522class FIND_TOOLS:
1523   """Some utilities to find files, libraries...
1524   """
1525   _fmt_search  = _('Checking for %s... ')
1526   _fmt_test    = _('Checking if %s... ')
1527   _fmt_chkpref = _('Checking prefix for %s (%s)... ')
1528   _fmt_home    = _('adjust to %s')
1529   _fmt_locate  = _('(locate)... ')
1530   _fmt_yes     = _('yes')
1531   _fmt_no      = _('no')
1532   dict_rearch = {
1533      'x86'    : 'shell|script|80.86',
1534      'i86pc'  : 'shell|script|80.86',
1535      'x86_64' : 'shell|script|x86.64',
1536      'ppc64'  : 'shell|script|64-bit PowerPC',
1537      'ia64'   : 'shell|script|ia.64',
1538   }
1539#-------------------------------------------------------------------------------
1540   def __init__(self, **kargs):
1541      self._print       = kargs.get('log', None)._print
1542      self.maxdepth     = kargs.get('maxdepth', 0)
1543      self.use_locate   = kargs.get('use_locate', False)
1544      self.prefshared   = kargs.get('prefshared', False)
1545      self.paths        = {
1546         'bin'    : [],
1547         'lib'    : [],
1548         'inc'    : [],
1549         'pymod'  : [],
1550      }
1551      self.std_dirs     = {
1552         'bin'    : _clean_path(kargs.get('bindirs', []), returns='list'),
1553         'lib'    : _clean_path(kargs.get('libdirs', []), returns='list'),
1554         'inc'    : _clean_path(kargs.get('incdirs', []), returns='list'),
1555         'pymod'  : [],
1556      }
1557      self.tag2var = {
1558         'bin'   : 'PATH',
1559         'lib'   : 'LD_LIBRARY_PATH',
1560         'inc'   : 'INCLUDEPATH',   # should not be used
1561         'pymod' : 'PYTHONPATH',
1562      }
1563      self.var2tag = {}
1564      for k, v in list(self.tag2var.items()):
1565         self.var2tag[v] = k
1566      for k in ('bin', 'lib', 'inc', 'pymod'):
1567         self._print("std_dirs['%s'] =" % k, self.std_dirs[k], DBG=True)
1568
1569      self._ext_static  = '.a'
1570      self._ext_shared  = '.so'
1571      if sys.platform in ("win32", "cygwin"):
1572          self._ext_static = self._ext_shared = '.lib'  # but not yet supported!
1573      elif sys.platform == 'darwin':
1574          self._ext_shared = '.dylib'
1575
1576      self.debug        = kargs.get('debug', False)
1577      self.Shell        = kargs['system'].local_shell
1578      self._tmpdir = kargs['system']._tmpdir
1579      self.arch         = kargs.get('arch', 'default')
1580      self.home_checked = []  # store vars which have already been checked
1581      self.noerror      = kargs.get('noerror', False)
1582      self._last_found  = None
1583      self._cache   = { 'bin' : {}, 'lib' : {}, 'inc' : {} }
1584      self._print('maxdepth = %s' % self.maxdepth, DBG=True)
1585      self.nbcpu = 1
1586      # "file" command
1587      self._command_file = None
1588      self._command_ar   = None
1589      self.configure_command_file()
1590
1591   def clear_temporary_folder(self):
1592      os.system('rm -rf %s' % self._tmpdir)
1593
1594#-------------------------------------------------------------------------------
1595   def configure_command_file(self):
1596      """Fill 'file' command arguments."""
1597      cmd = self.find_file('file', typ='bin')
1598      if cmd is None: # 'file' command not found !
1599         return
1600      # check for --dereference argument
1601      for arg in ('--dereference', '-L'):
1602         iret, out = self.Shell('%s %s %s' % (cmd, arg, cmd), verbose=self.debug)
1603         if iret == 0:
1604            cmd = '%s %s' % (cmd, arg)
1605            break
1606      self._command_file = cmd
1607      self._command_ar = self.find_file('ar', typ='bin')
1608
1609#-------------------------------------------------------------------------------
1610   def check_type(self, filename, typ='bin', arch=None):
1611      """Check that 'filename' has the right type."""
1612      if arch is None:
1613         arch = self.arch
1614      # unable to call file or arch unknown
1615      if not self._command_file or arch == 'default':
1616         return True
1617      if not typ in ('bin', 'lib'):
1618         return True
1619      # search arch using regexp
1620      re_arch = re.compile(self.dict_rearch[arch], re.IGNORECASE)
1621      iret, out = self.Shell('%s %s' % (self._command_file, filename), verbose=self.debug)
1622      if iret != 0:
1623         self._print('ERROR <check_type>: %s' % out)
1624         return False
1625      else:
1626         if typ == 'lib':
1627            # ascii text : GROUP (list of libs) !
1628            if re.search('ascii', out, re.IGNORECASE) != None:
1629               content = open(filename, 'r').read()
1630               mlib = re.search('GROUP.*\((.*?)\)', content, re.IGNORECASE)
1631               if mlib:
1632                  dirn = osp.dirname(filename)
1633                  llibs = mlib.group(1).strip().split()
1634                  self._print('GROUP lib found, test %s' % llibs, DBG=True)
1635                  ok = True
1636                  for lib in llibs:
1637                     if not osp.exists(osp.join(dirn, lib)):
1638                        if lib[:2] == '-l':
1639                           lib = 'lib' + lib[2:]
1640                     lib = osp.join(dirn, lib)
1641                     if not osp.exists(lib):
1642                        libtest = [lib + self._ext_static, lib + self._ext_shared]
1643                     else:
1644                        libtest = [lib,]
1645                     elem = False
1646                     for lib in libtest:
1647                        elem = self.check_type(lib, typ, arch)
1648                        if elem: break
1649                     if not elem:
1650                        ok = False
1651                        break
1652                  return ok
1653
1654            # dynamic lib : pass, static lib : extract the first object
1655            if re.search('archive', out, re.IGNORECASE) != None:
1656               # keep few lines and egrep .o to be sure to ignore comment or head line...
1657               jret, out2 = self.Shell('%s t %s | head -5 | egrep "\.o"' \
1658                        % (self._command_ar, filename), verbose=self.debug)
1659               if jret == 0:
1660                  prev = os.getcwd()
1661                  os.chdir(self._tmpdir)
1662                  doto = out2.splitlines()[0]
1663                  jret, out2 = self.Shell('%s x %s %s' % (self._command_ar, filename, doto),
1664                                          verbose=self.debug)
1665                  jret, out = self.Shell('%s %s' % (self._command_file, doto),
1666                                          verbose=self.debug)
1667                  os.chdir(prev)
1668      mat = re_arch.search(out)
1669      if mat is None:
1670         self._print('invalid type (%s): %s // %s' % (typ, filename, out), DBG=True)
1671      return mat is not None
1672
1673#-------------------------------------------------------------------------------
1674   def check_compiler_name(self, compiler, name):
1675      """Returns True/False the 'compiler' matches 'name'.
1676      """
1677      self._print(self._fmt_test % ('%s is %s' % (compiler, name)), term='')
1678      iret, out, outspl = self.get_product_version(compiler)
1679      res = False
1680      comment = ''
1681      if len(outspl) >= 3:
1682         res = name.lower() in (outspl[0].lower(), outspl[1].lower())
1683         comment = '   %s version %s' % tuple(outspl[1:3])
1684      if res:
1685         self._print(self._fmt_yes, term='')
1686      else:
1687         self._print(self._fmt_no, term='')
1688      self._print(comment)
1689      self._print('check_compiler_name  out =', out, DBG=True)
1690      return res
1691
1692#-------------------------------------------------------------------------------
1693   def check_compiler_version(self, compiler):
1694      """Prints 'compiler' version.
1695      """
1696      self._print(self._fmt_search % 'compiler version', term='')
1697      iret, out, outspl = self.get_product_version(compiler)
1698      l_out = out.splitlines()
1699      if len(l_out) > 0:
1700         rep = l_out[0]
1701      else:
1702         rep = '?'
1703      self._print(rep)
1704      return rep, outspl
1705
1706
1707   def get_cpu_number(self):
1708      """Returns the number of processors."""
1709      self._print(self._fmt_search % 'number of processors (core)', term='')
1710      if sys.platform == 'darwin':
1711         try:
1712             self.nbcpu = int(os.popen('sysctl -n hw.ncpu').read())
1713         except ValueError:
1714             pass
1715      else:
1716         iret, out = self.Shell('cat /proc/cpuinfo', verbose=self.debug)
1717         exp = re.compile('^processor\s+:\s+([0-9]+)', re.MULTILINE)
1718         l_ids = exp.findall(out)
1719         if len(l_ids) > 1:      # else: it should not !
1720            self.nbcpu = max([int(i) for i in l_ids]) + 1
1721      nbcpu, self.nbcpu = self.nbcpu, min(self.nbcpu, 8)
1722      self._print('%d (will use: make -j %d)' % (nbcpu, self.nbcpu))
1723
1724
1725   def get_path_typ(self, dict_list, typ):
1726      """Returns dict_list[typ] or more."""
1727      res = dict_list.get(typ)
1728      if res is None:
1729         res = dict_list['bin'] + dict_list['lib'] + dict_list['inc']
1730      else:
1731         res = res[:]
1732      return res
1733
1734#-------------------------------------------------------------------------------
1735   def find_file(self, filenames, paths=None, maxdepth=None, silent=False,
1736                 addto=None, typ='all', with_locate=False):
1737      """Returns absolute path of the first of 'filenames' located
1738      in paths+std_dirs (so search from paths first) or None if no one was found.
1739      """
1740      self._last_found = None
1741      if not type(filenames) in EnumTypes:
1742         filenames=[filenames,]
1743      if paths is None:
1744         paths = []
1745      if not type(paths) in EnumTypes:
1746         paths=[paths,]
1747      # give maximum chances to 'paths'
1748      paths = paths[:]
1749      paths.extend(self.get_path_typ(self.paths, typ))
1750      if maxdepth==None:
1751         maxdepth=self.maxdepth
1752      for name in filenames:
1753         if not silent:
1754            if not with_locate:
1755               self._print(self._fmt_search % name, term='')
1756            else:
1757               self._print(self._fmt_locate, term='')
1758         # Check the user's directories and then standard locations
1759         std_dirs = self.get_path_typ(self.std_dirs, typ)
1760         self._print('search_dirs : %s' % repr(paths), DBG=True)
1761         search_dirs=_getsubdirs(paths, std_dirs, maxdepth)
1762         if self.debug:
1763            self._print('search_dirs : \n%s' % os.linesep.join(search_dirs), DBG=True)
1764         for dir in search_dirs:
1765            f = osp.join(dir, name)
1766            if osp.exists(f):
1767               self._last_found = osp.abspath(f)
1768               chk = self.check_type(self._last_found, typ=typ)
1769               if chk:
1770                  if not silent:
1771                     self._print(self._last_found)
1772                  return self._last_found
1773         # try this 'name' using locate
1774         ldirs = self.locate(name, addto=addto, typ=typ)
1775         if len(ldirs) > 0 and not with_locate:
1776            found = self.find_file(name, ldirs, maxdepth, silent, typ=typ, with_locate=True)
1777            if found:
1778               return found
1779         elif not silent:
1780            self._print(self._fmt_no)
1781         # not found try next item of 'filenames'
1782      # Not found anywhere
1783      return None
1784
1785#-------------------------------------------------------------------------------
1786   def locate(self, filenames, addto=None, typ='bin'):
1787      """Returns dirname (none, one or more, always as list) of 'filename'.
1788      If addto != None, addto dirnames into 'addto'.
1789      """
1790      dnew = []
1791      if not self.use_locate:
1792         return dnew
1793      if addto is None:
1794         addto = []
1795      assert type(addto) is list, _('"addto" argument must be a list !')
1796      if not type(filenames) in (list, tuple):
1797         filenames=[filenames,]
1798      for name in filenames:
1799         iret, out = self.Shell('locate %s | egrep "/%s$"' % (name, name),
1800                           verbose=self.debug)
1801         if iret == 0:
1802            dirn = [osp.dirname(d) for d in out.splitlines()]
1803            for f in out.splitlines():
1804               if typ != 'bin' or (os.access(f, os.X_OK) and osp.isfile(f)):
1805                  d = osp.dirname(f)
1806                  if not d in addto:
1807                     dnew.append(d)
1808      addto.extend(dnew)
1809      return dnew
1810
1811#-------------------------------------------------------------------------------
1812   def find_and_set(self, cfg, var, filenames, paths=None, err=True,
1813         typ='bin', append=False, maxdepth=None, silent=False, prefix_to=None,
1814         reqpkg=None):
1815      """Uses find_file to search 'filenames' in paths.
1816      - If 'err' is True, raises SetupConfigureError if no one was found and
1817        print a message in reqpkg is set.
1818      - Value set in cfg[var] depends on 'typ' :
1819         typ='bin' : cfg[var]='absolute filename found'
1820         typ='inc' : cfg[var]='-Idirname'
1821         typ='lib' : cfg[var]='static lib' name or '-Ldirname -llib'
1822      - If cfg[var] already exists :
1823         if 'append' is False, cfg[var] is unchanged ('previously set' message)
1824         if 'append' is True, new found value is appended to cfg[var]
1825      - prefix_to allows to adjust the value with _real_home
1826        (only used for includes).
1827
1828      Returns:
1829         str: Path of the filename found, *None* otherwise.
1830      """
1831      conv = { 'bin' : 'bin', 'inc' : 'include', 'lib' : 'lib' }
1832      if not type(filenames) in EnumTypes:
1833         filenames=[filenames,]
1834      if maxdepth==None:
1835         maxdepth=self.maxdepth
1836      # insert in first place 'cfg[var]'/"type"
1837      if paths is None:
1838         paths = []
1839      if not type(paths) in EnumTypes:
1840         paths=[paths,]
1841      # give maximum chances to 'paths'
1842      paths_in = paths[:]
1843      for p in paths:
1844         paths_in.append(osp.join(p, conv[typ]))
1845      paths = paths_in
1846      self._print('find_and_set %s' % filenames, DBG=True)
1847      if var in cfg:
1848         paths.insert(0, osp.join(cfg[var], conv[typ]))
1849         paths.insert(0, osp.dirname(cfg[var]))
1850      if var not in cfg or append:
1851         found = self.find_file(filenames, paths, maxdepth, silent, typ=typ)
1852         adj = self._real_home(found, prefix_to)
1853         if found is None:
1854            if var in cfg and not append:
1855               del cfg[var]
1856            elif var not in cfg and append:
1857               cfg[var]=''
1858            if err and not self.noerror:
1859                msg = get_install_message(package=reqpkg, missed=' or '.join(filenames))
1860                raise SetupConfigureError(msg)
1861         else:
1862            root, ext = osp.splitext(found)
1863            dirname   = osp.abspath(osp.dirname(root))
1864            basename  = osp.basename(root)
1865            value = found
1866            if typ == 'lib':
1867               if ext == self._ext_shared:
1868                  value = '-L%s -l%s' % (dirname, re.sub('^lib', '', basename))
1869            elif typ == 'inc':
1870               if adj != None:
1871                  dirname = adj
1872               value = '-I%s' % dirname
1873            if var not in cfg or not append:
1874               cfg[var] = value
1875            elif cfg[var].find(value) < 0:
1876               cfg[var] = (cfg[var] + ' ' + value).strip()
1877            self.AddToPath(typ, value)
1878      else:
1879         found = None
1880         self._print(self._fmt_search % ' or '.join(filenames), term='')
1881         self._print(_("%s (previously set)") % cfg[var])
1882         # append = False donc on peut appeler AddToPath
1883         self.AddToPath(typ, cfg[var])
1884      return found
1885
1886#-------------------------------------------------------------------------------
1887   def findlib_and_set(self, cfg, var, libname, paths=None,
1888                    err=True, append=False, prefshared=None,
1889                    maxdepth=None, silent=False, prefix_to=None, reqpkg=None):
1890      """Same as find_and_set but expands 'libname' as static and shared
1891      library filenames.
1892
1893      Returns:
1894         str: Path of the filename found, *None* otherwise.
1895      """
1896      l_exts=[self._ext_static, self._ext_shared]
1897      if maxdepth==None:
1898         maxdepth=self.maxdepth
1899      if prefshared==None:
1900         prefshared=self.prefshared
1901      if prefshared:
1902         l_exts.reverse()
1903      if not type(libname) in EnumTypes:
1904         libname=[libname,]
1905      if paths is None:
1906         paths = []
1907      if not type(paths) in EnumTypes:
1908         paths=[paths,]
1909      l_names = []
1910      for name in libname:
1911         found = self._cache['lib'].get(name)
1912         if found:
1913            self._print(self._fmt_search % name, term='')
1914            self._print(_("%s (already found)") % found)
1915            if var not in cfg or not append:
1916               cfg[var] = found
1917            elif cfg[var].find(found) < 0:
1918               cfg[var] = (cfg[var] + ' ' + found).strip()
1919            return
1920         if name.find('/') > 0 or name.find('.') > 0:
1921            l_names.append(name)
1922         l_names.extend(['lib'+name+ext for ext in l_exts])
1923      #XXX perhaps we should add the same paths with lib64 first on 64 bits platforms ?
1924      pathlib = [osp.join(p, 'lib') for p in paths]
1925      paths = pathlib + paths
1926      return self.find_and_set(cfg, var, l_names, paths, err, 'lib', append, maxdepth,
1927                               silent, prefix_to, reqpkg)
1928
1929#-------------------------------------------------------------------------------
1930   def AddToCache(self, typ, var, value):
1931      """Store value to cache (only for libs)."""
1932      assert typ == 'lib'
1933      self._cache[typ][var] = value
1934
1935#-------------------------------------------------------------------------------
1936   def AddToPath(self, typ, var):
1937      """Get the directory of object of type 'typ' from 'var', and add it
1938      to corresponding paths items.
1939      'var' is supposed to be a result of find_and_set.
1940      """
1941      if not type(var) in StringTypes:
1942         return
1943      toadd=[]
1944      if   typ=='bin':
1945         for v in var.split():
1946            toadd.append(osp.dirname(v))
1947      elif typ=='lib':
1948         for v in var.split():
1949            if   v[:2]=='-L':
1950               toadd.append(v[2:])
1951            elif v[:2]=='-l':
1952               pass
1953            elif osp.isabs(v):
1954               toadd.append(osp.dirname(v))
1955      elif typ=='inc':
1956         for v in var.split():
1957            if v[:2]=='-I':
1958               toadd.append(v[2:])
1959            elif osp.isabs(v):
1960               if v.endswith('.h'):
1961                  toadd.append(osp.dirname(v))
1962               else:
1963                  toadd.append(v)
1964      elif typ=='pymod':
1965         for v in var.split():
1966            if osp.isabs(v):
1967               toadd.append(v)
1968      else:
1969         raise SetupError(_('unexpected type %s') % repr(typ))
1970      self._print("AddToPath .paths['%s'] =" % typ, self.paths[typ], DBG=True)
1971      for elt in toadd:
1972         if elt != '' and not elt in self.paths[typ]:
1973            if elt in self.std_dirs[typ]:
1974               self.paths[typ].append(elt)
1975               self._print('AddToPath OUT append %s : %s' % (typ, elt), DBG=True)
1976            else:
1977               self.paths[typ].insert(0, elt)
1978               self._print('AddToPath OUT insert %s : %s' % (typ, elt), DBG=True)
1979
1980#-------------------------------------------------------------------------------
1981   def GetPath(self, typ, add_cr=False):
1982      """Returns a representation of paths[typ] to set an environment variable.
1983      """
1984      if typ not in self.paths:
1985         raise SetupError(_('unexpected type %s') % repr(typ))
1986      res = _clean_path(self.paths[typ])
1987      if add_cr:
1988         res = res.replace(':', ':\\' + os.linesep)
1989      return res
1990
1991#-------------------------------------------------------------------------------
1992   def AddToPathVar(self, dico, key, new, export_to_env=True):
1993      """Add a path to a variable. If `export_to_env` is True, add the value
1994      into environment.
1995      """
1996      value_in = dico.get(key, '')
1997      if export_to_env:
1998         value_in = os.environ.get(key, '') + ':' + value_in
1999      lpath = value_in.split(':')
2000      if new is None:
2001         new = self.GetPath(self.var2tag[key]).split(':')
2002      else:
2003         self.AddToPath(self.var2tag[key], new)
2004      if type(new) not in (list, tuple):
2005         new = [new,]
2006      for p in new:
2007         if not p in lpath:
2008            lpath.append(p)
2009      dico[key] = _clean_path(lpath)
2010      if export_to_env:
2011         os.environ[key] = dico[key]
2012         self._print('AddToPathVar set %s = %s' % (key, dico[key]), DBG=True)
2013
2014#-------------------------------------------------------------------------------
2015   def GccPrintSearchDirs(self, compiler):
2016      """Use 'compiler' --print-search-dirs option to add some reliable paths.
2017      """
2018      self._print(self._fmt_search % '%s configured installation directory' % compiler, term='')
2019      iret, out = self.Shell('%s -print-search-dirs' % compiler, verbose=self.debug)
2020      lines = out.splitlines()
2021      ldec = []
2022      for lig in lines:
2023         ldec.extend(re.split('[=: ]+', lig))
2024      # le prefix est normalement sur la premiere ligne,
2025      # on essaie de ne garder que celui-ci
2026      ldirs = [osp.normpath(p) for p in ldec if p.startswith(os.sep)][:1]
2027      if len(ldirs) > 0:
2028         pref = ldirs[0]
2029         self.AddToPath('bin', osp.join(pref, 'bin'))
2030         self.AddToPath('lib', osp.join(pref, 'lib'))
2031         self.AddToPath('bin', compiler)
2032         prefbin = osp.dirname(compiler)
2033         if prefbin != pref:
2034            self.AddToPath('lib', prefbin)
2035            pref = ', '.join([pref, prefbin])
2036      else:
2037         pref = 'not found'
2038      self._print(pref)
2039
2040#-------------------------------------------------------------------------------
2041   def CheckFromLastFound(self, cfg, var, search):
2042      """Call _real_home and set the value in cfg[var].
2043      """
2044      val_in = self._last_found or ''
2045      self._print(self._fmt_chkpref % (var, val_in), term='')
2046      if val_in == '':
2047         self._print(_('unchanged'))
2048         return
2049      res = self._real_home(val_in, search)
2050      if search == 'lib' and self.arch.find('64') > -1:
2051          res64 = self._real_home(val_in, 'lib64')
2052          if res64:
2053              if res and len(res64) > len(res):
2054                  res = res64
2055      if res != None:
2056         self._print(self._fmt_home % res)
2057         cfg[var] = res
2058      else:
2059         self._print(_('%s not found in %s') % (search, val_in))
2060
2061#-------------------------------------------------------------------------------
2062   def _real_home(self, path, search):
2063      """Checks that the path contains 'search' (starting from the end of 'path').
2064      Return the parent of 'search' if found or None.
2065      """
2066      if not (type(path) in StringTypes and type(search) in StringTypes):
2067         return None
2068      p = path.split(os.sep)
2069      np = len(p)
2070      s = search.split(os.sep)
2071      ns = len(s)
2072      val = None
2073      for i in range(np):
2074         if p[np-1-i:np-1-i+ns] == s:
2075            val = os.sep.join(p[:np-1-i])
2076            break
2077      return val
2078
2079#-------------------------------------------------------------------------------
2080   def pymod_exists(self, module):
2081      """Checks if `module` is installed.
2082      """
2083      return self.check(partial(check_pymodule, module), module)
2084
2085#-------------------------------------------------------------------------------
2086   def check(self, func, name, silent=False, format='search'):
2087      """Execute a function for checking 'name'. 'func' returns True/False.
2088      Used to have the same output as searching files.
2089      """
2090      if not silent:
2091         if format == 'test':
2092            fmt = self._fmt_test
2093         else:
2094            fmt = self._fmt_search
2095         self._print(fmt % name, term='')
2096      # boolean or function
2097      if func is None:
2098         self._print()
2099         return None
2100      elif type(func) is str:
2101         self._print(func)
2102         return None
2103      elif type(func) is bool:
2104         response = func
2105      else:
2106         try:
2107            response = func()
2108         except None:
2109            response = False
2110      if response:
2111         self._print(self._fmt_yes)
2112      else:
2113         self._print(self._fmt_no)
2114      return response
2115
2116#-------------------------------------------------------------------------------
2117   def get_product_version(self, product, arg='--version'):
2118      """Returns the output of 'product' --version.
2119      """
2120      command = '%s %s' % (product, arg)
2121      iret, out = self.Shell(command, verbose=False)
2122      expr = re.compile('(.*)[ \t]+\((.*)\)[ \t]+(.*?)[ \(].*', re.MULTILINE)
2123      mat = expr.search(out)
2124      if mat is not None:
2125         outspl = mat.groups()
2126      else:
2127         outspl = out.split()
2128      return iret, out, outspl
2129
2130
2131def less_than_version(vers1, vers2):
2132   return version2tuple(vers1) < version2tuple(vers2)
2133
2134
2135def version2tuple(vers_string):
2136   """1.7.9alpha --> (1, 7, 9, 'alpha'), 1.8 --> (1, 8, 0, 'final')"""
2137   tupl0 = vers_string.split('.')
2138   val = []
2139   for v in tupl0:
2140      m = re.search('(^[ 0-9]+)(.*)', v)
2141      if m:
2142         val.append(int(m.group(1)))
2143         if m.group(2):
2144            val.append(m.group(2).replace('-', '').replace('_', '').strip())
2145      else:
2146         val.append(v)
2147   val.extend([0]*(3-len(val)))
2148   if type(val[-1]) is int:
2149      val.append('final')
2150   return tuple(val)
2151
2152
2153def export_parameters(setup_object, dict_cfg, filename, **kwargs):
2154   """Export config dict into filename."""
2155   content = """parameters = %s""" % pprint.pformat(dict_cfg)
2156   print(content)
2157   print(filename)
2158   open(filename, 'w').write(content)
2159
2160
2161def check_pymodule(name, path=None):
2162    """Check if a python module exists."""
2163    exists = True
2164    lmod = name.split('.')
2165    curmod = lmod[0]
2166    try:
2167        res = imp.find_module(curmod, path)
2168    except Exception:
2169        return False
2170    if len(lmod) > 1:
2171        try:
2172            curobj = imp.load_module(curmod, *res)
2173            exists = check_pymodule('.'.join(lmod[1:]), path=curobj.__path__)
2174        except:
2175            exists = False
2176    return exists
2177
2178
2179def get_absolute_path(path, follow_symlink=True):
2180    """Retourne le chemin absolu en suivant les liens éventuels.
2181    """
2182    if follow_symlink and osp.islink(path):
2183        path = osp.realpath(path)
2184    res = osp.normpath(osp.abspath(path))
2185    return res
2186
2187
2188def relative_symlink(src, dst, follow_symlink=False, force=False):
2189    """Create a symbolic link pointing to src named dst.
2190    Remove the commun part between dirnames to make the symlink as
2191    relative as possible.
2192    """
2193    src = get_absolute_path(src, follow_symlink=follow_symlink)
2194    dst = get_absolute_path(dst, follow_symlink=follow_symlink)
2195    lsrc = src.split(os.sep)
2196    ldst = dst.split(os.sep)
2197    common = []
2198    while len(lsrc) > 0 and len(ldst) > 0:
2199        psrc = lsrc.pop(0)
2200        pdst = ldst.pop(0)
2201        if psrc != pdst:
2202            lsrc = [os.pardir,] * len(ldst) + [psrc,] + lsrc
2203            src = os.sep.join(lsrc)
2204            break
2205        common.append(psrc)
2206    #print ">>>", src, dst, os.sep.join(common)
2207    if force and osp.exists(dst):
2208        os.remove(dst)
2209    os.symlink(src, dst)
2210
2211
2212def unexpandvars_string(text, vars=None):
2213    """Reverse of os.path.expandvars."""
2214    if vars is None:
2215        vars = ('ASTER_VERSION_DIR', 'ASTER_ETC', 'ASTER_ROOT', 'HOME')
2216    if type(text) is not str:
2217        return text
2218    for var in vars:
2219        if not os.environ.get(var):
2220            continue
2221        text = re.sub('%s( |\/|$)' % re.escape(os.environ[var]),
2222                      '$%s\\1' % var, text)
2223    return text
2224
2225def unexpandvars_list(enum, vars=None):
2226    """Unexpand all values of ``enum``."""
2227    new = []
2228    for val in enum:
2229        new.append(unexpandvars(val, vars))
2230    return new
2231
2232def unexpandvars_tuple(enum, vars=None):
2233    """Unexpand all values of ``enum``."""
2234    return tuple(unexpandvars_list(enum, vars))
2235
2236def unexpandvars_dict(dico, vars=None):
2237    """Unexpand all values of ``dico``."""
2238    new = {}
2239    for key, val in list(dico.items()):
2240        new[key] = unexpandvars(val, vars)
2241    return new
2242
2243def unexpandvars(obj, vars=None):
2244    """Unexpand the value of ``obj`` according to its type."""
2245    dfunc = {
2246        list : unexpandvars_list,
2247        tuple : unexpandvars_tuple,
2248        dict : unexpandvars_dict,
2249    }
2250    return dfunc.get(type(obj), unexpandvars_string)(obj, vars)
2251
2252def check_varname(vname):
2253   """Check that `vname`` is a valid Python variable name."""
2254   d = {}
2255   try:
2256      exec((vname + ' = 0'), d)
2257   except Exception as msg:
2258      raise SetupError(_(' invalid variable name %s' % repr(vname)))
2259