標籤:
原文地址:http://blog.solrex.cn/articles/packaging-1-src.html
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
Linux應用程式打包