# this is a build script that is intended to be run from scons (it will not work in python)
# it compiles the Fortran files needed to be used as Python packages for GSAS-II
#
# if the default options need to be overridden for this to work on your system, please let me
# know the details of what OS, compiler and python you are using as well as the command-line
# options you need. 
import platform
import sys
import os
import glob
import subprocess
#==========================================================================================
def is_exe(fpath):
    return os.path.exists(fpath) and os.access(fpath, os.X_OK)
def which_path(program):
    "emulates Unix which: finds a program in path, but returns the path"
    import os, sys
    if sys.platform == "win32" and os.path.splitext(program)[1].lower() != '.exe':
        program = program + '.exe'
    fpath, fname = os.path.split(program)
    if fpath:
        if is_exe(program):
            return fpath
    else:
        for path in os.environ["PATH"].split(os.pathsep):
            exe_file = os.path.join(path, program)
            if is_exe(exe_file):
                return path
    return ""
#==========================================================================================
# misc initializations
# need command-line options for fortran command and fortran options
F2PYflags = '' # compiler options for f2py command
# find a default f2py relative to the scons location. Should be in the same place as f2py
F2PYpath = ''
for program in ['f2py','../f2py']:
    if sys.platform == "win32" and os.path.splitext(program)[1].lower() != '.exe':
        program = program + '.exe'
    spath = os.path.split(sys.executable)[0]
    print spath
    f2pyprogram = os.path.normpath(os.path.join(spath,program))
    if is_exe(f2pyprogram):
        F2PYpath = os.path.split(f2pyprogram)[0]
        break
else:
    print 'Note: Using f2py from path (hope that works!)'
    F2PYpath = which_path('f2py')       # default path to f2py

GFORTpath = which_path('gfortran')   # path to compiler
FCompiler='gfortran'
G77path = which_path('g77')     # path to compiler
FORTpath = ""
FORTflags = ""
liblist = []
LDFLAGS = ''
#==========================================================================================
# configure platform dependent options here: 
if sys.platform == "win32":
    F2PYsuffix = '.pyd'
    if G77path != "":
      FCompiler='g77'
    elif GFORTpath != "":
      FCompiler='gfortran'
    else:
      print 'No Fortran compiler in path'
      sys.exit()
elif sys.platform == "darwin":
    LDFLAGS = '-undefined dynamic_lookup -bundle -static-libgfortran -static-libgcc'
    F2PYsuffix = '.so'
elif sys.platform == "linux2":
    #LDFLAGS = '-Wall -shared -static-libgfortran -static-libgcc' # does not work with gfortran 4.4.4 20100726 (Red Hat 4.4.4-13)
    F2PYsuffix = '.so'
else:
    print "Sorry, parameters for platform "+sys.platform+" are not yet defined"
    sys.exit()
#==========================================================================================
# help
if 'help' in COMMAND_LINE_TARGETS:
    print """
----------------------------------------------
Building Fortran routines for use with GSAS-II
----------------------------------------------

To build the compiled modules files needed to run GSAS-II, invoke this script:
    scons [options]
where the following options are defined (all are optional):

-Q      -- produces less output from scons

-n      -- causes scons to show but not execute the commands it will perform

-c      -- clean: causes scons to delete previously created files (don't use
   with install)

help    -- causes this message to be displayed (no compiling is done)

install -- causes the module files to be placed in an installation directory
   (../bin<X>NNv.v) rather than ../bin (<X> is mac, win or linux; NN is 64-
   or blank; v.v is the python version (2.6 or 2.7,...). Normally used only
   by Brian or Bob for distribution of compiled software.

The following options override defaults set in the scons script:

FCompiler=<name>  -- define the name of the fortran compiler, typically g77
   or gfortran; default is to use g77 on Windows and gfortran elsewhere. If
   you use something other than these, you must also specify F2PYflags.

FORTpath=<path>    -- define a path to the fortran program; default is to use
   first gfortran (g77 for Windows) found in path

FORTflags='string' -- string of options to be used for Fortran
   during library build step

F2PYpath=<path>    -- define a path to the f2py program; default is to use
   first f2py found in path

F2PYflags='string' -- defines optional flags to be supplied to f2py:
   Typically these option define which fortran compiler to use.

F2PYsuffix='.xxx'  -- extension for output module files (default: win: '.pyd',
   mac/linux: '.so')

LDFLAGS='string'   -- string of options to be used for f2py during link step

Note that at present, this has been tested with 32-bit python on windows and
Mac & 64 bit on linux. Python 3.x is not supported in GSAS-II yet.

examples:
    scons -Q
       (builds into ../bin for current platform using default options)
    scons -Q install
       (builds into ../bin<platform> for module distribution)
    scons -Q install F2PYpath=/Library/Frameworks/Python.framework/Versions/6.2/bin/
       (builds with a non-default [e.g. older] version of python)
    """
    #sys.exit()
#==========================================================================================
# override from command-line options
for var in ['F2PYflags','F2PYpath','F2PYsuffix','FCompiler','FORTpath','FORTflags','LDFLAGS']:
    if ARGUMENTS.get(var, None) is not None:
        print 'Setting',var,'to',ARGUMENTS.get(var),'based on command line'
        exec(var + "= ARGUMENTS.get('" + var +"')")
#==========================================================================================
# get the python version number from the python image in the f2py directory
# first check if we have a working path to f2py:
f2pyprogram = os.path.normpath(os.path.join(F2PYpath,'f2py'))
if sys.platform == "win32" and os.path.splitext(f2pyprogram)[1].lower() != '.exe':
    f2pyprogram = f2pyprogram + '.exe'
if not is_exe(f2pyprogram):
    print '''
ERROR: The f2py program was not found. If this program is installed
but not in your path, you should specify the path on the command line:
   scons -Q F2PYpath=/Library/Frameworks/Python.framework/Versions/6.2/bin/
   scons -Q F2PYpath=D:/Python27/Scripts
'''
    sys.exit()
# find the python location associated with the f2py in use. Note 
#   that on Windows it may be in the parent of the f2py location.
# then run it to get info about the verision and the number of bits
pythonpath = ''
for program in ['python','../python']:
    if sys.platform == "win32" and os.path.splitext(program)[1].lower() != '.exe':
        program = program + '.exe'
    pythonprogram = os.path.normpath(os.path.join(F2PYpath,program))
    if is_exe(pythonprogram):
        pythonpath = os.path.split(program)[0]
        break
else:
    print 'python not found'
    sys.exit()
p = subprocess.Popen(pythonprogram, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
p.stdin.write("import sys,platform;print str(sys.version_info[0]);print str(sys.version_info[1]);print platform.architecture()[0];sys.exit()")
p.stdin.close()
p.wait()
versions = p.stdout.readlines()
version = str(int(versions[0])) + '.' + str(int(versions[1]))
PlatformBits = versions[2][:-1]
#

# Install location
InstallLoc = '../bin'
if len(COMMAND_LINE_TARGETS) == 0:
    Default(InstallLoc)
elif 'install' in COMMAND_LINE_TARGETS:
    if PlatformBits == '64bit':
        bits = '64-'
    else:
        bits = ''
    if sys.platform == "win32":
        prefix = 'binwin'
    elif sys.platform == "darwin":
        prefix = 'binmac'
    elif sys.platform == "linux2":
        prefix = 'binlinux'
    InstallLoc = '../bin/' + prefix + bits + version
    Alias('install',InstallLoc)
#==========================================================================================
# use the compiler choice to set compiler options, but don't change anything
# specified on the command line
if FCompiler == 'gfortran':
    if FORTpath == "": FORTpath = GFORTpath
    if F2PYflags == "": F2PYflags = '--fcompiler=gnu95 --f77exec=gfortran --f77flags="-fno-range-check"'
#    if sys.platform == "linux2":
#        if FORTflags == "": FORTflags = ' -w -O2 -fPIC'
    if sys.platform == "linux2" and PlatformBits == '64bit':
        if FORTflags == "": FORTflags = ' -w -O2 -fPIC -m64'
    elif sys.platform == "linux2":
        if FORTflags == "": FORTflags = ' -w -O2 -fPIC -m32'
elif FCompiler == 'g77':
    if FORTpath == "": FORTpath = G77path
    if sys.platform == "win32":
        if F2PYflags == "": F2PYflags = '--compiler=mingw32'
        if FORTflags == "": FORTflags = ' -w -O2 -fno-automatic -finit-local-zero -malign-double -mwindows'
    else:
        if F2PYflags == "": F2PYflags = '--fcompiler=gnu --f77exec=g77 --f77flags="-fno-range-check"'

else:
    if FORTpath == "": print 'Likely error, FORTpath is not specified'
    if F2PYflags == "":
        print 'Error: specify a F2PYflags value'
        sys.exit()
#==========================================================================================
# Setup build Environment
env = Environment()
# Define a builder to run f2py 
def generate_f2py(source, target, env, for_signature):
    module = os.path.splitext(str(source[0]))[0]
    if len(liblist) > 0:
        for lib in liblist:
            module = module + ' ' + str(lib)
    return os.path.join(F2PYpath,'f2py')  + ' -c $SOURCE -m ' + module + ' ' + F2PYflags
f2py = Builder(generator = generate_f2py)
env.Append(BUILDERS = {'f2py' : f2py},)
# create a builder for the fortran compiler for library compilation so we can control how it is done
def generate_obj(source, target, env, for_signature):
    dir = os.path.split(str(source[0]))[0]
    obj = os.path.splitext(str(source[0]))[0]+'.o'
    return os.path.join(FORTpath,FCompiler)  + ' -c $SOURCE ' + FORTflags + ' -I' + dir + ' -o' + obj
fort = Builder(generator = generate_obj, suffix = '.o',
               src_suffix = '.for')
# create a library builder so we can control how it is done on windows
def generate_lib(source, target, env, for_signature):
    srclst = ""
    for s in source:
      srclst += str(s) + " "
    return os.path.join(FORTpath,'ar.exe')  + ' -rs $TARGET ' + srclst
lib = Builder(generator = generate_lib, suffix = '.a',
               src_suffix = '.o')
env.Append(BUILDERS = {'fort' : fort, 'lib' : lib},)

#==========================================================================================
# Setup build Environment
#    add compiler, f2py & python to path
if FORTpath != "":  env.PrependENVPath('PATH', FORTpath)
if F2PYpath != "":  env.PrependENVPath('PATH', F2PYpath)
if pythonpath != "" and pythonpath != F2PYpath: env.PrependENVPath('PATH', pythonpath)
#   add other needed environment variables
var = 'LDFLAGS'
if eval(var) != "": env['ENV'][var] = eval(var)

#==========================================================================================
# finally ready to build something!
# locate libraries to be built (subdirectories named *subs)
for sub in glob.glob('*subs'):
    filelist = []
    for file in glob.glob(os.path.join(sub,'*.for')):
        #target = os.path.splitext(file)[0]+'.o'
        target = env.fort(file) # connect .o files to .for files
        #print 'Compile: ',file, target
        filelist.append(target)
    #lib = Library(sub, Glob(os.path.join(sub,'*.for'))) # register library to be created
    if sys.platform == "win32":
	 lib = env.lib(sub, filelist) 
    else:
       lib = Library(sub, filelist) # register library to be created
    liblist.append(lib[0].name)
    filename = str(lib[0])
    #Install(InstallLoc,filename)
# find modules that need to be built
modlist = []
for src in glob.glob('*.for'):
    target = os.path.splitext(src)[0] + F2PYsuffix
    out = env.f2py(target,src)
    Depends(target, liblist) # make sure libraries are rebuilt if old
    modlist.append(out[0].name)
    env.Install(InstallLoc, out[0].name)
    #break # bail out early for testing
#==========================================================================================
# all done with setup, show the user the options and let scons do the work
print 80*'='
for var in ['FCompiler','FORTpath','FORTflags','F2PYflags','F2PYpath','F2PYsuffix','LDFLAGS']:
    print 'Variable',var,'is',eval(var)
print 'Using python at', pythonprogram
print 'Python/f2py version =',version,PlatformBits
print 'Install directory is',InstallLoc
print 'Will build object libraries:',
for lib in liblist: print " " + lib,
print ""
print 'f2py will build these modules:',
for mod in modlist: print " " + mod,
print ""
print 80*'='
#print env.Dump()
if 'help' in COMMAND_LINE_TARGETS: sys.exit()
