source: trunk/fsource/SConstruct @ 5007

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