-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathmfp.installer
314 lines (253 loc) · 8.57 KB
/
mfp.installer
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
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
#! /usr/bin/env python
"""
install_mfp.py -- installer for MFP binary distribution
"""
import argparse
import os
import re
import subprocess
import sys
from datetime import datetime
version = "0.1"
logfile = "install_mfp.log"
def logprint(msg=''):
"""
logprint: print message and log it
"""
print(msg)
with open(logfile, "a") as log:
log.write(msg + '\n')
def shellcmd(cmd, return_stdout=False):
"""
shellcmd: run a command in a shell and log stdout and stderr
"""
with open(logfile, "a") as log:
print(" " + cmd)
log.write("\n-- shell command -----------\n")
log.write(f"{cmd}\n")
proc = subprocess.run(cmd, shell=True, capture_output=True)
if proc.stdout:
log.write(">>>>> stdout\n")
log.write(proc.stdout.decode())
if proc.stderr:
log.write(">>>>> stderr\n")
log.write(proc.stderr.decode())
log.write(f">>>>> return code {proc.returncode}\n")
log.write("----------------------------\n")
if return_stdout:
return proc.stdout.decode()
return proc.returncode == 0
def template(source, target, variables):
"""
template: Expand ${VARIABLES} in a template file
"""
source_stat = os.stat(source)
source_mode = oct(source_stat.st_mode & 0o777)
logprint(f" {source} --> {target}")
actions = [
f"cp {source} {target}",
]
for varname, value in variables.items():
escaped_value = re.sub("/", r"\/", value)
actions.append(f"(cat {target} | sed -e 's/\\${{{varname}}}/{escaped_value}/g' > {target}.fixed) ")
actions.append(f"mv {target}.fixed {target}")
actions.append(f"chmod {source_mode[2:]} {target}")
shellcmd(" && ".join(actions))
def test_requirements(virtualenv):
"""
requirements for the install:
* python 3.10 or greater
* venv if --virtualenv
* pip if not --virtualenv (venv provides it automatically)
"""
all_good = True
logprint("Checking for install requirements...")
# python version
py_ver = sys.version_info
py_status = "ok ✅"
if (py_ver.major != 3) or (py_ver.minor < 10):
py_status = "FAIL ❌"
all_good = False
logprint(f" Python version >= 3.10... {py_ver.major}.{py_ver.minor}.{py_ver.micro} {py_status}")
# can import venv
if virtualenv:
venv_status = "FAIL ❌"
try:
import venv
venv_status = "ok ✅"
except:
all_good = False
logprint(f" Can import venv module... {venv_status}")
if not virtualenv:
pip_status = "FAIL ❌"
try:
import pip
pip_status = "ok ✅"
except:
all_good = False
logprint(f" Can import pip module... {pip_status}")
if all_good:
logprint(" All requirements present and accounted for 🎉\n")
return all_good
def install_virtualenv(prefix):
"""
install_virtualenv: create the base venv under the install prefix
note that the virtualenv is NOT at the prefix location, it's in
${prefix}/share/mfp/venv,
"""
import venv
python = sys.executable
venv_loc = f"{prefix}/share/mfp/venv"
logprint("Preparing virtualenv for install")
status = shellcmd(
f"mkdir -p {venv_loc} && {python} -m venv {venv_loc}"
)
if status:
logprint(" virtualenv created successfully 🎉")
else:
logprint(" Error while creating virtualenv, see logfile ❌")
return status
def install_depends(virtualenv, prefix, datadir):
"""
install_depends: apply requirements.txt
"""
venv_loc = f"{prefix}/share/mfp/venv"
activate = ""
if virtualenv:
activate = f". {venv_loc}/bin/activate && "
logprint("Installing required Python libraries")
status = shellcmd(
f"{activate}pip install -r {datadir}/requirements.txt"
)
if status:
logprint(" Requirements installed successfully 🎉")
else:
logprint(" Error while installing requirements, see logfile ❌")
return status
def install_wheels(virtualenv, prefix, datadir):
"""
install_wheels: install the MFP built wheel files
"""
venv_loc = f"{prefix}/share/mfp/venv"
activate = ""
if virtualenv:
activate = f". {venv_loc}/bin/activate && "
logprint("Installing MFP code")
status = shellcmd(
f"{activate}pip install {datadir}/*.whl"
)
if status:
logprint(" MFP wheels installed successfully 🎉")
else:
logprint(" Error while installing wheels, see logfile ❌")
return status
def install_static(prefix, datadir):
"""
install_static: Unpack data tarfiles into the prefix
"""
logprint("Installing data files")
status_1 = shellcmd(
f"tar zxf {datadir}/static.tar.gz -C {prefix}"
)
status_2 = shellcmd(
f"tar zxf {datadir}/mfpdsp.tar.gz -C {prefix}"
)
if status_1 and status_2:
logprint(" Data files installed successfully 🎉")
else:
logprint(" Error while installing data files, see logfile ❌")
return status_1 and status_2
def install_templated_files(virtualenv, prefix, datadir):
logprint("Expanding template files and installing")
template(
source=f"{datadir}/mfp.launcher",
target=f"{prefix}/bin/mfp",
variables=dict(
PREFIX=prefix,
VIRTUAL_PREFIX=f"{prefix}/share/mfp/venv" if virtualenv else "",
)
)
template(
source=f"{datadir}/mfp.desktop",
target=f"{prefix}/share/mfp/com.billgribble.mfp.desktop",
variables=dict(
PREFIX=prefix,
)
)
return True
def check_runtime_depends(prefix):
logprint("Checking for runtime dependencies of libmfpdsp.so ...")
libinfo = shellcmd(f"ldd {prefix}/lib/libmfpdsp.so", return_stdout=True)
all_good = True
for libline in libinfo.split("\n"):
libline = libline.strip()
if " => " not in libline:
continue
line_parts = libline.split(" => ")
if ".so" not in line_parts[1]:
all_good = False
logprint(f" Can't find library {line_parts[0]}, see logfile ❌")
return all_good
def main():
installer_dir = os.path.dirname(os.path.abspath(__file__))
parser = argparse.ArgumentParser(
description="install_mfp: installer for MFP"
)
parser.add_argument(
"--prefix", action="store", default="/opt/",
help="Base directory to install into"
)
parser.add_argument(
"--virtualenv", action="store_true",
help="Install Python dependencies into a virtualenv (recommended)"
)
parser.add_argument(
"--datadir", action="store", default=f"{installer_dir}/files/",
help="Location of built files to install"
)
args = vars(parser.parse_args())
with open(logfile, "a") as log:
log.write(f"\n==== starting installer run at {datetime.now().isoformat()}\n")
print()
logprint(f"MFP installer v{version}")
virtualenv = args.get("virtualenv", False)
prefix = os.path.abspath(args.get("prefix"))
datadir = args.get("datadir")
logprint(f"- Installing into {prefix}")
logprint(f"- Will {'' if virtualenv else 'not '}use a virtualenv for Python packages")
logprint(f"- Data for installation comes from {datadir}")
logprint(f"- Installer is logging to {os.getcwd()}/{logfile}")
logprint()
confirm = input("Does that look ok? (Y/n) ")
if confirm not in ('', 'y', 'yes', 'Y', 'YES'):
logprint("ok, canceling install.")
return
logprint("ok, beginning install.\n")
requirements_met = test_requirements(virtualenv)
if not requirements_met:
logprint("Unable to complete install. Make sure requirements are present and try again.")
return
if virtualenv:
status = install_virtualenv(prefix)
if not status:
return
status = install_depends(virtualenv, prefix, datadir)
if not status:
return
status = install_wheels(virtualenv, prefix, datadir)
if not status:
return
status = install_static(prefix, datadir)
if not status:
return
status = install_templated_files(virtualenv, prefix, datadir)
if not status:
return
logprint(f"\nInstallation complete! 🎉\n")
status = check_runtime_depends(prefix)
if not status:
logprint("Install the missing dependencies with your system package manager.")
return
logprint(f"\nEverything looks good! Launch with {prefix}/bin/mfp")
if __name__ == "__main__":
main()