xref: /petsc/config/BuildSystem/config/libraries.py (revision ede9db9363e1fdaaa09befd664c8164883ccce80)
1import config.base
2
3import os
4import re
5
6class Configure(config.base.Configure):
7  def __init__(self, framework, libraries = []):
8    config.base.Configure.__init__(self, framework)
9    self.headerPrefix  = ''
10    self.substPrefix   = ''
11    self.libraries     = libraries
12    self.rpathSkipDirs = [] # do not generate RPATH for dirs in this list; useful when compiling with stub libraries (.so) that do not have corresponding runtime library (.so.1) at this location. Check cuda.py for usage.
13    self.sysDirs       = ['/usr/lib','/lib','/usr/lib64','/lib64'] # skip conversion from full path to link line argument format for libraries in these dirs. For ex: some compilers internally use /usr/lib/libm.so that should not be converted to '-L/usr/lib -lm'
14    return
15
16  def setupDependencies(self, framework):
17    config.base.Configure.setupDependencies(self, framework)
18    self.setCompilers = framework.require('config.setCompilers', self)
19    self.compilers    = framework.require('config.compilers',    self)
20    self.headers      = framework.require('config.headers',      self)
21    self.types        = framework.require('config.types',        self)
22    return
23
24  def getLibArgumentList(self, library, with_rpath=True):
25    '''Return the proper link line argument for the given filename library as a list of options
26      - If the path is empty, return it unchanged
27      - If starts with - then return unchanged
28      - If the path ends in ".lib" return it unchanged
29      - If the path is absolute and the filename is "lib"<name>, return -L<dir> -l<name> (optionally including rpath flag)
30      - If the filename is "lib"<name>, return -l<name>
31      - If the path ends in ".so" or ".dylib" return it unchanged
32      - If the path ends in ".o" return it unchanged
33      - If the path is absolute, return it unchanged
34      - Otherwise return -l<library>'''
35    if not library:
36      return []
37    if library.startswith('${CC_LINKER_SLFLAG}'):
38      return [library] if with_rpath else []
39    if library.startswith('${FC_LINKER_SLFLAG}'):
40      return [library] if with_rpath else []
41    flagName  = self.language[-1]+'SharedLinkerFlag'
42    flagSubst = self.language[-1].upper()+'_LINKER_SLFLAG'
43    rpathFlag = ''
44    if hasattr(self.setCompilers, flagName) and not getattr(self.setCompilers, flagName) is None:
45      rpathFlag = getattr(self.setCompilers, flagName)
46    elif flagSubst in self.argDB:
47      rpathFlag = self.argDB[flagSubst]
48    if library.startswith('-L'):
49      dirname = library[2:]
50      if not dirname.startswith('$') and not os.path.isdir(dirname): self.logPrint('Warning! getLibArgumentList(): could not locate dir '+ dirname)
51      if dirname in self.sysDirs:
52          return []
53      elif with_rpath and rpathFlag and not dirname in self.rpathSkipDirs:
54        return [rpathFlag+dirname,library]
55      else:
56        return [library]
57    if library.lstrip()[0] == '-':
58      return [library]
59    if len(library) > 3 and library[-4:] == '.lib':
60      return [library.replace('\\ ',' ').replace(' ', '\\ ').replace('\\(','(').replace('(', '\\(').replace('\\)',')').replace(')', '\\)')]
61    if os.path.basename(library).startswith('lib'):
62      name = self.getLibName(library)
63      if ((len(library) > 2 and library[1] == ':') or os.path.isabs(library)):
64        dirname   = os.path.dirname(library).replace('\\ ',' ').replace(' ', '\\ ').replace('\\(','(').replace('(', '\\(').replace('\\)',')').replace(')', '\\)')
65        if dirname in self.sysDirs:
66          return [library]
67        if with_rpath and not dirname in self.rpathSkipDirs:
68          if hasattr(self.setCompilers, flagName) and not getattr(self.setCompilers, flagName) is None:
69            import pathlib
70            if pathlib.Path(library).suffix[1:].isnumeric(): # libfoo.so.1.0
71              return [getattr(self.setCompilers, flagName)+dirname,library]
72            else:
73              return [getattr(self.setCompilers, flagName)+dirname,'-L'+dirname,'-l'+name]
74          if flagSubst in self.argDB:
75            return [self.argDB[flagSubst]+dirname,'-L'+dirname,'-l'+name]
76        return ['-L'+dirname,'-l'+name]
77      else:
78        return ['-l'+name]
79    if os.path.splitext(library)[1] == '.so' or os.path.splitext(library)[1] == '.o' or os.path.splitext(library)[1] == '.dylib':
80      return [library]
81    if os.path.isabs(library):
82      return [library]
83    return ['-l'+library]
84
85  def getLibArgument(self, library):
86    '''Same as getLibArgumentList - except it returns a string instead of list.'''
87    return  ' '.join(self.getLibArgumentList(library))
88
89  def addRpathSkipDir(self, dirname):
90    '''Do not generate RPATH for this dir in getLibArgumentList.'''
91    if dirname not in self.rpathSkipDirs: self.rpathSkipDirs.append(dirname)
92
93  def addSysDir(self, dirname):
94    '''Add the dir to sysDirs[]'''
95    if dirname not in self.sysDirs: self.sysDirs.append(dirname)
96
97  def getLibName(library):
98    if os.path.basename(library).startswith('lib'):
99      return os.path.splitext(os.path.basename(library))[0][3:]
100    return library
101  getLibName = staticmethod(getLibName)
102
103  def getDefineName(self, library):
104    return 'HAVE_LIB'+self.getLibName(library).upper().replace('-','_').replace('=','_').replace('+','_').replace('.', '_').replace('/','_')
105
106  def getDefineNameFunc(self, funcName):
107    return 'HAVE_'+ funcName.upper()
108
109  def haveLib(self, library):
110    return self.getDefineName(library) in self.defines
111
112  def add(self, libName, funcs, libDir = None, otherLibs = [], prototype = '', call = '', fortranMangle = 0):
113    '''Checks that the library "libName" contains "funcs", and if it does defines HAVE_LIB"libName AND adds it to $LIBS"
114       - libDir may be a list of directories
115       - libName may be a list of library names'''
116    if not isinstance(libName, list): libName = [libName]
117    if self.check(libName, funcs, libDir, otherLibs, prototype, call, fortranMangle):
118      self.logPrint('Adding '+str(libName)+' to LIBS')
119      # Note: this MUST be setCompilers since it can happen before dispatch names is made
120      self.setCompilers.LIBS = self.toString(libName)+' '+self.setCompilers.LIBS
121      return 1
122    return 0
123
124  def toString(self,libs):
125    '''Converts a list of libraries to a string suitable for a linker'''
126    newlibs = []
127    frame = 0
128    for lib in libs:
129      if frame:
130        newlibs += [lib]
131        frame   = 0
132      elif lib == '-framework':
133        newlibs += [lib]
134        frame = 1
135      else:
136        newlibs += self.getLibArgumentList(lib)
137    return ' '.join(newlibs)
138
139  def toStringNoDupes(self,libs,with_rpath=True):
140    '''Converts a list of libraries to a string suitable for a linker, removes duplicates'''
141    '''Moves the flags that can be moved to the beginning of the string but always leaves the libraries and other items that must remain in the same order'''
142    newlibs = []
143    frame = 0
144    for lib in libs:
145      if frame:
146        newlibs += [lib]
147        frame   = 0
148      elif lib == '-framework':
149        newlibs += [lib]
150        frame = 1
151      else:
152        newlibs += self.getLibArgumentList(lib, with_rpath)
153    libs = newlibs
154    newldflags = []
155    newlibs = []
156    frame = 0
157    dupflags = ['-L']
158    flagName  = self.language[-1]+'SharedLinkerFlag'
159    if hasattr(self.setCompilers, flagName) and not getattr(self.setCompilers, flagName) is None:
160      dupflags.append(getattr(self.setCompilers, flagName))
161    for j in libs:
162      # remove duplicate -L, -Wl,-rpath options - and only consecutive -l options
163      if j in newldflags and any([j.startswith(flg) for flg in dupflags]): continue
164      if newlibs and j == newlibs[-1]: continue
165      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']:
166        newlibs.append(j)
167      else:
168        newldflags.append(j)
169    liblist = ' '.join(newldflags + newlibs)
170    return liblist
171
172  def getShortLibName(self,lib):
173    '''returns the short name for the library. Valid names are foo -lfoo or libfoo.[a,so,lib]'''
174    if lib.startswith('-l'):
175      libname = lib[2:]
176      return libname
177    if lib.startswith('-'): # must be some compiler options - not a library
178      return ''
179    if lib.endswith('.a') or lib.endswith('.so') or lib.endswith('.lib'):
180      libname = os.path.splitext(os.path.basename(lib))[0]
181      if lib.startswith('lib'): libname = libname[3:]
182      return libname
183    # no match - assuming the given name is already in short notation
184    return lib
185
186  def check(self, libName, funcs, libDir = None, otherLibs = [], prototype = '', call = '', fortranMangle = 0, cxxMangle = 0, cxxLink = 0, functionDefine = 0, examineOutput=lambda ret,out,err:None):
187    '''Checks that the library "libName" contains "funcs", and if it does defines HAVE_LIB"libName"
188       - libDir may be a list of directories
189       - libName may be a list of library names'''
190    if not isinstance(funcs,list): funcs = [funcs]
191    if not isinstance(libName, list): libName = [libName]
192    def genPreamble(f, funcName):
193      # Construct prototype
194      if self.language[-1] == 'FC':
195        return ''
196      if prototype:
197        if isinstance(prototype, str):
198          pre = prototype
199        else:
200          pre = prototype[f]
201      else:
202        # We use char because int might match the return type of a gcc2 builtin and its argument prototype would still apply.
203        pre = 'char '+funcName+'(void);'
204      # Capture the function call in a static function so that any local variables are isolated from
205      # calls to other library functions.
206      return pre + '\nstatic void _check_%s(void) { %s }' % (funcName, genCall(f, funcName, pre=True))
207    def genCall(f, funcName, pre=False):
208      if self.language[-1] != 'FC' and not pre:
209        return '_check_' + funcName + '();'
210      # Construct function call
211      if call:
212        if isinstance(call, str):
213          body = call
214        else:
215          body = call[f]
216      else:
217        body = funcName+'()'
218      if self.language[-1] != 'FC':
219        body += ';'
220      return body
221    # Handle Fortran mangling
222    if fortranMangle:
223      funcs = list(map(self.compilers.mangleFortranFunction, funcs))
224    if not funcs:
225      self.logPrint('No functions to check for in library '+str(libName)+' '+str(otherLibs))
226      return True
227    self.logPrint('Checking for functions ['+' '.join(funcs)+'] in library '+str(libName)+' '+str(otherLibs))
228    if self.language[-1] == 'FC':
229      includes = ''
230    else:
231      includes = '/* Override any gcc2 internal prototype to avoid an error. */\n'
232    # Handle C++ mangling
233    if self.language[-1] == 'Cxx' and not cxxMangle:
234      includes += '''
235#ifdef __cplusplus
236extern "C" {
237#endif
238'''
239    includes += '\n'.join([genPreamble(f, fname) for f, fname in enumerate(funcs)])
240    # Handle C++ mangling
241    if self.language[-1] == 'Cxx' and not cxxMangle:
242      includes += '''
243#ifdef __cplusplus
244}
245#endif
246'''
247    body = '\n'.join([genCall(f, fname) for f, fname in enumerate(funcs)])
248    # Setup link line
249    oldLibs = self.setCompilers.LIBS
250    if libDir:
251      if not isinstance(libDir, list): libDir = [libDir]
252      for dir in libDir:
253        self.setCompilers.LIBS += ' -L'+dir
254    # new libs may/will depend on system libs so list new libs first!
255    # Matt, do not change this without talking to me
256    if libName and otherLibs:
257      self.setCompilers.LIBS = ' '+self.toString(libName+otherLibs) +' '+ self.setCompilers.LIBS
258    elif otherLibs:
259      self.setCompilers.LIBS = ' '+self.toString(otherLibs) +' '+ self.setCompilers.LIBS
260    elif libName:
261      self.setCompilers.LIBS = ' '+self.toString(libName) +' '+ self.setCompilers.LIBS
262    if cxxMangle: compileLang = 'Cxx'
263    else:         compileLang = self.language[-1]
264    if cxxLink: linklang = 'Cxx'
265    else: linklang = self.language[-1]
266    self.pushLanguage(compileLang)
267
268    found = 1
269    if libName and libName[0].startswith('/'):
270      dir = os.path.dirname(libName[0])
271      lib = os.path.basename(libName[0])[:-1]
272      self.logPrint('Checking directory of requested libraries:'+dir+' for first library:'+lib)
273      found = 0
274      try:
275        files = os.listdir(dir)
276      except:
277        self.logPrint('Directory of requested libraries '+dir+' does not exist')
278      else:
279        self.logPrint('Files in directory:'+str(files))
280        for i in files:
281          if i.startswith(lib):
282            found = 1
283            break
284
285    if found and self.checkLink(includes, body, linkLanguage=linklang, examineOutput=examineOutput):
286      if hasattr(self.compilers, 'FC') and self.language[-1] == 'C':
287        if self.compilers.checkCrossLink(includes+'\nvoid dummy(void) {'+body+'}\n',"     program main\n      print*,'testing'\n      stop\n      end\n",language1='C',language2='FC'):
288          # define the symbol as found
289          if functionDefine: [self.addDefine(self.getDefineNameFunc(fname), 1) for f, fname in enumerate(funcs)]
290          # add to list of found libraries
291          elif libName:
292            for lib in libName:
293              shortlib = self.getShortLibName(lib)
294              if shortlib: self.addDefine(self.getDefineName(shortlib), 1)
295        else:
296          found = 0
297    else:
298      found = 0
299    self.setCompilers.LIBS = oldLibs
300    self.popLanguage()
301    return found
302
303  def checkClassify(self, libName, funcs, libDir=None, otherLibs=[], prototype='', call='', fortranMangle=0, cxxMangle=0, cxxLink=0):
304    '''Recursive decompose to rapidly classify functions as found or missing'''
305    import config
306    def functional(funcs):
307      named = config.NamedInStderr(funcs)
308      if self.check(libName, funcs, libDir, otherLibs, prototype, call, fortranMangle, cxxMangle, cxxLink, examineOutput = named.examineStderr):
309        return True
310      else:
311        return named.named
312    found, missing = config.classify(funcs, functional)
313    return found, missing
314
315  def checkMath(self):
316    '''Check for sin() in libm, the math library'''
317    self.math = None
318    funcs = ['sin', 'floor', 'log10', 'pow']
319    prototypes = ['#include <stdio.h>\ndouble sin(double);',
320                  '#include <stdio.h>\ndouble floor(double);',
321                  '#include <stdio.h>\ndouble log10(double);',
322                  '#include <stdio.h>\ndouble pow(double, double);']
323    calls = ['double x,y; int s = scanf("%lf",&x); y = sin(x); printf("%f %d",y,s)',
324             'double x,y; int s = scanf("%lf",&x); y = floor(x); printf("%f %d",y,s)',
325             'double x,y; int s = scanf("%lf",&x); y = log10(x); printf("%f %d",y,s)',
326             'double x,y; int s = scanf("%lf",&x); y = pow(x,x); printf("%f %d",y,s)']
327    if self.check('', funcs, prototype = prototypes, call = calls):
328      self.math = []
329    elif self.check('m', funcs, prototype = prototypes, call = calls):
330      self.math = ['libm.a']
331    if self.math == None:
332      raise RuntimeError('Cannot find basic math functions')
333    self.logPrint('CheckMath: using math library '+str(self.math))
334    return
335
336  def checkMathErf(self):
337    '''Check for erf() in libm, the math library'''
338    if not self.math is None and self.check(self.math, ['erf'], prototype = ['#include <math.h>'], call = ['double (*checkErf)(double) = erf;double x = 0,y; y = (*checkErf)(x); (void)y']):
339      self.logPrint('erf() found')
340      self.addDefine('HAVE_ERF', 1)
341    else:
342      self.logPrint('erf() not found')
343    return
344
345  def checkMathTgamma(self):
346    '''Check for tgamma() in libm, the math library'''
347    if not self.math is None and self.check(self.math, ['tgamma'], prototype = ['#include <math.h>'], call = ['double (*checkTgamma)(double) = tgamma;double x = 0,y; y = (*checkTgamma)(x); (void)y']):
348      self.logPrint('tgamma() found')
349      self.addDefine('HAVE_TGAMMA', 1)
350    else:
351      self.logPrint('tgamma() not found')
352    return
353
354  def checkMathLgamma(self):
355    '''Check for lgamma() in libm, the math library'''
356    if not self.math is None and self.check(self.math, ['lgamma'], prototype = ['#include <math.h>\n#include <stdlib.h>'], call = ['double (*checkLgamma)(double) = lgamma;double x = 1,y; y = (*checkLgamma)(x);if (y != 0.) abort()']):
357      self.logPrint('lgamma() found')
358      self.addDefine('HAVE_LGAMMA', 1)
359    elif not self.math is None and self.check(self.math, ['gamma'], prototype = ['#include <math.h>\n#include <stdlib.h>'], call = ['double (*checkLgamma)(double) = gamma;double x = 1,y; y = (*checkLgamma)(x);if (y != 0.) abort()']):
360      self.logPrint('gamma() found')
361      self.addDefine('HAVE_LGAMMA', 1)
362      self.addDefine('HAVE_LGAMMA_IS_GAMMA', 1)
363    else:
364      self.logPrint('lgamma() and gamma() not found')
365    return
366
367  def checkMathFenv(self):
368    '''Checks if <fenv.h> can be used with FE_DFL_ENV'''
369    if not self.math is None and self.check(self.math, ['fesetenv'], prototype = ['#include <fenv.h>'], call = ['fesetenv(FE_DFL_ENV)']):
370      self.addDefine('HAVE_FENV_H', 1)
371    else:
372      self.logPrint('<fenv.h> with FE_DFL_ENV not found')
373    if not self.math is None and self.check(self.math, ['feclearexcept'], prototype = ['#include <fenv.h>'], call = ['feclearexcept(FE_INEXACT)']):
374      self.addDefine('HAVE_FE_VALUES', 1)
375    else:
376      self.logPrint('<fenv.h> with FE_INEXACT not found')
377    return
378
379  def checkMathLog2(self):
380    '''Check for log2() in libm, the math library'''
381    if not self.math is None and self.check(self.math, ['log2'], prototype = ['#include <math.h>'], call = ['double (*checkLog2)(double) = log2; double x = 2.5, y = (*checkLog2)(x); (void)y']):
382      self.logPrint('log2() found')
383      self.addDefine('HAVE_LOG2', 1)
384    else:
385      self.logPrint('log2() not found')
386    return
387
388  def checkRealtime(self):
389    '''Check for presence of clock_gettime() in realtime library (POSIX Realtime extensions)'''
390    self.rt = None
391    funcs = ['clock_gettime']
392    prototypes = ['#include <time.h>']
393    calls = ['struct timespec tp; clock_gettime(CLOCK_REALTIME,&tp)']
394    if self.check('', funcs, prototype=prototypes, call=calls):
395      self.logPrint('realtime functions are linked in by default')
396      self.rt = []
397    elif self.check('rt', funcs, prototype=prototypes, call=calls):
398      self.logPrint('Using librt for the realtime library')
399      self.rt = ['librt.a']
400    else:
401      self.logPrint('No realtime library found')
402    return
403
404  def checkDynamic(self):
405    '''Check for the header and libraries necessary for dynamic library manipulation'''
406    if 'with-dynamic-loading' in self.argDB and not self.argDB['with-dynamic-loading']: return
407    self.check(['dl'], 'dlopen')
408    self.headers.check('dlfcn.h')
409    return
410
411  def checkShared(self, includes, initFunction, checkFunction, finiFunction = None, checkLink = None, libraries = [], initArgs = '&argc, &argv', boolType = 'int', noCheckArg = 0, defaultArg = '', executor = None, timeout = 60):
412    '''Determine whether a library is shared
413       - initFunction(int *argc, char *argv[]) is called to initialize some static data
414       - checkFunction(int *check) is called to verify that the static data wer set properly
415       - finiFunction() is called to finalize the data, and may be omitted
416       - checkLink may be given as ana alternative to the one in base.Configure'''
417    isShared = 0
418    if checkLink is None:
419      checkLink = self.checkLink
420      configObj = self
421    else:
422      if hasattr(checkLink, '__self__'):
423        configObj = checkLink.__self__
424      else:
425        configObj = self
426
427    # Fix these flags
428    oldFlags = self.setCompilers.LIBS
429    self.setCompilers.LIBS = ' '+self.toString(libraries)+' '+self.setCompilers.LIBS
430
431    # Make a library which calls initFunction(), and returns checkFunction()
432    lib1Name = os.path.join(self.tmpDir, 'lib1.'+self.setCompilers.sharedLibraryExt)
433    if noCheckArg:
434      checkCode = 'isInitialized = '+checkFunction+'();'
435    else:
436      checkCode = checkFunction+'(&isInitialized);'
437    codeBegin = '''
438#ifdef __cplusplus
439extern "C"
440#endif
441int init(int argc,  char *argv[]) {
442'''
443    body      = '''
444  %s isInitialized;
445
446  %s(%s);
447  %s
448  return (int) isInitialized;
449''' % (boolType, initFunction, initArgs, checkCode)
450    codeEnd   = '\n}\n'
451    if not checkLink(includes, body, cleanup = 0, codeBegin = codeBegin, codeEnd = codeEnd, shared = 1):
452      if os.path.isfile(configObj.compilerObj): os.remove(configObj.compilerObj)
453      self.setCompilers.LIBS = oldFlags
454      raise RuntimeError('Could not complete shared library check')
455    if os.path.isfile(configObj.compilerObj): os.remove(configObj.compilerObj)
456    os.rename(configObj.linkerObj, lib1Name)
457
458    # Make a library which calls checkFunction()
459    lib2Name = os.path.join(self.tmpDir, 'lib2.'+self.setCompilers.sharedLibraryExt)
460    codeBegin = '''
461#ifdef __cplusplus
462extern "C"
463#endif
464int checkInit(void) {
465'''
466    body      = '''
467  %s isInitialized;
468
469  %s
470''' % (boolType, checkCode)
471    if finiFunction:
472      body += '  if (isInitialized) '+finiFunction+'();\n'
473    body += '  return (int) isInitialized;\n'
474    codeEnd   = '\n}\n'
475    if not checkLink(includes, body, cleanup = 0, codeBegin = codeBegin, codeEnd = codeEnd, shared = 1):
476      if os.path.isfile(configObj.compilerObj): os.remove(configObj.compilerObj)
477      self.setCompilers.LIBS = oldFlags
478      raise RuntimeError('Could not complete shared library check')
479      return 0
480    if os.path.isfile(configObj.compilerObj): os.remove(configObj.compilerObj)
481    os.rename(configObj.linkerObj, lib2Name)
482
483    self.setCompilers.LIBS = oldFlags
484
485    # Make an executable that dynamically loads and calls both libraries
486    #   If the check returns true in the second library, the static data was shared
487    guard = self.headers.getDefineName('dlfcn.h')
488    if self.headers.headerPrefix:
489      guard = self.headers.headerPrefix+'_'+guard
490    defaultIncludes = '''
491#include <stdio.h>
492#include <stdlib.h>
493#ifdef %s
494#include <dlfcn.h>
495#endif
496    ''' % guard
497    body = '''
498  int   argc    = 1;
499  char *argv[2] = {(char *) "conftest", NULL};
500  void *lib;
501  int (*init)(int, char **);
502  int (*checkInit)(void);
503
504  lib = dlopen("'''+lib1Name+'''", RTLD_LAZY);
505  if (!lib) {
506    fprintf(stderr, "Could not open lib1.so: %s\\n", dlerror());
507    exit(1);
508  }
509  init = (int (*)(int, char **)) dlsym(lib, "init");
510  if (!init) {
511    fprintf(stderr, "Could not find initialization function\\n");
512    exit(1);
513  }
514  if (!(*init)(argc, argv)) {
515    fprintf(stderr, "Could not initialize library\\n");
516    exit(1);
517  }
518  lib = dlopen("'''+lib2Name+'''", RTLD_LAZY);
519  if (!lib) {
520    fprintf(stderr, "Could not open lib2.so: %s\\n", dlerror());
521    exit(1);
522  }
523  checkInit = (int (*)(void)) dlsym(lib, "checkInit");
524  if (!checkInit) {
525    fprintf(stderr, "Could not find initialization check function\\n");
526    exit(1);
527  }
528  if (!(*checkInit)()) {
529    fprintf(stderr, "Did not link with shared library\\n");
530    exit(2);
531  }
532  '''
533    oldLibs = self.setCompilers.LIBS
534    if self.haveLib('dl'):
535      self.setCompilers.LIBS += ' -ldl'
536    isShared = 0
537    try:
538      isShared = self.checkRun(defaultIncludes, body, defaultArg = defaultArg, executor = executor, timeout = timeout)
539    except RuntimeError as e:
540      if executor and str(e).find('Runaway process exceeded time limit') > -1:
541        raise RuntimeError('Timeout: Unable to run MPI program with '+executor+'\n\
542    (1) make sure this is the correct program to run MPI jobs\n\
543    (2) your network may be misconfigured; see https://petsc.org/release/faq/#mpi-network-misconfigure\n\
544    (3) you may have VPN running whose network settings may not play nice with MPI\n')
545
546    self.setCompilers.LIBS = oldLibs
547    if os.path.isfile(lib1Name) and self.framework.doCleanup: os.remove(lib1Name)
548    if os.path.isfile(lib2Name) and self.framework.doCleanup: os.remove(lib2Name)
549    if isShared:
550      self.logPrint('Library was shared')
551    else:
552      self.logPrint('Library was not shared')
553    return isShared
554
555  def checkExportedSymbols(self, flags, checkLink = None, libraries = [], defaultArg = '', executor = None, timeout = 60):
556    '''Determine whether an executable exports shared symbols
557       - checkLink may be given as an alternative to the one in base.Configure'''
558    exports = False
559
560    if 'USE_VISIBILITY_C' in self.types.defines:
561      visibility = '__attribute__((visibility ("default")))'
562    else:
563      visibility = ''
564
565    # Make an executable that dynamically loads a symbol it contains
566    guard = self.headers.getDefineName('dlfcn.h')
567    if self.headers.headerPrefix:
568      guard = self.headers.headerPrefix+'_'+guard
569    defaultIncludes = '''
570#include <stdio.h>
571#include <stdlib.h>
572#ifdef %s
573#include <dlfcn.h>
574#endif
575
576#define PETSC_DLLEXPORT %s
577
578extern PETSC_DLLEXPORT int foo(void) {
579  return 42;
580}
581    ''' % (guard, visibility)
582    body = '''
583  void *lib;
584  int (*foo)(void);
585
586  lib = dlopen(NULL, RTLD_LAZY);
587  if (!lib) {
588    fprintf(stderr, "Could not open executable: %s\\n", dlerror());
589    exit(1);
590  }
591  foo = (int (*)(void)) dlsym(lib, "foo");
592  if (!foo) {
593    fprintf(stderr, "Could not find function in executable\\n");
594    exit(1);
595  }
596  if ((*foo)() != 42) {
597    fprintf(stderr, "Could not run function\\n");
598    exit(1);
599  }
600  '''
601    oldFlags = self.setCompilers.CFLAGS
602    oldLibs  = self.setCompilers.LIBS
603    self.setCompilers.CFLAGS += ' '+flags
604    if self.haveLib('dl'):
605      self.setCompilers.LIBS += ' -ldl'
606    try:
607      exports = self.checkRun(defaultIncludes, body, defaultArg = defaultArg, executor = executor, timeout = timeout)
608    except RuntimeError as e:
609      self.logPrint('FAIL: '+str(e))
610    self.setCompilers.CFLAGS = oldFlags
611    self.setCompilers.LIBS   = oldLibs
612    if exports:
613      self.logPrint('Executable exports symbols for dlopen()')
614    else:
615      self.logPrint('Executable does not export symbols for dlopen()')
616    return exports
617
618  def checkPthreadMutex(self):
619    '''Check for pthread mutex support'''
620    # Per Pim Heeman from petsc-maint, OpenBSD does not support PTHREAD_PROCESS_SHARED, which is used by pcmpi.c
621    funcs = ['pthread_mutex_unlock', 'pthread_mutexattr_setpshared']
622    prototypes = ['#include <pthread.h>', '#include <pthread.h>']
623    calls = ['pthread_mutex_t m; pthread_mutex_unlock(&m)', 'pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED)']
624    self.pthreadmutex = []
625    if self.check('', funcs, prototype=prototypes, call=calls):
626      self.logPrint('pthread mutex are linked in by default')
627      self.pthreadmutex = []
628      self.addDefine('HAVE_PTHREAD_MUTEX',1)
629    elif self.check('pthread', funcs, prototype=prototypes, call=calls):
630      self.logPrint('Using libpthread for the mutex')
631      self.pthreadmutex = ['libpthread.a']
632      self.addDefine('HAVE_PTHREAD_MUTEX',1)
633    else:
634      self.logPrint('No pthread mutex support found')
635    return
636
637  def checkExecutableExportFlag(self):
638    '''Checks for the flag that allows executables to export symbols to dlsym()'''
639    # Right now, we just check some compilers, but we should make a test trying to load a symbol from the executable
640    # Discussion: https://stackoverflow.com/questions/6292473/how-to-call-function-in-executable-from-my-library/6298434#6298434
641    for flag in ['', '-Wl,-export_dynamic', '-Wl,-export-dynamic', '-export-dynamic']:
642      if self.checkExportedSymbols(flag):
643        self.addDefine('HAVE_EXECUTABLE_EXPORT', 1)
644        self.addMakeMacro('EXEFLAGS', flag)
645        break
646    return
647
648  def configure(self):
649    list(map(lambda args: self.executeTest(self.check, list(args)), self.libraries))
650    self.executeTest(self.checkMath)
651    self.executeTest(self.checkMathErf)
652    self.executeTest(self.checkMathTgamma)
653    self.executeTest(self.checkMathLgamma)
654    self.executeTest(self.checkMathFenv)
655    self.executeTest(self.checkMathLog2)
656    self.executeTest(self.checkRealtime)
657    self.executeTest(self.checkDynamic)
658    self.executeTest(self.checkPthreadMutex)
659    if not self.argDB['with-batch']:
660      self.executeTest(self.checkExecutableExportFlag)
661    return
662