Skip to content

Commit bc198b8

Browse files
committed
add zipapp support
1 parent 202f0bb commit bc198b8

File tree

4 files changed

+213
-0
lines changed

4 files changed

+213
-0
lines changed

testing/zipapp/Dockerfile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
FROM ubuntu:bionic
2+
RUN : \
3+
&& apt-get update \
4+
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
5+
python3 \
6+
python3-distutils \
7+
python3-venv \
8+
&& apt-get clean \
9+
&& rm -rf /var/lib/apt/lists/*
10+
11+
ENV LANG=C.UTF-8 PATH=/venv/bin:$PATH
12+
RUN : \
13+
&& python3.6 -mvenv /venv \
14+
&& pip install --no-cache-dir pip setuptools wheel no-manylinux --upgrade

testing/zipapp/entry

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#!/usr/bin/env python3
2+
import os.path
3+
import shutil
4+
import stat
5+
import sys
6+
import tempfile
7+
import zipfile
8+
9+
from pre_commit.file_lock import lock
10+
11+
CACHE_DIR = os.path.expanduser('~/.cache/pre-commit-zipapp')
12+
13+
14+
def _make_executable(filename: str) -> None:
15+
os.chmod(filename, os.stat(filename).st_mode | stat.S_IXUSR)
16+
17+
18+
def _ensure_cache(zipf: zipfile.ZipFile, cache_key: str) -> str:
19+
os.makedirs(CACHE_DIR, exist_ok=True)
20+
21+
cache_dest = os.path.join(CACHE_DIR, cache_key)
22+
lock_filename = os.path.join(CACHE_DIR, f'{cache_key}.lock')
23+
24+
if os.path.exists(cache_dest):
25+
return cache_dest
26+
27+
with lock(lock_filename, blocked_cb=lambda: None):
28+
# another process may have completed this work
29+
if os.path.exists(cache_dest):
30+
return cache_dest
31+
32+
tmpdir = tempfile.mkdtemp(prefix=os.path.join(CACHE_DIR, ''))
33+
try:
34+
zipf.extractall(tmpdir)
35+
# zip doesn't maintain permissions
36+
_make_executable(os.path.join(tmpdir, 'fakepython'))
37+
os.rename(tmpdir, cache_dest)
38+
except BaseException:
39+
shutil.rmtree(tmpdir)
40+
raise
41+
42+
return cache_dest
43+
44+
45+
def main() -> int:
46+
with zipfile.ZipFile(os.path.dirname(__file__)) as zipf:
47+
with zipf.open('CACHE_KEY') as f:
48+
cache_key = f.read().decode().strip()
49+
50+
cache_dest = _ensure_cache(zipf, cache_key)
51+
52+
fakepython = os.path.join(cache_dest, 'fakepython')
53+
cmd = (sys.executable, fakepython, '-mpre_commit', *sys.argv[1:])
54+
if sys.platform == 'win32': # https://bugs.python.org/issue19124
55+
import subprocess
56+
57+
if sys.version_info < (3, 7): # https://bugs.python.org/issue25942
58+
return subprocess.Popen(cmd).wait()
59+
else:
60+
return subprocess.call(cmd)
61+
else:
62+
os.execvp(cmd[0], cmd)
63+
64+
65+
if __name__ == '__main__':
66+
exit(main())

testing/zipapp/fakepython

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/usr/bin/env python3
2+
"""A shim executable to put dependencies on sys.path"""
3+
import argparse
4+
import os.path
5+
import runpy
6+
import sys
7+
8+
HERE = os.path.dirname(os.path.realpath(__file__))
9+
WHEELDIR = os.path.join(HERE, 'wheels')
10+
SITE_DIRS = frozenset(('dist-packages', 'site-packages'))
11+
12+
13+
def main() -> int:
14+
parser = argparse.ArgumentParser(add_help=False)
15+
parser.add_argument('-m')
16+
args, rest = parser.parse_known_args()
17+
18+
if args.m:
19+
# try and remove site-packages from sys.path so our packages win
20+
sys.path[:] = [
21+
p for p in sys.path
22+
if os.path.split(p)[1] not in SITE_DIRS
23+
]
24+
for wheel in sorted(os.listdir(WHEELDIR)):
25+
sys.path.append(os.path.join(WHEELDIR, wheel))
26+
if args.m == 'pre_commit' or args.m.startswith('pre_commit.'):
27+
sys.executable = os.path.abspath(__file__)
28+
sys.argv[1:] = rest
29+
runpy.run_module(args.m, run_name='__main__', alter_sys=True)
30+
return 0
31+
else:
32+
cmd = (sys.executable, *sys.argv[1:])
33+
if sys.platform == 'win32': # https://bugs.python.org/issue19124
34+
import subprocess
35+
36+
if sys.version_info < (3, 7): # https://bugs.python.org/issue25942
37+
return subprocess.Popen(cmd).wait()
38+
else:
39+
return subprocess.call(cmd)
40+
else:
41+
os.execvp(cmd[0], cmd)
42+
43+
44+
if __name__ == '__main__':
45+
exit(main())

testing/zipapp/make

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#!/usr/bin/env python3
2+
import argparse
3+
import base64
4+
import hashlib
5+
import os.path
6+
import shutil
7+
import subprocess
8+
import tempfile
9+
import zipapp
10+
import zipfile
11+
12+
HERE = os.path.dirname(os.path.realpath(__file__))
13+
IMG = 'make-pre-commit-zipapp'
14+
15+
16+
def _msg(s: str) -> None:
17+
print(f'\033[7m{s}\033[m')
18+
19+
20+
def _exit_if_retv(*cmd: str) -> None:
21+
if subprocess.call(cmd):
22+
raise SystemExit(1)
23+
24+
25+
def _check_no_shared_objects(wheeldir: str) -> None:
26+
for zip_filename in os.listdir(wheeldir):
27+
with zipfile.ZipFile(os.path.join(wheeldir, zip_filename)) as zipf:
28+
for filename in zipf.namelist():
29+
if filename.endswith('.so') or '.so.' in filename:
30+
raise AssertionError(zip_filename, filename)
31+
32+
33+
def _write_cache_key(version: str, wheeldir: str, dest: str) -> None:
34+
cache_hash = hashlib.sha256(f'{version}\n'.encode())
35+
for filename in sorted(os.listdir(wheeldir)):
36+
cache_hash.update(f'{filename}\n'.encode())
37+
with open(os.path.join(HERE, 'fakepython'), 'rb') as f:
38+
cache_hash.update(f.read())
39+
with open(os.path.join(dest, 'CACHE_KEY'), 'wb') as f:
40+
f.write(base64.urlsafe_b64encode(cache_hash.digest()).rstrip(b'='))
41+
42+
43+
def main() -> int:
44+
parser = argparse.ArgumentParser()
45+
parser.add_argument('version')
46+
args = parser.parse_args()
47+
48+
with tempfile.TemporaryDirectory() as tmpdir:
49+
wheeldir = os.path.join(tmpdir, 'wheels')
50+
os.mkdir(wheeldir)
51+
52+
_msg('building podman image...')
53+
_exit_if_retv('podman', 'build', '-q', '-t', IMG, HERE)
54+
55+
_msg('populating wheels...')
56+
_exit_if_retv(
57+
'podman', 'run', '--rm', '--volume', f'{wheeldir}:/wheels:rw', IMG,
58+
'pip', 'wheel', f'pre_commit=={args.version}',
59+
'--wheel-dir', '/wheels',
60+
)
61+
62+
_msg('validating wheels...')
63+
_check_no_shared_objects(wheeldir)
64+
65+
_msg('adding fakepython / __main__.py...')
66+
shutil.copy(os.path.join(HERE, 'fakepython'), tmpdir)
67+
mainfile = os.path.join(tmpdir, '__main__.py')
68+
shutil.copy(os.path.join(HERE, 'entry'), mainfile)
69+
70+
_msg('copying file_lock.py...')
71+
file_lock_py = os.path.join(HERE, '../../pre_commit/file_lock.py')
72+
file_lock_py_dest = os.path.join(tmpdir, 'pre_commit/file_lock.py')
73+
os.makedirs(os.path.dirname(file_lock_py_dest))
74+
shutil.copy(file_lock_py, file_lock_py_dest)
75+
76+
_msg('writing CACHE_KEY...')
77+
_write_cache_key(args.version, wheeldir, tmpdir)
78+
79+
filename = f'pre-commit-{args.version}.pyz'
80+
_msg(f'writing {filename}...')
81+
shebang = '/usr/bin/env python3'
82+
zipapp.create_archive(tmpdir, filename, interpreter=shebang)
83+
84+
return 0
85+
86+
87+
if __name__ == '__main__':
88+
exit(main())

0 commit comments

Comments
 (0)