source: trunk/GSASIIpath.py @ 2106

Last change on this file since 2106 was 1971, checked in by toby, 10 years ago

Add preferences option to edit configuration options

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 19.2 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: 1971 $"
123      that is set by subversion when the file is retrieved from subversion.
124
125    Place ``GSASIIpath.SetVersionNumber("$Revision: 1971 $")`` 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    return version
139
140def LoadConfigFile(filename):
141    '''Read a GSAS-II configuration file.
142    Comments (starting with "%") are removed, as are empty lines
143   
144    :param str filename: base file name (such as 'file.dat'). Files with this name
145      are located from the path and the contents of each are concatenated.
146    :returns: a list containing each non-empty (after removal of comments) line
147      found in every matching config file.
148    '''
149    info = []
150    for path in sys.path:
151        fil = os.path.join(path,filename)
152        if not os.path.exists(fil): continue
153        try:
154            i = 0
155            fp = open(fil,'r')
156            for line in fp:
157                expr = line.split('#')[0].strip()
158                if expr:
159                    info.append(expr)
160                    i += 1
161            print(str(i)+' lines read from config file '+fil)
162        finally:
163            fp.close()
164    return info
165
166
167# routines to interface with subversion
168def whichsvn():
169    '''Returns a path to the subversion exe file, if any is found.
170    Searches the current path as well as subdirectory "svn" and
171    "svn/bin" in the location of the GSASII source files.
172
173    :returns: None if svn is not found or an absolute path to the subversion
174      executable file.
175    '''
176    def is_exe(fpath):
177        return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
178    svnprog = 'svn'
179    if sys.platform == "win32": svnprog += '.exe'
180    pathlist = os.environ["PATH"].split(os.pathsep)
181    pathlist.insert(0,os.path.join(os.path.split(__file__)[0],'svn'))
182    pathlist.insert(1,os.path.join(os.path.split(__file__)[0],'svn','bin'))
183    for path in pathlist:
184        exe_file = os.path.join(path, svnprog)
185        if is_exe(exe_file):
186            return os.path.abspath(exe_file)
187
188def svnVersion():
189    '''Get the version number of the current subversion executable
190
191    :returns: a string with a version number such as "1.6.6" or None if
192      subversion is not found.
193
194    '''
195    import subprocess
196    svn = whichsvn()
197    if not svn: return
198
199    cmd = [svn,'--version','--quiet']
200    s = subprocess.Popen(cmd,
201                         stdout=subprocess.PIPE,stderr=subprocess.PIPE)
202    out,err = s.communicate()
203    if err:
204        print 'subversion error!\nout=',out
205        print 'err=',err
206        return None
207    return out.strip()
208
209def svnVersionNumber():
210    '''Get the version number of the current subversion executable
211
212    :returns: a fractional version number such as 1.6 or None if
213      subversion is not found.
214
215    '''
216    ver = svnVersion()
217    if not ver: return 
218    M,m = svnVersion().split('.')[:2]
219    return int(M)+int(m)/10.
220
221def svnGetLog(fpath=os.path.split(__file__)[0],version=None):
222    '''Get the revision log information for a specific version of the specified package
223
224    :param str fpath: path to repository dictionary, defaults to directory where
225       the current file is located.
226    :param int version: the version number to be looked up or None (default)
227       for the latest version.
228
229    :returns: a dictionary with keys (one hopes) 'author', 'date', 'msg', and 'revision'
230
231    '''
232    import subprocess
233    import xml.etree.ElementTree as ET
234    svn = whichsvn()
235    if not svn: return
236    if version is not None:
237        vstr = '-r'+str(version)
238    else:
239        vstr = '-rHEAD'
240
241    cmd = [svn,'log',fpath,'--xml',vstr]
242    s = subprocess.Popen(cmd,
243                         stdout=subprocess.PIPE,stderr=subprocess.PIPE)
244    out,err = s.communicate()
245    if err:
246        print 'out=',out
247        print 'err=',err
248        return None
249    x = ET.fromstring(out)
250    d = {}
251    for i in x.iter('logentry'):
252        d = {'revision':i.attrib.get('revision','?')}
253        for j in i:
254            d[j.tag] = j.text
255        break # only need the first
256    return d
257
258def svnGetRev(fpath=os.path.split(__file__)[0],local=True):
259    '''Obtain the version number for the either the last update of the local version
260    or contacts the subversion server to get the latest update version (# of Head).
261
262    :param str fpath: path to repository dictionary, defaults to directory where
263       the current file is located
264    :param bool local: determines the type of version number, where
265       True (default): returns the latest installed update
266       False: returns the version number of Head on the server
267
268    :Returns: the version number as an str or
269       None if there is a subversion error (likely because the path is
270       not a repository or svn is not found)
271    '''
272
273    import subprocess
274    import xml.etree.ElementTree as ET
275    svn = whichsvn()
276    if not svn: return
277    if local:
278        cmd = [svn,'info',fpath,'--xml']
279    else:
280        cmd = [svn,'info',fpath,'--xml','-rHEAD']
281    if svnVersionNumber() >= 1.6:
282        cmd += ['--non-interactive', '--trust-server-cert']
283    s = subprocess.Popen(cmd, stdout=subprocess.PIPE,stderr=subprocess.PIPE)
284    out,err = s.communicate()
285    if err:
286        print 'svn failed\n',out
287        print 'err=',err
288        return None
289    x = ET.fromstring(out)
290    for i in x.iter('entry'):
291        rev = i.attrib.get('revision')
292        if rev: return rev
293
294def svnFindLocalChanges(fpath=os.path.split(__file__)[0]):
295    '''Returns a list of files that were changed locally. If no files are changed,
296       the list has length 0
297
298    :param fpath: path to repository dictionary, defaults to directory where
299       the current file is located
300
301    :returns: None if there is a subversion error (likely because the path is
302       not a repository or svn is not found)
303
304    '''
305    import subprocess
306    import xml.etree.ElementTree as ET
307    svn = whichsvn()
308    if not svn: return
309    cmd = [svn,'status',fpath,'--xml']
310    s = subprocess.Popen(cmd,
311                         stdout=subprocess.PIPE,stderr=subprocess.PIPE)
312    out,err = s.communicate()
313    if err: return None
314    x = ET.fromstring(out)
315    changed = []
316    for i in x.iter('entry'):
317        if i.find('wc-status').attrib.get('item') == 'modified': 
318            changed.append(i.attrib.get('path'))
319    return changed
320
321def svnUpdateDir(fpath=os.path.split(__file__)[0],version=None):
322    '''This performs an update of the files in a local directory from a server.
323
324    :param str fpath: path to repository dictionary, defaults to directory where
325       the current file is located
326    :param version: the number of the version to be loaded. Used only
327       cast as a string, but should be an integer or something that corresponds to a
328       string representation of an integer value when cast. A value of None (default)
329       causes the latest version on the server to be used.
330    '''
331    import subprocess
332    svn = whichsvn()
333    if not svn: return
334    if version:
335        verstr = '-r' + str(version)
336    else:
337        verstr = '-rHEAD'
338    cmd = [svn,'update',fpath,verstr,
339           '--non-interactive',
340           '--accept','theirs-conflict','--force']
341    if svnVersionNumber() >= 1.6:
342        cmd += ['--trust-server-cert']
343    s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
344    out,err = s.communicate()
345    if err:
346        print(60*"=")
347        print ("****** An error was noted, see below *********")
348        print(60*"=")
349        print err
350        sys.exit()
351
352def svnUpgrade(fpath=os.path.split(__file__)[0]):
353    '''This reformats subversion files, which may be needed if an upgrade of subversion is
354    done.
355
356    :param str fpath: path to repository dictionary, defaults to directory where
357       the current file is located
358    '''
359    import subprocess
360    svn = whichsvn()
361    if not svn: return
362    cmd = [svn,'upgrade',fpath,'--non-interactive']
363    s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
364    out,err = s.communicate()
365    if err:
366        print("svn upgrade did not happen (this is probably OK). Messages:")
367        print err
368           
369def svnUpdateProcess(version=None,projectfile=None):
370    '''perform an update of GSAS-II in a separate python process'''
371    import subprocess
372    if not projectfile:
373        projectfile = ''
374    else:
375        projectfile = os.path.realpath(projectfile)
376        print 'restart using',projectfile
377    if not version:
378        version = ''
379    else:
380        version = str(version)
381    # start the upgrade in a separate interpreter (avoids loading .pyd files)
382    subprocess.Popen([sys.executable,__file__,projectfile,version])
383    sys.exit()
384
385def svnSwitchDir(rpath,filename,baseURL,loadpath=None):
386    '''This performs a switch command to move files between subversion trees.
387
388    This is currently used for moving tutorial web pages and demo files
389    into the GSAS-II source tree. Note that if the files were previously downloaded
390    the switch command will update the files to the newest version.
391   
392    :param str rpath: path to locate files, relative to the GSAS-II
393      installation path (defaults to path2GSAS2)
394    :param str URL: the repository URL
395    :param str loadpath: the prefix for the path, if specified. Defaults to path2GSAS2
396    '''
397    import subprocess
398    svn = whichsvn()
399    if not svn: return
400    URL = baseURL[:]
401    if baseURL[-1] != '/':
402        URL = baseURL + '/' + filename
403    else:
404        URL = baseURL + filename
405    if loadpath:
406        fpath = os.path.join(loadpath,rpath,filename)
407    else:
408        fpath = os.path.join(path2GSAS2,rpath,filename)
409    cmd = [svn,'switch',URL,fpath,
410           '--non-interactive','--trust-server-cert',
411           '--accept','theirs-conflict','--force']
412    if svnVersionNumber() > 1.6: cmd += ['--ignore-ancestry']
413    print("Loading files from "+URL+'\n  to '+fpath)
414    s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
415    out,err = s.communicate()
416    if err:
417        print(60*"=")
418        print ("****** An error was noted, see below *********")
419        print(60*"=")
420        print 'out=',out
421        print 'err=',err
422        return False
423    return True
424
425def svnInstallDir(URL,loadpath):
426    '''Load a subversion tree into a specified directory
427
428    :param str rpath: path to locate files, relative to the GSAS-II
429      installation path (defaults to path2GSAS2)
430    :param str URL: the repository URL
431    '''
432    import subprocess
433    svn = whichsvn()
434    if not svn: return
435    cmd = [svn,'co',URL,loadpath,'--non-interactive']
436    if svnVersionNumber() >= 1.6: cmd += ['--trust-server-cert']
437    print("Loading files from "+URL)
438    s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
439    out,err = s.communicate()
440    if err:
441        print(60*"=")
442        print ("****** An error was noted, see below *********")
443        print(60*"=")
444        print err
445        return False
446    return True
447           
448def IPyBreak_base():
449    '''A routine that invokes an IPython session at the calling location
450    This routine is only used when debug=True is set in config.py
451    '''
452    savehook = sys.excepthook # save the exception hook
453    try: 
454        from IPython.terminal.embed import InteractiveShellEmbed
455    except ImportError:
456        try:
457            # try the IPython 0.12 approach
458            from IPython.frontend.terminal.embed import InteractiveShellEmbed
459        except ImportError:
460            print 'IPython InteractiveShellEmbed not found'
461            return
462    import inspect
463    ipshell = InteractiveShellEmbed()
464
465    frame = inspect.currentframe().f_back
466    msg   = 'Entering IPython console inside {0.f_code.co_filename} at line {0.f_lineno}'.format(frame)
467    ipshell(msg,stack_depth=2) # Go up one level, to see the calling routine
468    sys.excepthook = savehook # reset IPython's change to the exception hook
469
470def exceptHook(*args):
471    '''A routine to be called when an exception occurs. It prints the traceback
472    with fancy formatting and then calls an IPython shell with the environment
473    of the exception location.
474   
475    This routine is only used when debug=True is set in config.py   
476    '''
477    from IPython.core import ultratb
478    if 'win' in sys.platform:
479        ultratb.FormattedTB(call_pdb=False,color_scheme='NoColor')(*args)
480    else:
481        ultratb.FormattedTB(call_pdb=False,color_scheme='LightBG')(*args)
482    try: 
483        from IPython.terminal.embed import InteractiveShellEmbed
484    except ImportError:
485        try:
486            # try the IPython 0.12 approach
487            from IPython.frontend.terminal.embed import InteractiveShellEmbed
488        except ImportError:
489            print 'IPython InteractiveShellEmbed not found'
490            return
491    import inspect
492    frame = inspect.getinnerframes(args[2])[-1][0]
493    msg   = 'Entering IPython console at {0.f_code.co_filename} at line {0.f_lineno}'.format(frame)
494    savehook = sys.excepthook # save the exception hook
495    InteractiveShellEmbed(banner1=msg)(local_ns=frame.f_locals,global_ns=frame.f_globals)
496    sys.excepthook = savehook # reset IPython's change to the exception hook
497
498def DoNothing():
499    '''A routine that does nothing. This is called in place of IPyBreak and pdbBreak
500    except when the debug option is set True in config.py
501    '''
502    pass 
503
504IPyBreak = DoNothing
505pdbBreak = DoNothing
506def InvokeDebugOpts():
507    'Called in GSASII.py to set up debug options'
508    if GetConfigValue('debug'):
509        print 'Debug on: IPython: Exceptions and G2path.IPyBreak(); pdb: G2path.pdbBreak()'
510        sys.excepthook = exceptHook
511        import pdb
512        global pdbBreak
513        pdbBreak = pdb.set_trace
514        global IPyBreak
515        IPyBreak = IPyBreak_base
516   
517if __name__ == '__main__':
518    '''What follows is called to update (or downdate) GSAS-II in a separate process.
519    '''
520    import subprocess
521    import time
522    time.sleep(1) # delay to give the main process a chance to exit
523    # perform an update and restart GSAS-II
524    project,version = sys.argv[1:3]
525    loc = os.path.dirname(__file__)
526    if version:
527        print("Regress to version "+str(version))
528        svnUpdateDir(loc,version=version)
529    else:
530        print("Update to current version")
531        svnUpdateDir(loc)
532    if project:
533        print("Restart GSAS-II with project file "+str(project))
534        subprocess.Popen([sys.executable,os.path.join(loc,'GSASII.py'),project])
535    else:
536        print("Restart GSAS-II without a project file ")
537        subprocess.Popen([sys.executable,os.path.join(loc,'GSASII.py')])
538    print 'exiting update process'
539    sys.exit()
540   
Note: See TracBrowser for help on using the repository browser.