File: package.py

package info (click to toggle)
slepc 3.24.1%2Bdfsg1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 122,028 kB
  • sloc: ansic: 104,353; javascript: 12,732; python: 5,958; f90: 3,312; cpp: 1,528; makefile: 761; xml: 679; sh: 347
file content (516 lines) | stat: -rw-r--r-- 21,726 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
#
#  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#  SLEPc - Scalable Library for Eigenvalue Problem Computations
#  Copyright (c) 2002-, Universitat Politecnica de Valencia, Spain
#
#  This file is part of SLEPc.
#  SLEPc is distributed under a 2-clause BSD license (see LICENSE).
#  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#

from __future__ import print_function
import os, sys, tempfile, shutil, tarfile
import log, argdb
from urllib.request import urlretrieve
from urllib import parse as urlparse_local
import subprocess
import socket
from shutil import which  # just to break compatibility with python2

# Fix parsing for nonstandard schemes
urlparse_local.uses_netloc.extend(['bk', 'ssh', 'svn'])

class Package:

  packages = []    # list of packages processed and added so far

  def __init__(self,argdb,log):
    self.installable     = False  # an already installed package can be picked --with-xxx-dir
    self.downloadable    = False  # package can be downloaded and installed with --download-xxx
    self.downloadpackage = 0
    self.packagetype     = ''     # can be 'gnu', 'cmake', 'source_c', or empty
    self.packagedir      = ''
    self.packagelibs     = ''
    self.packageincludes = ''
    self.packageurl      = ''
    self.buildflags      = ''
    self.log             = log
    self.supportsscalar  = ['real', 'complex']
    self.supportssingle  = False
    self.supports64bint  = False
    self.fortran         = False
    self.hasheaders      = False
    self.requested       = False
    self.havepackage     = False

  def RunCommand(self,instr):
    try:
      self.log.write('- '*35+'\nRunning command:\n'+instr+'\n'+'- '*35)
    except AttributeError: pass
    try:
      output = subprocess.check_output(instr,shell=True,stderr=subprocess.STDOUT)
      result = 0
    except subprocess.CalledProcessError as ex:
      output = ex.output
      result = ex.returncode
    output = output.decode(encoding='UTF-8',errors='replace').rstrip()
    try:
      self.log.write('Output:\n'+output+'\n'+'- '*35)
    except AttributeError: pass
    return (result,output)

  def ProcessArgs(self,argdb,petscpackages=''):
    if hasattr(self,'petscdepend') and self.petscdepend in petscpackages:
      self.requested = True
    if self.installable and not hasattr(self,'petscdepend'):
      string,found = argdb.PopPath('with-'+self.packagename+'-dir',exist=True)
      if found:
        self.requested = True
        self.packagedir = string
      string,found = argdb.PopString('with-'+self.packagename+'-lib')
      if found:
        if self.packagedir:
          self.log.Exit('Specify either "--with-'+self.packagename+'-dir" or "--with-'+self.packagename+'-lib%s", but not both!' % (' --with-'+self.packagename+'-include' if self.hasheaders else ''))
        self.requested = True
        self.packagelibs = string
      if self.hasheaders:
        string,found = argdb.PopString('with-'+self.packagename+'-include')
        if found:
          self.requested = True
          self.packageincludes = string
    if self.installable:
      value,found = argdb.PopBool('with-'+self.packagename)
      if found:
        self.requested = value
    if self.downloadable:
      flagsfound = False
      if self.packagetype == 'gnu':
        string,flagsfound = argdb.PopString('download-'+self.packagename+'-configure-arguments')
      elif self.packagetype == 'cmake':
        string,flagsfound = argdb.PopString('download-'+self.packagename+'-cmake-arguments')
      elif self.packagetype == 'source_c':
        string,flagsfound = argdb.PopString('download-'+self.packagename+'-cflags')
      url,flag,found = argdb.PopUrl('download-'+self.packagename)
      if found:
        if self.requested:
          self.log.Exit('Cannot request both download and install simultaneously')
        self.requested = True
        self.download = True
        self.packageurl = url
        self.downloadpackage = flag
      if flagsfound:
        if not getattr(self,'download',False):
          if self.packagetype == 'gnu':
            self.log.Exit('--download-'+self.packagename+'-configure-arguments must be used together with --download-'+self.packagename)
          elif self.packagetype == 'cmake':
            self.log.Exit('--download-'+self.packagename+'-cmake-arguments must be used together with --download-'+self.packagename)
          elif self.packagetype == 'source_c':
            self.log.Exit('--download-'+self.packagename+'-cflags must be used together with --download-'+self.packagename)
        self.buildflags = string

  def Process(self,slepcconf,slepcvars,slepcrules,slepc,petsc,archdir=''):
    self.make = petsc.make
    if petsc.buildsharedlib:
      self.slflag = petsc.cc_linker_slflag
    if self.requested:
      name = self.packagename.upper()
      if self.downloadpackage:
        if hasattr(self,'version') and self.packageurl=='':
          self.log.NewSection('Installing '+name+' version '+self.version+'...')
        else:
          self.log.NewSection('Installing '+name+'...')
        self.Precondition(slepc,petsc)
        self.DownloadAndInstall(slepcconf,slepcvars,slepc,petsc,archdir,slepc.prefixdir)
      elif self.installable:
        self.log.NewSection('Checking '+name+'...')
        self.Precondition(slepc,petsc)
        if petsc.buildsharedlib:
          self.packagelibs = self.DistilLibList(self.packagelibs,petsc)
        self.Check(slepcconf,slepcvars,petsc,archdir)
        if not self.havepackage: self.log.setLastFailed()
      try:
        self.LoadVersion(slepcconf)
        self.log.write('Version number for '+name+' is '+self.iversion)
      except AttributeError:
        pass
      if self.havepackage: Package.packages.append(self)
    else: # not requested
      if hasattr(self,'SkipInstall'):
        self.SkipInstall(slepcrules)

  def Precondition(self,slepc,petsc):
    package = self.packagename.upper()
    if petsc.scalar == 'complex':
      if 'complex' not in self.supportsscalar:
        self.log.Exit(package+' does not support complex scalars')
    elif petsc.scalar == 'real':
      if 'real' not in self.supportsscalar:
        self.log.Exit(package+' is supported only with complex scalars')
    if petsc.precision == 'single':
      if not self.supportssingle:
        self.log.Exit(package+' is supported only in double precision')
    elif petsc.precision != 'double':
      self.log.Exit('Precision '+petsc.precision+' is not supported for external packages')
    if petsc.ind64 and not self.supports64bint:
      self.log.Exit(package+' cannot be used with 64-bit integers')
    if self.downloadpackage and self.fortran and not hasattr(petsc,'fc'):
      self.log.Exit('Option --download-'+self.packagename+' requires a Fortran compiler')

  def Require(self,packagename):
    for p in Package.packages:
      if p.packagename.upper() == packagename.upper():
        return p
    self.log.Exit('The package '+self.packagename.upper()+' requires configuring also with '+packagename.upper()+". Run configure --help for details on how to install it")

  def DistilLibList(self,packagelibs,petsc):
    libs = []
    for l in packagelibs.split():
      if l.endswith(petsc.sl_linker_suffix):
        filename = os.path.basename(l)
        libs.append('-L'+l.rstrip(filename))
        libs.append(self.slflag+l.rstrip(filename))
        libs.append('-l'+filename.lstrip('lib').rstrip('.'+petsc.sl_linker_suffix))
      else:
        libs.append(l)
    newldflags = []
    newlibs = []
    dupflags = ['-L',self.slflag]
    for j in libs:
      # remove duplicate -L, -Wl,-rpath options - and only consecutive -l options
      if j in newldflags and any([j.startswith(flg) for flg in dupflags]): continue
      if newlibs and j == newlibs[-1]: continue
      if list(filter(j.startswith,['-l'])) or list(filter(j.endswith,['.lib','.a','.so','.o'])) or j in ['-Wl,-Bstatic','-Wl,-Bdynamic','-Wl,--start-group','-Wl,--end-group']:
        newlibs.append(j)
      else:
        newldflags.append(j)
    liblist = ' '.join(newldflags + newlibs)
    return liblist

  def GetArchiveName(self):
    '''Return name of archive after downloading'''
    if self.packageurl=='':
      archivename = self.archive
    else:
      parsed = urlparse_local.urlparse(self.packageurl)
      archivename = os.path.basename(parsed[2])
    if archivename[0] == 'v':
      archivename = archivename[1:]
    try:
      if archivename[0].isdigit() or int(archivename.split('.')[0],16):
        archivename = self.packagename+'-'+archivename
    except: pass
    return archivename

  def GetDirectoryName(self):
    '''Return name of the directory after extracting the tarball'''
    dirname = self.GetArchiveName()
    for suffix in ('.tar.gz','.tgz'):
      if dirname.endswith(suffix):
        dirname = dirname[:-len(suffix)]
    return dirname

  def MissingTarball(self,downloaddir):
    '''Check if tarball is missing in downloaddir'''
    if self.downloadable and getattr(self,'download',False):
      localFile = os.path.join(downloaddir,self.GetArchiveName())
      if not os.path.exists(localFile):
        url = self.packageurl
        if url=='':
          url = self.url
        return self.packagename+': '+url+' --> '+localFile

  def Download(self,externdir,downloaddir):
    # Quick return: check if source is already available
    if os.path.exists(os.path.join(externdir,self.GetDirectoryName())):
      self.log.write('Using '+os.path.join(externdir,self.GetDirectoryName()))
      return os.path.join(externdir,self.GetDirectoryName())

    if downloaddir:
      # Get tarball from download dir
      localFile = os.path.join(downloaddir,self.GetArchiveName())
      if not os.path.exists(localFile):
        self.log.Exit('Could not find file '+self.GetArchiveName()+' under '+downloaddir)
      url = localFile
      filename = os.path.basename(url)
    else:
      # Download tarball
      url = self.packageurl
      if url=='':
        url = self.url
      if os.path.exists(url):
        url = 'file:'+url
      filename = os.path.basename(urlparse_local.urlparse(url)[2])
      localFile = os.path.join(externdir,self.GetArchiveName())
      self.log.write('Downloading '+url+' to '+localFile)

      if os.path.exists(localFile):
        os.remove(localFile)
      try:
        sav_timeout = socket.getdefaulttimeout()
        socket.setdefaulttimeout(30)
        urlretrieve(url, localFile)
        socket.setdefaulttimeout(sav_timeout)
      except Exception as e:
        socket.setdefaulttimeout(sav_timeout)
        failureMessage = '''\
Unable to download package %s from: %s
* If URL specified manually - perhaps there is a typo?
* If your network is disconnected - please reconnect and rerun ./configure
* Or perhaps you have a firewall blocking the download
* You can run with --with-packages-download-dir=/adirectory and ./configure will instruct you what packages to download manually
* or you can download the above URL manually, to /yourselectedlocation/%s
  and use the configure option:
  --download-%s=/yourselectedlocation/%s
''' % (self.packagename.upper(), url, filename, self.packagename, filename)
        self.log.Exit(failureMessage)

    # Uncompress tarball
    extractdir = os.path.join(externdir,self.GetDirectoryName())
    self.log.write('Uncompressing '+localFile+' to directory '+extractdir)
    if os.path.exists(extractdir):
      for root, dirs, files in os.walk(extractdir, topdown=False):
        for name in files:
          os.remove(os.path.join(root,name))
        for name in dirs:
          os.rmdir(os.path.join(root,name))
    failureMessage = '''\
Downloaded package %s from: %s is not a tarball.
[or installed python cannot process compressed files]
* If you are behind a firewall - please fix your proxy and rerun ./configure
  For example at LANL you may need to set the environmental variable http_proxy (or HTTP_PROXY?) to  http://proxyout.lanl.gov
* You can run with --with-packages-download-dir=/adirectory and ./configure will instruct you what packages to download manually
* or you can download the above URL manually, to /yourselectedlocation/%s
  and use the configure option:
  --download-%s=/yourselectedlocation/%s
''' % (self.packagename.upper(), url, filename, self.packagename, filename)
    try:
      tf = tarfile.open(localFile)
    except tarfile.ReadError as e:
      self.log.Exit(str(e)+'\n'+failureMessage)
    else:
      if not tf: self.log.Exit(failureMessage)
      with tf:
        #git puts 'pax_global_header' as the first entry and some tar utils process this as a file
        firstname = tf.getnames()[0]
        if firstname == 'pax_global_header':
          firstmember = tf.getmembers()[1]
        else:
          firstmember = tf.getmembers()[0]
        # some tarfiles list packagename/ but some list packagename/filename in the first entry
        if firstmember.isdir():
          dirname = firstmember.name
        else:
          dirname = os.path.dirname(firstmember.name)
        tf.extractall(path=externdir)

    # fix file permissions for the untared tarballs
    try:
      # check if 'dirname' is set'
      if dirname:
        (result,output) = self.RunCommand('cd '+externdir+'; chmod -R a+r '+dirname+'; find '+dirname+r' -type d -name "*" -exec chmod a+rx {} \;')
        if dirname != self.GetDirectoryName():
          self.log.write('The directory name '+dirname+' is different from the expected one, renaming to '+self.GetDirectoryName())
          os.rename(os.path.join(externdir,dirname),os.path.join(externdir,self.GetDirectoryName()))
          dirname = self.GetDirectoryName()
      else:
        self.log.Warn('Could not determine dirname extracted by '+localFile+' to fix file permissions')
    except RuntimeError as e:
      self.log.Exit('Error changing permissions for '+dirname+' obtained from '+localFile+ ' : '+str(e))
    if not downloaddir:
      os.remove(localFile)
    return os.path.join(externdir,dirname)

  wd = 36

  def ShowHelp(self):
    wd = Package.wd
    if self.downloadable or self.installable:
      print(self.packagename.upper()+':')
    if self.downloadable:
      print(('  --download-'+self.packagename+'[=<fname>]').ljust(wd)+': Download and install '+self.packagename.upper())
      if self.packagetype == 'gnu':
        print(('  --download-'+self.packagename+'-configure-arguments=<flags>').ljust(wd)+': Indicate extra flags to configure '+self.packagename.upper())
      elif self.packagetype == 'cmake':
        print(('  --download-'+self.packagename+'-cmake-arguments=<flags>').ljust(wd)+': Indicate extra flags to build '+self.packagename.upper()+' with CMake')
      elif self.packagetype == 'source_c':
        print(('  --download-'+self.packagename+'-cflags=<flags>').ljust(wd)+': Indicate extra flags to compile '+self.packagename.upper())
    if self.installable:
      print(('  --with-'+self.packagename+'=<bool>').ljust(wd)+': Test for '+self.packagename.upper()+' (requires PETSc with %s)'%getattr(self,'petscdepend','').upper())
    if self.installable and not hasattr(self,'petscdepend'):
      print(('  --with-'+self.packagename+'-dir=<dir>').ljust(wd)+': Indicate the root directory of the '+self.packagename.upper()+' installation')
      print(('  --with-'+self.packagename+'-lib=<libraries>').ljust(wd)+': Indicate quoted list of libraries and link flags for '+self.packagename.upper())
      if self.hasheaders:
        print(('  --with-'+self.packagename+'-include=<dirs>').ljust(wd)+': Indicate the directory of the '+self.packagename.upper()+' include files')

  def ShowInfo(self):
    if self.havepackage:
      if hasattr(self,'version') and self.downloadpackage and self.packageurl=='':
        packagename = self.packagename.upper()+' version '+self.version
      else:
        packagename = self.packagename.upper()
      if hasattr(self,'petscdepend'):
        self.log.Println(packagename+' from %s linked by PETSc' % self.petscdepend.upper())
      elif hasattr(self,'packageflags'):
        self.log.Println(packagename+' library flags:')
        self.log.Println('  '+self.packageflags)
      else:
        self.log.Println(packagename+' installed')

  def Link(self,functions,callbacks,flags,givencode='',cflags='',clanguage='c',logdump=True):

    # Create temporary directory and makefile
    try:
      tmpdir = tempfile.mkdtemp(prefix='slepc-')
      if not os.path.isdir(tmpdir): os.mkdir(tmpdir)
    except:
      self.log.Exit('Cannot create temporary directory')
    try:
      with open(os.path.join(tmpdir,'makefile'),'w') as makefile:
        makefile.write('checklink: checklink.o\n')
        makefile.write('\t${CLINKER} -o checklink checklink.o ${LINKFLAGS} ${PETSC_SNES_LIB}\n')
        makefile.write('\t@${RM} -f checklink checklink.o\n')
        makefile.write('include '+os.path.join('${PETSC_DIR}','lib','petsc','conf','variables')+'\n')
        makefile.write('include '+os.path.join('${PETSC_DIR}','lib','petsc','conf','rules')+'\n')
        if cflags:
          if clanguage=='c++': makefile.write('CXXFLAGS='+cflags+'\n')
          else: makefile.write('CFLAGS='+cflags+'\n')
    except:
      self.log.Exit('Cannot create makefile in temporary directory')

    # Create source file
    if givencode == '':
      code = '#include "petscsnes.h"\n'
      for f in functions:
        code += 'PETSC_EXTERN int\n' + f + '();\n'

      for c in callbacks:
        code += 'int '+ c + '() { return 0; } \n'

      code += 'int main() {\n'
      code += 'PetscErrorCode ierr; Vec v; Mat m; KSP k;\n'
      code += 'ierr = PetscInitializeNoArguments();\n'
      code += 'ierr = VecCreate(PETSC_COMM_WORLD,&v);\n'
      code += 'ierr = MatCreate(PETSC_COMM_WORLD,&m);\n'
      code += 'ierr = KSPCreate(PETSC_COMM_WORLD,&k);\n'
      code += '(void)ierr;\n'
      for f in functions:
        code += f + '();\n'
      code += 'return 0;\n}\n'
    else:
      code = givencode

    with open(os.path.join(tmpdir,'checklink.cxx' if clanguage=='c++' else 'checklink.c'),'w') as cfile:
      cfile.write(code)
    if logdump:
      try:
        self.log.write('- '*35+'\nChecking link with code:\n')
        self.log.write(code)
      except AttributeError: pass

    # Try to compile test program
    (result, output) = self.RunCommand('cd ' + tmpdir + ';' + self.make + ' checklink LINKFLAGS="'+flags+'"')
    shutil.rmtree(tmpdir)

    if result:
      return (0,code + output)
    else:
      return (1,code + output)

  def FortranLink(self,functions,callbacks,flags):
    f = []
    for i in functions:
      f.append(i+'_')
    c = []
    for i in callbacks:
      c.append(i+'_')
    (result, output1) = self.Link(f,c,flags,logdump=False)
    output1 = '\n====== With underscore Fortran names\n' + output1
    if result: return ('UNDERSCORE',output1)

    f = []
    for i in functions:
      f.append(i.upper())
    c = []
    for i in callbacks:
      c.append(i.upper())
    (result, output2) = self.Link(f,c,flags,logdump=False)
    output2 = '\n====== With capital Fortran names\n' + output2
    if result: return ('CAPS',output2)

    output = '\n=== With linker flags: '+flags
    return ('',output + output1 + output2)

  def GenerateGuesses(self,name,archdir,word='lib'):
    installdirs = [os.path.join(os.path.sep,'usr','local'),os.path.join(os.path.sep,'opt')]
    if 'HOME' in os.environ:
      installdirs.insert(0,os.environ['HOME'])

    dirs = []
    for i in installdirs:
      dirs = dirs + [os.path.join(i,word)]
      for d in [name,name.upper(),name.lower()]:
        dirs = dirs + [os.path.join(i,d)]
        dirs = dirs + [os.path.join(i,d,word)]
        dirs = dirs + [os.path.join(i,word,d)]

    for d in dirs[:]:
      if not os.path.exists(d):
        dirs.remove(d)
    dirs = [''] + dirs + [os.path.join(archdir,word)]
    return dirs

  def FortranLib(self,slepcconf,slepcvars,dirs,libs,functions,callbacks = []):
    name = self.packagename.upper()
    mangling = ''
    if isinstance(libs, str): # user-provided string with link options
      flags = libs
      (mangling, output) = self.FortranLink(functions,callbacks,flags)
      error = output
    else:
      error = ''
      flags = ''
      for d in dirs:
        for l in libs:
          if d:
            if hasattr(self,'slflag'):
              flags = ' '.join([self.slflag + d] + ['-L' + d] + l)
            else:
              flags = ' '.join(['-L' + d] + l)
          else:
            flags = ' '.join(l)
          (mangling, output) = self.FortranLink(functions,callbacks,flags)
          error += output
          if mangling: break
        if mangling: break

    if mangling:
      self.log.write(output)
    else:
      self.log.write(error)
      self.log.Exit('Unable to link with '+name+' library in directories '+' '.join(dirs)+' with libraries and link flags '+flags)

    slepcconf.write('#define SLEPC_HAVE_' + name + ' 1\n#define SLEPC_' + name + '_HAVE_'+mangling+' 1\n')
    if not hasattr(self,'petscdepend'):
      slepcvars.write(name + '_LIB = '+flags+'\n')
      self.libflags = flags
      self.packageflags = flags
    self.havepackage = True

  def WriteMakefile(self,fname,builddir,cont):
    self.log.write('Using makefile:\n')
    self.log.write(cont)
    with open(os.path.join(builddir,fname),'w') as mfile:
      mfile.write(cont)

  def DefaultIncludePath(self,petsc,file):
    (result,output) = self.RunCommand('echo | '+petsc.cpp+' -Wp,-v -')
    if not result:
      import re
      dirs = re.findall('^ .*',output,re.MULTILINE)
      for s in dirs:
        d = s[1:]
        if os.path.isfile(os.path.join(d,file)):
          self.log.write('Found '+os.path.join(d,file))
          return d
    return '/usr/include'