source: trunk/fsource/SConstruct @ 3178

Last change on this file since 3178 was 3178, checked in by toby, 4 years ago

revised Scons input; builds on all platforms

File size: 16.3 KB
Line 
1# this is a build script that is intended to be run from scons (it will not work in python)
2# it compiles the Fortran files needed to be used as Python packages for GSAS-II
3#
4# if the default options need to be overridden for this to work on your system, please let us
5# know the details of what OS, compiler and python you are using as well as the command-line
6# options you need.
7import platform
8import sys
9import os
10import glob
11import subprocess
12import numpy as np
13#==========================================================================================
14def is_exe(fpath):
15    return os.path.exists(fpath) and os.access(fpath, os.X_OK)
16def which_path(program):
17    "emulates Unix which: finds a program in path, but returns the path"
18    import os, sys
19    if sys.platform == "win32" and os.path.splitext(program)[1].lower() != '.exe':
20        program = program + '.exe'
21    fpath, fname = os.path.split(program)
22    if fpath:
23        if is_exe(program):
24            return fpath
25    else:
26        for path in os.environ["PATH"].split(os.pathsep):
27            exe_file = os.path.join(path, program)
28            if is_exe(exe_file):
29                return path
30    return ""
31
32def GetBinaryDir():
33    # format current platform, Python & numpy version
34    if sys.platform == "win32":
35        prefix = 'win'
36    elif sys.platform == "darwin":
37        prefix = 'mac'
38    elif sys.platform.startswith("linux"):
39        prefix = 'linux'
40    else:
41        print(u'Unknown platform: '+sys.platform)
42        raise Exception('Unknown platform')
43    if platform.architecture()[0] == '64bit':
44        bits = '64'
45    else:
46        bits = '32'
47    pyver = 'p{}.{}'.format(*sys.version_info[0:2])
48    npver = 'n' + np.__version__[:np.__version__.find('.',2)]
49    return '_'.join([prefix,bits,pyver,npver])
50#==========================================================================================
51# misc initializations
52# need command-line options for fortran command and fortran options
53F2PYflags = '' # compiler options for f2py command
54F2PYpath = ARGUMENTS.get('F2PYpath', '')
55# find a default f2py relative to the scons location. Should be in the same place as f2py
56spath = os.path.normpath(os.path.split(sys.executable)[0])
57for pth in [F2PYpath,spath,os.path.normpath(os.path.join(spath,'..')),os.path.join(spath,'Scripts')]:
58    if not pth: continue
59    if sys.platform == "win32":
60        program = 'f2py.exe'
61    else:
62        program = 'f2py'
63    f2pyprogram = os.path.join(pth,program)
64    if is_exe(f2pyprogram):
65        F2PYpath,F2PYprog = os.path.split(f2pyprogram)
66        break
67    program = 'f2py.py'
68    f2pyprogram = os.path.join(pth,program)
69    if os.path.exists(f2pyprogram) and os.path.splitext(program)[1] == '.py':
70        F2PYpath,F2PYprog = os.path.split(f2pyprogram)
71        break
72else:
73    print ('Note: Using f2py from path (hope that works!)')
74    F2PYpath = which_path('f2py')       # default path to f2py
75    F2PYprog = 'f2py'
76# check if we have a working path to f2py:
77f2pyprogram = os.path.normpath(os.path.join(F2PYpath,F2PYprog))
78if os.path.exists(f2pyprogram) and os.path.splitext(program)[1] == '.py':
79    pass
80elif is_exe(f2pyprogram):
81    pass
82else:
83    print ('''
84ERROR: The f2py program was not found. If this program is installed
85but not in your path, you should specify the path on the command line:
86   scons -Q F2PYpath=/Library/Frameworks/Python.framework/Versions/6.2/bin/
87   scons -Q F2PYpath=D:/Python27/Scripts
88''')
89    sys.exit()
90
91GFORTpath = which_path('gfortran')   # path to compiler
92FCompiler='gfortran'
93G77path = which_path('g77')     # path to compiler
94FORTpath = ""
95FORTflags = ""
96LDFLAGS = ''
97#==========================================================================================
98# configure platform dependent options here:
99if sys.platform == "win32":
100    F2PYsuffix = '.pyd'
101    if G77path != "":
102      FCompiler='g77'
103    elif GFORTpath != "":
104      FCompiler='gfortran'
105    else:
106      print ('No Fortran compiler in path')
107      sys.exit()
108elif sys.platform == "darwin":
109    LDFLAGS = '-undefined dynamic_lookup -bundle'
110    F2PYsuffix = '.so'
111elif sys.platform.startswith("linux"):
112    #LDFLAGS = '-Wall -shared -static-libgfortran -static-libgcc' # does not work with gfortran 4.4.4 20100726 (Red Hat 4.4.4-13)
113    F2PYsuffix = '.so'
114else:
115    print ("Sorry, parameters for platform "+sys.platform+" are not yet defined")
116    sys.exit()
117if FCompiler == 'gfortran':
118    if ARGUMENTS.get('LIBGCC', '').upper().startswith('T'):
119        LDFLAGS += ' -static-libgcc'
120        print('LIBGCC')
121    if ARGUMENTS.get('LIBGFORTRAN', '').upper().startswith('T'):
122        LDFLAGS += ' -static-libgfortran'
123        print('LIBGfortran')
124   
125#==========================================================================================
126# help
127if 'help' in COMMAND_LINE_TARGETS:
128    print ("""
129----------------------------------------------
130Building Fortran routines for use with GSAS-II
131----------------------------------------------
132
133To build the compiled modules files needed to run GSAS-II, invoke this script:
134    scons [options]
135where the following options are defined (all are optional):
136
137-Q      -- produces less output from scons
138
139-n      -- causes scons to show but not execute the commands it will perform
140
141-c      -- clean: causes scons to delete previously created files (but not from
142   install directory)
143
144help    -- causes this message to be displayed (no compiling is done)
145
146install=T -- causes the module files to be placed in an installation directory
147   (../AllBinaries/<X>_NN_pv.v_nv.v) rather than ../bin, where:
148     <X> is mac, win or linux;
149     NN is 64 or 32;
150     pv.v is the Python version (p2.7 or p3.6,...) and
151     nv.v is the Numpy version (n1.13,...).
152   Normally used only by Brian or Bob for distribution of compiled software.
153
154The following options override defaults set in the scons script:
155
156FCompiler=<name>  -- define the name of the fortran compiler, typically g77
157   or gfortran; default is to use g77 on Windows and gfortran elsewhere. If
158   you use something other than these, you must also specify F2PYflags.
159
160FORTpath=<path>    -- define a path to the fortran program; default is to use
161   first gfortran (g77 for Windows) found in path
162
163FORTflags='string' -- string of options to be used for Fortran
164   during library build step
165
166F2PYpath=<path>    -- define a path to the f2py program; default is to use
167   first f2py found in path
168
169F2PYflags='string' -- defines optional flags to be supplied to f2py:
170   Typically these option define which fortran compiler to use.
171
172F2PYsuffix='.xxx'  -- extension for output module files (default: win: '.pyd',
173   mac/linux: '.so')
174
175LIBGCC=T -- adds the option -static-libgcc as an link option with gfortran. This
176   prevents use of the dynamic libgcc library, which must be then present on each
177   run-time computer. To use this, gfortran must be installed with the static
178   libraries.
179
180LIBGFORTRAN=T -- adds the option -static-libgfortran as an link option with
181   gfortran. This prevents use of the dynamic libgcc library, which then must be
182   present on each run-time computer. To use this, gfortran must be installed
183   with the static libraries.
184     
185LDFLAGS='string'   -- string of options to be used for f2py during link step
186
187Note that at present, this has been tested with 32-bit python on windows and
188Mac & 64 bit on linux. Python 3.x is not supported in GSAS-II yet.
189
190examples:
191    scons -Q
192       (builds into ../bin for current platform using default options)
193    scons -Q install
194       (builds into ../bin<platform> for module distribution)
195    scons -Q install F2PYpath=/Library/Frameworks/Python.framework/Versions/6.2/bin/
196       (builds with a non-default [e.g. older] version of python)
197    """)
198    #sys.exit()
199#==========================================================================================
200# override from command-line options
201for var in ['F2PYflags','F2PYpath','F2PYsuffix','FCompiler','FORTpath','FORTflags','LDFLAGS']:
202    if ARGUMENTS.get(var, None) is not None:
203        print ('Setting',var,'to',ARGUMENTS.get(var),'based on command line')
204        exec(var + "= ARGUMENTS.get('" + var +"')")
205#==========================================================================================
206# get the python version number from the python image in the f2py directory
207# find the python location associated with the f2py in use. Note
208#   that on Windows it may be in the parent of the f2py location.
209# then run it to get info about the verision and the number of bits
210pythonpath = ''
211for program in ['python','../python']:
212    if sys.platform == "win32" and os.path.splitext(program)[1].lower() != '.exe':
213        program = program + '.exe'
214    pythonprogram = os.path.normpath(os.path.join(F2PYpath,program))
215    if is_exe(pythonprogram):
216        pythonpath = os.path.split(program)[0]
217        break
218else:
219    print ('python not found')
220    sys.exit()
221#p = subprocess.Popen(pythonprogram, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
222#p.stdin.write("import sys,platform;print (str(sys.version_info[0]));print (str(sys.version_info[1]));print (platform.architecture()[0]);sys.exit()")
223#p.stdin.close()
224#p.wait()
225#versions = p.stdout.readlines()
226versions = platform.python_version_tuple()
227version = str(int(versions[0])) + '.' + str(int(versions[1]))
228PlatformBits = '64bits'
229if '32' in platform.architecture()[0]:
230   PlatformBits = '32bits'
231#PlatformBits = versions[2][:-1]
232#
233# Set install location
234if ARGUMENTS.get('install', '').upper().startswith('T'):
235    InstallLoc = os.path.join('..','AllBinaries', GetBinaryDir())
236else:
237    InstallLoc = os.path.join('..','bin')
238#==========================================================================================
239# use the compiler choice to set compiler options, but don't change anything
240# specified on the command line
241if FCompiler == 'gfortran':
242    if FORTpath == "": FORTpath = GFORTpath
243    if sys.platform.startswith("linux") and "64" in PlatformBits:
244        if FORTflags == "": FORTflags = ' -w -O2 -fPIC -m64'
245        if F2PYflags == "": F2PYflags = '--fcompiler=gnu95 --f77exec=gfortran --f77flags="-fno-range-check -m64"'# --arch="-arch x86_64"'
246    elif sys.platform.startswith("linux"):
247        if FORTflags == "": FORTflags = ' -w -O2 -fPIC -m32'
248        if F2PYflags == "": F2PYflags = '--fcompiler=gnu95 --f77exec=gfortran --f77flags="-fno-range-check -m32"'
249    elif sys.platform == "darwin": # now 64 bit only
250        #if "64" in PlatformBits:
251        LDFLAGS += " -arch x86_64 -m64"
252        if FORTflags == "": FORTflags = ' -w -O2 -m64'
253        if F2PYflags == "": F2PYflags = '--fcompiler=gnu95 --f77exec=gfortran --f77flags="-fno-range-check -m64"'
254    elif sys.platform == "win32" and "64" in PlatformBits:
255        if FORTflags == "": FORTflags = ' -w -O2 -m64'
256        if F2PYflags == "":
257            F2PYflags = '--compiler=mingw32 --fcompiler=gfortran --f77flags="-fno-range-check -m64"'
258    elif sys.platform == "win32":
259        #if FORTflags == "": FORTflags = ' -w -O2 -m32'
260        if F2PYflags == "":
261            F2PYflags = '--compiler=mingw32 --fcompiler=gfortran --f77flags="-fno-range-check"'
262elif FCompiler == 'g77':
263    if FORTpath == "": FORTpath = G77path
264    if sys.platform == "win32":
265        if F2PYflags == "": F2PYflags = '--compiler=mingw32 --fcompiler=g77'
266        if FORTflags == "": FORTflags = ' -w -O2 -fno-automatic -finit-local-zero -malign-double -mwindows'
267    else:
268        if F2PYflags == "": F2PYflags = '--fcompiler=gnu --f77exec=g77 --f77flags="-fno-range-check"'
269
270else:
271    if FORTpath == "": print ('Likely error, FORTpath is not specified')
272    if F2PYflags == "":
273        print ('Error: specify a F2PYflags value')
274        sys.exit()
275#==========================================================================================
276# Setup build Environment
277env = Environment()
278# Define a builder to run f2py
279def generate_f2py(source, target, env, for_signature):
280    module = os.path.splitext(str(source[0]))[0]
281    if len(liblist) > 0:
282        for lib in liblist:
283            module = module + ' ' + str(lib)
284    f2pyprogram = os.path.normpath(os.path.join(F2PYpath,F2PYprog))
285    if os.path.splitext(F2PYprog)[1] == '.py':     # use f2py.py if no f2py[.exe]
286        f2pycmd = pythonprogram + ' ' + f2pyprogram + ' -c $SOURCE ' + ' -m ' + module + ' ' + F2PYflags
287    else:
288        f2pycmd = f2pyprogram + ' -c $SOURCE' + ' -m ' + module + ' ' + F2PYflags
289    if sys.platform == "win32":
290        installcmd = "copy " + os.path.splitext(str(source[0]))[0] + '*' + F2PYsuffix + ' ' + InstallLoc
291    else:
292        installcmd = "cp " + os.path.splitext(str(source[0]))[0] + '*' + F2PYsuffix + ' ' + InstallLoc
293    return [f2pycmd, installcmd]
294f2py = Builder(generator = generate_f2py)
295env.Append(BUILDERS = {'f2py' : f2py},)
296# create a builder for the fortran compiler for library compilation so we can control how it is done
297def generate_obj(source, target, env, for_signature):
298    dir = os.path.split(str(source[0]))[0]
299    obj = os.path.splitext(str(source[0]))[0]+'.o'
300    return os.path.join(FORTpath,FCompiler)  + ' -c $SOURCE ' + FORTflags + ' -I' + dir + ' -o' + obj
301fort = Builder(generator = generate_obj, suffix = '.o', src_suffix = '.for')
302# create a library builder so we can control how it is done on windows
303def generate_lib(source, target, env, for_signature):
304    srclst = ""
305    for s in source:
306      srclst += str(s) + " "
307    return os.path.join(FORTpath,'ar.exe')  + ' -rs $TARGET ' + srclst
308lib = Builder(generator = generate_lib, suffix = '.a',
309               src_suffix = '.o')
310env.Append(BUILDERS = {'fort' : fort, 'lib' : lib},)
311
312#==========================================================================================
313# Setup build Environment
314#    add compiler, f2py & python to path
315if FORTpath != "":  env.PrependENVPath('PATH', FORTpath)
316if F2PYpath != "":  env.PrependENVPath('PATH', F2PYpath)
317if pythonpath != "" and pythonpath != F2PYpath: env.PrependENVPath('PATH', pythonpath)
318#   add other needed environment variables
319var = 'LDFLAGS'
320if eval(var) != "":
321    env['ENV'][var] = eval(var)
322    print("Setting environment variable {} to {}".format(var,eval(var)))
323if 'WINDIR' in os.environ: env['ENV']['WINDIR'] = os.environ['WINDIR']
324
325#==========================================================================================
326# finally ready to build something!
327# locate libraries to be built (subdirectories named *subs)
328liblist = []
329for sub in glob.glob('*subs'):
330    filelist = []
331    for file in glob.glob(os.path.join(sub,'*.for')):
332        #target = os.path.splitext(file)[0]+'.o'
333        target = env.fort(file) # connect .o files to .for files
334        #print ('Compile: ',file, target)
335        filelist.append(target)
336    #lib = Library(sub, Glob(os.path.join(sub,'*.for'))) # register library to be created
337    if sys.platform == "win32":
338       lib = env.lib(sub, filelist)
339    else:
340       lib = Library(sub, filelist) # register library to be created
341    liblist.append(lib[0].name)
342    filename = str(lib[0])
343# find modules that need to be built
344modlist = []
345for src in glob.glob('*.for'):
346    target = os.path.splitext(src)[0] + F2PYsuffix
347    out = env.f2py(target,src)
348    Clean(out, Glob(os.path.splitext(src)[0] + "*" + F2PYsuffix)) # this picks up old- & new-style .pyd/.so names
349    Depends(target, liblist) # make sure libraries are rebuilt if old
350    modlist.append(out[0].name)
351    #break # bail out early for testing
352#==========================================================================================
353# all done with setup, show the user the options and let scons do the work
354print (80*'=')
355for var in ['FCompiler','FORTpath','FORTflags','F2PYflags','F2PYpath','F2PYsuffix','LDFLAGS']:
356    print ('Variable',var,'is',eval(var))
357print ('Using python at', pythonprogram )
358print ('Python/f2py version =',version,PlatformBits)
359print ('Install directory is',InstallLoc)
360print ('Will build object libraries:',)
361for lib in liblist: print (" " + lib,)
362print ("")
363print ('f2py will build these modules:',)
364for mod in modlist: print (" " + mod,)
365print ("")
366print (80*'=')
367#print (env.Dump())
368if 'help' in COMMAND_LINE_TARGETS: sys.exit()
369if not os.path.exists(InstallLoc):
370    print('Creating '+InstallLoc)
371    os.makedirs(InstallLoc)
Note: See TracBrowser for help on using the repository browser.