Skip to content

Instantly share code, notes, and snippets.

@AstraLuma
Created October 18, 2018 21:29
Show Gist options
  • Save AstraLuma/54ff9d6245a0efc80b0a8fd2f8cba500 to your computer and use it in GitHub Desktop.
Save AstraLuma/54ff9d6245a0efc80b0a8fd2f8cba500 to your computer and use it in GitHub Desktop.
Simple zipapp maker
#!/usr/bin/env python3
"""
A little script to make minimal pyz packages for zipsafe programs.
NOTE: zip safe means:
* No package expects to be able to use open() to access data it shipped with
* No compiled modules
"""
import argparse
import os
import shutil
import subprocess
import sys
import tempfile
import zipapp
def do_pip(workingdir, pipargs, **_):
print(f"Installing packages {' '.join(pipargs)}")
proc = subprocess.run([
sys.executable, '-m', 'pip', 'install',
'--isolated', '--target', workingdir,
'--system', # Because debian
*pipargs,
])
if proc.returncode != 0:
sys.exit(f"pip returned code {proc.returncode}")
def do_files(workingdir, extrafiles, **_):
for fn in extrafiles:
print(f"Copying {fn}")
shutil.copy(fn, os.path.join(workingdir, fn))
def do_package(workingdir, destfile=None, mainfile=None, **_):
print(f"Building {destfile}")
if mainfile is None:
main = None
elif os.path.isfile(mainfile):
shutil.copy(mainfile, os.path.join(workingdir, '__main__.py'))
main = None
else:
main = mainfile
kwargs = {}
if sys.version_info >= (3, 7):
kwargs = dict(
filter=None, # TODO, to filter `bin` and `*.dist-info`
compressed=True,
)
zipapp.create_archive(
source=workingdir,
target=destfile,
interpreter=f"{sys.executable} -IO",
main=main,
**kwargs,
)
STEPS = [
do_pip,
do_files,
do_package,
]
def parse_args(argv=None):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('packages', nargs='*', metavar="<requirement specifier>",
help='packages to include')
parser.add_argument('-r', '--requirement', metavar="<file>",
action='append', dest='reqfiles', default=(),
help='Install from the given requirements file. This option can be used multiple times.')
parser.add_argument('-m', '--main', metavar="<file|module:function>",
required=True,
help='Main script or callable')
parser.add_argument('-o', '--output', metavar="<file>", default="app.pyz",
help='File to output to')
return parser.parse_args(argv)
def assemble_pipargs(opts):
pips = []
files = []
for reqfile in opts.reqfiles:
pips += ['-r', reqfile]
for pkg in opts.packages:
if os.path.isfile(pkg):
files.append(pkg)
else:
pips.append(pkg)
return pips, files
def main(argv=None):
opts = parse_args(argv)
pipargs, extrafiles = assemble_pipargs(opts)
with tempfile.TemporaryDirectory() as td:
kwargs = {
'workingdir': td,
'pipargs': pipargs,
'extrafiles': extrafiles,
'destfile': opts.output,
'mainfile': opts.main,
}
for func in STEPS:
func(**kwargs)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment