1. 應用程序打包技術之一(源代碼篇)
相信很多朋友都曾經為方便做某件事寫過自己的小程序(像我寫過的 casnet,sendsms),但很多怕都是藏在深山沒人識,最后不了了之,自己也把它們丟在角落里忘記了。
把這些小工具上傳到技術論壇或者 CSDN 下載頻道之類的網站,還是能收到一些關注的,而且還能攢積分和聲望。但是為什么不把它們發布出去呢?估計有幾個原因:源代碼太亂,編譯又挺復雜,不好意思給別人看;二進制程序包不會打,不知道該怎么發布。
因此我打算在本系列文章中分享一下我的程序打包經驗,目前來講計划有四篇:源代碼篇、deb 篇、rpm 篇和 exe 篇。這些技術在網上您都能找到,因為我也是從網上學習的,算是一個筆記和匯總吧。如果您有好的批評或建議,不妨在評論中指出,幫助我完善這篇文章。
源代碼篇
發行源代碼包是件最簡單的事情,因此也在最先介紹。有同學會說,打個壓縮包不就完了嘛。的確如此,但是在壓縮之前您也要有幾個注意事項:
1. 刪除版本管理目錄,比如 .svn,.cvs 之類的目錄。像 Subversion 版本管理軟件,會在每個目錄下都建立名為 .svn 的目錄,里面一般保存着目錄下文件的最新版本,可以用來 revert 修改。不刪除 .svn 目錄,會使源代碼包臃腫,而且最重要的是可能會有安全隱患。.svn 目錄下還包含您的用戶名和 SVN 服務器信息,可能您並不想讓別人知道。但是如果您網速夠快的話,可以重新 svn export 一份,而不是僅僅從您的 svn 樹上拷貝一份出來,那就沒有 .svn 目錄了。
2. 規整一下編譯過程,如果編譯過程不規范,您應該添加一個 README。如果您的代碼不是腳本,很可能是需要用戶編譯的。如果編譯過程規范,*nix(Linux/Unix, CygWin, Mingw 等) 下就是 ./configure, make, make install,用戶很容易理解。但如果編譯過程不規范,您就最好添加一下 README 或者 INSTALL 文件,指導用戶該如何編譯。使用 MS VisualStudio 的用戶要注意工程文件的整潔性,最好導出一個 Makefile(是的,VS 也可以用 Makefile),這樣用戶就不必打開項目,在 CMD 命令行用 nmake Makefile 就可以編譯了。
3. 刪除二進制中間文件。在 *nix 開發者中,這一般不難做到,Makefile 中一般都會寫一個 clean 目標;但是 MS VS 用戶一般不會注意那些編譯時生成的 .obj 文件。源代碼包就應該是源代碼,最多包含可執行程序和文檔,而不應該包含其它任何二進制的文件。否則您的源代碼包就會很大,而且對用戶也是困擾,到底哪些文件有用呢?
4. 修改編譯目標從 debug 版本到 release 版本。*nix 下,這一般意味着 CFLAGS 要改成 -O2 而不是 -g;VS 一般意味着將目標從 debug 轉為 release。這樣用戶生成的可執行程序才能足夠小和足夠快,他們如果有能力自己調試,會知道如何將選項改回去的。
5. 添加知識產權信息,就是授權協議。小程序大家一般都不在乎,但如果是您在這個項目上花了足夠的心血,還是最好選擇一個自己喜歡的授權協議。可以將協議聲明放在每個源文件的最前注釋中,也可以在項目的根目錄下放一個 license 文件。
6. 不要用 rar 包。在 Windows 下推薦使用 zip 格式壓縮;*nix 下推薦使用 .tar.gz 或者 .tar.bz2 格式。因為這些格式在這些系統上不需要安裝額外的軟件解壓。WinRAR 是一個商業軟件,而且 RAR 格式也是受版權保護的。
打包命令:
在 Windows 下,如果您使用開源軟件 7-zip 來壓縮 zip 包,可以使用這個命令(首先將 7-zip 可執行文件目錄添加到 PATH 環境變量中):
7z a foo.zip foo
*nix 下,就是下面幾個命令了:
tar czvf foo.tar.gz foo
tar cjvf foo.tar.bz2 foo
zip -r foo.zip foo
2. 應用程序打包技術之二(deb篇)
deb 是 Debian 系 Linux 使用的軟件包格式,也是我最欣賞的軟件包格式。我所知道的打 deb 軟件包的方法有兩種,一種是使用 checkinstall,另一種是使用 dpkg。
checkinstall 不僅僅可以用來打 deb 包,還可以打 rpm 和 tgz 包,而且使用方法相對簡單。但是 checkinstall 的運行不是那么穩定,我搞不懂它在什么情況下才能正常運行,而且它的定制性不是很強,使用時老是要交互地輸入些信息,所以我還是放棄了使用它來打包軟件。感興趣的朋友可以在網上搜索一下這個程序的使用方法。
dpkg 是 Debian 的“原生”包管理軟件,但是很多人不太願意使用 dpkg 來打包 deb。究其原因可能是需要寫麻煩的配置文件,但是寫配置文件的一個好處就是在下次打包時候可以直接用上次的配置文件,只修改一個版本號就可以了,而不用每次都需要填包信息。在介紹如何打 deb 包之前,我們現看一下如何解 deb 包。
$ sudo apt-get install tree
$ dpkg -X /var/cache/apt/archives/tree_1.5.1.1-1_i386.deb fakeroot
$ cd fakeroot
$ dpkg -e /var/cache/apt/archives/tree_1.5.1.1-1_i386.deb
$ tree
.
|-- DEBIAN
| |-- control
| `-- md5sums
`-- usr
|-- bin
| `-- tree
`-- share
|-- doc
| `-- tree
| |-- README
| |-- changelog.Debian.gz
| |-- changelog.gz
| `-- copyright
`-- man
`-- man1
`-- tree.1.gz
dpkg -X 是將 deb 包的內容文件釋放出來,dpkg -e 是將 deb 包的控制信息釋放出來。前面執行那個 sudo apt-get install tree 是為了將 tree_1.5.1.1-1_i386.deb 下載到本地 apt cache,如果您已經安裝過 tree 這個軟件,可以為 apt-get 加上 -d 參數,使其只下載而不安裝。
從上面 tree 命令的執行結果我們發現,deb 包解開后分兩部分:一部分是控制信息,在 DEBIAN 目錄下;一部分是安裝內容,在 usr 目錄下。現在您大概明白為什么我們使用 fakeroot 作為目錄名了,因為這個目錄就是一個"假根目錄",您在這個目錄下所有的修改,最后都會被映射到目標機的根目錄 / 下。比如 fakeroot/usr/bin/tree 這個文件,就會被安裝到 /usr/bin 下,以此類推。
只要您能理解 fakeroot 這個目錄映射,您就知道如何安放自己的文件了。為了讓生成的包將文件 foo 安裝到目錄 /usr/xx/yy 目錄下,您只用在 fakeroot 目錄下建立 usr/xx/yy 目錄,並將 foo 拷貝進去就行了。
好,下面進入關鍵的配置文件部分,關於 control 和 md5sums。
$ more DEBIAN/control
Package: tree
Version: 1.5.1.1-1
Architecture: i386
Maintainer: Ubuntu MOTU Developers
Original-Maintainer: Florian Ernst
Installed-Size: 92
Depends: libc6 (>= 2.6-1)
Section: utils
Priority: optional
Description: displays directory tree, in color
Displays an indented directory tree, using the same color assignments as
ls, via the LS_COLORS environment variable.
.
Homepage: http://mama.indstate.edu/users/ice/tree/
我們可以看到,control 文件中包含的主要是軟件的版本和維護者信息,我相信大家都能基本看懂上面這些信息什么意思:Package 包名(tree)、Version 版本(1.5.1.1-1)、Architecture 目標機架構(i386 386及以后)、Maintainer 維護者(Ubuntu MOTU Developers)、Original-Maintainer 原維護者(Florian Ernst)、Installed-Size 安裝后大小(92K)、Depends 依賴軟件包(libc6 不低於 2.6-1 版本)、Section 包分類(工具)、Priority 優先級(可選)、Description 包描述、Homepage 軟件主頁。
由於咱們分析這個包是 Ubuntu 發布的包,所以包信息給的比較全,其實並不是上面所有的信息都有必要提供(小聲說一句,就算全提供也不是很難吧?除了咱不用的,Original-Maintainer 這種就算了)。關於哪些信息比較重要,以及每個域的具體含義和可選項,可以參考 Debian 的文檔 Debian Policy Manual Chapter 5 - Control files and their fields 。
您也可以依樣畫葫蘆,寫一個類似的 control 文件放到 DEBIAN 目錄下,提供一些自己軟件包的信息,基本有這個配置文件就可以打包了。
$ more DEBIAN/md5sums
d60a3b4736f761dd1108cb89e58b9d4e usr/bin/tree
981ea0343c2a3eb37d5fc8b5ac5562df usr/share/man/man1/tree.1.gz
483a56158a07a730ec60fc36b3f81282 usr/share/doc/tree/README
ea56d78ae0d54693ae8f3c0908deeeff usr/share/doc/tree/copyright
4456e04c3c268eabcd10ee9b949a9b9a usr/share/doc/tree/changelog.gz
ec104db6914cfce2865a0d8c421512bb usr/share/doc/tree/changelog.Debian.gz
md5sums,這文件名一看,就知道是保存着軟件包中各文件的 md5 校驗值,用來校驗軟件包是否被損壞了。其實這個文件純屬“臘月三十逮兔子,有它沒它都過年”,您可以完全不提供它。
這樣呢,我們就准備好了 deb 包的內容文件和控制信息:控制文件放在了 fakeroot/DEBIAN 目錄下,內容文件放在 fakeroot/usr 下,目錄樹就像開頭 tree 命令的結果。下面只需要一個命令就能打出來 deb 安裝包了:
$ cd ..
$ dpkg -b fakeroot/ foo.deb
這時候當前目錄下就出現了 foo.deb。您可以使用 dpkg -I foo.deb 查看 foo.deb 的控制信息,dpkg -c foo.deb 查看 foo.deb 包含了什么文件,sudo dpkg -i 安裝 foo.deb。
小技巧:
1. 如果您懶得自己新建一個控制文件和目錄樹,您完全可以像本文開頭那樣,找一個簡單的軟件包,將它的內容和控制信息釋放出來,對它進行修改,然后打出來自己的包。
2. 生成 md5sums 文件不是什么難事,您只需要在 fakeroot 目錄使用下面這個命令:
$ md5sum `find usr -type f` > DEBIAN/md5sums
或者
$ find usr/ -type f -exec md5sum {} + > DEBIAN/md5sums
3. 將您的可執行文件拷貝到 fakeroot/usr 下並不一定要手動一個個拷。如果您使用 GNU 自動工具集,./configure 時加個參數 --prefix=fakeroot/usr/ 即可;如果您自己寫的 Makefile,可以在 Makefile 中使用一個變量 PREFIX=/usr,當您不加參數時,make install 的安裝目標就是 /usr 下,您可以使用 Makefile -e PREFIX=fakeroot/usr/ install 來覆蓋 Makefile 中的變量設置。
3. 應用程序打包技術之三(rpm 篇)
rpm 是 RedHat 系 Linux 使用的軟件包格式。流行的 Linux 發行版:Fedora, RHEL, OpenSUSE, Oracle 包括國產的紅旗 Linux,都采用 rpm 來管理軟件包。
我不是很喜歡 rpm 軟件包格式,原因主要有兩個,一個是它的依賴關系很難處理,另一個是控制文件比較復雜。但是 rpm 包有着非常廣泛的應用,也是一個提高生產力的重要工具。
像前一篇文章提到的一樣,checkinstall 也可以用來打 rpm 包,但我不是很熟悉這個軟件。這里我僅僅介紹如何使用原始的 rpm(rpmbuild) 工具來打 rpm 軟件包。
和前面 deb 包使用 fakeroot 一樣,rpmbuild 也是利用沙盒的方式來構建軟件包,除了控制文件比較繁瑣,過程可能還更自動一些。構建 rpm 包的工作目錄默認是 /usr/src/redhat,如果您在 Debian 下安裝了 rpm 這個軟件包,它可能是 /usr/src/rpm。您可以使用 rpm --eval %_topdir 查看自己的 rpm 工作目錄。
$ tree `rpm --eval %_topdir`
/usr/src/redhat
|-- BUILD
|-- RPMS
| |-- athlon
| |-- i386
| |-- i486
| |-- i586
| |-- i686
| `-- noarch
|-- SOURCES
|-- SPECS
`-- SRPMS
我們可以看到,/usr/src/redhat/ 下有幾個子目錄:SOURCES 用來存放源代碼包,SPECS 用來存放 spec 控制文件,BUILD 用來解壓源代碼包和構建軟件,RPMS 里存放的是打好的二進制應用程序 RPM 包,SRPMS 里存放的是打好的源代碼 RPM 包。但是,我們應該知道 _topdir 是可以在 spec 文件中修改的,否則我們只能用 root 才能在默認的工作目錄 /usr/src/redhat 下創建文件和執行命令。我們可以先把工作目錄樹拷貝到用戶自己的目錄中:
$ cp -r /usr/src/redhat ~/rpm
這樣,我們就可以在用戶目錄 ~/rpm 下打 rpm 包了。打 rpm 包之前的准備工作只有兩件:1. 准備好源代碼包並放置在 SOURCES 目錄下;2. 准備好 spec 控制文件並放在 SPECS 目錄下。如何打源代碼包我們已經在 《源代碼篇》中介紹過,下面我們主要來介紹 spec 文件的格式。下面是一個實例 spec 文件。
$ more ~/rpm/SPECS/casnet.spec
%define _topdir /home/solrex/rpm
Name: casnet
Version: 1.3
Release: 1
License: GPL
Packager: Solrex Yang
Summary: CASNET Client
Group: Network
Source: %{name}-%{version}.tar.gz
URL: http://share.solrex.cn/casnet/
Prefix: /usr%description
CASNET is a gui client for ip gateway of GUCAS(Graduate University of Chinese
Academy of Sciences), which is written in Python and PyGtk.%prep
%setup -q%build
%install
make -e PREFIX=%{prefix} install%files
%{prefix}/bin/casnetconf
%{prefix}/bin/casnet
%{prefix}/bin/casnet-gui
%{prefix}/share/casnet/casnetconf.py
%{prefix}/share/casnet/casnet.py
%{prefix}/share/casnet/casnet-gui.py
%{prefix}/share/casnet/pics/*.png
%{prefix}/share/applications/casnet.desktop
%{prefix}/share/icons/casnet.png
我們可以看到,spec 文件與 deb 包的 control 文件有很多相似之處:Name, Version, Release, License, Packager, URL 是軟件的名稱、版本、小版本、使用的協議、維護者和網址;Summary 和 %description 是軟件的信息;Group 是軟件所歸類別。剩下的就有些不同了:Source 是指軟件的源代碼包名稱,rpm 會到 SOURCES 目錄下找這個包;Prefix 是軟件的默認安裝位置;%prep、%build、%install 下分別是軟件的預處理、編譯和安裝命令;%files 是所有需要被打包進去的文件。
因此,作者需要將 spec 文件中的各個域填寫正確。%prep 下的 %setup 宏一般是用來將源代碼 tar 包釋放到 BUILD 目錄下;%build 下可以添加 %configure 宏和 make 命令,如果您的軟件需要編譯的話;%install 下是您的安裝命令;%files 下是您程序運行所需要的全部文件,其路徑即為您安裝完軟件后它應該在的位置。%{name} 可以用來指代 Name: 域的內容,%{version } 指代 Version: 域的內容,以此類推。
需要注意的是,rpmbuild 使用 BUILD/%{name}-%{version} 作為您的源代碼包釋放后的目錄,進入其進行編譯。因此當您使用 tar 打源代碼包時,tar 包的頂層目錄名應該為 %{name}-%{version} 而不是僅僅是 %{name},比如:
$ tar czvf casnet-1.3.tar.gz casnet-1.3
$ mv casnet-1.3.tar.gz ~/rpm/SOURCES
現在我們在 ~/rpm/SOURCES 下有了 casnet-1.3.tar.gz,在 ~/rpm/SPECS 目錄下有了 casnet.spec,那么我們就可以使用下面的命令生成 rpm 包了。如果您的安裝位置 Prefix 選擇的是一個系統目錄,比如 /usr, /usr/local 之類,您也許需要使用 root 權限來運行 rpmbuild 命令。
$ rpmbuild -ba ~/rpm/SPECS/casnet.spec
這樣我們就能在 ~/rpm/RPMS/i386 目錄下獲得我們生成的 rpm 軟件包了。rpmbuild 命令的執行過程是這樣的:首先根據 spec 文件定位源代碼包,然后將源代碼包釋放到 BUILD 目錄下,使用 spec 文件中給出的命令編譯和安裝,然后將 spec 中列的文件提取出來,按照包信息打出來 rpm 包。
我們可以使用 rpm -qpi 命令來查看 rpm 包的信息,rpm -qpl 命令來查看 rpm 包所包含的文件列表:
$ rpm -qpi ~/rpm/RPMS/i386/casnet-1.3-1.i386.rpm
Name : casnet Relocations: /usr
Version : 1.3 Vendor: (none)
Release : 1 Build Date: Thu 26 Feb 2009 08:03:50 PM CST
Install Date: (not installed) Build Host: laptop
Group : Network Source RPM: casnet-1.3-1.src.rpm
Size : 40883 License: GPL
Signature : (none)
Packager : Solrex Yang
URL : http://share.solrex.cn/casnet/
Summary : CASNET Client
Description :
CASNET is a gui client for ip gateway of GUCAS(Graduate University of Chinese
Academy of Sciences), which is written in Python and PyGtk.$ rpm -qpl ~/rpm/RPMS/i386/casnet-1.3-1.i386.rpm
/usr/bin/casnet
/usr/bin/casnet-gui
/usr/bin/casnetconf
/usr/share/applications/casnet.desktop
/usr/share/casnet/casnet-gui.py
/usr/share/casnet/casnet.py
/usr/share/casnet/casnetconf.py
/usr/share/casnet/pics/casnet.png
/usr/share/casnet/pics/offline.png
/usr/share/casnet/pics/online.png
/usr/share/icons/casnet.png