使用 Python 實現跨平臺的安裝程序

引言
我們在使用類 Unix 系統時,經常會用到一些以“.bin”或者“.run”結尾的安裝程序 (Installer)。(爲描述方便,這裏我們使用“Bin 安裝程序”來泛指這種安裝程序。)Bin 安裝程序不依賴於系統發行版自己的包 (package) 管理器來實現應用程序的安裝和卸載,而是完全自己控制安裝的整個過程,程序卸載的時候需要用戶執行應用程序安裝目錄下的卸載腳本來完成。
Bin 安裝程序最大的好處就是可以運行在多種類 Unix 平臺,以及基於相同核心的多個發行版上,而不需要關心繫統使用何種包管理器。在一定程度上實現了跨平臺。
但是,非常遺憾的是,這種安裝程序不能用在 Windows 平臺上。Windows 平臺上的安裝程序需要特別的製作。這個是由 Bin 安裝程序本身使用類 Unix 平臺通用的 Shell 腳本來實現整個安裝過程的引導和控製造成的。
好消息是,現在我們有了一種強大的,可以同時運行在類 Unix 平臺和 Windows 平臺的腳本 -Python。隨着 Python 的普及,越來越多的系統整合開始基於 Python 來完成,使得現在很大一部分類 Unix 平臺都默認部署了 Python 的運行環境。即使沒有默認安裝,用戶在安裝其它應用的時候可能也都安裝了 Python。
所以,我們能不能使用 Python 來實現一個可以在類 Unix 平臺和 Windows 平臺都通用的安裝程序呢?這個安裝程序又如何來實現呢?本文將和大家探討這個問題,並提供一個解決這個問題的思路。本文拋開被安裝程序的跨平臺能力,僅討論安裝程序本身的跨平臺特性和實現方法。
本文首先分析了 Bin 安裝程序的結構和工作原理 , 然後介紹如何應用 Python 實現類似功能 , 並對 Python 實現的侷限性以及可能的解決方案進行了探討。
回頁首
Bin 安裝程序的執行過程
Bin 安裝程序在運行的最初階段會提供一些嚮導界面 , 向用戶提供關於被安裝產品的相關信息 , 並引導用戶輸入安裝程序需要的配置信息 . 安裝程序獲取了需要的配置信息後 , 進入具體的安裝階段。
在安裝階段 , Bin 安裝程序首先展開一些包,這些包中包含一些安裝程序自身所依賴的庫和安裝程序的配置信息,以及將要安裝的用戶應用程序。
事實上,這些包被展開的過程分爲兩個階段。在第一個階段,安裝程序將這些包的壓縮文件從安裝程序自身中分離出來,生成單獨的壓縮文件。然後在第二階段,使用解壓縮工具將這些壓縮文件解壓,並設置需要的系統環境變量。最基本的是 PATH 和 JAVA_HOME 這兩個變量。
在這之後,安裝程序讀取安裝配置,取得用戶自定義的安裝腳本(pre-install, post-install),按照既定的順序執行它們。
最後,安裝程序將安裝過程中記錄下來的被安裝的文件列表和卸載程序模板,以及用戶自定義的卸載腳本(pre-uninstall, post-uninstall)合併,生成卸載程序,並放入用戶應用程序的安裝目錄。
回頁首
Bin 安裝程序的實現分析
那麼,Bin 安裝程序是如何實現這整個過程的控制的,安裝程序文件本身又是一個什麼樣的結構呢?我們接下來進行分析。
Bin 安裝程序本身其實是一個包含二進制數據的 Shell 腳本。簡單的安裝程序可以由一段腳本代碼加上一個壓縮包的二進制數據構成。複雜的可能會包含多段二進制數據。
文件的基本結構如下圖


圖示 1:Bin 文件結構示意圖
 
知道了文件結構之後,我們就要看看安裝程序是怎麼將自身文件內部的二進制數據分離出來了。其實這個有很多種方法實現。最基本的方法可以使用 tail 命令,或者使用 sed 命令。複雜些的可以使用讀取文件的方法。
接下來,我們用一個小范例來演示這個過程。
首先,隨便找些文件放到一個文件夾中,假設這個文件夾叫“app”。
然後,使用壓縮工具將這個文件夾壓縮成“app.tar.gz”文件。
接下來,我們準備一個簡單的安裝腳本。這個腳本僅僅將二進制文件分離出來,並在當前目錄下解壓。


清單 1:安裝腳本示例

 #!/usr/bin/env bash 


 echo 'Spliting binary data to app.tar.gz ...'
 sed -n -e '1,/^BINARY$!p' $0 > 'app.tar.gz'


 if [ -f “app.tar.gz” ]; then 
    echo 'Extracting app.tar.gz ...'
    tar xvf 'app.tar.gz' >/dev/null 2>&1 
 fi 


 echo “Finiahed”
 exit 0 
 BINARY


將這個腳本保存爲 install.sh。
最後,我們把之前做好的 app.tar.gz 文件和這個腳本合併,生成安裝程序


清單 2:合併腳本和 bin 文件

 $ cat install.sh app.tar.gz > app.bin


這樣這個 Bin 安裝程序就做好了。然後可以通過如下命令來測試這個安裝程序的執行情況。


清單 3:執行 app.bin

 $ chmod u+x app.bin 
 $ ./app.bin


如果一切正常的話,將會看到如下顯示,並且當前目錄下會出現“app.tar.gz”和名字爲“app”的文件夾。


清單 4:命令行輸出

 Spliting binary data to app.tar.gz ... 
 Extracting app.tar.gz …
 Finished


這樣一來,Bin 安裝程序最核心的問題就解決了,接下來,只需要在腳本中添加設置環境變量,讀取配置文件,加載依賴庫,執行安裝拷貝過程就可以了。
回頁首
使用 Python 實現跨平臺的安裝程序
根據上面的分析,我們知道,我們至少要在這個安裝程序中包含兩部分內容:
使用 Python 實現的安裝控制腳本
壓縮包的二進制內容
問題來了。Python 文件中不能直接包含二進制數據。Python 的編譯過程會將二進制數據中的一些內容誤認爲是一些奇怪的文字而產生編譯錯誤。
那麼我們該如何將壓縮包放入 Python 的腳本呢?
其實,我們可以使用 Base64 編碼。這種編碼將二進制數據轉換成使用 ASCII 字符表示的文本信息。而我們只要將這些文本信息作爲字符串的內容保存在 Python 腳本中就可以了。
Python 在它的標準安裝包中提供了這樣的編碼庫。這樣就不會產生需要額外依賴庫的問題了。
接下來我們通過一個範例來展示一下如何使用 Python 來實現上文範例中所實現的功能。
首先,我們創建一個叫 app 的目錄,在這個文件夾中添加一個包含以下內容的 README 文件。


清單 5:README 文件內容

這是一個安裝包的範例展示


將 app 文件夾壓縮成 app.tar.gz
然後,編寫 Python 的安裝控制腳本。內容如下:


清單 6:install.py

 if __name__ == '__main__': 
    import os 
    import base64 


    print 'Spliting binary data to app.tar.gz ...'
    open('tmp.base64', 'w').write(get_data()) 
    base64.decode(open('tmp.base64', 'r'), 
                       open('app.tar.gz', 'w')) 
    os.remove('tmp.base64') 
    os.mkdir('temp') 
    os.system('tar -zxvf app.tar.gz -C temp')


將這段代碼保存爲 install.py。
接下來,我們需要把 app.tar.gz 文件轉換成 Base64 編碼。我們可以通過如下 Python 代碼來實現這個轉換


清單 7:使用 Python 生成 Base64 數據

 import base64 
    base.encode(open('app.tar.gz', 'r'), 
                open('app.tar.gz.base64', 'w'))


輸出的 app.tar.gz.base64 文件中包含了描述這個壓縮包的 Base64 編碼。
接下來有兩個問題:
我們不能直接把這個文件和 install.py 合併。因爲單純的字符串在 Python 腳本中沒辦法訪問。所以我們需要作些加工。加工的方法可以是聲明一個叫做“data”的變量,也可以定義一個叫“get_data”的方法。本範例我們採用後者。
我們不能簡單的將包含 get_data 方法的文件直接 cat 到 install.py 的結尾。因爲對於 __main__ 來說,這個方法沒有聲明。所以我們需要把 get_data 方法添加到 __main__ 的前面。
這兩步過程我們可以通過下面這段 Python 代碼來處理


清單 7:合併 install.py 和 Base64 數據文件

 # create data template 
 data_template = [] 
 data_template.append('#!/usr/bin/env python\n') 
 data_template.append('def get_data():\n') 
 data_template.append('\treturn \'') 
 for line in lines: 
    data_template.append('%s\\\n' % line.strip()) 
 data_template.append('\'\n') 


 # create app.bin 
 install_temp = open('install.py', 'r').readlines() 


 app_bin = open('app.bin', 'w') 


 for line in data_template: 
    app_bin.write(line) 
 app_bin.write('\n') 


 for line in install_temp: 
    app_bin.write(line) 


 app_bin.flush() 
 app_bin.close()


這樣最終我們需要的 app.bin 文件就做好了,運行的效果和之前使用 Shell 腳本的 app.bin 是一樣的。
對於 Windows 平臺來說,這個範例只需要將 app 文件夾壓縮成 app.zip,解壓的命令“tar”替換成 winzip 之類的工具即可。實際應用中,可以使用 Python 提供的“zipfile”這個包提供的接口來完成。這樣就不牽扯平臺相關的命令了。這裏不對 zipfile 包做描述。
回頁首
更多的事情
代碼保密
通過上面方法制作的 app.bin 文件實際上是一個純文本的文件。用戶可以使用任何文本編輯器對它進行編輯。
如果不希望用戶編輯文件中的內容,可以使用 Python 提供的”py_compile”包來實現。
修改後的代碼如下:


清單 8:編譯安裝文件

 # create data template 
 data_template = [] 
 data_template.append('#!/usr/bin/env python\n') 
 data_template.append('def get_data():\n') 
 data_template.append('\treturn \'') 


 lines = open('app.tar.gz.base64', 'r') 
 for line in lines: 
    data_template.append('%s\\\n' % line.strip()) 
 data_template.append('\'\n') 


 # create app.bin 
 install_temp = open('install.py', 'r').readlines() 


 app_bin = open('app.py', 'w') 


 for line in data_template: 
    app_bin.write(line) 
    app_bin.write('\n') 


    for line in install_temp: 
        app_bin.write(line) 


    app_bin.flush() 
    app_bin.close() 


    py_compile.compile('app.py') 
    os.rename('app.pyc', 'app.bin')


註冊表訪問
對於 Windows 平臺,安裝程序可能需要訪問 Windows 的註冊表。Python 提供了一個 _winreg 的庫可以完成這方面的操作。
侷限與可能的解決方案
對於沒有安裝 Python 的系統,如同現在很多的 Bin 安裝程序在使用前需要安裝 Java 環境一樣, 使用 Python 製作的安轉程序之前需要先安裝 Python。對於用戶來說可能會感覺比較不適。對於 Windows 平臺,這個問題可以通過類似 py2exe 的方式,將安裝包依賴的 Python 庫封裝到安裝包自己內部來解決。
發佈了59 篇原創文章 · 獲贊 2 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章