# 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 us # 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 import numpy as np #========================================================================================== 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 "" def GetBinaryDir(): # format current platform, Python & numpy version if sys.platform == "win32": prefix = 'win' elif sys.platform == "darwin": prefix = 'mac' elif sys.platform.startswith("linux"): prefix = 'linux' else: print(u'Unknown platform: '+sys.platform) raise Exception('Unknown platform') if platform.architecture()[0] == '64bit': bits = '64' else: bits = '32' pyver = 'p{}.{}'.format(*sys.version_info[0:2]) npver = 'n' + np.__version__[:np.__version__.find('.',2)] return '_'.join([prefix,bits,pyver,npver]) #========================================================================================== # misc initializations # need command-line options for fortran command and fortran options F2PYflags = '' # compiler options for f2py command F2PYpath = ARGUMENTS.get('F2PYpath', '') # find a default f2py relative to the scons location. Should be in the same place as f2py spath = os.path.normpath(os.path.split(sys.executable)[0]) for pth in [F2PYpath,spath,os.path.normpath(os.path.join(spath,'..')),os.path.join(spath,'Scripts')]: if not pth: continue if sys.platform == "win32": program = 'f2py.exe' else: program = 'f2py' f2pyprogram = os.path.join(pth,program) if is_exe(f2pyprogram): F2PYpath,F2PYprog = os.path.split(f2pyprogram) break program = 'f2py.py' f2pyprogram = os.path.join(pth,program) if os.path.exists(f2pyprogram) and os.path.splitext(program)[1] == '.py': F2PYpath,F2PYprog = os.path.split(f2pyprogram) break else: print ('Note: Using f2py from path (hope that works!)') F2PYpath = which_path('f2py') # default path to f2py F2PYprog = 'f2py' # check if we have a working path to f2py: f2pyprogram = os.path.normpath(os.path.join(F2PYpath,F2PYprog)) if os.path.exists(f2pyprogram) and os.path.splitext(program)[1] == '.py': pass elif is_exe(f2pyprogram): pass else: 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() GFORTpath = which_path('gfortran') # path to compiler FCompiler='gfortran' G77path = which_path('g77') # path to compiler FORTpath = "" FORTflags = "" LDFLAGS = '' tmpdir = None #========================================================================================== # 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' F2PYsuffix = '.so' elif sys.platform.startswith("linux"): #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() if ARGUMENTS.get('TMP'): tmpdir = ARGUMENTS.get('TMP') if FCompiler == 'gfortran': if ARGUMENTS.get('LIBGCC', '').upper().startswith('T'): LDFLAGS += ' -static-libgcc' print('LIBGCC') if ARGUMENTS.get('LIBGFORTRAN', '').upper().startswith('T'): LDFLAGS += ' -static-libgfortran' print('LIBGfortran') #========================================================================================== # 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 (but not from install directory) help -- causes this message to be displayed (no compiling is done) install=T -- causes the module files to be placed in an installation directory (../AllBinaries/_NN_pv.v_nv.v) rather than ../bin, where: is mac, win or linux; NN is 64 or 32; pv.v is the Python version (p2.7 or p3.6,...) and nv.v is the Numpy version (n1.13,...). Normally used only by Brian or Bob for distribution of compiled software. The following options override defaults set in the scons script: FCompiler= -- 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= -- 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= -- 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') LIBGCC=T -- adds the option -static-libgcc as an link option with gfortran. This prevents use of the dynamic libgcc library, which must be then present on each run-time computer. To use this, gfortran must be installed with the static libraries. LIBGFORTRAN=T -- adds the option -static-libgfortran as an link option with gfortran. This prevents use of the dynamic libgcc library, which then must be present on each run-time computer. To use this, gfortran must be installed with the static libraries. LDFLAGS='string' -- string of options to be used for f2py during link step TMP= --- where is something like /tmp sets builds to be performed in that directory. Note that at present, this has been tested with 32-bit python on windows and Mac & 64 bit on linux. 32-bit builds with anaconda/gfortran in 32-bit Python is not working, at least not when installed in 64-bits Linux/Windows. examples: scons -Q (builds into ../bin for current platform using default options) scons -Q install=t (builds into ../bin for module distribution) """) #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 # 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() versions = platform.python_version_tuple() version = str(int(versions[0])) + '.' + str(int(versions[1])) PlatformBits = '64bits' if '32' in platform.architecture()[0]: PlatformBits = '32bits' #PlatformBits = versions[2][:-1] # # Set install location if ARGUMENTS.get('install', '').upper().startswith('T'): InstallLoc = os.path.join('..','AllBinaries', GetBinaryDir()) else: InstallLoc = os.path.join('..','bin') #========================================================================================== # 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 sys.platform.startswith("linux") and "64" in PlatformBits: if FORTflags == "": FORTflags = ' -w -O2 -fPIC -m64' if F2PYflags == "": F2PYflags = '--fcompiler=gnu95 --f77exec=gfortran --f77flags="-fno-range-check -m64"'# --arch="-arch x86_64"' elif sys.platform.startswith("linux"): if FORTflags == "": FORTflags = ' -w -O2 -fPIC -m32' if F2PYflags == "": F2PYflags = '--fcompiler=gnu95 --f77exec=gfortran --f77flags="-fno-range-check -m32"' elif sys.platform == "darwin": # now 64 bit only #if "64" in PlatformBits: LDFLAGS += " -arch x86_64 -m64" if FORTflags == "": FORTflags = ' -w -O2 -m64' if F2PYflags == "": F2PYflags = '--fcompiler=gnu95 --f77exec=gfortran --f77flags="-fno-range-check -m64"' elif sys.platform == "win32" and "64" in PlatformBits: if FORTflags == "": FORTflags = ' -w -O2 -m64' if F2PYflags == "": F2PYflags = '--compiler=mingw32 --fcompiler=gfortran --f77flags="-fno-range-check -m64"' elif sys.platform == "win32": # the next line may need to be removed. When compiling with a 32-bit machine? #if FORTflags == "": FORTflags = ' -w -O2 -m32' if F2PYflags == "": F2PYflags = '--compiler=mingw32 --fcompiler=gfortran --f77flags="-fno-range-check"' elif FCompiler == 'g77': if FORTpath == "": FORTpath = G77path if sys.platform == "win32": if F2PYflags == "": F2PYflags = '--compiler=mingw32 --fcompiler=g77' 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() if tmpdir: F2PYflags += " --build-dir " + tmpdir #========================================================================================== # Setup build Environment if sys.platform == "win32": env = Environment(ENV = os.environ) else: 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) f2pyprogram = os.path.normpath(os.path.join(F2PYpath,F2PYprog)) if os.path.splitext(F2PYprog)[1] == '.py': # use f2py.py if no f2py[.exe] f2pycmd = pythonprogram + ' ' + f2pyprogram + ' -c $SOURCE ' + ' -m ' + module + ' ' + F2PYflags else: f2pycmd = f2pyprogram + ' -c $SOURCE' + ' -m ' + module + ' ' + F2PYflags if sys.platform == "win32": installcmd = "copy " + os.path.splitext(str(source[0]))[0] + '*' + F2PYsuffix + ' ' + InstallLoc else: installcmd = "cp " + os.path.splitext(str(source[0]))[0] + '*' + F2PYsuffix + ' ' + InstallLoc return [f2pycmd, installcmd] 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) print("Setting environment variable {} to {}".format(var,eval(var))) if 'WINDIR' in os.environ: env['ENV']['WINDIR'] = os.environ['WINDIR'] #========================================================================================== # finally ready to build something! # locate libraries to be built (subdirectories named *subs) liblist = [] 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]) # 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) Clean(out, Glob(os.path.splitext(src)[0] + "*" + F2PYsuffix)) # this picks up old- & new-style .pyd/.so names Depends(target, liblist) # make sure libraries are rebuilt if old modlist.append(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() if not os.path.exists(InstallLoc): import datetime print('Creating '+InstallLoc) os.makedirs(InstallLoc) fp = open(os.path.join(InstallLoc,'Build.notes.txt'),'w') fp.write('Created {} on {}\n'.format(datetime.datetime.isoformat(datetime.datetime.now()), platform.node())) fp.close()