Created
October 18, 2018 21:29
-
-
Save AstraLuma/54ff9d6245a0efc80b0a8fd2f8cba500 to your computer and use it in GitHub Desktop.
Simple zipapp maker
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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