diff --git a/pyproject.toml b/pyproject.toml index 42bc6ee..de37ee9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,6 @@ [build-system] requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" [black] line_length = 120 diff --git a/setup.cfg b/setup.cfg index 22f8dda..0f63744 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,7 +24,7 @@ sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER [metadata] name = shiv -version = 0.5.2 +version = 1.0.0 description = A command line utility for building fully self contained Python zipapps. long_description = file: README.md long_description_content_type = text/markdown diff --git a/src/shiv/bootstrap/__init__.py b/src/shiv/bootstrap/__init__.py index c36697b..8ba843e 100644 --- a/src/shiv/bootstrap/__init__.py +++ b/src/shiv/bootstrap/__init__.py @@ -1,12 +1,11 @@ import compileall +import hashlib import os import runpy import shutil import site - -import hashlib -import sys import subprocess +import sys import zipfile from contextlib import contextmanager, suppress @@ -196,7 +195,11 @@ def bootstrap(): # pragma: no cover # determine if first run or forcing extract if not site_packages.exists() or env.force_extract: extract_site_packages( - archive, site_packages.parent, env.compile_pyc, env.compile_workers, env.force_extract, + archive, + site_packages.parent, + env.compile_pyc, + env.compile_workers, + env.force_extract, ) # get sys.path's length @@ -228,7 +231,7 @@ def bootstrap(): # pragma: no cover if preamble_bin.suffix == ".py": runpy.run_path( - preamble_bin.as_posix(), + str(preamble_bin), init_globals={"archive": sys.argv[0], "env": env, "site_packages": site_packages}, run_name="__main__", ) diff --git a/src/shiv/bootstrap/interpreter.py b/src/shiv/bootstrap/interpreter.py index 177be5e..3b59259 100644 --- a/src/shiv/bootstrap/interpreter.py +++ b/src/shiv/bootstrap/interpreter.py @@ -4,8 +4,8 @@ It is used to enter an interactive interpreter session from an executable created with ``shiv``. """ import code -import sys import runpy +import sys from pathlib import Path diff --git a/src/shiv/builder.py b/src/shiv/builder.py index 2a2195f..4523a07 100644 --- a/src/shiv/builder.py +++ b/src/shiv/builder.py @@ -126,7 +126,8 @@ def create_archive( write_to_zipapp(archive, arcname, data, zipinfo_datetime, compression, stat=path.stat()) if env.build_id is None: - # now that we have a hash of all the source files, use it as our build id + # Now that we have a hash of all the source files, use it as our build id if the user did not + # specify a custom one. env.build_id = contents_hash.hexdigest() # now let's add the shiv bootstrap code. diff --git a/src/shiv/cli.py b/src/shiv/cli.py index 1053706..ba943ee 100644 --- a/src/shiv/cli.py +++ b/src/shiv/cli.py @@ -1,7 +1,6 @@ +import hashlib import os import shutil - -import hashlib import sys import time @@ -17,13 +16,14 @@ from .bootstrap.environment import Environment from .constants import ( BUILD_AT_TIMESTAMP_FORMAT, + DEFAULT_SHEBANG, DISALLOWED_ARGS, DISALLOWED_PIP_ARGS, NO_ENTRY_POINT, NO_OUTFILE, NO_PIP_ARGS_OR_SITE_PACKAGES, SOURCE_DATE_EPOCH_DEFAULT, - SOURCE_DATE_EPOCH_ENV, DEFAULT_SHEBANG, + SOURCE_DATE_EPOCH_ENV, ) __version__ = "1.0.0" @@ -95,20 +95,39 @@ def copytree(src: Path, dst: Path) -> None: ) @click.option("--console-script", "-c", default=None, help="The console_script to invoke.") @click.option("--output-file", "-o", help="The path to the output file for shiv to create.") -@click.option("--python", "-p", help="The python interpreter to set as the shebang, a.k.a. whatever you want after '#!' (default is '/usr/bin/env python3')") +@click.option( + "--python", + "-p", + help=( + "The python interpreter to set as the shebang, a.k.a. whatever you want after '#!' " + "(default is '/usr/bin/env python3')" + ), +) @click.option( "--site-packages", help="The path to an existing site-packages directory to copy into the zipapp.", type=click.Path(exists=True), multiple=True, ) -@click.option("--build-id", help="Use a custom build id instead of an autogenerated one.", default=None) +@click.option( + "--build-id", + default=None, + help=( + "Use a custom build id instead of the default (a SHA256 hash of the contents of the build). " + "Warning: must be unique per build!" + ), +) @click.option("--compressed/--uncompressed", default=True, help="Whether or not to compress your zip.") @click.option( - "--compile-pyc", is_flag=True, help="Whether or not to compile pyc files during initial bootstrap.", + "--compile-pyc", + is_flag=True, + help="Whether or not to compile pyc files during initial bootstrap.", ) @click.option( - "--extend-pythonpath", "-E", is_flag=True, help="Add the contents of the zipapp to PYTHONPATH (for subprocesses).", + "--extend-pythonpath", + "-E", + is_flag=True, + help="Add the contents of the zipapp to PYTHONPATH (for subprocesses).", ) @click.option( "--reproducible", @@ -171,6 +190,13 @@ def main( if supplied_arg in disallowed: sys.exit(DISALLOWED_PIP_ARGS.format(arg=supplied_arg, reason=DISALLOWED_ARGS[disallowed])) + if build_id is not None: + click.secho( + "Warning! You have overridden the default build-id behavior, " + "executables created by shiv must have unique build IDs or unexpected behavior could occur.", + fg="yellow", + ) + sources: List[Path] = [] with TemporaryDirectory() as tmp_site_packages: diff --git a/src/shiv/constants.py b/src/shiv/constants.py index 41ed393..6a7c7ca 100644 --- a/src/shiv/constants.py +++ b/src/shiv/constants.py @@ -27,5 +27,6 @@ BUILD_AT_TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S" # The default shebang to use at the top of the pyz. -# We use "/usr/bin/env" here because it's cross-platform compatible (https://docs.python.org/3/using/windows.html#shebang-lines) +# We use "/usr/bin/env" here because it's cross-platform compatible +# https://docs.python.org/3/using/windows.html#shebang-lines DEFAULT_SHEBANG = "/usr/bin/env python3" diff --git a/test/test_cli.py b/test/test_cli.py index b4ef064..df3fd11 100644 --- a/test/test_cli.py +++ b/test/test_cli.py @@ -11,7 +11,7 @@ import pytest from click.testing import CliRunner -from shiv.cli import get_interpreter_path, console_script_exists, find_entry_point, main +from shiv.cli import console_script_exists, find_entry_point, main from shiv.constants import DISALLOWED_ARGS, DISALLOWED_PIP_ARGS, NO_OUTFILE, NO_PIP_ARGS_OR_SITE_PACKAGES from shiv.info import main as info_main from shiv.pip import install @@ -86,21 +86,6 @@ def test_no_outfile(self, runner): assert result.exit_code == 1 assert NO_OUTFILE in result.output - def test_find_interpreter(self): - - interpreter = get_interpreter_path() - - assert Path(interpreter).exists() - assert Path(interpreter).is_file() - - def test_find_interpreter_false(self): - - with mocked_sys_prefix(): - interpreter = get_interpreter_path() - - # should fall back on the current sys.executable - assert interpreter == sys.executable - @pytest.mark.parametrize("arg", [arg for tup in DISALLOWED_ARGS.keys() for arg in tup]) def test_disallowed_args(self, runner, arg): """This method tests that all the potential disallowed arguments match their error messages.""" diff --git a/tox.ini b/tox.ini index bbdb0ab..3c05a74 100644 --- a/tox.ini +++ b/tox.ini @@ -1,4 +1,5 @@ [tox] +isolated_build = True envlist = py36, py37, py38, py39 [gh-actions]