Search
Write a publication
Pull to refresh

Сборка Python-приложения в бинарник через Nuitka: особенности, проблемы и решения

Level of difficultyMedium

Введение

Python удобен для разработки, но его интерпретируемая природа создаёт проблемы при распространении. При использовании PyInstaller или аналогичных инструментов требуется, чтобы на целевом компьютере был установлен Python и необходимые зависимости. Nuitka позволяет компилировать Python-код в бинарный исполняемый файл, исключая необходимость в виртуальном окружении. Это повышает производительность и упрощает развёртывание.

Кроме того, Nuitka обеспечивает лучшую защиту от реверс-инжиниринга по сравнению с PyInstaller и cx_Freeze, так как код транслируется в C, а не просто упаковывается в архив с интерпретатором. Это делает его более устойчивым к декомпиляции и анализу. Альтернативным способом защиты может быть использование Cython, который компилирует Python-код в C-расширения, обеспечивая дополнительную защиту, но требует наличия виртуального окружения на целевой системе.

В статье разберём процесс сборки Python-приложения в бинарник через Nuitka, возникшие сложности и их решения.

Nuitka: как это работает

Nuitka транслирует Python-код в C и компилирует через компиляторы (GCC, Clang, MSVC), создавая исполняемый файл, не требующий интерпретатора Python. В отличие от PyInstaller и cx_Freeze, Nuitka выполняет настоящую компиляцию, а не просто упаковку кода. Итоговый бинарник содержит скомпилированные модули и может работать автономно, обеспечивая лучшую производительность, защиту от реверс-инжиниринга и уменьшение зависимости от среды выполнения.

Установка и сборка

Моё приложение разработано с использованием PySide6, так как Nuitka не очень хорошо работает с PyQt. В программе активно используются многопоточность (threading) и SVG-файлы, что накладывает дополнительные требования к сборке.

Использование виртуального окружения и версия python

Для работы использую venv, так как это стандартный инструмент для управления зависимостями в Python. Сам Python 3.12.

Установка Nuitka и зависимостей:

pip install nuitka
brew install clang

или

brew install gcc

Базовая сборка на Windows:

Флаги сборки:

  • --standalone — создаёт полностью независимый бинарник, включающий все зависимости.

  • --enable-plugin=pyside6 — включает поддержку PySide6.

  • --windows-console-mode=disable — отключает консольное окно при запуске GUI-приложения.

  • --include-package=имя_пакета — вручную включает указанный пакет в сборку.

  • --include-data-dir=абсолютный_путь=относительный_путь — включает файлы и папки в сборку, где слева указывается полный путь, а справа — путь внутри бинарника.

  • --onefile — упаковывает весь бинарник и зависимости в один исполняемый файл.

  • --onedir — создаёт папку с бинарником и всеми зависимостями отдельно.

Пример:

python -m nuitka 
    --standalone \
    --onedir \
    --enable-plugin=pyside6 \
    --windows-console-mode=disable \
    --include-package=Мой класс \
    --include-data-dir="папка/папка внутри/данные=папка внутри/папка" \
    --include-data-dir="папка/данные=данные" \
    --include-data-dir="данные=данные" \
    --windows-icon-from-ico="папка/иконка.ico" \
    --output-dir="папка/куда будем выводить файл" \
    --output-file="Название приложения.exe" \
    "путь/Исполняемый код.py"

Сборка на macOS

Флаги сборки для macOS:

  • --standalone — создаёт автономный бинарник с включёнными зависимостями.

  • --enable-plugin=pyside6 — поддержка PySide6.

  • --onefile — создаёт единый исполняемый файл.

  • --onedir — создаёт папку с бинарником и всеми зависимостями отдельно.

  • --macos-app-mode — формирует .app-пакет для macOS.

  • --macos-create-app-bundle — создаёт полноценный macOS-совместимый .app-бандл.

  • --force-stderr-spec=файл.log — перенаправляет стандартный поток ошибок в указанный файл.

  • --force-stdout-spec=файл.log — перенаправляет стандартный поток вывода в указанный файл.

Пример команды сборки на macOS:

python -m nuitka \
    --standalone \
    --onedir \
    --enable-plugin=pyside6 \
    --macos-app-mode \
    --macos-create-app-bundle \
    --include-package=Мой_класс \
    --include-data-dir="папка/данные=данные" \
    --output-dir="папка/куда будем выводить файл" \
    --macos-app-icon="иконка.icns" \
    --output-file="Название_приложения.app" \
    --force-stderr-spec="error.log" \
    --force-stdout-spec="output.log" \
    "путь/Исполняемый_код.py"

Итоговый результат

После успешной сборки с Nuitka мы получаем полностью автономный бинарник, который можно запускать без установленного Python и дополнительных зависимостей. В зависимости от флагов (--onefile или --onedir), это либо один исполняемый файл, либо папка с бинарником и всеми необходимыми библиотеками.

На Windows получаем две папки: dist и build. В dist находится итоговый исполняемый файл (если используется --onedir).

На macOS я рекомендую использовать только --onedir, далее объясню почему. В результате сборки получаем аналогичные папки dist и build, а также готовое .app-приложение.

После успешной сборки с Nuitka мы получаем полностью автономный бинарник, который можно запускать без установленного Python и дополнительных зависимостей. В зависимости от флагов (--onefile или --onedir), это либо один исполняемый файл, либо папка с бинарником и всеми необходимыми библиотеками.

На Windows это .exe-файл, а на macOS — .app-пакет.

Проблемы и их решения

1. Nuitka не поддерживает PyQt, рекомендуется использовать PySide

PyQt не всегда корректно компилируется в бинарник через Nuitka, поэтому рекомендуется использовать PySide6. Nuitka имеет встроенный плагин для PySide6, который упрощает сборку и уменьшает вероятность ошибок.

2. Проблема с путями к данным в собранном приложении

После сборки бинарника пути к данным могут отличаться от исходных. Для правильной работы с ресурсами можно использовать следующую функцию, которая корректно определяет путь к файлам как в исходном коде, так и в собранном приложении:

import os
import sys

def resource_path(relative_path):
    """Возвращает абсолютный путь к ресурсу, учитывая режим упаковки и macOS .app."""
    if sys.platform == "darwin":  # macOS
        base_path = os.path.abspath(
            os.path.join(os.path.dirname(sys.executable), "..", "..", "..") 
            # здесь можно добавить или убрать ".." в зависимости от глубины вложенности
        )
    else:  # Windows или Linux
        if getattr(sys, 'frozen', False):
            base_path = os.path.dirname(sys.executable)
        else:
            base_path = os.path.dirname(os.path.abspath(__file__))
    
    return os.path.join(base_path, relative_path)

Пример использования:

logo_label = QSvgWidget(resource_path("Logo.svg"))

3. Проблема вывода print в macOS .app

Если в приложении есть print-выводы в консоль, то при свёртке в .app на macOS программа не понимает, куда отправлять вывод, и крашится. Самое интересное, что если запускать .app через терминал командой open, программа работает корректно.

Решение: Использование флага --force-stdout-spec=файл.log перенаправляет вывод print в лог-файл, предотвращая падение программы:

--force-stdout-spec=output.log

Заключение

Для завершённого вида на Windows создаём ярлык с путём до исполняемого файла.

На macOS используем команду для создания .dmg-образа:

hdiutil create -fs HFS+ -srcfolder "папка вывода/исполняемый файл" -volname "Название" "Название.dmg"

После этого получаем законченное приложение (особенно на MacOS). Поэтому я и рекомендовал на MacOS делать onedir, ведь при сборе в .dmg все файлы укомплектуются в .app.

В этой статье разобраны основные проблемы, возникающие при сборке Python-приложений в бинарники с помощью Nuitka. Это не полный список возможных сложностей, и если я столкнусь с новыми проблемами, обязательно расскажу о них в следующей статье.

Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.