source: trunk/fsource/SConstruct @ 5125

Last change on this file since 5125 was 5125, checked in by toby, 23 months ago

successful Raspberry Pi install

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