source: trunk/fsource/SConstruct @ 3674

Last change on this file since 3674 was 3674, checked in by toby, 5 years ago

misc cosmetic & build changes

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