"""compile.py - compile Python C-extension

   Commandline usage: 
     python compile.py <filename>

   Usage from within Python:
     import compile
     compile.compile(<filename>,..)

   Ole Nielsen, Duncan Gray Oct 2001      
"""     

#NumPy ------------------------------------
# Something like these lines recommended in "Converting from NUMARRAY to NUMPY"
import numpy
I_dirs = '-I"%s" ' % numpy.get_include()
#NumPy ------------------------------------

# FIXME (Ole): Although this script says it works with a range of compilers,
# it has only really been used with gcc.

import os, string, sys, types

separation_line = '---------------------------------------'      
 
def compile(FNs=None, CC=None, LD = None, SFLAG = None, verbose = 1):
  """compile(FNs=None, CC=None, LD = None, SFLAG = None):
  
     Compile FN(s) using compiler CC (e.g. mpicc), 
     Loader LD and shared flag SFLAG.
     If CC is absent use default compiler dependent on platform
     if LD is absent CC is used.
     if SFLAG is absent platform default is used
     FNs can be either one filename or a list of filenames
     In the latter case, the first will be used to name so file.
  """
  
  # Input check
  #
  assert not FNs is None, 'No filename provided'

  if not type(FNs) == types.ListType:
    FNs = [FNs]


  libext = 'so' #Default extension (Unix)
  libs = ''
  version = sys.version[:3]
  
  # Determine platform and compiler
  #
  if sys.platform == 'sunos5':  #Solaris
    if CC:
      compiler = CC
    else:  
      compiler = 'gcc'
    if LD:
      loader = LD
    else:  
      loader = compiler
    if SFLAG:
      sharedflag = SFLAG
    else:  
      sharedflag = 'G'
      
  elif sys.platform == 'osf1V5':  #Compaq AlphaServer
    if CC:
      compiler = CC
    else:  
      compiler = 'cc'
    if LD:
      loader = LD
    else:  
      loader = compiler
    if SFLAG:
      sharedflag = SFLAG
    else:  
      sharedflag = 'shared'    
      
  elif sys.platform == 'linux2':  #Linux
    if CC:
      compiler = CC
    else:  
      compiler = 'gcc'
    if LD:
      loader = LD
    else:  
      loader = compiler
    if SFLAG:
      sharedflag = SFLAG
    else:  
      sharedflag = 'shared'    
      
  elif sys.platform == 'darwin':  #Mac OS X:
    if CC:
      compiler = CC
    else:  
      compiler = 'cc'
    if LD:
      loader = LD
    else:  
      loader = compiler
    if SFLAG:
      sharedflag = SFLAG
    else:  
      sharedflag = 'bundle -flat_namespace -undefined suppress'

  elif sys.platform == 'cygwin':  #Cygwin (compilation same as linux)
    if CC:
      compiler = CC
    else:  
      compiler = 'gcc'
    if LD:
      loader = LD
    else:  
      loader = compiler
    if SFLAG:
      sharedflag = SFLAG
    else:  
      sharedflag = 'shared'
    libext = 'dll'
    libs = '/lib/python%s/config/libpython%s.dll.a' %(version,version)
      
  elif sys.platform == 'win32':  #Windows
    if CC:
      compiler = CC
    else:  
      compiler = 'gcc.exe' #Some systems require this (a security measure?) 
    if LD:
      loader = LD
    else:  
      loader = compiler
    if SFLAG:
      sharedflag = SFLAG
    else:  
      sharedflag = 'shared'
     
    # As of python2.5, .pyd is the extension for python extension
    # modules.
    if sys.version_info[0:2] >= (2, 5):
      libext = 'pyd'
    else:
      libext = 'dll'

    libs, is_found = set_python_dll_path()
      
  else:
    if verbose: print "Unrecognised platform %s - revert to default"\
                %sys.platform
    
    if CC:
      compiler = CC
    else:  
      compiler = 'cc'
    if LD:
      loader = LD
    else:  
      loader = 'ld'
    if SFLAG:
      sharedflag = SFLAG
    else:  
      sharedflag = 'G'


  # Verify that compiler can be executed
  print 'Compiler: %s, version ' %compiler,
  sys.stdout.flush()
  s = '%s -dumpversion' %(compiler)
  err = os.system(s)
  print
  
  if err != 0:
      msg = 'Unable to execute compiler: %s. ' %compiler
      msg += 'Make sure it is available on the system path.\n'
      msg += 'One way to check this is to run %s on ' %compiler
      msg += 'the commandline.'
      raise Exception, msg    

  
  # Find location of include files
  #
  if sys.platform == 'win32':  #Windows
    python_include = os.path.join(sys.exec_prefix, 'include')    
  else:  
    python_include = os.path.join(os.path.join(sys.exec_prefix, 'include'),
                                  'python' + version)

  # Check existence of Python.h
  #
  headerfile = python_include + os.sep + 'Python.h'
  try:
    open(headerfile, 'r')
  except:
    raise """Did not find Python header file %s.
    Make sure files for Python C-extensions are installed. 
    In debian linux, for example, you need to install a
    package called something like python2.3-dev""" %headerfile



  # Add Python path + utilities to includelist (see ticket:31)
  # Assume there is only one 'utilities' dir under path dirs
  
  utilities_include_dir = None
  for pathdir in sys.path:

    utilities_include_dir = pathdir + os.sep + 'utilities'
    #print pathdir
    #print utilities_include_dir
    try:
      os.stat(utilities_include_dir)
    except OSError:
      pass
    else:
      #print 'Found %s to be used as include dir' %utilities_include_dir
      break

  # This is hacky since it
  # assumes the location of the compile_all that determines buildroot
  try:
    utilities_include_dir = buildroot + os.sep + "source" + os.sep + "anuga" \
                            + os.sep + 'utilities'
  except:
    # This will make compile work locally
    utilities_include_dir = '.'
    utilities_include_dir = '../utilities'


    
  try:
    os.stat(utilities_include_dir)
  except OSError: 
    utilities_include_dir = buildroot + os.sep + 'utilities'
  
  
  
  # Check filename(s)
  #
  object_files = ''
  for FN in FNs:        
    root, ext = os.path.splitext(FN)
    if ext == '':
      FN = FN + '.c'
    elif ext.lower() != '.c':
      raise Exception, "Unrecognised extension: " + FN
    
    try:
      open(FN, 'r')
    except:
      raise Exception, "Could not open: " + FN

    if not object_files: root1 = root  #Remember first filename        
    object_files += root + '.o '  
  
  
    # Compile
    #
    if utilities_include_dir is None:    
      s = '%s -c %s -I"%s" -o "%s.o" -Wall -O3'\
          %(compiler, FN, python_include, root)
    else:
      if FN == "triangle.c" or FN == "mesh_engine_c_layer.c":
        s = '%s -c %s %s -I"%s" -I"%s" -o "%s.o" -O3 -DTRILIBRARY=1 -DNO_TIMER=1'\
            % (compiler, FN, I_dirs, python_include, utilities_include_dir, root)
      elif FN == "polygon_ext.c":
        # gcc 4.3.x is problematic with this file if '-O3' is used
        s = '%s -c %s %s -I"%s" -I"%s" -o "%s.o" -Wall'\
            %(compiler, FN, I_dirs, python_include, utilities_include_dir, root)
      else:
        s = '%s -c %s %s -I"%s" -I"%s" -o "%s.o" -Wall -O3'\
            %(compiler, FN, I_dirs, python_include, utilities_include_dir, root)

    if os.name == 'posix' and os.uname()[4] in ['x86_64', 'ia64']:
      # Extra flags for 64 bit architectures.
      #   AMD Opteron will give x86_64
      #   Itanium will give ia64
      #
      # Second clause will always fail on Win32 because uname is UNIX specific
      # but won't get past first clause

      s += ' -fPIC' # Use position independent code for 64 bit archs
      #s += ' -m64' # Used to be necessary for AMD Opteron
      
      
    if verbose:
      print s

    # Doesn't work on Windows anyway  
    #else:
    #  s = s + ' 2> /dev/null' #Suppress errors
    
    try:
      err = os.system(s)
      if err != 0:
          raise 'Attempting to compile %s failed - please try manually' %FN 
    except:
      raise 'Could not compile %s - please try manually' %FN  

  
  # Make shared library (*.so or *.dll)
  if libs is "":
    s = '%s -%s %s -o %s.%s -lm' %(loader, sharedflag, object_files, root1, libext)
  else:
    s = '%s -%s %s -o %s.%s "%s" -lm' %(loader, sharedflag, object_files, root1, libext, libs)
    
  if verbose:
    print s

  # Doesn't work on Windows anyway      
  #else:
  #  s = s + ' 2> /dev/null' #Suppress warnings
  
  try:  
    err=os.system(s)
    if err != 0:        
        raise 'Attempting to link %s failed - please try manually' %root1     
  except:
    raise 'Could not link %s - please try manually' %root1
    

def can_use_C_extension(filename):
    """Determine whether specified C-extension
    can and should be used.
    """

    from anuga.config import use_extensions

    from os.path import splitext

    root, ext = splitext(filename)
    
    C=False
    if use_extensions:
        try:
            s = 'import %s' %root
            #print s
            exec(s)
        except:
            try:
                open(filename)
            except:
                msg = 'C extension %s cannot be opened' %filename
                print msg                
            else:    
                print '------- Trying to compile c-extension %s' %filename
            
                try:
                    compile(filename)
                except:
                    print 'WARNING: Could not compile C-extension %s'\
                          %filename
                else:
                    try:
                        exec('import %s' %root)
                    except:
                        msg = 'C extension %s seems to compile OK, '
                        msg += 'but it can still not be imported.'
                        raise msg
                    else:
                        C=True
        else:
            C=True
            
    if not C:
        pass
        print 'NOTICE: C-extension %s not used' %filename

    return C


def set_python_dll_path():
  """ On windows, find which of the two usual hiding places the python
  dll is located.

  If the file can't be found, return None.
  """
  import sys
  from os import access, F_OK
  
  version = sys.version[:3]
  v = version.replace('.','')
  dllfilename = 'python%s.dll' %(v)
  libs = os.path.join(sys.exec_prefix,dllfilename)

  is_found = True   
  if access(libs,F_OK) == 0 :
    # Sometimes python dll is hidden in %WINDIR%/system32
    libs = os.path.join(os.environ.get('WINDIR', 'C:\WINNT'), 'system32',
                        dllfilename)
    if access(libs,F_OK) == 0 :
      # could not find the dll
      libs = os.path.join(sys.exec_prefix,dllfilename)
      is_found = False
  return libs, is_found

def check_python_dll():
  libs, is_found = set_python_dll_path()
  if not is_found:
    print "%s not found.\nPlease install.\nIt is available on the web." \
              %(libs)
    import sys; sys.exit()
    
      
if __name__ == '__main__':
  from os.path import splitext
  
  if sys.platform == 'win32':
    check_python_dll()
  if len(sys.argv) > 1:
      files = sys.argv[1:]
      for filename in files:
          root, ext = splitext(filename)

          if ext <> '.c':
              print 'WARNING (compile.py): Skipping %s. I only compile C-files.' %filename
      
  else:  
      #path = os.path.split(sys.argv[0])[0] or os.getcwd()
      path = '.'
      files = os.listdir(path)
      
      

  for filename in files:
      root, ext = splitext(filename)

      if ext == '.c':
          for x in ['.dll', '.so']:
              try:
                  os.remove(root + x)
              except:
                  pass

          print separation_line
          print 'Trying to compile c-extension %s in %s'\
                %(filename, os.getcwd())
          try:
            if filename == 'triang.c': 
              compile(['triang.c','triangle.c'])
            elif  filename == 'mesh_engine_c_layer.c': 
              compile(['mesh_engine_c_layer.c','triangle.c'])
            else:
              compile(filename)
          except Exception, e:
              msg = 'Could not compile C extension %s\n' %filename
              msg += str(e)
              raise Exception, msg
          else:
              print 'C extension %s OK' %filename
          print    
        

