Bypass tilde expansion if GPODDER_HOME is not declared.
[gpodder.git] / tools / mac-osx / launcher.py
blobfe789b5a3c3aab54d16e5291c8e349cc4d7c3c6b
1 import os
2 import os.path
3 import platform
4 import re
5 import runpy
6 import subprocess
7 import sys
8 import time
9 import traceback
10 from os.path import join
11 from subprocess import PIPE, CalledProcessError, Popen
14 class MakeCertPem:
15 """ create openssl cert bundle from system certificates """
17 def __init__(self, openssl):
18 self.openssl = openssl
20 def is_valid_cert(self, cert):
21 """ check if cert is valid according to openssl"""
22 cmd = [self.openssl, "x509", "-inform", "pem", "-checkend", "0", "-noout"]
23 # print("D: is_valid_cert %r" % cmd)
24 proc = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
25 stdout, stderr = proc.communicate(cert)
26 # print("out: %s; err:%s; ret:%i" % (stdout, stderr, proc.returncode))
27 return proc.returncode == 0
29 def get_certs(self):
30 """ extract System's certificates then filter them by validity
31 and return a list of text of valid certs
32 """
33 cmd = ["security", "find-certificate", "-a", "-p",
34 "/System/Library/Keychains/SystemRootCertificates.keychain"]
35 cert_re = re.compile(b"^-----BEGIN CERTIFICATE-----$"
36 + b".+?"
37 + b"^-----END CERTIFICATE-----$", re.M | re.S)
38 try:
39 certs_str = subprocess.check_output(cmd)
40 all_certs = cert_re.findall(certs_str)
41 print("I: extracted %i certificates" % len(all_certs))
42 valid_certs = [cert for cert in all_certs
43 if self.is_valid_cert(cert)]
44 print("I: of which %i are valid certificates" % len(valid_certs))
45 return valid_certs
46 except OSError:
47 print("E: extracting certificates using %r" % cmd)
48 traceback.print_exc()
49 except CalledProcessError as err:
50 print(("E: extracting certificates using %r, exit=%i" %
51 (cmd, err.returncode)))
53 @staticmethod
54 def write_certs(certs, dest):
55 """ write concatenated certs to dest """
56 with open(dest, "wb") as output:
57 output.write(b"\n".join(certs))
59 def regen(self, dest):
60 """ main program """
61 print("I: make_cert_pem %s %s" % (self.openssl, dest))
62 certs = self.get_certs()
63 if certs is None:
64 print("E: no certificate extracted")
65 return -1
66 else:
67 self.write_certs(certs, dest)
68 print("I: updated %s with %i certificates" % (dest, len(certs)))
69 return 0
72 # print("launcher.py sys.argv=", sys.argv)
73 bundlepath = sys.argv.pop(0)
74 app = os.path.basename(sys.argv[0])
76 bundle_contents = join(bundlepath, 'Contents')
77 bundle_res = join(bundle_contents, 'Resources')
79 bundle_lib = join(bundle_res, 'lib')
80 bundle_bin = join(bundle_res, 'bin')
81 bundle_data = join(bundle_res, 'share')
82 bundle_etc = join(bundle_res, 'etc')
84 os.environ['CHARSETALIASDIR'] = bundle_lib
85 os.environ['DYLD_LIBRARY_PATH'] = bundle_lib
86 os.environ['GTK_DATA_PREFIX'] = bundle_res
87 os.environ['GTK_EXE_PREFIX'] = bundle_res
88 os.environ['GTK_PATH'] = bundle_res
89 os.environ['LD_LIBRARY_PATH'] = bundle_lib
90 os.environ['XDG_CONFIG_DIRS'] = bundle_etc
91 os.environ['XDG_DATA_DIRS'] = bundle_data
93 os.environ['PANGO_LIBDIR'] = bundle_lib
94 os.environ['PANGO_RC_FILE'] = join(bundle_etc, 'pango', 'pangorc')
95 os.environ['PANGO_SYSCONFDIR'] = bundle_etc
96 os.environ['GDK_PIXBUF_MODULE_FILE'] = join(bundle_lib, 'gdk-pixbuf-2.0',
97 '2.10.0', 'loaders.cache')
98 if int(platform.release().split('.')[0]) > 10:
99 os.environ['GTK_IM_MODULE_FILE'] = join(bundle_etc, 'gtk-3.0',
100 'gtk.immodules')
102 os.environ['GI_TYPELIB_PATH'] = join(bundle_lib, 'girepository-1.0')
104 # for forked python
105 os.environ['PYTHONHOME'] = bundle_res
106 # Set $PYTHON to point inside the bundle
107 PYVER = 'python3.9'
108 sys.path.append(bundle_res)
109 print('System Path:\n', '\n'.join(sys.path))
111 # see https://gpodder.github.io/docs/user-manual.html#gpodder-home-folder-and-download-location
112 # To override gPodder home and/or download directory:
113 # 1. uncomment (remove the pound sign and space) at the beginning of the relevant line
114 # 2. replace ~/gPodderData or ~/gPodderDownloads with the path you want for your gPodder home
115 # (you can move the original folder in the Finder first,
116 # then drag and drop to the launcher.py in TextEdit to ensure the correct path is set)
117 # uncomment the following line to override gPodder home
118 # os.environ['GPODDER_HOME'] = expanduser('~/gPodderData')
119 # uncomment the following line to override gPodder download directory
120 # os.environ['GPODDER_DOWNLOAD_DIR'] = expanduser('~/gPodderDownloads')
122 for k, v in os.environ.items():
123 print("%s=%s" % (k, v))
126 def gpodder_home():
127 # don't inadvertently create the new gPodder home,
128 # it would be preferred to the old one
129 default_path = join(os.environ['HOME'], 'Library', 'Application Support', 'gPodder')
130 cands = [
131 os.path.expanduser(os.environ.get('GPODDER_HOME')) if 'GPODDER_HOME' in os.environ else None,
132 default_path,
133 join(os.environ['HOME'], 'gPodder'),
135 for cand in cands:
136 if cand and os.path.exists(cand):
137 return cand
138 return default_path
141 gphome = gpodder_home()
142 os.makedirs(join(gphome, 'openssl'), exist_ok=True)
143 # generate cert.extracted.pem
144 cert_gen = join(gphome, 'openssl', 'cert.extracted.pem')
145 cert_pem = join(gphome, 'openssl', 'cert.pem')
146 regen = False
147 if not os.path.isfile(cert_gen):
148 regen = True
149 else:
150 last_modified = os.stat(cert_gen).st_mtime
151 regen = last_modified < time.time() - 3600 * 7
153 if regen:
154 print('(Re)generating', cert_pem)
155 openssl = join(bundle_bin, 'openssl')
156 MakeCertPem(openssl).regen(cert_gen)
157 else:
158 print('No regenerating', cert_gen, 'it\'s fresh enough')
160 # and link to it by default. Users may want to point cert.pem to MacPorts
161 # /opt/local/etc/openssl/cert.pem, for instance.
162 if not os.path.exists(cert_pem):
163 os.symlink(os.path.basename(cert_gen), cert_pem)
164 # Set path to CA files
165 os.environ['SSL_CERT_FILE'] = cert_pem
167 if app == 'run-python':
168 python_exe = join(bundle_contents, 'MacOS', 'python3')
169 # executable is repeated as argv[0].
170 # Old sys.argv[0] points to Contents/MacOS so must be removed
171 args = [python_exe] + sys.argv[1:]
172 # print("running", args)
173 os.execv(python_exe, args)
174 elif app == 'run-pip':
175 python_exe = join(bundle_contents, 'MacOS', 'python3')
176 pip = join(bundle_contents, 'Resources', 'bin', 'pip3')
177 # executable is repeated as argv[0].
178 # Old sys.argv[0] points to Contents/MacOS so must be removed
179 args = [python_exe, pip] + sys.argv[1:]
180 # print("running", args)
181 os.execv(python_exe, args)
182 else:
183 import runpy
184 runpy.run_path(join(bundle_bin, app), run_name='__main__')