source: trunk/makeMacApp.py @ 4749

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

fine-tune Mac installer for 11.0

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 9.0 KB
Line 
1#!/usr/bin/env python
2'''
3*makeMacApp: Create Mac Applet*
4===============================
5
6This script creates an AppleScript app bundle to launch GSAS-II. The app is
7created in the directory where the GSAS-II script (.../GSASII/GSASII.py)
8is located. A softlink to Python is created inside that app bundle,
9but the softlink name is GSAS-II so that "GSAS-II" shows up as the name
10of the app in the menu bar, etc. rather than "Python". A soft link named
11GSAS-II.py, referencing the GSASII.py script, is created so that some file
12menu items also are labeled with GSAS-II (but not the right capitalization,
13alas).
14
15This has been tested with several versions of Python interpreters
16from Anaconda and does not require pythonw (Python.app). It tests to
17make sure that a wxpython script will run inside the app but if not,
18it searches for a pythonw image and tries that.
19
20Run this script with no arguments or with one or two arguments.
21The first argument, if supplied, is a reference to the GSASII.py script,
22which can have a relative or absolute path (the absolute path is determined).
23If not supplied, the GSASII.py script will be used from the directory where
24this (makeMacApp.py) script is found. The second argument, if supplied,
25provides the name for the app to be created. This can be used to create
26multiple copies of the app using different Python versions (likely use for
27development only).
28'''
29
30from __future__ import division, print_function
31import sys, os, os.path, stat, shutil, subprocess, plistlib
32def Usage():
33    print("\n\tUsage: python "+sys.argv[0]+" [<GSAS-II script>] [project]\n")
34    sys.exit()
35
36def RunPython(image,cmd):
37    'Run a command in a python image'
38    try:
39        err=None
40        p = subprocess.Popen([image,'-c',cmd],stdout=subprocess.PIPE,stderr=subprocess.PIPE)
41        out = p.stdout.read()
42        err = p.stderr.read()
43        p.communicate()
44        return out,err
45    except Exception(err):
46        return '','Exception = '+err
47
48AppleScript = ''
49'''Contains an AppleScript to start GSAS-II, launching Python and the
50GSAS-II python script
51'''
52
53if __name__ == '__main__' and sys.platform == "darwin":
54    project="GSAS-II"
55
56    # set scriptdir: find the main GSAS-II script if not on command line
57    if len(sys.argv) == 1:
58        script = os.path.abspath(os.path.join(
59                os.path.split(__file__)[0],
60                "GSASII.py"
61                ))
62    elif len(sys.argv) == 2:
63        script = os.path.abspath(sys.argv[1])
64    elif len(sys.argv) == 3:
65        script = os.path.abspath(sys.argv[1])
66        project = sys.argv[2]
67    else:
68        Usage()
69        raise Exception
70    # make sure we found it
71    if not os.path.exists(script):
72        print("\nFile "+script+" not found")
73        Usage()
74    if os.path.splitext(script)[1].lower() != '.py':
75        print("\nScript "+script+" does not have extension .py")
76        Usage()
77    # where the app will be created
78    scriptdir = os.path.split(script)[0]
79   
80    appPath = os.path.abspath(os.path.join(scriptdir,project+".app"))
81    iconfile = os.path.join(scriptdir,'gsas2.icns') # optional icon file
82   
83    AppleScript = '''(*   GSAS-II AppleScript by B. Toby (brian.toby@anl.gov)
84     It can launch GSAS-II by double clicking or by dropping a data file
85     or folder over the app.
86     It runs GSAS-II in a terminal window.
87*)
88
89(* test if a file is present and exit with an error message if it is not  *)
90on TestFilePresent(appwithpath)
91        tell application "System Events"
92                if (file appwithpath exists) then
93                else
94                        display dialog "Error: file " & appwithpath & " not found. If you have moved this file recreate the AppleScript with bootstrap.py." with icon caution buttons {{"Quit"}}
95                        return
96                end if
97        end tell
98end TestFilePresent
99
100(*
101------------------------------------------------------------------------
102this section responds to a double-click. No file is supplied to GSAS-II
103------------------------------------------------------------------------
104*)
105on run
106        set python to "{:s}"
107        set appwithpath to "{:s}"
108        set env to "{:s}"
109        TestFilePresent(appwithpath)
110        TestFilePresent(python)
111        tell application "Terminal"
112                do script env & python & " " & appwithpath & "; exit"
113        end tell
114end run
115
116(*
117-----------------------------------------------------------------------------------------------
118this section handles starting with files dragged into the AppleScript
119 o it goes through the list of file(s) dragged in
120 o then it converts the colon-delimited macintosh file location to a POSIX filename
121 o for every non-directory file dragged into the icon, it starts GSAS-II, passing the file name
122------------------------------------------------------------------------------------------------
123*)
124
125on open names
126        set python to "{:s}"
127        set appwithpath to "{:s}"
128        set env to "{:s}"
129 
130        TestFilePresent(appwithpath)
131        repeat with filename in names
132                set filestr to (filename as string)
133                if filestr ends with ":" then
134                        (* should not happen, skip directories *)
135                else
136                        (* if this is an input file, open it *)
137                        set filename to the quoted form of the POSIX path of filename
138                        tell application "Terminal"
139                                activate
140                                do script env & python & " " & appwithpath & " " & filename & "; exit"
141                        end tell
142                end if
143        end repeat
144end open
145'''
146    # create a link named GSAS-II.py to the script
147    newScript = os.path.join(scriptdir,'GSAS-II.py')
148    if os.path.exists(newScript): # cleanup
149        print("\nRemoving sym link",newScript)
150        os.remove(newScript)
151    os.symlink(os.path.split(script)[1],newScript)
152    script=newScript
153
154    # find Python used to run GSAS-II and set a new to use to call it
155    # inside the app that will be created
156    pythonExe = os.path.realpath(sys.executable)
157    newpython =  os.path.join(appPath,"Contents","MacOS",project)
158
159    # create an app using this python and if that fails to run wx, look for a
160    # pythonw and try that with wx
161    for i in 1,2,3:
162        if os.path.exists(appPath): # cleanup
163            print("\nRemoving old "+project+" app ("+str(appPath)+")")
164            shutil.rmtree(appPath)
165       
166        shell = os.path.join("/tmp/","appscrpt.script")
167        f = open(shell, "w")
168        f.write(AppleScript.format(newpython,script,'',newpython,script,''))
169        f.close()
170
171        try: 
172            subprocess.check_output(["osacompile","-o",appPath,shell],stderr=subprocess.STDOUT)
173        except subprocess.CalledProcessError as msg:
174            print('''Error compiling AppleScript.
175            Report the next message along with details about your Mac to toby@anl.gov''')
176            print(msg.output)
177            sys.exit()
178        # create a link to the python inside the app, if named to match the project
179        if pythonExe != newpython: os.symlink(pythonExe,newpython)
180
181        # test if newpython can run wx
182        testout,errout = RunPython(newpython,'import numpy; import wx; wx.App(); print("-OK-")')
183        if isinstance(testout,bytes): testout = testout.decode()
184        if "-OK-" in testout:
185            break
186        elif i == 1:
187            print('Run of wx in',pythonExe,'failed, looking for pythonw')
188            pythonExe = os.path.join(os.path.split(sys.executable)[0],'pythonw')
189            if not os.path.exists(pythonExe):
190                print('Warning no pythonw found with ',sys.executable,
191                      '\ncontinuing, hoping for the best')
192        elif i == 2:
193            print('Warning could not run wx with',pythonExe,
194                      'will try with that external to app')
195            newpython = pythonExe
196        else:
197            print('Warning still could not run wx with',pythonExe,
198                      '\ncontinuing, hoping for the best')
199
200    # change the icon
201    oldicon = os.path.join(appPath,"Contents","Resources","droplet.icns")
202    #if os.path.exists(iconfile) and os.path.exists(oldicon):
203    if os.path.exists(iconfile):
204        shutil.copyfile(iconfile,oldicon)
205
206    # Edit the app plist file to restrict the type of files that can be dropped
207    if hasattr(plistlib,'load'):
208        fp = open(os.path.join(appPath,"Contents",'Info.plist'),'rb')
209        d = plistlib.load(fp)
210        fp.close()
211    else:
212        d = plistlib.readPlist(os.path.join(appPath,"Contents",'Info.plist'))
213    d['CFBundleDocumentTypes'] = [{
214        'CFBundleTypeExtensions': ['gpx'],
215        'CFBundleTypeName': 'GSAS-II project',
216        'CFBundleTypeRole': 'Editor'}]
217   
218    if hasattr(plistlib,'dump'):
219        fp = open(os.path.join(appPath,"Contents",'Info.plist'),'wb')
220        plistlib.dump(d,fp)
221        fp.close()
222    else:
223        plistlib.writePlist(d,os.path.join(appPath,"Contents",'Info.plist'))
224
225    # open & save the file in the editor to help set authorization levels
226    osascript = '''
227    tell application "Script Editor"
228       set MyName to open "{}"
229       save MyName
230       close MyName
231       quit
232    end tell   
233'''.format(appPath)
234    print(script)
235    subprocess.Popen(["osascript","-e",osascript])
236       
237    print("\nCreated "+project+" app ("+str(appPath)+
238          ").\nViewing app in Finder so you can drag it to the dock if, you wish.")
239    subprocess.call(["open","-R",appPath])
Note: See TracBrowser for help on using the repository browser.