source: trunk/fsource/SConstruct @ 3203

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

binary Py3 updates

File size: 16.1 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=t
194       (builds into ../bin<platform-dir> for module distribution)
195    """)
196    #sys.exit()
197#==========================================================================================
198# override from command-line options
199for var in ['F2PYflags','F2PYpath','F2PYsuffix','FCompiler','FORTpath','FORTflags','LDFLAGS']:
200    if ARGUMENTS.get(var, None) is not None:
201        print ('Setting',var,'to',ARGUMENTS.get(var),'based on command line')
202        exec(var + "= ARGUMENTS.get('" + var +"')")
203#==========================================================================================
204# get the python version number from the python image in the f2py directory
205# find the python location associated with the f2py in use. Note
206#   that on Windows it may be in the parent of the f2py location.
207# then run it to get info about the verision and the number of bits
208pythonpath = ''
209for program in ['python','../python']:
210    if sys.platform == "win32" and os.path.splitext(program)[1].lower() != '.exe':
211        program = program + '.exe'
212    pythonprogram = os.path.normpath(os.path.join(F2PYpath,program))
213    if is_exe(pythonprogram):
214        pythonpath = os.path.split(program)[0]
215        break
216else:
217    print ('python not found')
218    sys.exit()
219#p = subprocess.Popen(pythonprogram, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
220#p.stdin.write("import sys,platform;print (str(sys.version_info[0]));print (str(sys.version_info[1]));print (platform.architecture()[0]);sys.exit()")
221#p.stdin.close()
222#p.wait()
223#versions = p.stdout.readlines()
224versions = platform.python_version_tuple()
225version = str(int(versions[0])) + '.' + str(int(versions[1]))
226PlatformBits = '64bits'
227if '32' in platform.architecture()[0]:
228   PlatformBits = '32bits'
229#PlatformBits = versions[2][:-1]
230#
231# Set install location
232if ARGUMENTS.get('install', '').upper().startswith('T'):
233    InstallLoc = os.path.join('..','AllBinaries', GetBinaryDir())
234else:
235    InstallLoc = os.path.join('..','bin')
236#==========================================================================================
237# use the compiler choice to set compiler options, but don't change anything
238# specified on the command line
239if FCompiler == 'gfortran':
240    if FORTpath == "": FORTpath = GFORTpath
241    if sys.platform.startswith("linux") and "64" in PlatformBits:
242        if FORTflags == "": FORTflags = ' -w -O2 -fPIC -m64'
243        if F2PYflags == "": F2PYflags = '--fcompiler=gnu95 --f77exec=gfortran --f77flags="-fno-range-check -m64"'# --arch="-arch x86_64"'
244    elif sys.platform.startswith("linux"):
245        if FORTflags == "": FORTflags = ' -w -O2 -fPIC -m32'
246        if F2PYflags == "": F2PYflags = '--fcompiler=gnu95 --f77exec=gfortran --f77flags="-fno-range-check -m32"'
247    elif sys.platform == "darwin": # now 64 bit only
248        #if "64" in PlatformBits:
249        LDFLAGS += " -arch x86_64 -m64"
250        if FORTflags == "": FORTflags = ' -w -O2 -m64'
251        if F2PYflags == "": F2PYflags = '--fcompiler=gnu95 --f77exec=gfortran --f77flags="-fno-range-check -m64"'
252    elif sys.platform == "win32" and "64" in PlatformBits:
253        if FORTflags == "": FORTflags = ' -w -O2 -m64'
254        if F2PYflags == "":
255            F2PYflags = '--compiler=mingw32 --fcompiler=gfortran --f77flags="-fno-range-check -m64"'
256    elif sys.platform == "win32":
257        #if FORTflags == "": FORTflags = ' -w -O2 -m32'
258        if F2PYflags == "":
259            F2PYflags = '--compiler=mingw32 --fcompiler=gfortran --f77flags="-fno-range-check"'
260elif FCompiler == 'g77':
261    if FORTpath == "": FORTpath = G77path
262    if sys.platform == "win32":
263        if F2PYflags == "": F2PYflags = '--compiler=mingw32 --fcompiler=g77'
264        if FORTflags == "": FORTflags = ' -w -O2 -fno-automatic -finit-local-zero -malign-double -mwindows'
265    else:
266        if F2PYflags == "": F2PYflags = '--fcompiler=gnu --f77exec=g77 --f77flags="-fno-range-check"'
267
268else:
269    if FORTpath == "": print ('Likely error, FORTpath is not specified')
270    if F2PYflags == "":
271        print ('Error: specify a F2PYflags value')
272        sys.exit()
273#==========================================================================================
274# Setup build Environment
275env = Environment()
276# Define a builder to run f2py
277def generate_f2py(source, target, env, for_signature):
278    module = os.path.splitext(str(source[0]))[0]
279    if len(liblist) > 0:
280        for lib in liblist:
281            module = module + ' ' + str(lib)
282    f2pyprogram = os.path.normpath(os.path.join(F2PYpath,F2PYprog))
283    if os.path.splitext(F2PYprog)[1] == '.py':     # use f2py.py if no f2py[.exe]
284        f2pycmd = pythonprogram + ' ' + f2pyprogram + ' -c $SOURCE ' + ' -m ' + module + ' ' + F2PYflags
285    else:
286        f2pycmd = f2pyprogram + ' -c $SOURCE' + ' -m ' + module + ' ' + F2PYflags
287    if sys.platform == "win32":
288        installcmd = "copy " + os.path.splitext(str(source[0]))[0] + '*' + F2PYsuffix + ' ' + InstallLoc
289    else:
290        installcmd = "cp " + os.path.splitext(str(source[0]))[0] + '*' + F2PYsuffix + ' ' + InstallLoc
291    return [f2pycmd, installcmd]
292f2py = Builder(generator = generate_f2py)
293env.Append(BUILDERS = {'f2py' : f2py},)
294# create a builder for the fortran compiler for library compilation so we can control how it is done
295def generate_obj(source, target, env, for_signature):
296    dir = os.path.split(str(source[0]))[0]
297    obj = os.path.splitext(str(source[0]))[0]+'.o'
298    return os.path.join(FORTpath,FCompiler)  + ' -c $SOURCE ' + FORTflags + ' -I' + dir + ' -o' + obj
299fort = Builder(generator = generate_obj, suffix = '.o', src_suffix = '.for')
300# create a library builder so we can control how it is done on windows
301def generate_lib(source, target, env, for_signature):
302    srclst = ""
303    for s in source:
304      srclst += str(s) + " "
305    return os.path.join(FORTpath,'ar.exe')  + ' -rs $TARGET ' + srclst
306lib = Builder(generator = generate_lib, suffix = '.a',
307               src_suffix = '.o')
308env.Append(BUILDERS = {'fort' : fort, 'lib' : lib},)
309
310#==========================================================================================
311# Setup build Environment
312#    add compiler, f2py & python to path
313if FORTpath != "":  env.PrependENVPath('PATH', FORTpath)
314if F2PYpath != "":  env.PrependENVPath('PATH', F2PYpath)
315if pythonpath != "" and pythonpath != F2PYpath: env.PrependENVPath('PATH', pythonpath)
316#   add other needed environment variables
317var = 'LDFLAGS'
318if eval(var) != "":
319    env['ENV'][var] = eval(var)
320    print("Setting environment variable {} to {}".format(var,eval(var)))
321if 'WINDIR' in os.environ: env['ENV']['WINDIR'] = os.environ['WINDIR']
322
323#==========================================================================================
324# finally ready to build something!
325# locate libraries to be built (subdirectories named *subs)
326liblist = []
327for sub in glob.glob('*subs'):
328    filelist = []
329    for file in glob.glob(os.path.join(sub,'*.for')):
330        #target = os.path.splitext(file)[0]+'.o'
331        target = env.fort(file) # connect .o files to .for files
332        #print ('Compile: ',file, target)
333        filelist.append(target)
334    #lib = Library(sub, Glob(os.path.join(sub,'*.for'))) # register library to be created
335    if sys.platform == "win32":
336       lib = env.lib(sub, filelist)
337    else:
338       lib = Library(sub, filelist) # register library to be created
339    liblist.append(lib[0].name)
340    filename = str(lib[0])
341# find modules that need to be built
342modlist = []
343for src in glob.glob('*.for'):
344    target = os.path.splitext(src)[0] + F2PYsuffix
345    out = env.f2py(target,src)
346    Clean(out, Glob(os.path.splitext(src)[0] + "*" + F2PYsuffix)) # this picks up old- & new-style .pyd/.so names
347    Depends(target, liblist) # make sure libraries are rebuilt if old
348    modlist.append(out[0].name)
349    #break # bail out early for testing
350#==========================================================================================
351# all done with setup, show the user the options and let scons do the work
352print (80*'=')
353for var in ['FCompiler','FORTpath','FORTflags','F2PYflags','F2PYpath','F2PYsuffix','LDFLAGS']:
354    print ('Variable',var,'is',eval(var))
355print ('Using python at', pythonprogram )
356print ('Python/f2py version =',version,PlatformBits)
357print ('Install directory is',InstallLoc)
358print ('Will build object libraries:',)
359for lib in liblist: print (" " + lib,)
360print ("")
361print ('f2py will build these modules:',)
362for mod in modlist: print (" " + mod,)
363print ("")
364print (80*'=')
365#print (env.Dump())
366if 'help' in COMMAND_LINE_TARGETS: sys.exit()
367if not os.path.exists(InstallLoc):
368    print('Creating '+InstallLoc)
369    os.makedirs(InstallLoc)
Note: See TracBrowser for help on using the repository browser.