前言

使用 PySide 開發桌面應用 , 雖然開發方便 , 但總不能直接將程式碼給使用者 , 要他們裝這個套件 , 裝那個程式庫的 。 本文山姆鍋介紹如何使用 PyInstaller 這個工具來打包 PySide 開發的桌面應用程式 。

使用 PySide 實現 Python 系統托盤圖示 這篇文章 , 山姆鍋分享如何實現跨平台系統托盤圖示的程式案例 。 本文延續該文章 , 說明如何使用 PyInstaller 來打包 Python 桌面應用 。 對於打包 Python 應用 , 山姆鍋目前的首選是 PyInstaller , 主要是因為可以跨平台支援 , 但目前它還不完整支援 Python 3, 這也是山姆鍋還停留在使用 Python 2 的主因 。

本文所使用的原始碼在 GitHub 上可以找到 , 基本目錄結構如下 :

.
├── LICENSE
├── avashell
│   ├── __init__.py
│   ├── launcher.py
│   ├── shell_base.py
│   ├── shell_pyside.py
│   ├── utils.py
├── avashell.spec
├── readme.rst
├── requirements.txt
├── res
│   ├── icon.icns
│   ├── icon.ico
│   └── icon.png
└── setup.py

PyInstaller 規格檔

除了簡單的例子外 ,PyInstaller 需要一個規格描述檔 ( 副檔名通常為 .spec) 才知道如何打包應用程式以及相依的程式庫 。 根據 PyInstaller 產生出來的規格檔 , 山姆鍋修改成適合 avashell 這個範例程式的用途 。 底下是範例程式打包規格檔內容 :

# -*- mode: python -*-
# -*- coding: utf-8 -*-

from __future__ import print_function

import sys

app_name = 'avashell'   #1
exe_name = 'avashell'   #2

res_path = os.path.join('.', 'res')  #3

if sys.platform == 'win32':
    exe_name = exe_name + '.exe'    # 4
    run_upx = False

elif sys.platform.startswith('linux'):
    run_strip = True
    run_upx = False

elif sys.platform.startswith('darwin'):
    run_upx = False

else:
    print("Unsupported operating system")
    sys.exit(-1)

a = Analysis(['avashell/launcher.py'],  #5
             pathex=[],
             hiddenimports=[],
             hookspath=None,
             runtime_hooks=None)

pyz = PYZ(a.pure)

exe = EXE(pyz,
          a.scripts,
          exclude_binaries=True,
          name=exe_name,
          debug=False,
          strip=None,
          upx=True,
          icon= os.path.join(res_path, 'icon.ico'), #6
          console=False)

coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               Tree(res_path, 'res', excludes=['*.pyc']),   #7

               a.datas,
               strip=None,
               upx=run_upx,
               name=app_name)

if sys.platform.startswith('darwin'):   #8
    app = BUNDLE(coll,
                name='Avashell.app',
                appname=exe_name,
                icon=os.path.join(res_path, 'icon.icns'))  # 9

這個規格檔 , 將執行範例程式所需的程式碼以及相依程式庫打包成單一目錄 [1], 細節請參考 PyInstaller 文件 , 底下僅針對標記的地方重點說明 :

  1. 應用路徑名稱 , 如 :/avashell/... 中的 avashell。
  2. 執行檔的名稱 , 如 :/.../avashell.exe 中的 avashell.exe。
  3. 圖形等資源檔案路徑 , 需要另外加入打包後的目錄 , 參考第 7 點 。
  4. 如果是 Windows 平台 , 則執行檔名稱改為原先名稱加上 .exe, 在這個範例就是 avashell.exe
  5. avashell/launcher.py 是整個程式的進入點 ,PyInstaller 會分析這個模組以及它連結到的其他模組決定哪些程式庫需要涵括進來 。
  6. 就我所知 , 這個只有在 Windows 平台用到 , 設定執行檔的圖示 。 如此 , 執行檔在檔案總管便可顯示客製化後的圖示 。
  7. 這個是用來將整個子目錄涵括進打包後的目錄中 , 在這個例子 , 我們需要將圖示檔案涵括進來 。
  8. 如果是 OS X 平台 , 另外建立一個 OS X 的 app bundle, 可從 Finder 直接執行 。
  9. App bundle 使用的圖示檔 , 格式是 .icns

開始打包應用程式

雖然有跨平台支援 , 但是 PyInstaller 必須要在應用程式預計執行的平台上執行 , 也就是不能做到類似交叉編譯 (cross-compile) 的機制 。 底下小節 , 分別針對 Windows, OS X 以及 Ubuntu 環境 , 示範如何進行打包的準備以及建置工作 。 所有的指令 , 都假設在專案的根目錄執行 。

Windows 環境

執行環境組態

  • 作業系統 :Windows 7 Professional Edition 64-bit
  • Python 版本 : Anaconda Python 2.7.10 64-bit
  • PySide 版本 :1.2.2
  • PyInstaller 版本 : 2.1

安裝相依套件

  • 安裝 PyWin32

    Windows 比其它平台麻煩的一點 ,PyInstaller 需要 PyWin32 這個程式庫才能運作 。 到 這裏 下載安裝程式後 , 執行安裝 。

  • 安裝其他套件 執行 pip -r requirements.txt 安裝所需的 PySide 以及 PyInstaller 套件 。

產生發行包裹

執行下列指令來執行打包的動作 :

pyinstaller.exe avashell.spec --clean -y

其中 , --clean 告訴 PyInstaller 清除舊的資料 , -y 則是不用再次確認刪除 。 打包好的資料放在 dist/avashell 這個目錄中 。 在這個目錄應該要有個 avashell.exe 執行檔 。

執行時期截圖

下圖是範例程式在 Windows 下實際執行的截圖 :

Avashell screenshot

OS X 環境

執行環境組態

  • 作業系統 :OS X Yosemite 10.10.5
  • Python 版本 : 2.7.10 64-bit
  • PySide 版本 :1.2.2
  • PyInstaller 版本 : 2.1

這裏我們必須使用 Homebrew 來安裝另一個 Python 環境 , 而不使用系統內建的 Python。 使用 Anaconda 的 Python 發行包應該也行 , 但如果未來想使用 OS X 的 notification center 功能時 ,Anaconda 的版本無法正常運作 。

安裝相依套件

執行 pip -r requirements.txt 安裝所需的 PySide 以及 PyInstaller 套件 。

產生發行包裹

執行下列指令來執行打包的動作 :

pyinstaller avashell.spec --clean -y

除了 dist/avashell/ 這個目錄外 , 另外應該有一個 dist/Avashell.app 目錄 , 這個目錄是一個 App bundle, 可以執行下列指令或者由 Finder 圖形介面啟動 :

open dist/Avashell.app

執行時期截圖

下圖是範例程式在 OS X 實際執行時的截圖 :

Avashell screenshot

可以看到山姆鍋的名字 :-)

Ubuntu 環境

執行環境組態 :

  • 作業系統 :Ubuntu 14.04 Amd64
  • Python 版本 : 系統內建 ,2.7.6
  • PySide 版本 : 1.2.2
  • PyInstaller: 使用 GitHub 上的開發版本 。

使用 Anaconda 的 Python 發行包會發生托盤圖示無法顯示的問題 , 目前不清楚原因 , 也許是山姆鍋測試環境的問題 。

安裝相依性套件

使用 pip 安裝的 PyInstaller 所產生的發行包裹會發生 找不到 QtGui 模組 的錯誤 , 需要使用下列指令直接下載開發版本 :

git clone https://github.com/pyinstaller/pyinstaller.git pyinst

另外再使用下列指令安裝 PySide:

sudo pip install pyside==1.2.2

因為直接安裝在系統內建的 Python 環境 , 需要 root 權限 。 試過在 virtualenv 環境下進行 , 但遇到 PySide 無法安裝的問題 。

產生發行包裹

python pyinst/pyinstaller.py avashell.spec --clean -y

執行時期截圖

下圖是範例程式在 Ubuntu 14.04 桌面環境實際執行時的截圖 :

Avashell screenshot

結語

雖然有 PySide 這樣的跨平台圖形介面工具 , 但要寫真正能夠跨平台的程式還是有許多需要考慮的地方 。 即使像是本文所使用這樣一個簡單的桌面程式 , 如您所見 , 還是會遇到不同平台間相容性的問題 。

參考資料

PyInstaller: https://github.com/pyinstaller/pyinstaller

PyInstaller 文件 : http://pythonhosted.org/PyInstaller/


[1]PyInstaller 也支援打包成單一執行檔的模式 。