跳转到主要内容

支持完整语言和CPython兼容性的Python编译器

项目描述

当您开始使用 Nuitka 时,建议首先阅读此文档。在本页中,您将了解有关 Nuitka 的基础知识,例如许可证类型、用例、要求以及致谢。

Nuitka 是 Python 编译器。它用 Python 编写。它是对 Python 解释器的无缝替代或扩展,当它用相应的 Python 版本运行时,会编译 Python 2(2.6、2.7)和 Python 3(3.4 - 3.11)的所有构造。

然后,它以一种极其兼容的方式同时执行未编译的代码和编译后的代码。

您可以自由使用所有 Python 库模块和所有扩展模块。

Nuitka 将 Python 模块转换为 C 级别的程序,然后使用 libpython 以及它自己的静态 C 文件来执行,就像 CPython 一样。

所有优化都是为了避免不必要的开销。没有优化是针对移除兼容性的,尽管偶尔会进行一些小的改进,比如提供更完整的错误消息,但有一个完整的兼容模式可以禁用这一点。

要求

为确保 Nuitka 运行顺畅,请确保遵循系统要求,包括以下组件:

深度:

1

本地:

C 编译器

您需要一个支持 C11 或 C++03 的 C 编译器。

目前,这意味着您需要使用以下编译器之一

  • Windows 上的 MinGW64 C11 编译器必须基于 gcc 11.2 或更高版本。如果没有找到可用的 C 编译器,它将 自动 下载,这是推荐的安装方式,因为 Nuitka 还会为您升级它。

  • Windows 上的 Visual Studio 2022 或更高版本 [2]。推荐使用英文语言包以获得最佳效果(Nuitka 会过滤掉垃圾输出,但仅限于英文)。如果已安装,它将默认使用。

  • 在其他平台上,至少需要版本 5.1 的 gcc 编译器,以及低于该版本的至少版本 4.4 的 g++ 编译器作为替代。

  • macOS X 和大多数 FreeBSD 架构上的 clang 编译器。

  • 在 Windows 上,如果 Visual Studio 安装程序提供了,可以使用 Windows 上的 clang-cl 编译器。

Python

Python 2(2.6、2.7)和 Python 3(3.4 — 3.11)受支持。如果任何时候有一个不在列表中的稳定 Python 版本,请放心,它正在被开发中,并将被添加。

操作系统

支持的操作系统:Linux、FreeBSD、NetBSD、macOS和Windows(32位/64位/ARM)。

其他操作系统也应该可以正常工作。可移植性预计总体良好,但例如Nuitka的内部Scons使用可能需要适应或需要传递标志。请确保匹配Python和C编译器的架构,否则你将得到难以理解的错误信息。

架构

支持的架构包括x86、x86_64(amd64)和arm,可能还有更多。

预期其他架构也将正常工作,因为Nuitka通常不使用任何特定的硬件。这些只是经过测试并已知是好的架构。欢迎提供反馈。一般来说,Debian支持的架构也可以认为是好的和经过测试的。

用法

命令行

执行Nuitka的推荐方法是 <the_right_python> -m nuitka,以确保使用的是正确的Python解释器,这样更容易与Nuitka匹配。

执行Nuitka的第二好方法是直接从源代码签出或存档执行,不更改环境变量,最重要的是,你不需要为Nuitka更改 PYTHONPATH。你只需直接执行 nuitkanuitka-run 脚本,而无需更改环境。你可能想要将 bin 目录添加到你的 PATH 中,以便于使用,但这步是可选的。

此外,如果你想要使用正确的解释器执行,那么请确保执行 <the_right_python> bin/nuitka 并保持简单。

Nuitka 提供了一个 --help 选项,用于输出它能做什么

nuitka --help

nuitka-run 命令与 nuitka 相同,但默认设置不同。它尝试编译 直接执行 Python 脚本

nuitka-run --help

这个不同的选项是 --run,它将第一个非选项之后的参数传递给创建的二进制文件,因此它在某种程度上更类似于纯 python 将执行的操作。

安装

对于大多数系统,Nuitka 的 下载页面 上会有相应的软件包。但您也可以像上面描述的那样从源代码安装它,但像任何其他 Python 程序一样,它也可以通过正常的 python setup.py install 程序安装。

关于与 GitHub 工作流的集成,有一个名为 Nuitka-Action 的工具,您应该使用它,这将使其集成变得非常简单。不过,您应该从本地编译开始,这对于使用 Nuitka 进行跨平台编译来说将是最容易的。

许可证

Nuitka 采用 Apache License 2.0 许可;除非您遵守许可,否则不得使用它。

您可以在 https://apache.ac.cn/licenses/LICENSE-2.0 获取许可证的副本。

除非适用法律要求或书面同意,否则根据许可证分发的软件是按“原样”分发的,不提供任何形式的明示或暗示保证。有关许可证的具体语言规定权限和限制,请参阅许可证。

Windows下的教程设置和构建

如果您什么都没有安装,以下是一些基本步骤,当然如果您已经安装了任何部分,只需跳过即可。

设置

安装 Python

  • https://pythonlang.cn/downloads/windows 下载并安装 Python

  • 选择 Windows x86-64 web-based installer(64 位 Python,推荐)或 x86 executable(32 位 Python)安装程序。

  • 使用命令 python --version 验证其是否正常工作。

安装 Nuitka

  • python -m pip install nuitka

  • 使用命令 python -m nuitka --version 验证

编写一些代码并测试

为 Python 代码创建一个文件夹

  • mkdir HelloWorld

  • 创建一个名为 hello.py 的 Python 文件

def talk(message):
    return "Talk " + message


def main():
    print(talk("Hello World"))


if __name__ == "__main__":
    main()

测试您的程序

像往常一样操作。在代码工作不正常的情况下运行 Nuitka 并不会更容易调试。

python hello.py

使用以下命令构建

python -m nuitka hello.py

运行它

执行在 hello.py 附近创建的 hello.exe

分发

要分发,使用 --standalone 选项进行构建,这将不会输出单个可执行文件,而是一个完整的文件夹。将生成的 hello.dist 文件夹复制到另一台计算机并运行它。

您还可以尝试 --onefile,它确实创建了一个单个文件,但在转向它之前,请确保仅独立运行即可,因为它会使调试变得更难,例如在缺少数据文件的情况下。

用例

用例 1 — 嵌入所有模块的程序编译

如果您想递归地编译整个程序,而不仅仅是主程序的单个文件,可以这样做

python -m nuitka --follow-imports program.py

如果您有一个包含动态加载文件的源目录,即不能通过PYTHONPATH(这是推荐的方式)递归查找的目录,您始终可以要求指定的目录也应包含在可执行文件中

python -m nuitka --follow-imports --include-plugin-directory=plugin_dir program.py

用例2 — 扩展模块编译

如果您想编译单个扩展模块,只需这样做

python -m nuitka --module some_module.py

生成的文件some_module.so可以用作some_module.py的替代品。

用例3 — 包编译

如果您需要编译整个包并嵌入所有模块,这也是可行的,使用Nuitka如下

python -m nuitka --module some_package --include-package=some_package

用例 4 — 程序分发

对于其他系统的分发,存在独立模式,该模式会生成一个可以指定 --standalone 的文件夹。

python -m nuitka --standalone program.py

在此模式下默认情况下会跟踪所有导入。您可以通过指定 --nofollow-import-to 有选择地排除模块,但这样在程序运行时尝试导入它们时将引发 ImportError。这可能会导致不同的行为,但如果做得明智,也可能提高您的编译时间。

要包含数据文件,请使用选项 --include-data-files=<source>=<target>,其中源是一个文件系统路径,但目标必须是相对指定的。对于独立模式,您也可以手动复制它们,但这可以进行额外的检查,而对于单文件模式,则无法进行手动复制。

要复制目录中的某些或所有文件,请使用选项 --include-data-files=/etc/*.txt=etc/,您可以为文件指定shell模式,以及将它们放入的子目录,由结尾的反斜杠指示。

以下所述的非代码数据文件都是不符合这些标准的所有文件。

后缀

理由

解决方案

.py

Nuitka 会修剪要包含的stdlib模块。如果它看不到Python代码,则不会分析依赖关系,因此它将无法正常工作。

在这些模块上使用 --include-module 代替。

.pyc

.py 相同。

从它们的源代码而不是这些模块上使用 --include-module

.pyo

.pyc 相同。

从它们的源代码而不是这些模块上使用 --include-module

.pyw

.py 相同。

要包含多个程序,请使用多个 --main 参数。

.pyi

这些被忽略,因为它们是类似代码的,但在运行时不必要。对于实际依赖于它们的懒惰包,我们提供了一个编译时解决方案,消除了这种需求。

如果有第三方软件需要它,请提出一个问题。

.pyx

这些被忽略,因为它们是Cython源代码,在运行时不使用。

.dll

这些被忽略,因为它们 通常 不是数据文件。对于第三方软件包确实将它们用作数据的情况,例如.NET包,我们通过包配置来解决这个问题。

为这些创建Nuitka包配置,包括使用它们的包的 dll 部分。对于罕见的情况,可能需要具有特殊配置的数据文件部分。

.dylib

这些被忽略,因为它们是macOS扩展模块或DLL。

需要添加配置,包括缺少的 dll 部分或 depends

.so

这些被忽略,因为它们是Linux、BSD等扩展模块或DLL。

需要添加配置,包括缺少的 dll 部分或 depends

.exe

这些是Windows的二进制文件。

您可以将Nuitka包配置添加为包含它们作为DLL,并标记为 executable: yes

.bin

非Windows系统存在二进制文件,否则与.exe相同。

文件夹也会被忽略,这些是site-packagesdist-packagesvendor-packages,它们会包含完整的虚拟环境,这通常不是好事。此外,__pycache__文件夹也总是被忽略。在非MacOS系统上,.DS_Store文件也会被忽略,而py.typed文件夹只对IDE有意义,像.pyi文件一样被忽略。

要复制包含所有非代码文件的整个文件夹,可以使用--include-data-dir=/path/to/images=images,这将把它们放在目标位置,如果你想使用--noinclude-data-files选项来移除它们。代码文件如上所述,包括DLL、可执行文件、Python文件等,将被忽略。对于这些文件,你可以使用--include-data-files=/binaries/*.exe=binary/的形式强制包含它们,但这并不推荐,并且已知在运行时会引起问题。

对于包数据,有更好的方法,即使用--include-package-data,它会自动检测包的所有非代码数据文件并将它们复制过来。它甚至接受类似shell样式的模式。它可以省去你自己查找包目录的需要,并且当可用时应优先使用。在功能上它与--include-data-dir非常相似,但它有一个好处,就是为你找到正确的文件夹。

在数据文件方面,你主要得自己处理。Nuitka会跟踪由流行包需要的数据文件,但可能不完整。如果你在这些文件中遇到问题,请提出问题。更好的是,提出Nuitka包配置的改进PR。我们希望第三方软件能够直接运行。

当这工作正常时,如果你愿意,可以使用单文件模式。

python -m nuitka --onefile program.py

这将创建一个单独的二进制文件,在目标位置提取它自己后运行程序。但请注意,相对于程序访问文件会受到 影响,确保也阅读了单文件:查找文件这一节。

# Create a binary that unpacks into a temporary folder
python -m nuitka --onefile program.py

对于解包,默认情况下使用唯一的用户临时路径,然后将其删除,但是这个默认的--onefile-tempdir-spec="{TEMP}/onefile_{PID}_{TIME}"可以被路径指定覆盖,使用缓存路径,避免重复解包,例如使用--onefile-tempdir-spec="{CACHE_DIR}/{COMPANY}/{PRODUCT}/{VERSION}",它使用版本信息和用户特定的缓存目录。

目前,这些扩展标记可用:

标记

这将展开为以下内容

示例

{TEMP}

用户临时文件目录

C:\Users...\AppData\Locals\Temp

{PID}

进程ID

2772

{TIME}

自纪元以来秒数的时间。

1299852985

{PROGRAM}

可执行文件的完整运行时文件名。

C:\SomeWhere\YourOnefile.exe

{PROGRAM_BASE}

可执行文件运行时文件名的无后缀。

C:\SomeWhere\YourOnefile

{CACHE_DIR}

用户的缓存目录。

C:\Users\SomeBody\AppData\Local

{COMPANY}

作为--company-name提供的值

YourCompanyName

{PRODUCT}

值以--product-name给出

您的产品名称

{VERSION}

--file-version--product-version的组合

3.0.0.0-1.0.0.0

{HOME}

用户的家目录。

/home/somebody

{NONE}

当为文件输出提供时,使用None

请参阅以下说明

{NULL}

当为文件输出提供时,使用os.devnull

请参阅以下说明

用例5 — Setuptools Wheel

如果您已经有了由setup.pysetup.cfgpyproject.toml驱动的软件轮创建,将Nuitka用于其中非常简单。

让我们从最常用的setuptools方法开始,当然您已经安装了Nuitka,您可以直接执行目标bdist_nuitka而不是bdist_wheel。它接受所有选项,并允许您指定一些Nuitka特有的选项。

# For setup.py if you don't use other build systems:
setup(
   # Data files are to be handled by setuptools and not Nuitka
   package_data={"some_package": ["some_file.txt"]},
   ...,
   # This is to pass Nuitka options.
   command_options={
      'nuitka': {
         # boolean option, e.g. if you cared for C compilation commands
         '--show-scons': True,
         # options without value, e.g. enforce using Clang
         '--clang': None,
         # options with single values, e.g. enable a plugin of Nuitka
         '--enable-plugin': "pyside2",
         # options with several values, e.g. avoiding including modules
         '--nofollow-import-to' : ["*.tests", "*.distutils"],
      },
   },
)

# For setup.py with other build systems:
# The tuple nature of the arguments is required by the dark nature of
# "setuptools" and plugins to it, that insist on full compatibility,
# e.g. "setuptools_rust"

setup(
   # Data files are to be handled by setuptools and not Nuitka
   package_data={"some_package": ["some_file.txt"]},
   ...,
   # This is to pass Nuitka options.
   ...,
   command_options={
      'nuitka': {
         # boolean option, e.g. if you cared for C compilation commands
         '--show-scons': ("setup.py", True),
         # options without value, e.g. enforce using Clang
         '--clang': ("setup.py", None),
         # options with single values, e.g. enable a plugin of Nuitka
         '--enable-plugin': ("setup.py", "pyside2"),
         # options with several values, e.g. avoiding including modules
         '--nofollow-import-to' : ("setup.py", ["*.tests", "*.distutils"]),
      }
   },
)

如果您出于某种原因无法或不想更改目标,您可以在setup.py中添加以下内容。

# For setup.py
setup(
   ...,
   build_with_nuitka=True
)

或者,您可以将它放在setup.cfg

[metadata]
build_with_nuitka = true

最后但同样重要的是,Nuitka还支持新的build元数据,因此当您已经有了pyproject.toml时,只需简单替换或添加此值

[build-system]
requires = ["setuptools>=42", "wheel", "nuitka", "toml"]
build-backend = "nuitka.distutils.Build"

# Data files are to be handled by setuptools and not Nuitka
[tool.setuptools.package-data]
some_package = ['data_file.txt']

[tool.nuitka]
# These are not recommended, but they make it obvious to have effect.

# boolean option, e.g. if you cared for C compilation commands, leading
# dashes are omitted
show-scons = true

# options with single values, e.g. enable a plugin of Nuitka
enable-plugin = "pyside2"

# options with several values, e.g. avoiding including modules, accepts
# list argument.
nofollow-import-to = ["*.tests", "*.distutils"]

用例 6 — 多目标

如果您有多个程序,每个程序都应该可执行,过去您需要多次编译并部署所有这些程序。在独立模式下,这当然意味着您相当浪费,因为虽然可以共享文件夹,但 Nuitka 并不支持。

现在有了 多目标。有一个选项 --main,它可以替换或添加给定的位置参数。它可以多次给出。当多次给出时,Nuitka 将创建一个包含所有给定程序代码的二进制文件,但共享它们使用的模块。因此,它们不需要多次分发。

让我们称主路径的基本名称为入口点。这些名称必须当然不同。然后创建的二进制文件可以执行任何一个入口点,并将对 sys.argv[0] 的反应。因此,如果以正确的方式执行(例如使用 subprocess 或 OS API 来控制此名称),或者通过重命名、复制二进制文件或创建到它的符号链接,您就可以实现这一奇迹。

这允许将非常不同的程序组合成一个。

此模式与独立、单文件和仅加速模式一起工作。它不适用于模块模式。

用例 7 — 使用 GitHub 工作流程构建

为了与 GitHub 工作流程集成,这里有一个 Nuitka-Action,您应该使用它,它使集成变得非常简单。但是,您应该从本地编译开始,这对于使用 Nuitka 进行跨平台编译将是最简单的。

这是一个在所有 3 个操作系统上构建的示例工作流程。

jobs:
build:
   strategy:
      matrix:
      os: [macos-latest, ubuntu-latest, windows-latest]

   runs-on: ${{ matrix.os }}

   steps:
      - name: Check-out repository
      uses: actions/checkout@v3

      - name: Setup Python
      uses: actions/setup-python@v4
      with:
         python-version: '3.10'
         cache: 'pip'
         cache-dependency-path: |
            **/requirements*.txt

      - name: Install your Dependencies
      run: |
         pip install -r requirements.txt -r requirements-dev.txt

      - name: Build Executable with Nuitka
      uses: Nuitka/Nuitka-Action@main
      with:
         nuitka-version: main
         script-name: your_main_program.py
         # many more Nuitka options available, see action doc, but it's best
         # to use nuitka-project: options in your code, so e.g. you can make
         # a difference for macOS and create an app bundle there.
         onefile: true

      - name: Upload Artifacts
      uses: actions/upload-artifact@v3
      with:
         name: ${{ runner.os }} Build
         path: | # match what's created for the 3 OSes
            build/*.exe
            build/*.bin
            build/*.app/**/*

如果您的应用程序是 GUI,例如 your_main_program.py 应该包含这些注释,如 Nuitka 代码选项 中所述,因为在这种情况下,它应该是捆绑包。

# Compilation mode, standalone everywhere, except on macOS there app bundle
# nuitka-project-if: {OS} in ("Windows", "Linux", "FreeBSD"):
#    nuitka-project: --onefile
# nuitka-project-if: {OS} == "Darwin":
#    nuitka-project: --standalone
#    nuitka-project: --macos-create-app-bundle
#
# Debugging options, controlled via environment variable at compile time.
# nuitka-project-if: os.getenv("DEBUG_COMPILATION", "no") == "yes"
#     nuitka-project: --enable-console
# nuitka-project-else:
#     nuitka-project: --disable-console

调整

图标

为了美观,您可能需要指定图标。在 Windows 上,您可以提供一个图标文件、模板可执行文件或 PNG 文件。所有这些都可以工作,甚至可以组合使用。

# These create binaries with icons on Windows
python -m nuitka --onefile --windows-icon-from-ico=your-icon.png program.py
python -m nuitka --onefile --windows-icon-from-ico=your-icon.ico program.py
python -m nuitka --onefile --windows-icon-template-exe=your-icon.ico program.py

# These create application bundles with icons on macOS
python -m nuitka --macos-create-app-bundle --macos-app-icon=your-icon.png program.py
python -m nuitka --macos-create-app-bundle --macos-app-icon=your-icon.icns program.py

macOS 权限

可以使用选项 --macos-app-protected-resource 添加 macOS 应用程序捆绑包的权限,所有值都在 此 Apple 页面 上列出。

一个示例值可以是 --macos-app-protected-resource=NSMicrophoneUsageDescription:Microphone access,用于请求访问麦克风。在冒号之后,应给出描述性文本。

控制台窗口

在Windows上,除非您明确要求,否则程序会打开控制台。Nuitka默认这样做,实际上只适用于终端程序,或者需要查看输出的程序。在pythonw.exepython.exe之间有区别。Nuitka通过选项--disable-console实现了这一点。Nuitka建议您在使用PySide6等GUI包时考虑这一点,例如wx,但它将决定权留给了您。如果您的程序是控制台应用程序,只需使用--enable-console即可,这将消除Nuitka输出的这些类型。

启动画面

当程序启动缓慢时,启动画面很有用。Onefile启动本身并不慢,但您的程序可能很慢,而且您真的不知道将使用的计算机有多快,因此这可能是个好主意。幸运的是,使用Nuitka,在Windows上添加它们很容易。

对于启动画面,您需要将其指定为PNG文件,然后确保在您的程序准备好时禁用启动画面,例如,已完成导入、准备窗口、连接到数据库、希望启动画面消失。这里我们使用项目语法将代码与创建结合,编译这个

# nuitka-project: --onefile
# nuitka-project: --onefile-windows-splash-screen-image={MAIN_DIRECTORY}/Splash-Screen.png

# Whatever this is, obviously
print("Delaying startup by 10s...")
import time, tempfile, os
time.sleep(10)

# Use this code to signal the splash screen removal.
if "NUITKA_ONEFILE_PARENT" in os.environ:
   splash_filename = os.path.join(
      tempfile.gettempdir(),
      "onefile_%d_splash_feedback.tmp" % int(os.environ["NUITKA_ONEFILE_PARENT"]),
   )

   if os.path.exists(splash_filename):
      os.unlink(splash_filename)

print("Done... splash should be gone.")
...

# Rest of your program goes here.

报告

对于您程序和Nuitka打包的分析,有可用的编译报告。您还可以通过提供模板来制作自定义报告,Nuitka内置了一些。这些报告包含了所有详细信息,例如,当尝试导入一个模块但找不到时,您可以看到它发生的位置。对于错误报告,强烈建议提供报告。

版本信息

您可以将版权和商标信息、公司名称、产品名称等附加到您的编译中。然后在Windows上创建的二进制文件的版本信息或macOS上的应用程序捆绑包中使用。如果您发现缺少某些内容,请告诉我们。

典型问题

部署模式

默认情况下,Nuitka没有使用--deployment编译,这保留了一系列安全防护和辅助工具,旨在调试Nuitka的错误使用。

这是一个新功能,实现了一组保护和辅助工具,这里进行了文档说明。

克隆炸弹(自我执行)

因此,在编译之后,sys.executable是编译后的二进制文件。对于像multiprocessingjoblibloky这样的包,它们通常期望从一个完整的python中运行,有sys.executable,然后能够使用其选项,如-c command-m module_name,然后能够临时或永久地作为服务守护进程启动其他代码。

然而,使用Nuitka,它会再次执行您的程序,并将这些参数放入sys.argv中,您可能忽略它们,然后您再次克隆自己以启动辅助守护进程。有时这会导致产生CPU计数进程,这些进程又产生CPU计数进程,这就是所谓的克隆炸弹,几乎在所有系统中,这都会很容易地将它们冻结至死。

这就是为什么默认Nuitka会出现这种情况

./hello.dist/hello.bin -l fooL -m fooM -n fooN -o fooO -p
Error, the program tried to call itself with '-m' argument. Disable with '--no-deployment-flag=self-execution'.

您的程序可能有它自己的命令行解析,并不使用尝试重新执行的不可支持包。在这种情况下,您需要在编译时使用--no-deployment-flag=self-execution,这将禁用这个特定的防护。

误导信息

一些包会输出他们认为有助于了解失败导入原因的信息。对于编译程序来说,这种情况非常普遍,往往完全错误。我们尝试在非部署模式下修复这些问题。以下是一个示例,我们将一个要求pip安装的消息(这不是问题所在)改为指向使imageio插件工作的include命令。

- module-name: 'imageio.core.imopen'
  anti-bloat:
    - replacements_plain:
        '`pip install imageio[{config.install_name}]` to install it': '`--include-module={config.module_name}` with Nuitka to include it'
        'err_type = ImportError': 'err_type = RuntimeError'
      when: 'not deployment'

还有更多...

部署模式相对较新,并且持续添加更多功能,例如,关于FileNotFoundError的功能即将推出。

禁用所有功能

当然,可以使用--deployment一次性禁用所有这些助手,但请注意,对于调试,您可能希望重新启用它。您可能想使用Nuitka项目选项和环境变量来使此操作有条件。

您是否应该全部禁用它们?

我们认为,禁用应该仅是选择性发生的,但随着PyPI的升级,您的代码更改,所有这些问题都可能悄悄返回。部署模式的节省空间目前可以忽略不计,因此尽量不这样做,但请检查现有内容,如果知道它不会影响您,或者如果它会影响您,您就不需要它。其中一些未来的功能将明显针对初学者级别的使用。

Windows病毒扫描器

使用Nuitka默认设置编译的Windows二进制文件,并且没有采取其他措施,可能会被一些AV供应商识别为恶意软件。这是可以避免的,但只有在Nuitka商业版中才有实际的支持和如何做到的说明,将其视为典型的商业需求。 https://nuitka.net/doc/commercial.html

Linux独立版本

对于Linux独立版本来说,构建在其他Linux版本上运行的二进制文件相当困难。这主要是因为在Linux上,许多软件是专门针对特定的DLL构建的。例如,使用的glibc会被编码到构建的二进制文件中,它将无法与较旧的glibc一起运行,仅举一个关键的例子。

解决方案是在您希望支持的最早版本的操作系统上构建。选择这个版本并设置它可能很繁琐,登录也是如此,保持其安全,因为这是您放置源代码的地方。

为此,Nuitka商业版提供了基于容器的构建,您可以使用。这使用专用优化的Python构建,针对CentOS 7,并支持通过使用最新的C编译链的这种方式,甚至是最新的Python和非常旧的操作系统。为了支持Linux上的Nuitka开发,您需要购买它 https://nuitka.net/doc/commercial.html,但即使是赞助许可证也将比自行构建便宜。

程序导致系统崩溃(fork炸弹)

fork炸弹是一种不断自我启动的程序。由于编译程序的sys.executable不是Python解释器,而且一些尝试以更好的方式执行多进程的包通常通过这种方式重新启动自己,Nuitka需要并确实对这些已知包进行了处理。然而,您可能会遇到检测失败的这种情况。请参阅上面的部署选项,这是禁用此保护所必需的。

当fork炸弹容易发生时,所有可用的内存和CPU都会被使用,即使是功能最强大的构建系统有时也会崩溃,有时需要强制重启。

对于分叉炸弹,我们可以使用 --experimental=debug-self-forking 来观察其行为,并且我们有一个技巧,可以防止分叉炸弹在轰炸中取得实际成功。将此代码放在程序的开始部分。

import os, sys

if "NUITKA_LAUNCH_TOKEN" not in os.environ:
   sys.exit("Error, need launch token or else fork bomb suspected.")
else:
   del os.environ["NUITKA_LAUNCH_TOKEN"]

实际上,Nuitka 正在尝试在不使用部署选项的情况下获取它们,查找“-c”和“-m”选项,但可能并不完美,或者与包(不再)很好地工作。

内存问题和编译器错误

在某些情况下,C 编译器可能会崩溃,表示无法分配内存或输入被截断,或类似的错误消息,很明显。这些是示例错误消息,它们是内存不足的明确迹象,错误消息层出不穷。

# gcc
fatal error: error writing to -: Invalid argument
Killed signal terminated program
# MSVC
fatal error C1002: compiler is out of heap space in pass 2
fatal error C1001: Internal compiler error

您可以考虑以下几种选项。

请求 Nuitka 使用更少的内存

有一个专门的选项 --low-memory,它影响 Nuitka 的决策,使其在编译过程中避免大量使用内存,但代价是编译时间增加。

避免 32 位 C 编译器/汇编器内存限制

不要使用 32 位编译器,而要使用 64 位编译器。如果您在 Windows 上使用 32 位 Python,您绝对应该使用 MSVC 作为 C 编译器,而不是 MinGW64。MSVC 是一个交叉编译器,在该平台上可以比 gcc 使用更多的内存。如果您不在 Windows 上,这当然不是一种选择。此外,使用 64 位 Python 也会起作用。

使用最小化的虚拟环境

当您从实际安装编译时,该安装可能已经安装了您软件的许多可选依赖项。某些软件将对这些依赖项进行导入,而 Nuitka 也将编译它们。这不仅可能是一些麻烦制造者,而且还需要更多的内存,所以请将其删除。当然,您必须在尝试编译之前检查您的程序是否具有所有必需的依赖项,否则编译后的程序同样无法运行。

是否使用 LTO 编译

使用 --lto=yes--lto=no,您可以将 C 编译切换为仅生成字节码,而不是直接生成汇编代码和机器代码,而是在最后进行整个程序优化。这将显著改变内存使用情况,如果错误来自汇编器,使用 LTO 将肯定避免这种情况。

将 C 编译器切换为 clang

人们报告说,由于 gcc 的错误或内存使用,某些程序无法编译,但在 Linux 上使用 clang 可以正常工作。在 Windows 上,这仍然是一个选项,但需要首先实现自动下载的包含它的 gcc。由于 MSVC 已知在内存效率方面更有效,您应该选择它,如果您想使用 Clang,MSVC 中也支持它。

为您的嵌入式 Linux 添加更大的交换文件

在 RAM 不够的系统上,您需要使用交换空间。交换空间不足可能是原因之一,增加更多的交换空间,或者至少添加一个,可能解决该问题,但请注意,当编译器在交换空间之间来回切换时,这将使事情变得极其缓慢,因此请首先考虑下一个提示,或者将其放在提示之上。

限制编译作业的数量

使用 Nuitka 的 --jobs 选项,它不会同时启动许多 C 编译器实例,每个实例都在争夺有限的 RAM 资源。通过选择一个值为一,只有一个 C 编译器实例将运行,在一个 8 核系统上,这将减少内存的使用量,这是自然的选择。

动态 sys.path

如果您的脚本修改了 sys.path,例如插入与它相关的源代码目录,Nuitka 将无法看到这些目录。然而,如果您将 PYTHONPATH 设置为结果值,它将能够编译它,并从这些路径中找到使用的模块。

手动加载 Python 文件

私有代码中一个非常常见的模式是它扫描某些类型的插件目录,例如使用 os.listdir,然后考虑 Python 文件名,接着打开文件并在其上执行 exec。这种方法对 Python 代码有效,但对于编译代码,您应该使用这种更干净的方法,它适用于纯 Python 代码并且更加安全。

# Using a package name, to locate the plugins. This is also a sane
# way to organize them into a directory.
scan_path = scan_package.__path__

for item in pkgutil.iter_modules(scan_path):
   importlib.import_module(scan_package.__name__ + "." + item.name)

   # You may want to do it recursively, but we don't do this here in
   # this example. If you'd like to, handle that in this kind of branch.
   if item.ispkg:
      ...

独立程序中缺少数据文件

如果您的程序找不到数据文件,可能会引起各种不同的行为,例如,一个包可能会抱怨它不是正确的版本,因为 VERSION 文件检查默认为一个未知的版本。缺少图标文件或帮助文本可能会引发奇怪的错误。

通常,文件不存在时的错误路径甚至可能是错误的,会揭示编程错误,例如未绑定局部变量。请仔细检查这些异常,记住这可能是原因。如果您的程序在没有独立程序的情况下运行正常,那么数据文件可能是原因。

当然,表示文件缺失的最常见错误是未捕获的 FileNotFoundError,并附有文件名。您应该找出缺少文件的包,然后使用 --include-package-data(最好是),或者 --include-data-dir/--include-data-files 来包含它们。

独立程序中缺少 DLLs/EXEs

Nuitka 有处理复制 DLL 的插件。例如,对于 NumPy、SciPy、Tkinter 等。

这些需要特殊处理才能在其他系统上运行。手动复制是不够的,会引发奇怪的错误。有时新版本的包,尤其是 NumPy,可能不受支持。在这种情况下,您将必须提出问题,并使用较旧版本。

如果您想手动添加 DLL 或 EXE,因为它是您自己的项目,您将必须使用用户 Yaml 文件来描述它们的位置。这已经在 Nuitka 包配置 页面中详细说明,并附有示例。

独立程序中的依赖性蔓延

有些包只需要一个导入,但对于 Nuitka 来说,这意味着要包括超过一千个包(字面上的意思)。Pandas 是一个主要例子,它想连接并使用几乎所有可以想象到的功能。多个框架用于语法高亮几乎所有可以想象的内容,这需要时间。

Nuitka 将必须学习有效的缓存来处理这个问题,以应对未来的挑战。目前,您将不得不处理这些巨大的编译时间。

在对抗依赖性蔓延的战斗中,应该应用一项主要武器,即 anti-bloat 插件,它提供了一些有趣的能力,可以用于阻止不必要的导入,并在它们出现的地方给出错误。例如,使用 --noinclude-pytest-mode=nofollow --noinclude-setuptools-mode=nofollow,以及例如也使用 --noinclude-custom-mode=setuptools:error,让编译器对特定包报错。请确保检查其帮助输出。它可以为每个选择的模块,例如,强制例如 PyQt5 被视为在独立模式下未安装。

它还由一个配置文件驱动,anti-bloat.yml,您可以对其做出贡献,移除包中的典型膨胀。请不要犹豫,增强它并向 Nuitka 提出带有它的 PR。

独立程序:查找文件

通常情况下能够正常工作的标准代码,在这里也适用,你应该参考 os.path.dirname(__file__) 或使用所有像 pkgutilpkg_resourcesimportlib.resources 这样的包来定位靠近独立二进制文件的数据文件。

如果你是指引用靠近二进制文件的数据文件所在的 .dist 文件夹的位置,那么有 __compiled__.containing_dir 也可以抽象化与 --macos-create-app-bundle 和具有更深层结构的 .app 文件夹的所有差异。

# This will find a file *near* your app or dist folder
try:
   open(os.path.join(__compiled__.containing_dir, "user-provided-file.txt"))
except NameError:
   open(os.path.join(os.path.dirname(sys.argv[0]), "user-provided-file.txt"))

Onefile:查找文件

在单文件模式下,主模块的 sys.argv[0]__file__ 之间有一个区别,这是由于使用引导程序到临时位置引起的。第一个将是原始的可执行文件路径,而第二个将是引导程序解包到的临时或永久路径。数据文件将在后一个位置,你的原始环境文件将在前一个位置。

给定两个文件,一个你期望靠近你的可执行文件,另一个你期望在单文件二进制文件内部,可以这样访问它们。

# This will find a file *near* your onefile.exe
open(os.path.join(os.path.dirname(sys.argv[0]), "user-provided-file.txt"))
# This will find a file *inside* your onefile.exe
open(os.path.join(os.path.dirname(__file__), "user-provided-file.txt"))

# This will find a file *near* your onefile binary and work for standalone too
try:
   open(os.path.join(__compiled__.containing_dir, "user-provided-file.txt"))
except NameError:
   open(os.path.join(os.path.dirname(sys.argv[0]), "user-provided-file.txt"))

Windows程序无控制台不显示错误

出于调试目的,删除 --disable-console 或使用与上面文档中 --onefile-tempdir-spec 的路径一样使用 --force-stdout-spec--force-stderr-spec 选项。这些可以是相对于程序或绝对路径,因此你可以看到给出的输出。

深度复制未编译的函数

有时人们使用这种代码,对于 PyPI 上的包,我们通过即时对源代码进行补丁来处理。如果这是你自己的代码,以下是你可以做的事情

def binder(func, name):
   result = types.FunctionType(func.__code__, func.__globals__, name=func.__name__, argdefs=func.__defaults__, closure=func.__closure__)
   result = functools.update_wrapper(result, func)
   result.__kwdefaults__ = func.__kwdefaults__
   result.__name__ = name
   return result

编译过的函数不能用来创建未编译的函数,因此上面的代码将不起作用。但是,有一个专门的 clone 方法,这是针对它们的,所以使用这个代替。

def binder(func, name):
   try:
      result = func.clone()
   except AttributeError:
      result = types.FunctionType(func.__code__, func.__globals__, name=func.__name__, argdefs=func.__defaults__, closure=func.__closure__)
      result = functools.update_wrapper(result, func)
      result.__kwdefaults__ = func.__kwdefaults__

   result.__name__ = name
   return result

模块:扩展模块不能直接执行

一个包可以用 Nuitka 编译,没有问题,但在执行时,python -m compiled_module 不会起作用,并给出错误 No code object available for AssertsTest,因为编译过的模块不是源代码,Python 不会直接加载它。最接近的可能是 python -c "import compile_module",你可能需要自己调用主函数。

为了支持这一点,CPython 的 runpy 和/或 ExtensionFileLoader 需要改进,以便 Nuitka 可以为其编译模块对象提供 Python 使用。

提示

代码中的 Nuitka 选项

向 Nuitka 提供选项的一种干净的方法是将它们放入你编译的主文件中,你将始终为你的程序使用这些选项。甚至还有条件选项和支持使用预定义变量的选项,以下是一个例子

# Compilation mode, support OS-specific options
# nuitka-project-if: {OS} in ("Windows", "Linux", "Darwin", "FreeBSD"):
#    nuitka-project: --onefile
# nuitka-project-else:
#    nuitka-project: --standalone

# The PySide2 plugin covers qt-plugins
# nuitka-project: --enable-plugin=pyside2
# nuitka-project: --include-qt-plugins=qml

注释必须位于行首,并在其中使用缩进,类似于Python中的条件块结束。目前没有其他关键字,如上所示。

您可以在其中放置任意的Python表达式,例如,如果您想访问某个包的版本信息,可以使用__import__("module_name").__version__,如果需要启用或禁用某些Nuitka设置的话。Nuitka所做的唯一不是Python表达式的事情是,对于一组预定义的变量,它会展开{variable}

支持变量的表格

变量

这将展开为以下内容

示例

{OS}

使用的操作系统名称

Linux、Windows、Darwin、FreeBSD、OpenBSD

{Version}

Nuitka的版本

例如:(1, 6, 0)

{Commercial}

Nuitka商业版本的版本

例如:(2, 1, 0)

{Arch}

使用的架构

x86_64、arm64等

{MAIN_DIRECTORY}

编译文件的目录

some_dir/maybe_relative

{Flavor}

Python的变体

例如:Debian Python、Anaconda Python

当您想要指定相对于主脚本的文件名时,建议使用{MAIN_DIRECTORY},例如在数据文件选项或用户包配置yaml文件中使用。

# nuitka-project: --include-data-files={MAIN_DIRECTORY}/my_icon.png=my_icon.png
# nuitka-project: --user-package-configuration-file={MAIN_DIRECTORY}/user.nuitka-package.config.yml

Python命令行标志

为了将像-O-S这样的标志传递给Python,以便用于编译后的程序,Nuitka有一个命令行选项名称--python-flag=,它可以模拟这些选项。

支持最重要的那些,当然也可以添加更多。

缓存编译结果

C编译器在调用相同的输入文件时,将花费很长时间和大量的CPU资源来重复编译。请确保在使用gcc时安装和配置了ccache(甚至在Windows上也是如此)。这将使重复编译的速度大大加快,即使事情还不够完美,即程序的变化可能导致许多C文件更改,需要重新编译而不是使用缓存的结果。

在Windows上,使用gcc,Nuitka支持使用ccache.exe,它会自动从官方来源下载。这是在Windows上推荐的用法,因为其他版本可能会挂起。

Nuitka会在系统PATH中找到ccache,也可以通过将NUITKA_CCACHE_BINARY设置为二进制文件的完整路径来提供,这在CI系统中可能需要非标准的设置。

对于MSVC编译器和ClangCL设置,使用clcache是自动的,并且包含在Nuitka中。

在macOS和Intel上,会自动从我们的网站下载一个ccache二进制文件,对于arm64架构,建议使用此设置,它会安装Homebrew和ccache。如果机器是那种类型的,Nuitka会自动选择它。您不需要也不应该使用Homebrew与Nuitka一起使用,因为它不适合独立的部署,但我们可以从那里获取ccache

export HOMEBREW_INSTALL_FROM_API=1
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
eval $(/opt/homebrew/bin/brew shellenv)
brew install ccache

控制缓存的位置

所有类型缓存结果、下载、由C和Nuitka缓存的编译结果,都存储在由appdirs包确定的平台相关目录中。但是,您可以通过将环境变量NUITKA_CACHE_DIR设置为基本目录来覆盖它。这在家目录不持久,但其他路径是持久的环境中使用。

对这些缓存也有单独的缓存控制,以下是在开始编译之前可以设置的环镜变量表,以使Nuitka将这些缓存存储在完全独立的空问中。

缓存名称

环境变量

数据存放位置

下载

NUITKA_CACHE_DIR_DOWNLOADS

已下载的内容,例如依赖关系遍历工具

ccache

NUITKA_CACHE_DIR_CCACHE

gcc创建的对象文件

clcache

NUITKA_CACHE_DIR_CLCACHE

MSVC创建的对象文件

字节码

NUITKA_CACHE_DIR_BYTECODE

降级模块的字节码

dll依赖

NUITKA_CACHE_DIR_DLL_DEPENDENCIES

dll依赖关系

运行器

避免运行nuitka二进制文件,使用python -m nuitka将确保您使用的是您认为正在使用的内容。使用错误的Python将使它对好代码给出SyntaxError,对已安装的模块给出ImportError。这发生在您使用Python2运行Python3代码或相反时。通过显式调用相同的Python解释器二进制文件,您可以完全避免这个问题。

最快的C编译器

在Windows上,64位Python的pystone.exe最快的二进制文件,使用MinGW64证明比MSVC快得多,大约提高了20%的分数。因此,建议使用它而不是MSVC。使用Clang7的clang-cl.exe比MSVC快,但仍然比MinGW64慢得多,而且使用起来更困难,所以不建议使用。

在Linux上,对于pystone.bin,由clang6生成的二进制文件比gcc-6.3快,但不是特别快。由于gcc通常已经安装,因此建议现在使用它。

C编译时间的差异尚未检查。

意外的减速

像标准CPython一样使用Python DLL可能导致意外的减速,例如在处理Unicode字符串的未编译代码中。这是因为调用DLL而不是在DLL中驻留会产生开销,并且这种情况甚至发生在DLL自身上,比一个包含在一个二进制文件中的Python慢。

因此,如果可行,应目标静态链接,这在目前只适用于非Windows上的Anaconda Python、Debian Python2、自行编译的Python(不要激活--enable-shared,不需要)和用pyenv创建的安装。

独立可执行文件和依赖项

为Windows创建独立可执行文件的传统方法涉及使用外部依赖关系遍历工具将必要的库与编译的可执行文件一起复制到分发文件夹。

有许多方法可以找到缺失的内容。不要手动将东西复制到文件夹中,特别是DLL,因为这不会起作用。相反,制作错误报告,以便由Nuitka正确处理这些内容。

Windows资源错误

在Windows上,Windows Defender工具和Windows索引服务都会扫描刚刚创建的二进制文件,而Nuitka希望与之一起工作,例如添加更多资源,然后由于锁定而随机阻止操作。请确保将这些服务排除在编译阶段之外。

Windows独立程序的再分发

无论使用 MingW 还是 MSVC 编译,独立程序都需要依赖于 Visual C 运行时库。Nuitka 会尝试通过从您的系统中复制它们来分发这些依赖的 DLL。

从 Microsoft Windows 10 开始,Microsoft 随系统捆绑了 ucrt.dll(通用 C 运行时库),该库处理对 api-ms-crt-*.dll 的调用。

对于较早的 Windows 平台(以及 wine/ReactOS),在执行 Nuitka 独立编译程序之前,您应该考虑安装 Visual C 运行时库。

根据所使用的 C 编译器,您需要在目标机器上安装以下 redist 版本。但是请注意,除非您想针对 Windows 7,否则始终建议使用基于 14.3 的版本进行编译,因为它工作得更好,并且得到更好的支持。

Visual C 版本

Redist 年份

CPython

14.3

2022

3.11

14.2

2019

3.5-3.10

14.1

2017

3.5-3.8

14.0

2015

3.5-3.8

10.0

2010

3.4

9.0

2008

2.6, 2.7

当使用 Nuitka 下载的 MingGW64 时,您需要以下 redist 版本

MingGW64 版本

Redist 年份

CPython

WinLibs 自动下载

2015

2.6, 2.7, 3.4- 3.11

一旦在目标系统上安装了相应的运行时库,您可以从您的 Nuitka 编译的 dist 文件夹中删除所有 api-ms-crt-*.dll 文件。

在运行时检测 Nuitka

Nuitka 与其他工具不同,它不使用 sys.frozen,因为这通常会导致无端触发低效代码。对于 Nuitka,我们有一个模块属性 __compiled__ 来测试特定模块是否被编译,还有一个函数属性 __compiled__ 来测试特定函数是否被编译。

为 Nuitka C 编译提供额外选项

Nuitka 会将环境变量 CCFLAGSLDFLAGS 的值应用于编译过程中,这些值是在它认为必要的基础上确定的。当然,请注意,这只有在您知道自己在做什么时才有效,因此如果这造成问题,只有在完美了解信息的情况下才提出。

在 64 位 Windows 系统上生成 32 位二进制文件

Nuitka 会自动将目标架构设置为所使用的 Python 的架构。如果是 64 位,它将创建一个 64 位二进制文件;如果是 32 位,它将创建一个 32 位二进制文件。在下载 Python 时,您可以选择位数。在 python -m nuitka --version 的输出中,有一行是关于架构的。它是 Arch: x86_64 对于 64 位,而对于 32 位则是简单的 Arch: x86

C 编译器将被选择以与之大致匹配。如果您明确指定了它,并且它不匹配,您将收到关于不匹配的警告,并且会通知您您的编译器选择已被拒绝。

编译报告

当您使用 --report=compilation-report.xml 时,Nuitka 将创建一个包含有关编译和打包过程的详细信息的 XML 文件。该报告的完整性会随着每个版本的发布而不断增长,并揭示了模块使用尝试、编译时间、插件影响、数据文件路径、DLL 和为什么包含或不包含某些内容的原因。

目前,报告在某些地方包含绝对路径以及您的私人信息。目标是默认将其混合,因为我们还希望能够比较来自不同设置的编译报告,例如带有更新包的设置,并查看 Nuitka 的变化。然而,该报告建议用于错误报告。

此外,还有一种形式可供选择,其中报告是自由形式的,并根据您的 Jinja2 模板以及 Nuitka 中包含的模板。可以访问用于生成 XML 文件相同的信息。但是,目前这还没有文档记录,但我们计划添加一个包含数据的数据表。然而,对于熟悉 Jinja2 的源代码阅读者来说,现在已经可以轻松做到这一点。

如果您有一个模板,可以使用以下方式 --report-template=your_template.rst.j2:your_report.rst,当然,reStructuredText 的使用只是一个示例。您可以使用 Markdown、您自己的 XML 或任何适合您的内容。Nuitka 将使用编译报告数据扩展模板。

目前,Nuitka 包含以下报告。您只需使用名称作为文件名,Nuitka 将选择该报告。

报告名称

状态

目的

LicenseReport

实验性

包含许可证文本的编译使用的发行版

性能

本章概述了 Nuitka 当前在性能方面的预期。这是一个正在进行的工作,我们会随着进展进行更新。当前性能测量的重点是 Python 2.7,但稍后也会包含 3.x。

pystone 结果

这些结果是此类输出的最高值,运行 pystone 1000 次,取最小值。这个想法是最快的运行最有意义,并消除了使用峰值。

echo "Uncompiled Python2"
for i in {1..100}; do BENCH=1 python2 tests/benchmarks/pystone.py ; done | sort -rn | head -n 1
python2 -m nuitka --lto=yes --pgo tests/benchmarks/pystone.py
echo "Compiled Python2"
for i in {1..100}; do BENCH=1 ./pystone.bin ; done | sort -n | head -rn 1

echo "Uncompiled Python3"
for i in {1..100}; do BENCH=1 python3 tests/benchmarks/pystone3.py ; done | sort -rn | head -n 1
python3 -m nuitka --lto=yes --pgo tests/benchmarks/pystone3.py
echo "Compiled Python3"
for i in {1..100}; do BENCH=1 ./pystone3.bin ; done | sort -rn | head -n 1

Python

未编译

编译 LTO

编译 PGO

Debian Python 2.7

137497.87 (1.000)

460995.20 (3.353)

503681.91 (3.663)

Nuitka Python 2.7

144074.78 (1.048)

479271.51 (3.486)

511247.44 (3.718)

报告问题或错误

如果您遇到任何问题、错误或想法,请访问 Nuitka 错误跟踪器 并报告它们。

报告错误的最佳实践

  • 请始终在您的报告中包含以下信息,针对底层 Python 版本。您可以将这些信息轻松地复制并粘贴到您的报告中。它包含比您想象的更多的信息。请不要手动编写。当然,您可以添加以下内容:

    python -m nuitka --version
  • 尽量使您的示例最小化。也就是说,尽量删除对问题贡献不大的代码。理想情况下,创建一个小的可复现程序,使用 print 显示不同的结果,当程序编译或本地运行时。

  • 如果问题随机发生(即不是每次都发生),请尝试将环境变量 PYTHONHASHSEED 设置为 0,禁用哈希随机化。如果这使问题消失,请尝试以 1 为步长增加,直到出现每次都发生的问题,并将其包含在您的报告中。

  • 不要在您的报告中包含生成的代码。给定适当的输入,它是冗余的,而且我不太可能在没有更改 Python 或 Nuitka 源代码并重新运行它的能力的情况下查看它。

  • 不要发送文本截图,这是不好的和懒惰的。相反,从控制台捕获文本输出。

不支持的功能

代码对象 co_code 属性

对于本地编译的函数,代码对象是空的。Nuitka 的编译函数对象没有字节码,因此无法提供它。

PDB

没有跟踪编译函数以附加调试器。

项目详情


下载文件

下载适合您平台的文件。如果您不确定选择哪个,请了解更多关于 安装包 的信息。

源代码发行版

py2wasm-2.6.3.tar.gz (85.5 MB 查看哈希值)

上传时间

支持