source: trunk/fsource/SConstruct @ 5132

Last change on this file since 5132 was 5132, checked in by toby, 9 months ago

correct M1 Mac bin path name

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