Skip to content

Project Packaging Tutorial

Overview

UnifyPy 2.0 is a cross-platform Python application packaging tool that supports packaging Python projects into native installation packages for Windows (EXE), macOS (DMG), and Linux (DEB).

Core Features: Interactive configuration wizard, smart path handling, automatic permission management, parallel builds, rollback system.

Full documentation: UnifyPy GitHub Repository

Environment Preparation

1. Install Project Dependencies

bash
# Enter the project directory
cd /path/to/py-xiaozhi

# Install project dependencies
pip install -r requirements.txt        # Windows/Linux
pip install -r requirements_mac.txt    # macOS

2. Install UnifyPy

bash
pip install unifypy

3. Platform-Specific Tools

Windows

Install Inno Setup (for creating installers)

  • After installation, configure the path in build.json, or set the INNO_SETUP_PATH environment variable

macOS

  • No additional installation needed (UnifyPy includes built-in create-dmg tool)

Linux

Install packaging tools:

bash
# DEB format packaging tools
sudo apt-get install dpkg-dev fakeroot

Install build environment (Important):

bash
# Install development tools and libraries (needed by NumPy and other dependencies)
sudo apt update
sudo apt install -y build-essential python3-dev python3-pip python3-setuptools \
    libopenblas-dev liblapack-dev gfortran patchelf autoconf automake \
    libtool cmake libssl-dev libatlas-base-dev

# Install build system
pip install meson ninja
sudo apt install -y meson ninja-build

NumPy Build Environment Configuration:

bash
# Set environment variables
export BLAS=openblas
export LAPACK=openblas
export NPY_NUM_BUILD_JOBS=$(nproc)

# Verify NumPy is available
python -c "import numpy; print('NumPy version:', numpy.__version__)"

Tip: Using a conda environment is recommended to avoid most compilation issues:

bash
conda create -n build_env python=3.9
conda activate build_env
conda install numpy scipy openblas-devel
pip install unifypy

Inject FFmpeg before packaging (required)

libs/ffmpeg/ is gitignored and is not shipped in the repo. You must inject portable ffmpeg/ffprobe before packaging, otherwise end users without a system FFmpeg cannot use music decode.

Do not cp $(which ffmpeg) from Homebrew / apt / Chocolatey — those are dynamically linked and break on clean machines.

bash
cd /path/to/py-xiaozhi

# macOS: install static-ffmpeg first (extract embedded binaries; never brew ffmpeg)
uv pip install static-ffmpeg   # or: pip install static-ffmpeg

# Auto-detect host, or pass plat/arch explicitly:
./scripts/bundle_ffmpeg.sh              # auto-detect
./scripts/bundle_ffmpeg.sh mac arm64    # mac Apple Silicon
./scripts/bundle_ffmpeg.sh win x64      # Windows
./scripts/bundle_ffmpeg.sh linux x64    # Linux x86_64
./scripts/bundle_ffmpeg.sh linux arm64  # Linux ARM64

# Verify
ls -lh libs/ffmpeg/*/
# mac: must not link Homebrew
otool -L libs/ffmpeg/mac/arm64/ffmpeg | grep -E 'homebrew|Cellar' && echo FAIL || echo OK

The script writes:

text
libs/ffmpeg/<plat>/<arch>/ffmpeg[.exe]
libs/ffmpeg/<plat>/<arch>/ffprobe[.exe]

and runs an isolated-PATH -version smoke test (non-zero exit on failure).

Then package

bash
unifypy . --config build.json --clean

libs is already included in packaging datas, so injected binaries land in the installer.

Quick Start

bash
# Launch the configuration wizard in the project directory
cd /path/to/py-xiaozhi
unifypy . --init

The wizard guides you through:

  1. Basic info configuration (name, version, entry file)
  2. Target platform selection (Windows/macOS/Linux)
  3. PyInstaller configuration (single-file/windowed mode, data directories)
  4. Platform-specific configuration (permissions, icons, install options)
  5. Auto-generate build.json and optionally build immediately

Method 2: Use Existing Configuration

The project already includes a pre-configured build.json. Inject FFmpeg first (see previous section), then package:

bash
# 1) Inject portable ffmpeg/ffprobe
./scripts/bundle_ffmpeg.sh

# 2) Package
# Basic packaging
unifypy . --config build.json

# Clean rebuild
unifypy . --config build.json --clean

# Verbose output (recommended)
unifypy . --config build.json --verbose

# Generate executable only
unifypy . --config build.json --skip-installer

# Parallel build for multiple formats
unifypy . --config build.json --parallel --max-workers 4

Platform-Specific Packaging Commands

Windows

bash
unifypy . --config build.json

Output:

  • Executable: dist/xiaozhi/
  • Installer: dist/installer/xiaozhi-1.0.0-setup.exe

macOS

bash
# Development mode (auto-configure permissions)
unifypy . --config build.json --development

# Production mode
unifypy . --config build.json

Output:

  • Application bundle: dist/xiaozhi/XiaoZhi.app
  • Disk image: dist/installer/xiaozhi-1.0.0.dmg

Linux

bash
unifypy . --config build.json

Output:

  • Executable: dist/xiaozhi/
  • DEB package: dist/installer/xiaozhi-1.0.0.deb

Linux Platform Notes

NumPy Compilation Issues

Symptoms: Compilation errors, missing BLAS/LAPACK libraries, etc.

Solutions:

bash
# Option 1: Check and install missing dev libraries
sudo apt install -y libopenblas-dev liblapack-dev gfortran

# Option 2: Use pre-built wheel packages (recommended)
pip install numpy --force-reinstall

# Option 3: Set env vars and reinstall
export BLAS=openblas
export LAPACK=openblas
export NPY_NUM_BUILD_JOBS=4
pip install numpy --no-cache-dir

# Option 4: Use conda environment (most stable)
conda install numpy scipy openblas-devel

Dependency Compatibility

The packaged program won't run on other Linux systems?

Solution: Build on an older Linux distribution. Recommended:

  • Ubuntu 18.04 LTS or 20.04 LTS
  • CentOS 7

Or use static linking:

bash
pip install staticx

FAQ

Q1: Don't know how to configure on first use?

bash
unifypy . --init  # Use the interactive wizard

Q2: Packaging failed?

bash
# Clean and retry with verbose output
unifypy . --config build.json --clean --verbose

Q3: macOS permission configuration issues?

bash
# Use development mode to auto-generate permission files
unifypy . --config build.json --development --verbose

Q4: Dependency conflicts?

bash
# Create a unified environment
conda create -n packaging python=3.9
conda activate packaging
pip install unifypy
pip install -r requirements.txt

Q5: Can't find configuration file path?

UnifyPy 2.0 uses smart path handling; relative paths in the configuration file are automatically resolved to absolute paths relative to the project directory.

Example:

json
{
  "icon": "assets/icon.png"  // Automatically resolves to /path/to/project/assets/icon.png
}

Advanced Features

Rollback System

bash
# List available rollback sessions
unifypy . --list-rollback

# Rollback to a specific session
unifypy . --rollback SESSION_ID

Parallel Builds

bash
# Parallel build for multiple formats
unifypy . --config build.json --parallel --max-workers 4

macOS Auto Permission Management

UnifyPy automatically generates entitlements.plist and updates Info.plist:

bash
# Development mode (includes debug permissions)
unifypy . --config build.json --development

# Production mode (essential permissions only)
unifypy . --config build.json --production

More Information

Best Practices

  1. Environment Management: Use conda to create a dedicated packaging environment
  2. First-Time Use: Use --init wizard to quickly generate configuration
  3. Debug Builds: Always use the --verbose flag
  4. Clean Builds: Regularly use --clean to ensure clean builds
  5. Linux Packaging: Build on Ubuntu 18.04/20.04 to ensure compatibility
  6. macOS Packaging: Use --development mode during the development phase
  7. FFmpeg inject: Run ./scripts/bundle_ffmpeg.sh before every clean package build; libs/ffmpeg/ is not committed