1 | #!/usr/bin/env python |
---|
2 | ''' |
---|
3 | *makeMacApp: Create Mac Applet* |
---|
4 | =============================== |
---|
5 | |
---|
6 | This script creates an AppleScript app bundle to launch GSAS-II. The app is |
---|
7 | created in the directory where the GSAS-II script (.../GSASII/GSASII.py) |
---|
8 | is located. A softlink to Python is created inside that app bundle, |
---|
9 | but the softlink name is GSAS-II so that GSAS-II shows up as the name |
---|
10 | of the app rather than Python in the menu bar, etc. A soft link named |
---|
11 | GSAS-II.py, referencing the GSASII.py script, is created so that some file |
---|
12 | menu items also are labeled with GSAS-II (but not the right capitalization, |
---|
13 | alas). |
---|
14 | |
---|
15 | This has been tested with several versions of Python interpreters |
---|
16 | from Anaconda and does not require pythonw (Python.app). It tests to |
---|
17 | make sure that a wx python script will run inside Python and if not, |
---|
18 | it searches for a pythonw image and tries that. |
---|
19 | |
---|
20 | Run this script with no arguments or with one or two arguments. |
---|
21 | The first argument, if supplied, is a reference to the GSASII.py script, |
---|
22 | which can have a relative or absolute path (the absolute path is determined). |
---|
23 | If not supplied, the GSASII.py script will be used from the directory where |
---|
24 | this (makeMacApp.py) script is found. The second argument, if supplied, |
---|
25 | provides the name for the app to be created. This can be used to create |
---|
26 | multiple copies of the app using different Python versions (likely use for |
---|
27 | development only). |
---|
28 | ''' |
---|
29 | |
---|
30 | from __future__ import division, print_function |
---|
31 | import sys, os, os.path, stat, shutil, subprocess, plistlib |
---|
32 | def Usage(): |
---|
33 | print("\n\tUsage: python "+sys.argv[0]+" [<GSAS-II script>] [project]\n") |
---|
34 | sys.exit() |
---|
35 | |
---|
36 | def 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 | |
---|
48 | AppleScript = '' |
---|
49 | '''Contains an AppleScript to start GSAS-II, launching Python and the |
---|
50 | GSAS-II python script |
---|
51 | ''' |
---|
52 | |
---|
53 | if __name__ == '__main__': |
---|
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 *) |
---|
90 | on 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 |
---|
98 | end TestFilePresent |
---|
99 | |
---|
100 | (* |
---|
101 | ------------------------------------------------------------------------ |
---|
102 | this section responds to a double-click. No file is supplied to GSAS-II |
---|
103 | ------------------------------------------------------------------------ |
---|
104 | *) |
---|
105 | on 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 | activate |
---|
113 | do script env & python & " " & appwithpath & "; exit" |
---|
114 | end tell |
---|
115 | end run |
---|
116 | |
---|
117 | (* |
---|
118 | ----------------------------------------------------------------------------------------------- |
---|
119 | this section handles starting with files dragged into the AppleScript |
---|
120 | o it goes through the list of file(s) dragged in |
---|
121 | o then it converts the colon-delimited macintosh file location to a POSIX filename |
---|
122 | o for every non-directory file dragged into the icon, it starts GSAS-II, passing the file name |
---|
123 | ------------------------------------------------------------------------------------------------ |
---|
124 | *) |
---|
125 | |
---|
126 | on open names |
---|
127 | set python to "{:s}" |
---|
128 | set appwithpath to "{:s}" |
---|
129 | set env to "{:s}" |
---|
130 | |
---|
131 | TestFilePresent(appwithpath) |
---|
132 | repeat with filename in names |
---|
133 | set filestr to (filename as string) |
---|
134 | if filestr ends with ":" then |
---|
135 | (* should not happen, skip directories *) |
---|
136 | else |
---|
137 | (* if this is an input file, open it *) |
---|
138 | set filename to the quoted form of the POSIX path of filename |
---|
139 | tell application "Terminal" |
---|
140 | activate |
---|
141 | do script env & python & " " & appwithpath & " " & filename & "; exit" |
---|
142 | end tell |
---|
143 | end if |
---|
144 | end repeat |
---|
145 | end open |
---|
146 | ''' |
---|
147 | # create a link named GSAS-II.py to the script |
---|
148 | newScript = os.path.join(scriptdir,'GSAS-II.py') |
---|
149 | if os.path.exists(newScript): # cleanup |
---|
150 | print("\nRemoving sym link",newScript) |
---|
151 | os.remove(newScript) |
---|
152 | os.symlink(os.path.split(script)[1],newScript) |
---|
153 | script=newScript |
---|
154 | |
---|
155 | # find Python used to run GSAS-II and set a new to use to call it |
---|
156 | # inside the app that will be created |
---|
157 | pythonExe = os.path.realpath(sys.executable) |
---|
158 | newpython = os.path.join(appPath,"Contents","MacOS",project) |
---|
159 | |
---|
160 | # create an app using this python and if that fails to run wx, look for a |
---|
161 | # pythonw and try that with wx |
---|
162 | for i in 1,2,3: |
---|
163 | if os.path.exists(appPath): # cleanup |
---|
164 | print("\nRemoving old "+project+" app ("+str(appPath)+")") |
---|
165 | shutil.rmtree(appPath) |
---|
166 | |
---|
167 | shell = os.path.join("/tmp/","appscrpt.script") |
---|
168 | f = open(shell, "w") |
---|
169 | f.write(AppleScript.format(newpython,script,'',newpython,script,'')) |
---|
170 | f.close() |
---|
171 | |
---|
172 | try: |
---|
173 | subprocess.check_output(["osacompile","-o",appPath,shell],stderr=subprocess.STDOUT) |
---|
174 | except subprocess.CalledProcessError as msg: |
---|
175 | print('''Error compiling AppleScript. |
---|
176 | Report the next message along with details about your Mac to toby@anl.gov''') |
---|
177 | print(msg.output) |
---|
178 | sys.exit() |
---|
179 | # create a link to the python inside the app, if named to match the project |
---|
180 | if pythonExe != newpython: os.symlink(pythonExe,newpython) |
---|
181 | |
---|
182 | # test if newpython can run wx |
---|
183 | testout,errout = RunPython(newpython,'import numpy; import wx; wx.App(); print("-OK-")') |
---|
184 | if isinstance(testout,bytes): testout = testout.decode() |
---|
185 | if "-OK-" in testout: |
---|
186 | break |
---|
187 | elif i == 1: |
---|
188 | print('Run of wx in',pythonExe,'failed, looking for pythonw') |
---|
189 | pythonExe = os.path.join(os.path.split(sys.executable)[0],'pythonw') |
---|
190 | if not os.path.exists(pythonExe): |
---|
191 | print('Warning no pythonw found with ',sys.executable, |
---|
192 | '\ncontinuing, hoping for the best') |
---|
193 | elif i == 2: |
---|
194 | print('Warning could not run wx with',pythonExe, |
---|
195 | 'will try with that external to app') |
---|
196 | newpython = pythonExe |
---|
197 | else: |
---|
198 | print('Warning still could not run wx with',pythonExe, |
---|
199 | '\ncontinuing, hoping for the best') |
---|
200 | |
---|
201 | # change the icon |
---|
202 | oldicon = os.path.join(appPath,"Contents","Resources","droplet.icns") |
---|
203 | #if os.path.exists(iconfile) and os.path.exists(oldicon): |
---|
204 | if os.path.exists(iconfile): |
---|
205 | shutil.copyfile(iconfile,oldicon) |
---|
206 | |
---|
207 | # Edit the app plist file to restrict the type of files that can be dropped |
---|
208 | if hasattr(plistlib,'load'): |
---|
209 | fp = open(os.path.join(appPath,"Contents",'Info.plist'),'rb') |
---|
210 | d = plistlib.load(fp) |
---|
211 | fp.close() |
---|
212 | else: |
---|
213 | d = plistlib.readPlist(os.path.join(appPath,"Contents",'Info.plist')) |
---|
214 | d['CFBundleDocumentTypes'] = [{ |
---|
215 | 'CFBundleTypeExtensions': ['gpx'], |
---|
216 | 'CFBundleTypeName': 'GSAS-II project', |
---|
217 | 'CFBundleTypeRole': 'Editor'}] |
---|
218 | |
---|
219 | if hasattr(plistlib,'dump'): |
---|
220 | fp = open(os.path.join(appPath,"Contents",'Info.plist'),'wb') |
---|
221 | plistlib.dump(d,fp) |
---|
222 | fp.close() |
---|
223 | else: |
---|
224 | plistlib.writePlist(d,os.path.join(appPath,"Contents",'Info.plist')) |
---|
225 | |
---|
226 | # open & save the file in the editor to help set authorization levels |
---|
227 | osascript = ''' |
---|
228 | tell application "Script Editor" |
---|
229 | activate |
---|
230 | set MyName to open "{}" |
---|
231 | save MyName |
---|
232 | close MyName |
---|
233 | quit |
---|
234 | end tell |
---|
235 | '''.format(appPath) |
---|
236 | print(script) |
---|
237 | subprocess.Popen(["osascript","-e",osascript]) |
---|
238 | |
---|
239 | print("\nCreated "+project+" app ("+str(appPath)+ |
---|
240 | ").\nViewing app in Finder so you can drag it to the dock if, you wish.") |
---|
241 | subprocess.call(["open","-R",appPath]) |
---|