Skip to main content

Build and distribute portable Python application by all-in-one configuration file.

Project description

PyPortable Installer

python -m pyportable_installer --help

点此查看视频演示

pyportable-installer 是一个 Python 项目打包工具, 它受启发于 poetry, 并旨在作为 pyinstaller 的替代品出现.

pyportable-installer 通过一个 all-in-one 配置文件 来管理打包工作, 通过该文件可将您的 Python 项目打包为 "免安装版" 的软件, 用户无需安装 Python 程序或第三方依赖 (注: 该特性需要您在配置中启用虚拟环境选项), 真正做到 "开箱即用, 双击启动".

特性

pyportable-installer 具有以下特点:

  1. 打包后的体积很小. 在不附带虚拟环境的情况下, 与您的源代码同等量级 (这通常只有几百 KB ~ 几 MB 之间)

  2. 易于使用. 您只需要维护一个 "pyproject.json" 配置文件即可. 在快速迭代的环境下, 您甚至只需要更改版本号就能立即生成新的打包结果

  3. 打包速度快. 一个中小型项目在数秒间即可生成打包结果

  4. 源代码加密. pyportable-installer 内置了多种编译和混淆器方案, 其中推荐使用 pyportable-crypto 库或 pyarmor 库对源代码进行混淆, 保障代码安全

  5. 开箱即用. pyportable-installer 打包后的目录结构非常清晰, 如下示例:

    my_project
    |= dist
       |= hello_world_0.1.0  # 打包结果
          |= build  # 一些构建信息
             |- mainifest.json
          |= src    # 您的源代码将被编译并放置在此目录下
             |- ...
          |= lib    # 一些自定义的第三方库会放在此目录下, 并在程序启动时加载
             |= pyportable_runtime  # 用于解算加密后的源代码, 保障代码安全
                |- ...
          |= venv   # 自带的虚拟环境 (可选)
             |- ...
          |- README.html    # 自述文档
          |- Hello.exe      # 双击即可启动!
    
  6. 不破坏相对路径. 在打包后的 src 目录下, 所有文件夹仍然维持着原项目的目录结构. 程序在启动时会将工作目录切换到启动脚本所在的目录, 这意味着您在原项目中所使用的相对路径, 在打包后仍然保持一致

  7. 摆脱虚拟环境 (该特性仍处于实验阶段). 您可以在 配置文件/虚拟环境配置项 中启用 "depsland" 选项, 客户端在安装 depsland 软件后, 运行您发布的程序时会自动部署依赖, 这样真正做到打包体积控制, 加快分发并减轻用户下载负担

  8. 无痛更新 (该特性将在后续版本提供). 运行软件目录下的 build/update.exe 即可获取软件的最新版本

  9. 激活和授权 (该特性将在后续版本提供). 该特性由 pyarmor 提供, pyportable-installer 将其同样整合在 all-in-one 配置文件中

工作流程

它的流程可以概括如下:

  1. 准备您要打包的项目

  2. 在项目的根目录下新建一个 all-in-one 配置文件: "pyproject.json"[^1]

    1. 通过 python -m pyportable_installer init 在目录下生成模板文件

    2. 根据 配置手册 查阅每个字段的格式和作用, 完成项目配置的填写

  3. 通过 pyportable-installer 处理此配置文件, 完成打包:

    方法 1. 命令行运行:

    # python -m pyportable_installer build --help
    python -m pyportable_installer build
    

    方法 2. 脚本运行:

    from pyportable_installer import full_build
    full_build('./pyproject.json')
    

pyportable-installer 会为您的项目生成:

注: 这里演示的是启用 pyportable-crypto 选项加密的效果.

  1. 加密后的源代码文件

    1. 加密后的文件后缀仍然是 '.py'

    2. 加密后的文件由 ~/lib/pyportable_runtime 包在运行时解码

    3. 使用文本编辑器打开加密文件, 其密文如下所示:

  2. 一个 exe 格式的启动器

  3. 自定义的启动器图标 (注: 缺省图标为 python.ico)

  4. 一个干净的虚拟环境 (这是可选的)

  5. 整个打包后的结果会以文件夹的形式存在

之后, 您可以将该文件夹制作为压缩文件, 并作为 "免安装版" 的软件发布.

安装和使用

通过 pip 安装:

pip install pyportable-installer

注意事项:

  1. pyportable-installer 最新版本为 4.4.0+ (截至 2022-03-29)
  2. 上代版本 (3.3.3 及以前) 已不推荐使用
  3. pyportable-installer 需要 Python 3.8 及以上的解释器运行

下面以一个 "Hello World" 项目为例, 介绍具体的打包工作:

假设 "Hello World" 的项目结构如下:

hello_world_project # 项目目录
|= dist             # 暂时是一个空目录
|= hello_world      # 源代码目录
   |- main.py
   |  ~ def say_hello(file):
   |  ~     with open(file, 'r') as f:
   |  ~         for name in f:
   |  ~             print(f'Hello {name}!')
   |  ~
   |  ~ if __name__ == '__main__':
   |  ~     say_hello('../data/names.txt')
|= data
   |- names.txt
|- pyproject.json
|- README.md

在项目根目录下新建 'pyproject.json', 填写以下内容:

注: '◆◇◆' 标记的是必填的项, 其他可使用缺省设置.

{
    // 注意: 凡是配置项中涉及到路径的填写, 均使用相对于本文件的相对路径, 或者使
    // 用绝对路径. pyportable-installer 会自动将它们转换到打包目录的内部结构中.

    // ◆◇◆ 应用名称 ◆◇◆
    "app_name": "Hello World",
    // ◆◇◆ 应用版本 ◆◇◆
    "app_version": "0.1.0",
    "description": "",
    "authors": [],
    "build": {
        // ◆◇◆ 项目目录的入口 ◆◇◆
        "proj_dir": "./hello_world",
        // ◆◇◆ 打包结果放在哪里 ◆◇◆
        //     注意打包结果的父目录必须事先存在, 且打包结果所在的目录事先应不存在.
        //     否则打包活动将中止.
        "dist_dir": "./dist/{app_name_snake}_{app_version}",
        // ◆◇◆ 启动器配置 ◆◇◆
        //  1. 启动器配置是一个字典.
        //  2. 字典的键是要生成的启动器. 键可以使用花括号语法指代一个名称, 例如 
        //     "{app_name}" 指代 "Hello World", "{app_name_lower}" 指代 "hello 
        //     world" 等 (详见配置手册).
        //  3. 字典的值是该启动器的配置.
        "launchers": {
            "{app_name}": {
                "file": "./hello_world/main.py",
                "icon": "",
                "function": "say_hello",
                "args": ["../data/names.txt"],
                "kwargs": {}
            }
        },
        "readme": "",
        // ◆◇◆ 要加入到打包的附件 ◆◇◆
        "attachments": {
            "./data/names.txt": "assets"
        },
        "attachments_exclusions": [],
        "attachments_exist_scheme": "overwrite",
        "module_paths": [],
        "module_paths_scheme": "translate",
        // ◆◇◆ 目标应用的 python 解释器版本 ◆◇◆
        "python_version": "3.8",
        "venv": {
            "enabled": true,
            "mode": "source_venv",
            "options": {
                "depsland": {
                    "venv_name": "{app_name_snake}_venv",
                    "venv_id": "",
                    "requirements": [],
                    "offline": false,
                    "local": ""
                },
                "source_venv": {
                    "path": "",
                    "copy_venv": true
                },
                "pip": {
                    "requirements": [],
                    "pypi_url": "https://pypi.python.org/simple/",
                    "offline": false,
                    "local": ""
                },
                "embed_python": {
                    "path": ""
                }
            }
        },
        "compiler": {
            "enabled": true,
            "mode": "pyportable_crypto",
            "options": {
                "cythonize": {
                    "c_compiler": "msvc",
                    "python_path": "auto_detect"
                },
                "pyarmor": {
                    "license": "trial",
                    "obfuscate_level": 0
                },
                "pyc": {
                    "optimize_level": 0
                },
                "pyportable_crypto": {
                    "key": "{random}"
                },
                "zipapp": {
                    "password": ""
                }
            }
        },
        "experimental_features": {
            "add_pywin32_support": false,
            "platform": "system_default"
        },
        "enable_console": true
    },
    "note": "",
    "pyportable_installer_version": "4.4.0"
}

注: 更多用法请参考 配置手册.

运行以下代码即可生成打包结果:

from pyportable_installer import full_build
full_build('pyproject.json')

# 当增量更新时, 运行以下:
# from pyportable_installer import min_build
# min_build('pyproject.json')

# 如不需要加密源代码, 运行以下 (仅用于调试!):
# from pyportable_installer import debug_build
# debug_build('pyproject.json')

生成的安装包位于 hello_world/dist/hello_world_0.1.0:

hello_world
|= dist
   |= hello_world_0.1.0
      |= build
         |- manifest.json       # 1. 应用构建信息 (数据已自动去敏, 可随项目一起发布)
      |= src
         |= hello_world
            |- main.py          # 2. 这是加密后的脚本, 与源文件同名
         |= .pylauncher_conf
            |- __main__.pkl     # 5. pylauncher 会从这里读取启动配置信息
         |= data
            |- names.txt
         |- pylauncher.py       # 4. 启动器 (exe) 会调用这个文件
      |- Hello World.exe        # 3. 双击启动!
      |= lib
         |= pyportable_runtime
            |- __init__.py
            |- inject.pyd
|- ...

注意事项

  1. 要生成的打包目录事先应不存在, 否则 pyportable-installer 会中止打包
  2. 如果您启用了虚拟环境选项, 则安装路径不能包含中文, 否则可能导致启动失败 (该问题可能与 Embedded Python 解释器有关)
  3. pyportable-installer 需要 Python 3.8 及以上的解释器运行. 打包的目标 Python 版本可调 (目前对 Python 2.7 和 Python 3.7 以下的版本未做充分测试)

FAQ

程序在启动后会出现一个黑色的控制台, 该怎么隐藏?

在配置文件中将 "enable_console" 选项关闭:

{
    // ...
    "build": {
        // ...
        "enable_console": false
    }
}

运行报错: 没有找到 tkinter 库

这是因为您发布的项目中的虚拟环境使用的是嵌入式 Python, 而嵌入式 Python 并没有自带 tkinter 库.

解决方法 1 (适用于 v4.0.0b3+): 在配置文件中的实验性功能中启用 "add_tkinter_support":

{
    // ...
    "build": {
        // ...
        "experimental_features": {
            "add_tkinter_support": {
                "enable": true,
                "system_python_path": "_auto_detect"
            }
        }
    }
}

解决方法 2: 请参考此文: 如何将 Tk 套件加入到嵌入式 Python 中.

pywin32, win32, win32clipboard 等相关问题

解决方法 1 (适用于 v4.0.0b4+): 在配置文件中的实验性功能中启用 "add_pywin32_support".

解决方法 2: 请参考此文: Pywin32 库相关问题产生原因及解决方法.

如何添加图标文件

图标仅支持 ico 格式. 如果您手上只有 png, jpg 等格式的图片文件, 可通过在线网站转换:

如何通过配置文件实现: 打包路径用英文, 启动器名字用中文?

更改配置文件如下所示:

方案 1:

{
    "app_name": "你好世界",  // 定义应用名为中文. 将生成 "你好世界.exe"
    "build": {
        "dist_dir": "dist/hello_world_{app_version}",  // 这里用英文路径
        // ...
    },
    // ...
}

方案 2 (推荐):

{
    "app_name": "Hello World",  // 仍保持原来的名字
    "build": {
        "launchers": {
            // 单独修改启动器的名字
            "你好世界": {
                "file": "...",
                "icon": "",
                "args": [],
                "kwargs": {}
            }
        },
        // ...
    },
    // ...
}

pyportable-installer 加密的安全吗?

pyportable-installer 以插件的方式集成了多个编译器支持. 您可以在 配置文件:compiler:name 中选择合适的加密方案.

完整的支持列表和各编译器差异可在手册 (TODO) 中查询. 开发者也可以定义自己的编译器来保障安全 (需要继承 pyportable_installer.compilers.base_compiler.BaseCompiler).

这里简要介绍下推荐的编译器:

pyportable-crypto

pyportable-installer 的姊妹项目之一, 提供开源免费的加密措施.

pyportable-crypto 使用随机步骤生成器产生一个密钥机, 再将密钥机通过 cython 编译为 pyd 文件 (inject.pyd). 由于密钥机内置了密钥 (非固定码文保存), 在发布后附带此密钥机可以解算经同一密钥加密的 py 文件.

值得注意的是 pyportable-crypto 的开发仍处于非常早期的阶段, 任何问题和反馈都欢迎提交到 issues. 在正式版推出之前, 建议使用下面要介绍的 pyarmor 库.

pyarmor

阅读此文了解: PyArmor 的安全性.

注意: 请勿使用 pyarmor 试用版加密您的产品! 任何人都可以通过公开的 license 获取到源代码内容. 如需在正式产品中使用 pyarmor, 请 购买 pyarmor 使用许可.

虚拟环境各个配置方案的区别, 我该如何选择?

TODO

depsland

TODO

[^1]: 该文件名和文件位置是可以更改的. 为方便表述, 本文谨以根目录下的 pyproject.json 作为参考.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release. See tutorial on generating distribution archives.

Built Distribution

pyportable_installer-4.4.2-py3-none-any.whl (511.9 kB view hashes)

Uploaded py3

Supported by

AWS AWS Cloud computing Datadog Datadog Monitoring Facebook / Instagram Facebook / Instagram PSF Sponsor Fastly Fastly CDN Google Google Object Storage and Download Analytics Huawei Huawei PSF Sponsor Microsoft Microsoft PSF Sponsor NVIDIA NVIDIA PSF Sponsor Pingdom Pingdom Monitoring Salesforce Salesforce PSF Sponsor Sentry Sentry Error logging StatusPage StatusPage Status page