source: trunk/fsource/SConstruct @ 5006

Last change on this file since 5006 was 5006, checked in by toby, 18 months ago

try to fix broken windows build

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