source: trunk/GSASIIpath.py @ 2316

Last change on this file since 2316 was 2134, checked in by toby, 9 years ago

better display of G2 version number

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 19.3 KB
Line 
1# -*- coding: utf-8 -*-
2'''
3*GSASIIpath: locations & updates*
4---------------------------------
5
6Routines for dealing with file locations, etc.
7
8Determines the location of the compiled (.pyd or .so) libraries.
9
10Interfaces with subversion (svn):
11Determine the subversion release number by determining the highest version number
12where :func:`SetVersionNumber` is called (best done in every GSASII file).
13Other routines will update GSASII from the subversion server if svn can be
14found.
15
16Accesses configuration options, as defined in config.py
17'''
18
19import os
20import sys
21import platform
22# see if a directory for local modifications is defined. If so, stick that in the path
23if os.path.exists(os.path.expanduser('~/.G2local/')):
24    sys.path.insert(0,os.path.expanduser('~/.G2local/'))
25    import glob
26    fl = glob.glob(os.path.expanduser('~/.G2local/GSASII*.py*'))
27    files = ""
28    prev = None
29    for f in sorted(fl): # make a list of files, dropping .pyc files where a .py exists
30        f = os.path.split(f)[1]
31        if os.path.splitext(f)[0] == prev: continue
32        prev = os.path.splitext(f)[0]
33        if files: files += ", "
34        files += f
35    if files:
36        print("*"*75)
37        print("Warning: the following source files are locally overridden in "+os.path.expanduser('~/.G2local/'))
38        print("  "+files)
39        print("*"*75)
40           
41
42# determine a binary path for the pyd files based on the host OS and the python version, 
43# path is relative to location of the script that is called as well as this file
44# this must be imported before anything that imports any .pyd/.so file for GSASII
45bindir = None
46if sys.platform == "win32":
47    if platform.architecture()[0] == '64bit':
48        bindir = 'binwin64-%d.%d' % sys.version_info[0:2]
49    else:
50        bindir = 'binwin%d.%d' % sys.version_info[0:2]
51elif sys.platform == "darwin":
52    import platform
53    if platform.architecture()[0] == '64bit':
54        bindir = 'binmac64-%d.%d' % sys.version_info[0:2]
55    else:
56        bindir = 'binmac%d.%d' % sys.version_info[0:2]
57    if platform.mac_ver()[0].startswith('10.5.'):
58        bindir += '_10.5'
59elif sys.platform == "linux2":
60    if platform.architecture()[0] == '64bit':
61        bindir = 'binlinux64-%d.%d' % sys.version_info[0:2]
62    else:
63        bindir = 'binlinux%d.%d' % sys.version_info[0:2]
64for loc in sys.path[0],os.path.abspath(os.path.split(__file__)[0]):
65    if bindir:
66        if os.path.exists(os.path.join(loc,bindir)) and os.path.join(loc,bindir) not in sys.path: 
67            sys.path.insert(0,os.path.join(loc,bindir))
68        # is there a bin directory? (created by a local compile), if so put
69        # that at the top of the path
70    if os.path.exists(os.path.join(loc,'bin')) and os.path.getsize(os.path.join(loc,'bin')):
71        bindir = 'bin'
72        if os.path.join(loc,'bin') not in sys.path: 
73            sys.path.insert(0,os.path.join(loc,bindir))
74print 'GSAS-II binary directory: ',os.path.join(loc,bindir)
75if bindir == None:
76    raise Exception,"**** ERROR GSAS-II binary libraries not found, GSAS-II fails ****"
77# add the data import and export directory to the search path
78path2GSAS2 = os.path.dirname(os.path.realpath(__file__)) # location of this file; save before any changes in pwd
79newpath = os.path.join(path2GSAS2,'imports')
80if newpath not in sys.path: sys.path.append(newpath)
81newpath = os.path.join(path2GSAS2,'exports')
82if newpath not in sys.path: sys.path.append(newpath)
83
84# setup read of config.py, if present
85try:
86    import config
87    configDict = config.__dict__
88    import inspect
89    vals = [True for i in inspect.getmembers(config) if '__' not in i[0]]
90    print str(len(vals))+' values read from config file '+os.path.abspath(config.__file__)
91except ImportError:
92    configDict = {}
93   
94def GetConfigValue(key,default=None):
95    '''Return the configuration file value for key or a default value if not present
96   
97    :param str key: a value to be found in the configuration (config.py) file
98    :param default: a value to be supplied is none is in the config file or
99      the config file is not found. Defaults to None
100    :returns: the value found or the default.
101    '''
102    return configDict.get(key,default)
103
104def SetConfigValue(parmdict):
105    '''Set configuration variables from a dictionary where elements are lists
106    First item in list is the default value and second is the value to use.
107    '''
108    global configDict
109    for var in parmdict:
110        if var in configDict:
111            del configDict[var]
112        if parmdict[var][1] is None: continue
113        if parmdict[var][1] == '': continue
114        if parmdict[var][0] == parmdict[var][1]: continue
115        configDict[var] = parmdict[var][1]
116
117# routines for looking a version numbers in files
118version = -1
119def SetVersionNumber(RevString):
120    '''Set the subversion version number
121
122    :param str RevString: something like "$Revision: 2134 $"
123      that is set by subversion when the file is retrieved from subversion.
124
125    Place ``GSASIIpath.SetVersionNumber("$Revision: 2134 $")`` in every python
126    file.
127    '''
128    try:
129        RevVersion = int(RevString.split(':')[1].split()[0])
130        global version
131        version = max(version,RevVersion)
132    except:
133        pass
134       
135def GetVersionNumber():
136    '''Return the maximum version number seen in :func:`SetVersionNumber`
137    '''
138    if version > 1000:
139        return version
140    else:
141        return "unknown"
142
143def LoadConfigFile(filename):
144    '''Read a GSAS-II configuration file.
145    Comments (starting with "%") are removed, as are empty lines
146   
147    :param str filename: base file name (such as 'file.dat'). Files with this name
148      are located from the path and the contents of each are concatenated.
149    :returns: a list containing each non-empty (after removal of comments) line
150      found in every matching config file.
151    '''
152    info = []
153    for path in sys.path:
154        fil = os.path.join(path,filename)
155        if not os.path.exists(fil): continue
156        try:
157            i = 0
158            fp = open(fil,'r')
159            for line in fp:
160                expr = line.split('#')[0].strip()
161                if expr:
162                    info.append(expr)
163                    i += 1
164            print(str(i)+' lines read from config file '+fil)
165        finally:
166            fp.close()
167    return info
168
169
170# routines to interface with subversion
171def whichsvn():
172    '''Returns a path to the subversion exe file, if any is found.
173    Searches the current path as well as subdirectory "svn" and
174    "svn/bin" in the location of the GSASII source files.
175
176    :returns: None if svn is not found or an absolute path to the subversion
177      executable file.
178    '''
179    def is_exe(fpath):
180        return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
181    svnprog = 'svn'
182    if sys.platform == "win32": svnprog += '.exe'
183    pathlist = os.environ["PATH"].split(os.pathsep)
184    pathlist.insert(0,os.path.join(os.path.split(__file__)[0],'svn'))
185    pathlist.insert(1,os.path.join(os.path.split(__file__)[0],'svn','bin'))
186    for path in pathlist:
187        exe_file = os.path.join(path, svnprog)
188        if is_exe(exe_file):
189            return os.path.abspath(exe_file)
190
191def svnVersion():
192    '''Get the version number of the current subversion executable
193
194    :returns: a string with a version number such as "1.6.6" or None if
195      subversion is not found.
196
197    '''
198    import subprocess
199    svn = whichsvn()
200    if not svn: return
201
202    cmd = [svn,'--version','--quiet']
203    s = subprocess.Popen(cmd,
204                         stdout=subprocess.PIPE,stderr=subprocess.PIPE)
205    out,err = s.communicate()
206    if err:
207        print 'subversion error!\nout=',out
208        print 'err=',err
209        return None
210    return out.strip()
211
212def svnVersionNumber():
213    '''Get the version number of the current subversion executable
214
215    :returns: a fractional version number such as 1.6 or None if
216      subversion is not found.
217
218    '''
219    ver = svnVersion()
220    if not ver: return 
221    M,m = svnVersion().split('.')[:2]
222    return int(M)+int(m)/10.
223
224def svnGetLog(fpath=os.path.split(__file__)[0],version=None):
225    '''Get the revision log information for a specific version of the specified package
226
227    :param str fpath: path to repository dictionary, defaults to directory where
228       the current file is located.
229    :param int version: the version number to be looked up or None (default)
230       for the latest version.
231
232    :returns: a dictionary with keys (one hopes) 'author', 'date', 'msg', and 'revision'
233
234    '''
235    import subprocess
236    import xml.etree.ElementTree as ET
237    svn = whichsvn()
238    if not svn: return
239    if version is not None:
240        vstr = '-r'+str(version)
241    else:
242        vstr = '-rHEAD'
243
244    cmd = [svn,'log',fpath,'--xml',vstr]
245    s = subprocess.Popen(cmd,
246                         stdout=subprocess.PIPE,stderr=subprocess.PIPE)
247    out,err = s.communicate()
248    if err:
249        print 'out=',out
250        print 'err=',err
251        return None
252    x = ET.fromstring(out)
253    d = {}
254    for i in x.iter('logentry'):
255        d = {'revision':i.attrib.get('revision','?')}
256        for j in i:
257            d[j.tag] = j.text
258        break # only need the first
259    return d
260
261def svnGetRev(fpath=os.path.split(__file__)[0],local=True):
262    '''Obtain the version number for the either the last update of the local version
263    or contacts the subversion server to get the latest update version (# of Head).
264
265    :param str fpath: path to repository dictionary, defaults to directory where
266       the current file is located
267    :param bool local: determines the type of version number, where
268       True (default): returns the latest installed update
269       False: returns the version number of Head on the server
270
271    :Returns: the version number as an str or
272       None if there is a subversion error (likely because the path is
273       not a repository or svn is not found)
274    '''
275
276    import subprocess
277    import xml.etree.ElementTree as ET
278    svn = whichsvn()
279    if not svn: return
280    if local:
281        cmd = [svn,'info',fpath,'--xml']
282    else:
283        cmd = [svn,'info',fpath,'--xml','-rHEAD']
284    if svnVersionNumber() >= 1.6:
285        cmd += ['--non-interactive', '--trust-server-cert']
286    s = subprocess.Popen(cmd, stdout=subprocess.PIPE,stderr=subprocess.PIPE)
287    out,err = s.communicate()
288    if err:
289        print 'svn failed\n',out
290        print 'err=',err
291        return None
292    x = ET.fromstring(out)
293    for i in x.iter('entry'):
294        rev = i.attrib.get('revision')
295        if rev: return rev
296
297def svnFindLocalChanges(fpath=os.path.split(__file__)[0]):
298    '''Returns a list of files that were changed locally. If no files are changed,
299       the list has length 0
300
301    :param fpath: path to repository dictionary, defaults to directory where
302       the current file is located
303
304    :returns: None if there is a subversion error (likely because the path is
305       not a repository or svn is not found)
306
307    '''
308    import subprocess
309    import xml.etree.ElementTree as ET
310    svn = whichsvn()
311    if not svn: return
312    cmd = [svn,'status',fpath,'--xml']
313    s = subprocess.Popen(cmd,
314                         stdout=subprocess.PIPE,stderr=subprocess.PIPE)
315    out,err = s.communicate()
316    if err: return None
317    x = ET.fromstring(out)
318    changed = []
319    for i in x.iter('entry'):
320        if i.find('wc-status').attrib.get('item') == 'modified': 
321            changed.append(i.attrib.get('path'))
322    return changed
323
324def svnUpdateDir(fpath=os.path.split(__file__)[0],version=None):
325    '''This performs an update of the files in a local directory from a server.
326
327    :param str fpath: path to repository dictionary, defaults to directory where
328       the current file is located
329    :param version: the number of the version to be loaded. Used only
330       cast as a string, but should be an integer or something that corresponds to a
331       string representation of an integer value when cast. A value of None (default)
332       causes the latest version on the server to be used.
333    '''
334    import subprocess
335    svn = whichsvn()
336    if not svn: return
337    if version:
338        verstr = '-r' + str(version)
339    else:
340        verstr = '-rHEAD'
341    cmd = [svn,'update',fpath,verstr,
342           '--non-interactive',
343           '--accept','theirs-conflict','--force']
344    if svnVersionNumber() >= 1.6:
345        cmd += ['--trust-server-cert']
346    s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
347    out,err = s.communicate()
348    if err:
349        print(60*"=")
350        print ("****** An error was noted, see below *********")
351        print(60*"=")
352        print err
353        sys.exit()
354
355def svnUpgrade(fpath=os.path.split(__file__)[0]):
356    '''This reformats subversion files, which may be needed if an upgrade of subversion is
357    done.
358
359    :param str fpath: path to repository dictionary, defaults to directory where
360       the current file is located
361    '''
362    import subprocess
363    svn = whichsvn()
364    if not svn: return
365    cmd = [svn,'upgrade',fpath,'--non-interactive']
366    s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
367    out,err = s.communicate()
368    if err:
369        print("svn upgrade did not happen (this is probably OK). Messages:")
370        print err
371           
372def svnUpdateProcess(version=None,projectfile=None):
373    '''perform an update of GSAS-II in a separate python process'''
374    import subprocess
375    if not projectfile:
376        projectfile = ''
377    else:
378        projectfile = os.path.realpath(projectfile)
379        print 'restart using',projectfile
380    if not version:
381        version = ''
382    else:
383        version = str(version)
384    # start the upgrade in a separate interpreter (avoids loading .pyd files)
385    subprocess.Popen([sys.executable,__file__,projectfile,version])
386    sys.exit()
387
388def svnSwitchDir(rpath,filename,baseURL,loadpath=None):
389    '''This performs a switch command to move files between subversion trees.
390
391    This is currently used for moving tutorial web pages and demo files
392    into the GSAS-II source tree. Note that if the files were previously downloaded
393    the switch command will update the files to the newest version.
394   
395    :param str rpath: path to locate files, relative to the GSAS-II
396      installation path (defaults to path2GSAS2)
397    :param str URL: the repository URL
398    :param str loadpath: the prefix for the path, if specified. Defaults to path2GSAS2
399    '''
400    import subprocess
401    svn = whichsvn()
402    if not svn: return
403    URL = baseURL[:]
404    if baseURL[-1] != '/':
405        URL = baseURL + '/' + filename
406    else:
407        URL = baseURL + filename
408    if loadpath:
409        fpath = os.path.join(loadpath,rpath,filename)
410    else:
411        fpath = os.path.join(path2GSAS2,rpath,filename)
412    cmd = [svn,'switch',URL,fpath,
413           '--non-interactive','--trust-server-cert',
414           '--accept','theirs-conflict','--force']
415    if svnVersionNumber() > 1.6: cmd += ['--ignore-ancestry']
416    print("Loading files from "+URL+'\n  to '+fpath)
417    s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
418    out,err = s.communicate()
419    if err:
420        print(60*"=")
421        print ("****** An error was noted, see below *********")
422        print(60*"=")
423        print 'out=',out
424        print 'err=',err
425        return False
426    return True
427
428def svnInstallDir(URL,loadpath):
429    '''Load a subversion tree into a specified directory
430
431    :param str rpath: path to locate files, relative to the GSAS-II
432      installation path (defaults to path2GSAS2)
433    :param str URL: the repository URL
434    '''
435    import subprocess
436    svn = whichsvn()
437    if not svn: return
438    cmd = [svn,'co',URL,loadpath,'--non-interactive']
439    if svnVersionNumber() >= 1.6: cmd += ['--trust-server-cert']
440    print("Loading files from "+URL)
441    s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
442    out,err = s.communicate()
443    if err:
444        print(60*"=")
445        print ("****** An error was noted, see below *********")
446        print(60*"=")
447        print err
448        return False
449    return True
450           
451def IPyBreak_base():
452    '''A routine that invokes an IPython session at the calling location
453    This routine is only used when debug=True is set in config.py
454    '''
455    savehook = sys.excepthook # save the exception hook
456    try: 
457        from IPython.terminal.embed import InteractiveShellEmbed
458    except ImportError:
459        try:
460            # try the IPython 0.12 approach
461            from IPython.frontend.terminal.embed import InteractiveShellEmbed
462        except ImportError:
463            print 'IPython InteractiveShellEmbed not found'
464            return
465    import inspect
466    ipshell = InteractiveShellEmbed()
467
468    frame = inspect.currentframe().f_back
469    msg   = 'Entering IPython console inside {0.f_code.co_filename} at line {0.f_lineno}'.format(frame)
470    ipshell(msg,stack_depth=2) # Go up one level, to see the calling routine
471    sys.excepthook = savehook # reset IPython's change to the exception hook
472
473def exceptHook(*args):
474    '''A routine to be called when an exception occurs. It prints the traceback
475    with fancy formatting and then calls an IPython shell with the environment
476    of the exception location.
477   
478    This routine is only used when debug=True is set in config.py   
479    '''
480    from IPython.core import ultratb
481    if 'win' in sys.platform:
482        ultratb.FormattedTB(call_pdb=False,color_scheme='NoColor')(*args)
483    else:
484        ultratb.FormattedTB(call_pdb=False,color_scheme='LightBG')(*args)
485    try: 
486        from IPython.terminal.embed import InteractiveShellEmbed
487    except ImportError:
488        try:
489            # try the IPython 0.12 approach
490            from IPython.frontend.terminal.embed import InteractiveShellEmbed
491        except ImportError:
492            print 'IPython InteractiveShellEmbed not found'
493            return
494    import inspect
495    frame = inspect.getinnerframes(args[2])[-1][0]
496    msg   = 'Entering IPython console at {0.f_code.co_filename} at line {0.f_lineno}'.format(frame)
497    savehook = sys.excepthook # save the exception hook
498    InteractiveShellEmbed(banner1=msg)(local_ns=frame.f_locals,global_ns=frame.f_globals)
499    sys.excepthook = savehook # reset IPython's change to the exception hook
500
501def DoNothing():
502    '''A routine that does nothing. This is called in place of IPyBreak and pdbBreak
503    except when the debug option is set True in config.py
504    '''
505    pass 
506
507IPyBreak = DoNothing
508pdbBreak = DoNothing
509def InvokeDebugOpts():
510    'Called in GSASII.py to set up debug options'
511    if GetConfigValue('debug'):
512        print 'Debug on: IPython: Exceptions and G2path.IPyBreak(); pdb: G2path.pdbBreak()'
513        sys.excepthook = exceptHook
514        import pdb
515        global pdbBreak
516        pdbBreak = pdb.set_trace
517        global IPyBreak
518        IPyBreak = IPyBreak_base
519   
520if __name__ == '__main__':
521    '''What follows is called to update (or downdate) GSAS-II in a separate process.
522    '''
523    import subprocess
524    import time
525    time.sleep(1) # delay to give the main process a chance to exit
526    # perform an update and restart GSAS-II
527    project,version = sys.argv[1:3]
528    loc = os.path.dirname(__file__)
529    if version:
530        print("Regress to version "+str(version))
531        svnUpdateDir(loc,version=version)
532    else:
533        print("Update to current version")
534        svnUpdateDir(loc)
535    if project:
536        print("Restart GSAS-II with project file "+str(project))
537        subprocess.Popen([sys.executable,os.path.join(loc,'GSASII.py'),project])
538    else:
539        print("Restart GSAS-II without a project file ")
540        subprocess.Popen([sys.executable,os.path.join(loc,'GSASII.py')])
541    print 'exiting update process'
542    sys.exit()
543   
Note: See TracBrowser for help on using the repository browser.