source: trunk/fsource/SConstruct @ 4969

Last change on this file since 4969 was 4969, checked in by toby, 5 months ago

move hist/phase docs to Phase/Data? section; cleanup

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