在上一篇文章中,我们展示了如何使用shiv将一个Django项目捆绑成一个单一的文件进行分发和部署。以单个文件的形式运行一个大型的Python项目感觉就像魔术一样--这很好,直到你需要调试一个问题。在这一点上,你需要了解事情是如何运作的,以及在引擎盖下发生了什么。考虑到这一点,让我们来揭开shiv的神秘面纱。
Zipapps
Shiv使用了Python的一个鲜为人知的特性,即ZIP应用或 "zipapp"。Zipapp提供了一种将多个Python文件捆绑到一个ZIP文件中的方法,然后Python解释器可以执行。这里有一个微不足道的例子。
$ mkdir myapp
$ echo 'print("hello world")' > myapp/__main__.py
$ python -m zipapp myapp
$ python myapp.pyz
hello world
很酷,是吧?在你有兼容版本的Python的任何地方复制myapp.pyz ,它就可以工作了。
但现实世界的项目并不那么简单。它们有依赖性,有需要包含的非Python文件,还有复杂的构建步骤。
进入Shiv
LinkedIn的人们建立了shiv来解决传统zipapp的缺点。它包括zipapp中的依赖项,并确保它们在运行时在PYTHONPATH 。Shiv只需要用于创建。产生的zipapp是可执行的,不需要任何额外的工具。你可以认为shiv是一个围绕pip ,它在幕后使用下载和安装依赖项的包装器。这里是一个为awscli 创建zipapp的例子。
$ shiv --output-file=aws.pyz --entry-point=awscli.clidriver.main awscli
这是从PyPI下载awscli 和它的所有依赖项,并创建一个zipapp (aws.pyz),执行时将运行一个由--entry-point 指定的Python函数。该文件可以被复制到任何具有兼容的Python版本的系统中,并像二进制可执行文件一样执行(./aws.pyz 或python aws.pyz )。
引擎盖下
好消息是,shiv 是出奇的简单。当我们建立一个新的归档文件时,shiv 做以下工作。
- 使用
pip,将所有的依赖项下载到一个临时目录中 - 编写一些在引导过程中使用的元数据到
environment.json - 创建一个引导脚本,在执行zipapp时使用。
- 将这些文件捆绑到压缩文件中
- 在压缩包的顶部插入一个shebang,这样它就可以作为一个可执行文件使用。
Bootstrap脚本的职责是。
- 将依赖文件解压到一个唯一的路径。如果它在之前的运行中已经存在,则跳过这一步。
- 将该路径插入到
PYTHONPATH。 - 执行我们在创建过程中定义的
--entry-point。
唯一的路径是通过结合zipapp的文件名和在构建时产生的UUID(存储在environment.json )来确定的。它默认存储在~/.shiv ,但可以通过设置SHIV_ROOT 环境变量来改变。执行后,我们可以看到以下情况。
cd ~/.shiv && find . -maxdepth 2
.
./aws_3e32a16c-6652-44cc-a561-3784814d736e
./aws_3e32a16c-6652-44cc-a561-3784814d736e/site-packages
site-packages 目录看起来就像你在标准 Python 安装或 virtualenv 中找到的site-packages 目录。正如我们所看到的,这在构建时被分配了一个 UUID 为3e32a16c-6652-44cc-a561-3784814d736e ,这可以通过检查包含的元数据来确认。
$ unzip -p aws.pyz environment.json | jq .build_id
"3e32a16c-6652-44cc-a561-3784814d736e"
这个目录被添加到PYTHONPATH ,像这样。
$ echo "import sys, pprint; pprint.pprint(sys.path)" | \
SHIV_INTERPRETER=1 ./aws.pyz
Python 3.7.3 (default, Jun 11 2019, 01:05:09)
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> ['./aws.pyz',
'/usr/local/lib/python37.zip',
'/usr/local/lib/python3.7',
'/usr/local/lib/python3.7/lib-dynload',
'/home/user/.shiv/aws_3e32a16c-6652-44cc-a561-3784814d736e/site-packages',
'/usr/local/lib/python3.7/site-packages']
注意:环境变量SHIV_INTERPRETER 允许我们使用zipapp的环境下拉到一个Python shell。
第五项,/home/user/.shiv/... 是通过bootstrap脚本注入的。其他的是Python的默认值。
如果你喜欢在一个没有全局站点包的隔离环境中运行,可以使用Python的-S 标志。
$ echo "import sys, pprint; pprint.pprint(sys.path)" | \
SHIV_INTERPRETER=1 python -S aws.pyz
Python 3.7.3 (default, Jun 11 2019, 01:05:09)
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> ['aws.pyz',
'/usr/local/lib/python37.zip',
'/usr/local/lib/python3.7',
'/usr/local/lib/python3.7/lib-dynload',
'/home/user/.shiv/aws_3e32a16c-6652-44cc-a561-3784814d736e/site-packages']
一旦解压,你可以检查磁盘上的文件,如果你想做一些棘手的调试,甚至可以编辑它们。除非设置了环境变量SHIV_FORCE_EXTRACT ,否则Shiv不会覆盖先前解压的zipapp的文件。
就这样了
事实证明,shiv是非常直接的。我最喜欢它的一点是它的简单性。当我的代码出现问题时,很容易就能找到原因并排除shiv。代码有很好的注释,很容易理解(这里是bootstrap脚本)。它也很稳定,而且看起来功能完整,这意味着不必对着一个移动的目标工作。