source: trunk/GSASIIpath.py @ 4732

Last change on this file since 4732 was 4732, checked in by toby, 10 months ago

workarounds for missing files

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 46.6 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASIIpath - file location & update routines
3########### SVN repository information ###################
4# $Date: 2021-01-07 05:13:16 +0000 (Thu, 07 Jan 2021) $
5# $Author: toby $
6# $Revision: 4732 $
7# $URL: trunk/GSASIIpath.py $
8# $Id: GSASIIpath.py 4732 2021-01-07 05:13:16Z toby $
9########### SVN repository information ###################
10'''
11*GSASIIpath: locations & updates*
12---------------------------------
13
14Routines for dealing with file locations, etc.
15
16Determines the location of the compiled (.pyd or .so) libraries.
17
18Interfaces with subversion (svn):
19Determine the subversion release number by determining the highest version number
20where :func:`SetVersionNumber` is called (best done in every GSASII file).
21Other routines will update GSASII from the subversion server if svn can be
22found.
23
24Accesses configuration options, as defined in config.py
25'''
26
27from __future__ import division, print_function
28import os
29import sys
30import platform
31import glob
32import subprocess
33import numpy as np
34g2home = 'https://subversion.xray.aps.anl.gov/pyGSAS'
35'Define the location of the GSAS-II subversion repository'
36   
37path2GSAS2 = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) # location of this file; save before any changes in pwd
38
39# convert version numbers as '1.2.3' to integers (1002) and back (to 1.2)
40fmtver = lambda v: str(v//1000)+'.'+str(v%1000)
41intver = lambda vs: sum([int(i) for i in vs.split('.')[0:2]]*np.array((1000,1)))
42
43def GetConfigValue(key,default=None):
44    '''Return the configuration file value for key or a default value if not present
45   
46    :param str key: a value to be found in the configuration (config.py) file
47    :param default: a value to be supplied is none is in the config file or
48      the config file is not found. Defaults to None
49    :returns: the value found or the default.
50    '''
51    try:
52        return configDict.get(key,default)
53    except NameError: # this happens when building docs
54        return None
55
56def SetConfigValue(parmdict):
57    '''Set configuration variables from a dictionary where elements are lists
58    First item in list is the default value and second is the value to use.
59    '''
60    global configDict
61    for var in parmdict:
62        if var in configDict:
63            del configDict[var]
64        if isinstance(parmdict[var],tuple):
65            configDict[var] = parmdict[var]
66        else:
67            if parmdict[var][1] is None: continue
68            if parmdict[var][1] == '': continue
69            if parmdict[var][0] == parmdict[var][1]: continue
70            configDict[var] = parmdict[var][1]
71
72def addPrevGPX(fil,configDict):
73    '''Add a GPX file to the list of previous files.
74    Move previous names to start of list. Keep most recent five files
75    '''
76    fil = os.path.abspath(os.path.expanduser(fil))
77    if 'previous_GPX_files' not in configDict: return
78    try:
79        pos = configDict['previous_GPX_files'][1].index(fil) 
80        if pos == 0: return
81        configDict['previous_GPX_files'][1].pop(pos) # if present, remove if not 1st
82    except ValueError:
83        pass
84    except AttributeError:
85        configDict['previous_GPX_files'][1] = []
86    configDict['previous_GPX_files'][1].insert(0,fil)
87    configDict['previous_GPX_files'][1] = configDict['previous_GPX_files'][1][:5]
88
89# routines for looking a version numbers in files
90version = -1
91def SetVersionNumber(RevString):
92    '''Set the subversion version number
93
94    :param str RevString: something like "$Revision: 4732 $"
95      that is set by subversion when the file is retrieved from subversion.
96
97    Place ``GSASIIpath.SetVersionNumber("$Revision: 4732 $")`` in every python
98    file.
99    '''
100    try:
101        RevVersion = int(RevString.split(':')[1].split()[0])
102        global version
103        version = max(version,RevVersion)
104    except:
105        pass
106       
107def GetVersionNumber():
108    '''Return the maximum version number seen in :func:`SetVersionNumber`
109    '''
110    if version > 1000:
111        return version
112    else:
113        return "unknown"
114
115def LoadConfigFile(filename):
116    '''Read a GSAS-II configuration file.
117    Comments (starting with "%") are removed, as are empty lines
118   
119    :param str filename: base file name (such as 'file.dat'). Files with this name
120      are located from the path and the contents of each are concatenated.
121    :returns: a list containing each non-empty (after removal of comments) line
122      found in every matching config file.
123    '''
124    info = []
125    for path in sys.path:
126        fil = os.path.join(path,filename)
127        if not os.path.exists(fil): continue
128        try:
129            i = 0
130            fp = open(fil,'r')
131            for line in fp:
132                expr = line.split('#')[0].strip()
133                if expr:
134                    info.append(expr)
135                    i += 1
136            print(str(i)+' lines read from config file '+fil)
137        finally:
138            fp.close()
139    return info
140
141
142# routines to interface with subversion
143proxycmds = []
144'Used to hold proxy information for subversion, set if needed in whichsvn'
145svnLocCache = None
146'Cached location of svn to avoid multiple searches for it'
147
148def MakeByte2str(arg):
149    '''Convert output from subprocess pipes (bytes) to str (unicode) in Python 3.
150    In Python 2: Leaves output alone (already str).
151    Leaves stuff of other types alone (including unicode in Py2)
152    Works recursively for string-like stuff in nested loops and tuples.
153
154    typical use::
155
156        out = MakeByte2str(out)
157
158    or::
159
160        out,err = MakeByte2str(s.communicate())
161   
162    '''
163    if isinstance(arg,str): return arg
164    if isinstance(arg,bytes):
165        try:
166            return arg.decode()
167        except:
168            if GetConfigValue('debug'): print('Decode error')
169            return arg
170    if isinstance(arg,list):
171        return [MakeByte2str(i) for i in arg]
172    if isinstance(arg,tuple):
173        return tuple([MakeByte2str(i) for i in arg])
174    return arg
175               
176def getsvnProxy():
177    '''Loads a proxy for subversion from the file created by bootstrap.py
178    '''
179    global proxycmds
180    proxycmds = []
181    proxyinfo = os.path.join(os.path.expanduser('~/.G2local/'),"proxyinfo.txt")
182    if not os.path.exists(proxyinfo):
183        proxyinfo = os.path.join(path2GSAS2,"proxyinfo.txt")
184    if not os.path.exists(proxyinfo):
185        return '','',''
186    fp = open(proxyinfo,'r')
187    host = fp.readline().strip()
188    # allow file to begin with comments
189    while host.startswith('#'):
190        host = fp.readline().strip()
191    port = fp.readline().strip()
192    etc = []
193    line = fp.readline()
194    while line:
195        etc.append(line.strip())
196        line = fp.readline()
197    fp.close()
198    setsvnProxy(host,port,etc)
199    return host,port,etc
200
201def setsvnProxy(host,port,etc=[]):
202    '''Sets the svn commands needed to use a proxy
203    '''
204    global proxycmds
205    proxycmds = []
206    host = host.strip()
207    port = port.strip()
208    if host: 
209        proxycmds.append('--config-option')
210        proxycmds.append('servers:global:http-proxy-host='+host)
211        if port:
212            proxycmds.append('--config-option')
213            proxycmds.append('servers:global:http-proxy-port='+port)
214    for item in etc:
215        proxycmds.append(item)
216       
217def whichsvn():
218    '''Returns a path to the subversion exe file, if any is found.
219    Searches the current path after adding likely places where GSAS-II
220    might install svn.
221
222    :returns: None if svn is not found or an absolute path to the subversion
223      executable file.
224    '''
225    # use a previosuly cached svn location
226    global svnLocCache
227    if svnLocCache: return svnLocCache
228    # prepare to find svn
229    is_exe = lambda fpath: os.path.isfile(fpath) and os.access(fpath, os.X_OK)
230    svnprog = 'svn'
231    if sys.platform.startswith('win'): svnprog += '.exe'
232    host,port,etc = getsvnProxy()
233    if GetConfigValue('debug') and host:
234        print('DBG_Using proxy host {} port {}'.format(host,port))
235    # add likely places to find subversion when installed with GSAS-II
236    pathlist = os.environ["PATH"].split(os.pathsep)
237    pathlist.insert(0,os.path.split(sys.executable)[0])
238    pathlist.insert(1,path2GSAS2)
239    for rpt in ('..','bin'),('..','Library','bin'),('svn','bin'),('svn',),('.'):
240        pt = os.path.normpath(os.path.join(path2GSAS2,*rpt))
241        if os.path.exists(pt):
242            pathlist.insert(0,pt)   
243    # search path for svn or svn.exe
244    for path in pathlist:
245        exe_file = os.path.join(path, svnprog)
246        if is_exe(exe_file):
247            try:
248                p = subprocess.Popen([exe_file,'help'],stdout=subprocess.PIPE)
249                res = p.stdout.read()
250                if not res: return
251                p.communicate()
252                svnLocCache = os.path.abspath(exe_file)
253                return svnLocCache
254            except:
255                pass       
256    svnLocCache = None
257
258def svnVersion(svn=None):
259    '''Get the version number of the current subversion executable
260
261    :returns: a string with a version number such as "1.6.6" or None if
262      subversion is not found.
263
264    '''
265    if not svn: svn = whichsvn()
266    if not svn: return
267
268    cmd = [svn,'--version','--quiet']
269    s = subprocess.Popen(cmd,
270                         stdout=subprocess.PIPE,stderr=subprocess.PIPE)
271    out,err = MakeByte2str(s.communicate())
272    if err:
273        print ('subversion error!\nout=%s'%out)
274        print ('err=%s'%err)
275        s = '\nsvn command:  '
276        for i in cmd: s += i + ' '
277        print(s)
278        return None
279    return out.strip()
280
281def svnVersionNumber(svn=None):
282    '''Get the version number of the current subversion executable
283
284    :returns: a fractional version number such as 1.6 or None if
285      subversion is not found.
286
287    '''
288    ver = svnVersion(svn)
289    if not ver: return 
290    M,m = ver.split('.')[:2]
291    return int(M)+int(m)/10.
292
293def svnGetLog(fpath=os.path.split(__file__)[0],version=None):
294    '''Get the revision log information for a specific version of the specified package
295
296    :param str fpath: path to repository dictionary, defaults to directory where
297       the current file is located.
298    :param int version: the version number to be looked up or None (default)
299       for the latest version.
300
301    :returns: a dictionary with keys (one hopes) 'author', 'date', 'msg', and 'revision'
302
303    '''
304    import xml.etree.ElementTree as ET
305    svn = whichsvn()
306    if not svn: return
307    if version is not None:
308        vstr = '-r'+str(version)
309    else:
310        vstr = '-rHEAD'
311
312    cmd = [svn,'log',fpath,'--xml',vstr]
313    if proxycmds: cmd += proxycmds
314    s = subprocess.Popen(cmd,
315                         stdout=subprocess.PIPE,stderr=subprocess.PIPE)
316    out,err = MakeByte2str(s.communicate())
317    if err:
318        print ('out=%s'%out)
319        print ('err=%s'%err)
320        s = '\nsvn command:  '
321        for i in cmd: s += i + ' '
322        print(s)
323        return None
324    x = ET.fromstring(out)
325    d = {}
326    for i in x.iter('logentry'):
327        d = {'revision':i.attrib.get('revision','?')}
328        for j in i:
329            d[j.tag] = j.text
330        break # only need the first
331    return d
332
333svnLastError = ''
334def svnGetRev(fpath=os.path.split(__file__)[0],local=True):
335    '''Obtain the version number for the either the last update of the local version
336    or contacts the subversion server to get the latest update version (# of Head).
337
338    :param str fpath: path to repository dictionary, defaults to directory where
339       the current file is located
340    :param bool local: determines the type of version number, where
341       True (default): returns the latest installed update
342       False: returns the version number of Head on the server
343
344    :Returns: the version number as an str or
345       None if there is a subversion error (likely because the path is
346       not a repository or svn is not found). The error message is placed in
347       global variable svnLastError
348    '''
349
350    import xml.etree.ElementTree as ET
351    svn = whichsvn()
352    if not svn: return
353    if local:
354        cmd = [svn,'info',fpath,'--xml']
355    else:
356        cmd = [svn,'info',fpath,'--xml','-rHEAD']
357    if svnVersionNumber() >= 1.6:
358        cmd += ['--non-interactive', '--trust-server-cert']
359    if proxycmds: cmd += proxycmds
360    # if GetConfigValue('debug'):
361    #     s = 'subversion command:\n  '
362    #     for i in cmd: s += i + ' '
363    #     print(s)
364    s = subprocess.Popen(cmd, stdout=subprocess.PIPE,stderr=subprocess.PIPE)
365    out,err = MakeByte2str(s.communicate())
366    if err:
367        print ('svn failed\n%s'%out)
368        print ('err=%s'%err)
369        s = '\nsvn command:  '
370        for i in cmd: s += i + ' '
371        print(s)
372        global svnLastError
373        svnLastError = err
374        return None
375    x = ET.fromstring(out)
376    for i in x.iter('entry'):
377        rev = i.attrib.get('revision')
378        if rev: return rev
379
380def svnFindLocalChanges(fpath=os.path.split(__file__)[0]):
381    '''Returns a list of files that were changed locally. If no files are changed,
382       the list has length 0
383
384    :param fpath: path to repository dictionary, defaults to directory where
385       the current file is located
386
387    :returns: None if there is a subversion error (likely because the path is
388       not a repository or svn is not found)
389
390    '''
391    import xml.etree.ElementTree as ET
392    svn = whichsvn()
393    if not svn: return
394    cmd = [svn,'status',fpath,'--xml']
395    if proxycmds: cmd += proxycmds
396    s = subprocess.Popen(cmd,
397                         stdout=subprocess.PIPE,stderr=subprocess.PIPE)
398    out,err = MakeByte2str(s.communicate())
399    if err: return None
400    x = ET.fromstring(out)
401    changed = []
402    for i in x.iter('entry'):
403        if i.find('wc-status').attrib.get('item') == 'modified': 
404            changed.append(i.attrib.get('path'))
405    return changed
406
407def svnCleanup(fpath=os.path.split(__file__)[0],verbose=True):
408    '''This runs svn cleanup on a selected local directory.
409
410    :param str fpath: path to repository dictionary, defaults to directory where
411       the current file is located
412    '''
413    svn = whichsvn()
414    if not svn: return
415    if verbose: print(u"Performing svn cleanup at "+fpath)
416    cmd = [svn,'cleanup',fpath]
417    if verbose: showsvncmd(cmd)       
418    s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
419    out,err = MakeByte2str(s.communicate())
420    if err:
421        print(60*"=")
422        print("****** An error was noted, see below *********")
423        print(60*"=")
424        print(err)
425        s = '\nsvn command:  '
426        for i in cmd: s += i + ' '
427        print(s)
428        #raise Exception('svn cleanup failed')
429        return False
430    elif verbose:
431        print(out)
432    return True
433       
434def svnUpdateDir(fpath=os.path.split(__file__)[0],version=None,verbose=True):
435    '''This performs an update of the files in a local directory from a server.
436
437    :param str fpath: path to repository dictionary, defaults to directory where
438       the current file is located
439    :param version: the number of the version to be loaded. Used only
440       cast as a string, but should be an integer or something that corresponds to a
441       string representation of an integer value when cast. A value of None (default)
442       causes the latest version on the server to be used.
443    '''
444    svn = whichsvn()
445    if not svn: return
446    if version:
447        verstr = '-r' + str(version)
448    else:
449        verstr = '-rHEAD'
450    if verbose: print(u"Updating files at "+fpath)
451    cmd = [svn,'update',fpath,verstr,
452           '--non-interactive',
453           '--accept','theirs-conflict','--force']
454    if svnVersionNumber() >= 1.6:
455        cmd += ['--trust-server-cert']
456    if proxycmds: cmd += proxycmds
457    #if verbose or GetConfigValue('debug'):
458    if verbose:
459        s = 'subversion command:\n  '
460        for i in cmd: s += i + ' '
461        print(s)
462    s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
463    out,err = MakeByte2str(s.communicate())
464    if err:
465        print(60*"=")
466        print("****** An error was noted, see below *********")
467        print(60*"=")
468        print(err)
469        s = '\nsvn command:  '
470        for i in cmd: s += i + ' '
471        print(s)
472        if svnCleanup(fpath):
473            s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
474            out,err = MakeByte2str(s.communicate())
475            if err:
476                print(60*"=")
477                print("****** Drat, failed again: *********")
478                print(60*"=")
479                print(err)
480            else:
481                return
482        if 'Checksum' in err:  # deal with Checksum problem
483            err = svnChecksumPatch(svn,fpath,verstr)
484            if err:
485                print('error from svnChecksumPatch\n\t',err)
486            else:
487                return
488        raise Exception('svn update failed')
489    elif verbose:
490        print(out)
491
492def showsvncmd(cmd):
493    s = '\nsvn command:  '
494    for i in cmd: s += i + ' '
495    print(s)
496
497def svnChecksumPatch(svn,fpath,verstr):
498    '''This performs a fix when svn cannot finish an update because of
499    a Checksum mismatch error. This seems to be happening on OS X for
500    unclear reasons.
501    '''
502    print('\nAttempting patch for svn Checksum mismatch error\n')
503    svnCleanup(fpath)
504    cmd = ['svn','update','--set-depth','empty',
505               os.path.join(fpath,'bindist')]
506    showsvncmd(cmd)       
507    s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
508    out,err = MakeByte2str(s.communicate())
509    #if err: print('error=',err)
510    cmd = ['svn','switch',g2home+'/trunk/bindist',
511               os.path.join(fpath,'bindist'),
512               '--non-interactive', '--trust-server-cert', '--accept',
513               'theirs-conflict', '--force', '-rHEAD', '--ignore-ancestry']
514    showsvncmd(cmd)       
515    s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
516    out,err = MakeByte2str(s.communicate())
517    DownloadG2Binaries(g2home,verbose=True)
518    cmd = ['svn','update','--set-depth','infinity',
519               os.path.join(fpath,'bindist')]
520    showsvncmd(cmd)       
521    s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
522    out,err = MakeByte2str(s.communicate())
523    #if err: print('error=',err)
524    cmd = [svn,'update',fpath,verstr,
525                       '--non-interactive',
526                       '--accept','theirs-conflict','--force']
527    if svnVersionNumber() >= 1.6:
528        cmd += ['--trust-server-cert']
529    if proxycmds: cmd += proxycmds
530    showsvncmd(cmd)       
531    s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
532    out,err = MakeByte2str(s.communicate())
533    #if err: print('error=',err)
534    return err
535       
536def svnUpgrade(fpath=os.path.split(__file__)[0]):
537    '''This reformats subversion files, which may be needed if an upgrade of subversion is
538    done.
539
540    :param str fpath: path to repository dictionary, defaults to directory where
541       the current file is located
542    '''
543    svn = whichsvn()
544    if not svn: return
545    cmd = [svn,'upgrade',fpath,'--non-interactive']
546    if proxycmds: cmd += proxycmds
547    s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
548    out,err = MakeByte2str(s.communicate())
549    if err:
550        print("svn upgrade did not happen (this is probably OK). Messages:")
551        print (err)
552        s = '\nsvn command:  '
553        for i in cmd: s += i + ' '
554        print(s)
555
556def svnUpdateProcess(version=None,projectfile=None,branch=None):
557    '''perform an update of GSAS-II in a separate python process'''
558    if not projectfile:
559        projectfile = ''
560    else:
561        projectfile = os.path.realpath(projectfile)
562        print ('restart using %s'%projectfile)
563    if branch:
564        version = branch
565    elif not version:
566        version = ''
567    else:
568        version = str(version)
569    # start the upgrade in a separate interpreter (avoids loading .pyd files)
570    ex = sys.executable
571    if sys.platform == "darwin": # mac requires pythonw which is not always reported as sys.executable
572        if os.path.exists(ex+'w'): ex += 'w'
573    proc = subprocess.Popen([ex,__file__,projectfile,version])
574    if sys.platform != "win32":
575        proc.wait()
576    sys.exit()
577
578def svnSwitchDir(rpath,filename,baseURL,loadpath=None,verbose=True):
579    '''This performs a switch command to move files between subversion trees.
580    Note that if the files were previously downloaded,
581    the switch command will update the files to the newest version.
582   
583    :param str rpath: path to locate files, relative to the GSAS-II
584      installation path (defaults to path2GSAS2)
585    :param str URL: the repository URL
586    :param str loadpath: the prefix for the path, if specified. Defaults to path2GSAS2
587    :param bool verbose: if True (default) diagnostics are printed
588    '''
589    svn = whichsvn()
590    if not svn: return
591    URL = baseURL[:]
592    if baseURL[-1] != '/':
593        URL = baseURL + '/' + filename
594    else:
595        URL = baseURL + filename
596    if loadpath:
597        fpath = os.path.join(loadpath,rpath,filename)
598        svntmp = os.path.join(loadpath,'.svn','tmp')
599    else:
600        fpath = os.path.join(path2GSAS2,rpath,filename)
601        svntmp = os.path.join(path2GSAS2,'.svn','tmp')
602    # fix up problems with missing empty directories
603    if not os.path.exists(fpath):
604        print('Repairing missing directory',fpath)
605        cmd = [svn,'revert',fpath]
606        s = subprocess.Popen(cmd,stderr=subprocess.PIPE)
607        out,err = MakeByte2str(s.communicate())
608        if out: print(out)
609        if err: print(err)
610    if not os.path.exists(svntmp):
611        print('Repairing missing directory',svntmp)
612        cmd = ['mkdir',svntmp]
613        s = subprocess.Popen(cmd,stderr=subprocess.PIPE)
614        out,err = MakeByte2str(s.communicate())
615        if out: print(out)
616        if err: print(err)
617       
618    cmd = [svn,'switch',URL,fpath,
619           '--non-interactive','--trust-server-cert',
620           '--accept','theirs-conflict','--force','-rHEAD']
621    if svnVersionNumber(svn) > 1.6: cmd += ['--ignore-ancestry']
622    if proxycmds: cmd += proxycmds
623    if verbose:
624        print(u"Loading files to "+fpath+u"\n  from "+URL)
625    s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
626    out,err = MakeByte2str(s.communicate())
627    if err:
628        print(60*"=")
629        print ("****** An error was noted, see below *********")
630        print(60*"=")
631        print ('out=%s'%out)
632        print ('err=%s'%err)
633        s = '\nsvn command:  '
634        for i in cmd: s += i + ' '
635        print(s)
636        if svnCleanup(fpath):
637            s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
638            out,err = MakeByte2str(s.communicate())
639            if err:
640                print(60*"=")
641                print("****** Drat, failed again: *********")
642                print(60*"=")
643                print(err)
644            else:
645                return True
646        return False
647    if verbose:
648        s = '\nsvn command:  '
649        for i in cmd: s += i + ' '
650        print(s)
651        print('\n=== Output from svn switch'+(43*'='))
652        print(out.strip())
653        print((70*'=')+'\n')
654    return True
655
656def svnInstallDir(URL,loadpath):
657    '''Load a subversion tree into a specified directory
658
659    :param str URL: the repository URL
660    :param str loadpath: path to locate files
661
662    '''
663    svn = whichsvn()
664    if not svn: return
665    cmd = [svn,'co',URL,loadpath,'--non-interactive']
666    if svnVersionNumber() >= 1.6: cmd += ['--trust-server-cert']
667    print("Loading files from "+URL)
668    if proxycmds: cmd += proxycmds
669    s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
670    out,err = MakeByte2str(s.communicate())   #this fails too easily
671    if err:
672        print(60*"=")
673        print ("****** An error was noted, see below *********")
674        print(60*"=")
675        print (err)
676        s = '\nsvn command:  '
677        for i in cmd: s += i + ' '
678        print(s)
679        if svnCleanup(loadpath):
680            s = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
681            out,err = MakeByte2str(s.communicate())
682            if err:
683                print(60*"=")
684                print("****** Drat, failed again: *********")
685                print(60*"=")
686                print(err)
687                return False
688        else:
689            return False
690    print ("Files installed at: "+loadpath)
691    return True
692
693def svnGetFileStatus(fpath=os.path.split(__file__)[0],version=None):
694    '''Compare file status to repository (svn status -u)
695
696    :returns: updatecount,modcount,locked where
697       updatecount is the number of files waiting to be updated from
698       repository
699       modcount is the number of files that have been modified locally
700       locked  is the number of files tagged as locked
701    '''
702    import xml.etree.ElementTree as ET
703    svn = whichsvn()
704    if version is not None:
705        vstr = '-r'+str(version)
706    else:
707        vstr = '-rHEAD'
708    cmd = [svn,'st',fpath,'--xml','-u',vstr]
709    if proxycmds: cmd += proxycmds
710    s = subprocess.Popen(cmd,
711                         stdout=subprocess.PIPE,stderr=subprocess.PIPE)
712    out,err = MakeByte2str(s.communicate())
713    if err:
714        print ('out=%s'%out)
715        print ('err=%s'%err)
716        s = '\nsvn command:  '
717        for i in cmd: s += i + ' '
718        print(s)
719        return None
720
721    locked = 0
722    updatecount = 0
723    modcount = 0
724    x = ET.fromstring(out)
725    for i0 in x.iter('entry'):
726        filename = i0.attrib.get('path','?')
727        wc_rev = ''
728        status = ''
729        switched = ''
730        for i1 in i0.iter('wc-status'):
731            wc_rev = i1.attrib.get('revision','')
732            status = i1.attrib.get('item','')
733            switched = i1.attrib.get('switched','')
734            if i1.attrib.get('wc-locked',''): locked += 1
735        if status == "unversioned": continue
736        if switched == "true": continue
737        if status == "modified":
738            modcount += 1
739        elif status == "normal":
740            updatecount += 1
741        file_rev = ''
742        for i2 in i1.iter('commit'):
743            file_rev = i2.attrib.get('revision','')
744        local_status = ''
745        for i1 in i0.iter('repos-status'):
746            local_status = i1.attrib.get('item','')
747        #print(filename,wc_rev,file_rev,status,local_status,switched)
748    return updatecount,modcount,locked
749
750def GetBinaryPrefix():
751    if sys.platform == "win32":
752        prefix = 'win'
753    elif sys.platform == "darwin":
754        prefix = 'mac'
755    elif sys.platform.startswith("linux"):
756        prefix = 'linux'
757    else:
758        print(u'Unknown platform: '+sys.platform)
759        raise Exception('Unknown platform')
760    if platform.architecture()[0] == '64bit':
761        bits = '64'
762    else:
763        bits = '32'
764
765    # format current python version
766    pyver = 'p{}.{}'.format(*sys.version_info[0:2])
767
768    items = [prefix,bits,pyver]
769    return '_'.join(items)
770
771def svnList(URL,verbose=True):
772    '''Get a list of subdirectories from and svn repository
773    '''   
774    svn = whichsvn()
775    if not svn:
776        print('**** unable to load files: svn not found ****')
777        return ''
778    # get binaries matching the required type -- other than for the numpy version
779    cmd = [svn, 'list', URL,'--non-interactive', '--trust-server-cert']
780    if proxycmds: cmd += proxycmds
781    if verbose:
782        s = 'Running svn command:\n  '
783        for i in cmd: s += i + ' '
784        print(s)
785    p = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
786    res,err = MakeByte2str(p.communicate())
787    return res
788
789def DownloadG2Binaries(g2home,verbose=True):
790    '''Download GSAS-II binaries from appropriate section of the
791    GSAS-II svn repository based on the platform, numpy and Python
792    version
793    '''   
794    bindir = GetBinaryPrefix()
795    #npver = 'n{}.{}'.format(*np.__version__.split('.')[0:2])
796    inpver = intver(np.__version__)
797    svn = whichsvn()
798    if not svn:
799        print('**** unable to load files: svn not found ****')
800        return ''
801    # get binaries matching the required type -- other than for the numpy version
802    cmd = [svn, 'list', g2home + '/Binaries/','--non-interactive', '--trust-server-cert']
803    if proxycmds: cmd += proxycmds
804    if verbose:
805        s = 'Running svn command:\n  '
806        for i in cmd: s += i + ' '
807        print(s)
808    p = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
809    res,err = MakeByte2str(p.communicate())
810    versions = {}
811    for d in res.split():
812        if d.startswith(bindir):
813            v = intver(d.rstrip('/').split('_')[3].lstrip('n'))
814            versions[v] = d
815    intVersionsList = sorted(versions.keys())
816    if not intVersionsList:
817        print('No binaries located matching',bindir)
818        return
819    elif inpver < min(intVersionsList):
820        vsel = min(intVersionsList)
821        print('Warning: The current numpy version, {}, is older than\n\tthe oldest dist version, {}'
822              .format(np.__version__,fmtver(vsel)))
823    elif inpver >= max(intVersionsList):
824        vsel = max(intVersionsList)
825        if verbose: print(
826                'FYI: The current numpy version, {}, is newer than the newest dist version {}'
827                .format(np.__version__,fmtver(vsel)))
828    else:
829        vsel = min(intVersionsList)
830        for v in intVersionsList:
831            if v <= inpver:
832                vsel = v
833            else:
834                if verbose: print(
835                        'FYI: Selecting dist version {} as the current numpy version, {},\n\tis older than the next dist version {}'
836                        .format(fmtver(vsel),np.__version__,fmtver(v)))
837                break
838    distdir = g2home + '/Binaries/' + versions[vsel]
839    # switch reset command: distdir = g2home + '/trunk/bindist'
840    svnSwitchDir('bindist','',distdir,verbose=verbose)
841    return os.path.join(path2GSAS2,'bindist')
842
843# def svnTestBranch(loc=None):
844#     '''Returns the name of the branch directory if the installation has been switched.
845#     Returns none, if not a branch
846#     the test 2frame branch. False otherwise
847#     '''
848#     if loc is None: loc = path2GSAS2
849#     svn = whichsvn()
850#     if not svn:
851#         print('**** unable to load files: svn not found ****')
852#         return ''
853#     cmd = [svn, 'info', loc]
854#     if proxycmds: cmd += proxycmds
855#     p = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
856#     res,err = MakeByte2str(p.communicate())
857#     for l in res.split('\n'):
858#         if "Relative URL:" in l: break
859#     if "/branch/" in l:
860#         return l[l.find("/branch/")+8:].strip()
861#     else:
862#         return None
863   
864def svnSwitch2branch(branch=None,loc=None,svnHome=None):
865    '''Switch to a subversion branch if specified. Switches to trunk otherwise.
866    '''
867    if svnHome is None: svnHome = g2home
868    svnURL = svnHome + '/trunk'
869    if branch:
870        if svnHome.endswith('/'):
871            svnURL = svnHome[:-1]
872        else:
873            svnURL = svnHome
874        if branch.startswith('/'):
875            svnURL += branch
876        else:
877            svnURL += '/' + branch
878    svnSwitchDir('','',svnURL,loadpath=loc)
879   
880
881def IPyBreak_base(userMsg=None):
882    '''A routine that invokes an IPython session at the calling location
883    This routine is only used when debug=True is set in config.py
884    '''
885    savehook = sys.excepthook # save the exception hook
886    try: 
887        from IPython.terminal.embed import InteractiveShellEmbed
888    except ImportError:
889        try:
890            # try the IPython 0.12 approach
891            from IPython.frontend.terminal.embed import InteractiveShellEmbed
892        except ImportError:
893            print ('IPython InteractiveShellEmbed not found')
894            return
895    import inspect
896    ipshell = InteractiveShellEmbed()
897
898    frame = inspect.currentframe().f_back
899    msg   = 'Entering IPython console inside {0.f_code.co_filename} at line {0.f_lineno}\n'.format(frame)
900    if userMsg: msg += userMsg
901    ipshell(msg,stack_depth=2) # Go up one level, to see the calling routine
902    sys.excepthook = savehook # reset IPython's change to the exception hook
903
904try:
905    from IPython.core import ultratb
906except:
907    pass
908def exceptHook(*args):
909    '''A routine to be called when an exception occurs. It prints the traceback
910    with fancy formatting and then calls an IPython shell with the environment
911    of the exception location.
912   
913    This routine is only used when debug=True is set in config.py   
914    '''
915    import IPython.core
916    if sys.platform.startswith('win'):
917        IPython.core.ultratb.FormattedTB(call_pdb=False,color_scheme='NoColor')(*args)
918    else:
919        IPython.core.ultratb.FormattedTB(call_pdb=False,color_scheme='LightBG')(*args)
920
921    try: 
922        from IPython.terminal.embed import InteractiveShellEmbed
923    except ImportError:
924        try:
925            # try the IPython 0.12 approach
926            from IPython.frontend.terminal.embed import InteractiveShellEmbed
927        except ImportError:
928            print ('IPython InteractiveShellEmbed not found')
929            return
930    import inspect
931    frame = inspect.getinnerframes(args[2])[-1][0]
932    msg   = 'Entering IPython console at {0.f_code.co_filename} at line {0.f_lineno}\n'.format(frame)
933    savehook = sys.excepthook # save the exception hook
934    try: # try IPython 5 call 1st
935        class c(object): pass
936        pseudomod = c() # create something that acts like a module
937        pseudomod.__dict__ = frame.f_locals
938        InteractiveShellEmbed(banner1=msg)(module=pseudomod,global_ns=frame.f_globals)
939    except:
940        InteractiveShellEmbed(banner1=msg)(local_ns=frame.f_locals,global_ns=frame.f_globals)
941    sys.excepthook = savehook # reset IPython's change to the exception hook
942
943def DoNothing():
944    '''A routine that does nothing. This is called in place of IPyBreak and pdbBreak
945    except when the debug option is set True in config.py
946    '''
947    pass 
948
949IPyBreak = DoNothing
950pdbBreak = DoNothing
951def InvokeDebugOpts():
952    'Called in GSASII.py to set up debug options'
953    if GetConfigValue('debug'):
954        print ('Debug on: IPython: Exceptions and G2path.IPyBreak(); pdb: G2path.pdbBreak()')
955        import pdb
956        global pdbBreak
957        pdbBreak = pdb.set_trace
958        try:
959            import IPython
960            global IPyBreak
961            IPyBreak = IPyBreak_base
962            if any('SPYDER' in name for name in os.environ):
963                print('Running from Spyder, skipping exception trapping')
964            else:
965                sys.excepthook = exceptHook
966        except:
967            pass
968
969def TestSPG(fpth):
970    '''Test if pyspg.[so,.pyd] can be run from a location in the path
971    '''
972    if not os.path.exists(fpth): return False
973    if not glob.glob(os.path.join(fpth,'pyspg.*')): return False
974    savpath = sys.path[:]
975    sys.path = [fpth]
976    # test to see if a shared library can be used
977    try:
978        import pyspg
979        pyspg.sgforpy('P -1')
980    except Exception as err:
981        print(70*'=')
982        print('Failed to run pyspg in {}\nerror: {}'.format(fpth,err))
983        print(70*'=')
984        sys.path = savpath
985        return False
986    sys.path = savpath
987    return True
988   
989# see if a directory for local modifications is defined. If so, stick that in the path
990if os.path.exists(os.path.expanduser('~/.G2local/')):
991    sys.path.insert(0,os.path.expanduser('~/.G2local/'))
992    fl = glob.glob(os.path.expanduser('~/.G2local/GSASII*.py*'))
993    files = ""
994    prev = None
995    for f in sorted(fl): # make a list of files, dropping .pyc files where a .py exists
996        f = os.path.split(f)[1]
997        if os.path.splitext(f)[0] == prev: continue
998        prev = os.path.splitext(f)[0]
999        if files: files += ", "
1000        files += f
1001    if files:
1002        print("*"*75)
1003        print("Warning: the following source files are locally overridden in "+os.path.expanduser('~/.G2local/'))
1004        print("  "+files)
1005        print("*"*75)
1006
1007BinaryPathLoaded = False
1008def SetBinaryPath(printInfo=False, loadBinary=True):
1009    '''
1010    Add location of GSAS-II shared libraries (binaries: .so or .pyd files) to path
1011   
1012    This routine must be executed after GSASIIpath is imported and before any other
1013    GSAS-II imports are done.
1014    '''
1015    # do this only once no matter how many times it is called
1016    global BinaryPathLoaded
1017    if BinaryPathLoaded: return
1018    try:
1019        inpver = intver(np.__version__)
1020    except (AttributeError,TypeError): # happens on building docs
1021        return
1022    binpath = None
1023    binprfx = GetBinaryPrefix()
1024    for loc in os.path.abspath(sys.path[0]),os.path.abspath(os.path.split(__file__)[0]):
1025        # Look at bin directory (created by a local compile) before looking for standard dist files
1026        searchpathlist = [os.path.join(loc,'bin')]
1027        # also look for matching binary dist in loc/AllBinaries
1028        versions = {}
1029        for d in glob.glob(os.path.join(loc,'AllBinaries',binprfx+'*')):
1030            v = intver(d.rstrip('/').split('_')[3].lstrip('n'))
1031            versions[v] = d
1032        searchpathlist = [os.path.join(loc,'bin')]
1033        vmin = None
1034        vmax = None
1035        for v in sorted(versions.keys()):
1036            if v <= inpver:
1037                vmin = v
1038            elif v > inpver:
1039                vmax = v
1040                break
1041        if vmin in versions:
1042            searchpathlist.append(versions[vmin])
1043        if vmax in versions:
1044            searchpathlist.append(versions[vmax])
1045        searchpathlist.append(os.path.join(loc,'bindist'))
1046        for fpth in searchpathlist:
1047            if TestSPG(fpth):
1048                binpath = fpth
1049                break       
1050        if binpath: break
1051    if binpath:                                            # were GSAS-II binaries found
1052        sys.path.insert(0,binpath)
1053        if printInfo:
1054            print('GSAS-II binary directory: {}'.format(binpath))
1055        BinaryPathLoaded = True
1056    elif not loadBinary:
1057        raise Exception
1058    else:                                                  # try loading them
1059        if printInfo:
1060            print('Attempting to download GSAS-II binary files...')
1061        try:
1062            binpath = DownloadG2Binaries(g2home)
1063        except AttributeError:   # this happens when building in Read The Docs
1064            if printInfo:
1065                print('Problem with download')
1066        if binpath and TestSPG(binpath):
1067            if printInfo:
1068                print('GSAS-II binary directory: {}'.format(binpath))
1069            sys.path.insert(0,binpath)
1070            BinaryPathLoaded = True
1071        # this must be imported before anything that imports any .pyd/.so file for GSASII
1072        else:
1073            #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1074            #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1075            # patch: use old location based on the host OS and the python version, 
1076            # path is relative to location of the script that is called as well as this file
1077            BinaryPathLoaded = True
1078            bindir = None
1079            if sys.platform == "win32":
1080                if platform.architecture()[0] == '64bit':
1081                    bindir = 'binwin64-%d.%d' % sys.version_info[0:2]
1082                else:
1083                    bindir = 'binwin%d.%d' % sys.version_info[0:2]
1084            elif sys.platform == "darwin":
1085                if platform.architecture()[0] == '64bit':
1086                    bindir = 'binmac64-%d.%d' % sys.version_info[0:2]
1087                else:
1088                    bindir = 'binmac%d.%d' % sys.version_info[0:2]
1089                #if platform.mac_ver()[0].startswith('10.5.'):
1090                #    bindir += '_10.5'
1091            elif sys.platform.startswith("linux"):
1092                if platform.architecture()[0] == '64bit':
1093                    bindir = 'binlinux64-%d.%d' % sys.version_info[0:2]
1094                else:
1095                    bindir = 'binlinux%d.%d' % sys.version_info[0:2]
1096            for loc in os.path.abspath(sys.path[0]),os.path.abspath(os.path.split(__file__)[0]):
1097            # Look at bin directory (created by a local compile) before standard dist
1098            # that at the top of the path
1099                fpth = os.path.join(loc,bindir)
1100                binpath = fpth
1101                if TestSPG(fpth):
1102                    sys.path.insert(0,binpath)
1103                    if printInfo:
1104                        print('\n'+75*'*')
1105                        print('  Warning. Using an old-style GSAS-II binary library. This is unexpected')
1106                        print('  and will break in future GSAS-II versions. Please contact toby@anl.gov')
1107                        print('  so we can learn what is not working on your installation.')
1108                        print('GSAS-II binary directory: {}'.format(binpath))
1109                        print(75*'*')
1110                    break
1111            else:
1112            # end patch
1113            #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1114            #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1115                if printInfo:
1116                    print(75*'*')
1117                    print('Use of GSAS-II binary directory {} failed!'.format(binpath))
1118                    print(75*'*')
1119                raise Exception("**** ERROR GSAS-II binary libraries not found, GSAS-II cannot run ****")
1120
1121    # add the data import and export directory to the search path
1122    newpath = os.path.join(path2GSAS2,'imports')
1123    if newpath not in sys.path: sys.path.append(newpath)
1124    newpath = os.path.join(path2GSAS2,'exports')
1125    if newpath not in sys.path: sys.path.append(newpath)
1126
1127    # setup read of config.py, if present
1128    global configDict
1129    try:
1130        import config
1131        configDict = config.__dict__
1132        import inspect
1133        vals = [True for i in inspect.getmembers(config) if '__' not in i[0]]
1134        if printInfo:
1135            print (str(len(vals))+' values read from config file '+os.path.abspath(config.__file__))
1136    except ImportError:
1137        configDict = {'Clip_on':True}
1138    except Exception as err:
1139        print(60*'*',"\nError reading config.py file")
1140        if printInfo:
1141            import traceback
1142            print(traceback.format_exc())
1143        print(60*'*')
1144        configDict = {'Clip_on':True}
1145
1146def MacStartGSASII(g2script,project=''):
1147    '''Start a new instance of GSAS-II by opening a new terminal window and starting
1148    a new GSAS-II process. Used on Mac OS X only.
1149
1150    :param str g2script: file name for the GSASII.py script
1151    :param str project: GSAS-II project (.gpx) file to be opened, default is blank
1152      which opens a new project
1153    '''
1154    if project and os.path.splitext(project)[1] != '.gpx':
1155        print('file {} cannot be used. Not GSAS-II project (.gpx) file'.format(project))
1156        return
1157    if project and not os.path.exists(project):
1158        print('file {} cannot be found.'.format(project))
1159        return 
1160    elif project:
1161        project = os.path.abspath(project)
1162    g2script = os.path.abspath(g2script)
1163    pythonapp = sys.executable
1164    if os.path.exists(pythonapp+'w'): pythonapp += 'w'
1165    script = '''
1166set python to "{}"
1167set appwithpath to "{}"
1168set filename to "{}"
1169
1170tell application "Terminal"
1171     activate
1172     do script python & " " & appwithpath & " " & filename & "; exit"
1173end tell
1174'''.format(pythonapp,g2script,project)
1175    subprocess.Popen(["osascript","-e",script])
1176
1177def MacRunScript(script):
1178    '''Start a bash script in a new terminal window.
1179    Used on Mac OS X only.
1180
1181    :param str script: file name for a bash script
1182    '''
1183    script = os.path.abspath(script)
1184    osascript = '''
1185set bash to "/bin/bash"
1186set filename to "{}"
1187
1188tell application "Terminal"
1189     activate
1190     do script bash & " " & filename & "; exit"
1191end tell
1192'''.format(script)
1193    subprocess.Popen(["osascript","-e",osascript])
1194   
1195def findConda():
1196    '''Determines if GSAS-II has been installed as g2conda or gsas2full
1197    with conda located relative to this file.
1198    We could also look for conda relative to the python (sys.executable)
1199    image, but I don't want to muck around with python that someone else
1200    installed.
1201    '''
1202    parent = os.path.split(path2GSAS2)[0]
1203    if sys.platform != "win32":
1204        activate = os.path.join(parent,'bin','activate')
1205        conda = os.path.join(parent,'bin','conda')
1206    else:
1207        activate = os.path.join(parent,'Scripts','activate.bat')
1208        conda = os.path.join(parent,'condabin','conda.bat')
1209    if os.path.exists(activate) and os.path.exists(conda):
1210        return conda,activate
1211    else:
1212        return None
1213
1214def runScript(cmds=[], wait=False, G2frame=None):
1215    '''run a shell script of commands in an external process
1216   
1217    :param list cmds: a list of str's, each ietm containing a shell (cmd.exe
1218      or bash) command
1219    :param bool wait: if True indicates the commands should be run and then
1220      the script should return. If False, then the currently running Python
1221      will exit. Default is False
1222    :param wx.Frame G2frame: provides the location of the current .gpx file
1223      to be used to restart GSAS-II after running the commands, if wait
1224      is False. Default is None which prevents restarting GSAS-II regardless of
1225      the value of wait.
1226    '''
1227    import tempfile
1228    if not cmds:  #debug
1229        print('nothing to do in runScript')
1230        return
1231    if sys.platform != "win32":
1232        suffix = '.sh'
1233    else:
1234        suffix = '.bat'
1235       
1236    fp = tempfile.NamedTemporaryFile(mode='w', suffix=suffix, delete=False)
1237    shellname = fp.name
1238    for line in cmds:
1239        fp.write(line)
1240        fp.write('\n')
1241
1242    if not wait:
1243        if G2frame:
1244            projectfile = ''
1245            if G2frame.GSASprojectfile:
1246                projectfile = os.path.realpath(G2frame.GSASprojectfile)
1247            main = os.path.join(path2GSAS2,'GSASII.py')
1248            ex = sys.executable
1249            if sys.platform == "darwin": # mac requires pythonw which is not always reported as sys.executable
1250                if os.path.exists(ex+'w'): ex += 'w'
1251            print ('restart using ',' '.join([ex,main,projectfile]))
1252            fp.write(' '.join([ex,main,projectfile]))
1253            fp.write('\n')
1254    fp.close()
1255
1256    # start the upgrade in a separate interpreter (avoids loading .pyd files)
1257    if sys.platform != "win32":
1258        proc = subprocess.Popen(['bash',shellname])
1259    else:
1260        proc = subprocess.Popen([shellname],shell=True)
1261    if wait:
1262        proc.wait()
1263    else:
1264        if sys.platform != "win32": proc.wait()
1265        sys.exit()
1266   
1267if __name__ == '__main__':
1268    '''What follows is called to update (or downdate) GSAS-II in a separate process.
1269    '''
1270    import time
1271    time.sleep(1) # delay to give the main process a chance to exit
1272    # perform an update and restart GSAS-II
1273    try:
1274        project,version = sys.argv[1:3]
1275    except ValueError:
1276        project = None
1277        version = 'trunk'
1278    loc = os.path.dirname(__file__)
1279    if version == 'trunk':
1280        svnSwitch2branch('')
1281    elif '/' in version:
1282        svnSwitch2branch(version)
1283    elif version:
1284        print("Regress to version "+str(version))
1285        svnUpdateDir(loc,version=version)
1286    else:
1287        print("Update to current version")
1288        svnUpdateDir(loc)
1289    ex = sys.executable
1290    if sys.platform == "darwin": # mac requires pythonw which is not always reported as sys.executable
1291        if os.path.exists(ex+'w'): ex += 'w'
1292    if project:
1293        print("Restart GSAS-II with project file "+str(project))
1294        subprocess.Popen([ex,os.path.join(loc,'GSASII.py'),project])
1295    else:
1296        print("Restart GSAS-II without a project file ")
1297        subprocess.Popen([ex,os.path.join(loc,'GSASII.py')])
1298    print ('exiting update process')
1299    sys.exit()
Note: See TracBrowser for help on using the repository browser.