Source code for shiv.builder

"""
This module is a slightly modified implementation of Python's "zipapp" module.

We've copied a lot of zipapp's code here in order to backport support for compression.
https://docs.python.org/3.7/library/zipapp.html#cmdoption-zipapp-c

"""
import contextlib
import zipfile
import stat
import sys
import zipapp

from pathlib import Path
from typing import Any, IO, Generator, Union

# Typical maximum length for a shebang line
BINPRM_BUF_SIZE = 128

# zipapp __main__.py template
MAIN_TEMPLATE = """\
# -*- coding: utf-8 -*-
import {module}
{module}.{fn}()
"""


[docs]def write_file_prefix(f: IO[Any], interpreter_path: Path) -> None: """Write a shebang line. .. note:: Shiv explicitly uses `-sE` as start up flags to prevent contamination of sys.path. :param f: An open file handle. :param interpreter_path: A path to a python interpreter. """ # fall back to /usr/bin/env if the interp path is too long if len(interpreter_path.as_posix()) > BINPRM_BUF_SIZE: shebang = f"/usr/bin/env {interpreter_path.name}" else: shebang = interpreter_path.as_posix() f.write(b"#!" + shebang.encode(sys.getfilesystemencoding()) + b" -sE\n")
@contextlib.contextmanager def maybe_open(archive: Union[str, Path], mode: str) -> Generator[IO[Any], None, None]: if isinstance(archive, (str, Path)): with Path(archive).open(mode=mode) as f: yield f else: yield archive
[docs]def create_archive( source: Path, target: Path, interpreter: Path, main: str, compressed: bool = True ) -> None: """Create an application archive from SOURCE. A slightly modified version of stdlib's `zipapp.create_archive <https://docs.python.org/3/library/zipapp.html#zipapp.create_archive>`_ """ # Check that main has the right format. mod, sep, fn = main.partition(":") mod_ok = all(part.isidentifier() for part in mod.split(".")) fn_ok = all(part.isidentifier() for part in fn.split(".")) if not (sep == ":" and mod_ok and fn_ok): raise zipapp.ZipAppError("Invalid entry point: " + main) main_py = MAIN_TEMPLATE.format(module=mod, fn=fn) with maybe_open(target, "wb") as fd: # write shebang write_file_prefix(fd, interpreter) # determine compression compression = zipfile.ZIP_DEFLATED if compressed else zipfile.ZIP_STORED # create zipapp with zipfile.ZipFile(fd, "w", compression=compression) as z: for child in source.rglob("*"): # skip compiled files if child.suffix == '.pyc': continue arcname = child.relative_to(source) z.write(child.as_posix(), arcname.as_posix()) # write main z.writestr("__main__.py", main_py.encode("utf-8")) # make executable if interpreter and not hasattr(target, "write"): target.chmod(target.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)