source: trunk/GSASIIpath.py @ 5551

Last change on this file since 5551 was 5551, checked in by toby, 2 years ago

add fast pixel map support (AIRXD)

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