/
a4.in
242 lines (206 loc) · 8.83 KB
/
a4.in
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
#!@PYTHON@
import os, argparse, pathlib, sys, platform, shutil
DEBUG=0
def MSG(*args):
if DEBUG:
print(*args)
else:
pass
PYTHON=pathlib.Path(r'@PYTHON@')
def add_at_start(envvar,pathcomponents):
"""
Add path components at start of envvar, but only if they're not already in the path somewhere.
@return 1 if changes made, 0 otherwise
@TODO what about Windows path munging?
"""
# convert pathcomponents to absolute paths
pathcomponents = [i.resolve() for i in pathcomponents]
if not os.environ.get(envvar):
# env var is not yet set; simply set it to requested value
v = SEP.join([str(p) for p in pathcomponents])
MSG("CREATED %s=%s" % (envvar,v))
os.environ[envvar]=v
return 1
changed = 0
# convert what's in there to absolute paths for comparison
envvals = [pathlib.Path(i).resolve() for i in os.environ[envvar].split(SEP)]
oldenv = os.environ[envvar]
found = []
for p in reversed(pathcomponents):
if p not in envvals:
envvals.insert(0,p)
MSG("INSERTED into %s: %s"%(envvar,p))
changed = 1
# TODO if changes made, let's check for repeated path components and remove them (PyGTK All-in-one does this)
os.environ[envvar]=SEP.join([str(i) for i in envvals])
if not changed:
MSG("VAR %s UNCHANGED" % envvar)
else:
MSG("VAR %s CHANGED" % envvar)
MSG("OLD %s = %s" % (envvar,oldenv))
MSG("NEW %s = %s" % (envvar,os.environ[envvar]))
return changed
def shebang(script,args):
"""This excessively complex function allows us to launch shebang-encoded scripts when 'a4' is
run on Windows. It depends on the 'cygpath' program, which in turn depends on some of the
MSYS2 environment variables. Does nothing on Linux. Arguably it would be much easier to just
ask MSYS2 bash to look after all this directly."""
if not platform.system()== "Windows":
return [script] + args
p = pathlib.Path(script)
if not p.is_file() or not os.access(p, os.X_OK):
MSG("checking PATHEXT extensions...")
found = None
for pe in os.environ.get('PATHEXT').split(";"):
exe = pathlib.Path(str(script) + pe)
MSG(f"checking for {exe}")
if exe.is_file():
found = exe
break
if not found:
raise RuntimeError(f"script '{script}' either doesn't exist is or not executable")
return [found]+args
with open(p,'r') as f:
line1 = f.readline()
if line1.startswith("#!"):
interp = line1[2:].strip().split()
if interp:
pi = pathlib.Path(interp[0])
if str(pi)[0] == "/":
# MSYS path... convert to Windows
wp = subprocess.run([shutil.which('cygpath'),'-w',pi],capture_output=True,check=True,encoding='utf-8')
MSG(f"WP = {wp.stdout}")
pi = pathlib.Path(wp.stdout.strip())
if not pi.is_absolute():
raise RuntimeError(f"in script {p}, the shebang line must contain an absolute path (got: '{pi}')")
if not pi.is_file() or not os.access(pi, os.X_OK):
raise RuntimeError(f"in script {p}, interpreter '{pi}' is not a file or is not flagged as executable")
return [pi] + interp[1:] + [script] + args
raise RuntimeError(f"Invalid shebang line in {script}")
# does not start with shebang, could be natively executable (we'll just try)
return [script]+args
if __name__ == "__main__":
p = argparse.ArgumentParser(description='ASCEND launcher')
p.add_argument('--debug',action='store_true',help='Show launcher debug output')
g = p.add_mutually_exclusive_group()
g.add_argument('--gdb',action='store_true',help='Run action via GDB')
g.add_argument('--valgrind',action='store_true',help='Run action via Valgrind')
s = p.add_subparsers(help='sub-command help')
p_open = s.add_parser('open',help = 'Open file in ASCEND GUI')
p_open.set_defaults(action='open')
p_open.add_argument('file',type=pathlib.Path)
p_open.add_argument('--model','-m', help="Name of MODEL to instantiate");
p_gui = s.add_parser('gui',help = 'Simply open the ASCEND GUI')
p_gui.set_defaults(action='gui')
p_run = s.add_parser('run',help = 'Run model and solve (without GUI, via QRSlv solver)')
if sys.version_info.major == 3 and sys.version_info.minor < 8:
class ExtendAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
items = getattr(namespace, self.dest) or []
items.extend(values)
setattr(namespace, self.dest, items)
p_run.register('action', 'extend', ExtendAction)
p_run.set_defaults(action='run')
p_run.add_argument('file',type=pathlib.Path,help='ASCEND model file to be opened')
p_run.add_argument('--model','-m', help="Name of MODEL to instantiate (defaults to filename without extension)");
p_run.add_argument('-p', '--print', dest='printvars', action='extend', nargs='+', help='Variables to print (can be used multiple times). Implies --no-test.')
p_run.add_argument('--no-test','-n', help="Suppress running of 'self_test' method after solving");
p_scr = s.add_parser('script',help = 'Run a Python or other script within ASCEND development environment')
p_scr.set_defaults(action='script')
p_scr.add_argument('file',type=pathlib.Path,help='Script file to be run')
p_scr.add_argument('rest',nargs=argparse.REMAINDER,help='Additional arguments to be passed to the script')
p_sh = s.add_parser('env',help = 'Create a sub-shell in which ASCEND environment variables are set.')
p_sh.set_defaults(action='env')
p_sh.add_argument('--shell',default='bash',help='Shell to run')
p_sh.add_argument('--label',action='store_false',default=True,help="Suppress adding 'a4>' text at start of prompt")
p_sh.add_argument('rest',nargs=argparse.REMAINDER,help='Extra arguments to send to shell')
p_sh = s.add_parser('q',help = 'Quick shortcut test -- test a4.in source for details.')
p_sh.set_defaults(action='q')
args = p.parse_args()
if args.debug:
DEBUG=1
MSG("Debugging output turned on!")
else:
sys.tracebacklimit=0
import subprocess, os
sourceroot=pathlib.Path("@SOURCE_ROOT@")
script=os.path.join(sys.argv[0])
SEP = os.pathsep
if platform.system()=="Windows":
LD_LIBRARY_PATH="PATH"
elif platform.system()=="Darwin":
LD_LIBRARY_PATH="DYLD_LIBRARY_PATH"
else:
LD_LIBRARY_PATH="LD_LIBRARY_PATH"
restart = []
# for Mac, add our local PyGTK libraries at start of Python path:
#if platform.system()=="Darwin":
# sys.path.insert(0,'dist/gtk.bundle/python')
# ensure that we have our 'models' dir in the ASCENDLIBRARY path
modeldirs = [sourceroot / 'models']
if add_at_start('ASCENDLIBRARY',modeldirs):
restart.append('ASCENDLIBRARY')
solverdir = sourceroot / 'solvers'
solverdirs = [solverdir / d.lower() for d in "@WITH_SOLVERS@".split(",")]
if add_at_start('ASCENDSOLVERS',solverdirs):
restart.append('ASCENDSOLVERS')
# ensure that we have our shared libraries in the LD_LIBRARY_PATH
libdirs = [sourceroot, pathlib.Path.home() / '.local/lib']
if add_at_start(LD_LIBRARY_PATH,libdirs):
restart.append(LD_LIBRARY_PATH)
# ensure our pygtk dir is in the PYTHONPATH
pypath = [sourceroot / 'ascxx', sourceroot / 'pygtk']
# also FPROPS
pypath += [sourceroot / 'models' / 'johnpye' / 'fprops' / 'python']
if add_at_start('PYTHONPATH',pypath):
restart.append('PYTHONPATH')
MSG("ARGS",args)
precmd = []
if args.gdb:
precmd = ['gdb','--args']
elif args.valgrind:
VALGRIND = shutil.which('valgrind')
if VALGRIND == None:
raise RuntimeError("Could not locate 'valgrind' in the PATH")
precmd = [VALGRIND]+ '--suppressions=tools/valgrind/suppressions --tool=memcheck --leak-check=full --show-reachable=yes'.split(' ')
cmd = precmd
if not hasattr(args,'action') or args.action == 'gui':
MSG("launching GUI...")
cmd += [PYTHON,sourceroot/'pygtk'/'ascdev']
elif args.action == 'open':
cmd += [PYTHON,sourceroot/'pygtk'/'ascdev', args.file]
if args.model:
cmd += ['-m',args.model]
elif args.action == 'run':
# note that we cannot avoid invoking a subprocess here, because
# we cannot update LD_LIBRARY_PATH for the running process, which means
# that ascpy cannot successfully load libascend.so.
cmd += [PYTHON,sourceroot/'ascxx'/'runmodel.py',args.file]
if args.model:
cmd += ["--model",args.model]
if args.printvars:
cmd += ["--print"] + args.printvars
if args.no_test:
cmd += ["--no-test"]
#print(f"CMD = {cmd}")
elif args.action == 'q':
# for the convenience of the developer -- hack this for your temporary needs
cmd += [PYTHON,sourceroot/'pygtk'/'ascdev', sourceroot/'models'/'johnpye'/'extpy'/'extpytest.a4c']
elif args.action == 'script':
cmd += shebang(args.file,args.rest)
elif args.action == 'env' :
shargs = []
if os.environ.get('ASCEND_SHELL'):
raise RuntimeError("You are already inside an ASCEND shell")
if args.label:
assert(args.shell=='bash')
os.environ['PS1'] = r'a4> \[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
shargs += ['--norc']
os.environ['ASCEND_SHELL'] = '1'
cmd += [args.shell] + shargs + args.rest
else:
print("unrecognised command")
sys.exit(1);
MSG("CMD =",cmd)
proc = subprocess.run(cmd)
# vim:syntax=python:ts=4:sw=4:noet