用 RPM 打包軟件,第 3 部分

安裝和卸載腳本的工作原理

安裝和卸載腳本看起來很簡單,但它們工作原理中的一些意外可能會引起大問題。

這裏是一些基本信息。可以將下列四節中的任意一個添加到 .spec 文件, 它列出了在您的包安裝期間各個點上運行的 shell 腳本:

%pre
在安裝包之前運行
%post
在安裝包之後運行
%preun
在卸載包之前運行
%postun
在卸載包之後運行

尤其要注意 %install 與這些節之間的差異。構建 RPM 時, %install 在開發機器上運行; 它應該將產品安裝在開發機器上或安裝到一個構建根目錄中。 另一方面,這些節指定當用戶正在安裝或卸載您的 RPM 包時將在 用戶的機器上運行什麼。

這裏有一個示例,是在前文基礎上建立的。我們要使用 install-info 將 GNU indent 的 info 文件添加到目錄中。


清單 1. indent-4.spec
# Simplistic example of install scripts - do not use
Summary: GNU indent
Name: indent
Version: 2.2.6
        
          Release: 4
        
Source0: %{name}-%{version}.tar.gz
License: GPL
Group: Development/Tools
BuildRoot: %{_builddir}/%{name}-root
%description
The GNU indent program reformats C code to any of a variety of
formatting standards, or you can define your own.
%prep
%setup -q
%build
./configure
make
%install
rm -rf $RPM_BUILD_ROOT
make DESTDIR=$RPM_BUILD_ROOT install
        
          %post
        
if [ -x /sbin/install-info ]; then
  /sbin/install-info /usr/local/info/indent.info /usr/local/info/dir
fi
%preun
if [ -x /sbin/install-info ]; then
  /sbin/install-info --delete /usr/local/info/indent.info /usr/local/info/dir
fi
%clean
rm -rf $RPM_BUILD_ROOT
%files
%defattr(-,root,root)
/usr/local/bin/indent
%doc /usr/local/info/indent.info
%doc %attr(0444,root,root) /usr/local/man/man1/indent.1
%doc COPYING AUTHORS README NEWS
      

請注意,在嘗試使用 install-info 工具之前,首先對它進行檢查。我們不希望只是因爲我們不能提供至產品文檔的鏈接而使安裝失敗。

但也有可能在某種情況下您希望安裝過程失敗。一種好的技術是使用 %pre 腳本來檢查安裝前提條件,它們比 RPM 可以直接支持的更復雜。 如果不符合前提條件,那麼腳本以非零狀態退出,而且 RPM 不會繼續安裝。

另外請注意,我們必須小心地使用卸載腳本來撤銷安裝腳本。

沒有那麼簡單:升級使每件事情都變得複雜

現在,讓我們着手升級。如果用戶只安裝和刪除您自己的包,那麼前面節中的指令將正常工作;但在升級期間,它們會完全失效。

以下是 RPM 如何執行升級:

  • 運行新包的 %pre
  • 安裝新文件
  • 運行新包的 %post
  • 運行舊包的 %preun
  • 刪除新文件未覆蓋的所有舊文件
  • 運行舊包的 %postun

如果我們使用前面的示例來升級,那麼 RPM 最後將運行 %postun 腳本, 它將除去我們在安裝腳本中所做的所有工作!使用 RPM 的一般開發人員可能不會想到這一點。 我不會嘗試解釋其原因,只是解釋您必須爲此做點什麼。

相當幸運的是,在一定程度上,腳本有一種方法可以告之是否正在安裝、刪除或升級包。每個腳本都被傳遞單一命令行參數 ― 一個數字。 這應該告訴腳本 在當前包完成安裝或卸載之後將安裝多少個包的副本

只查看在各種情況下傳遞的值或許更容易,而不是嘗試計算它。

這裏是在安裝期間傳遞的實際值:

  • 運行新包的 %pre (1)
  • 安裝新文件
  • 運行新包的 %post (1)

這裏是在升級期間傳遞的值:

  • 運行新包的 %pre (2)
  • 安裝新文件
  • 運行新包的 %post (2)
  • 運行舊包的 %preun (1)
  • 刪除新文件未覆蓋的任何舊文件
  • 運行舊包的 %postun (1)

這裏是在刪除期間傳遞的值:

  • 運行舊包的 %preun (0)
  • 刪除文件
  • 運行舊包的 %postun (0)

可以通過將類似下例的一些東西添加到您的包中來自己測試它。 然後創建一個帶稍高發行版號的新包,安裝第一個,然後升級到第二個,最後卸載它,以查看所有可能性。 當然,在信任的公共社區上發佈任何 RPM 之前,您總是要對它進行幾次這樣的嘗試。


清單 2. 腳本執行的測試順序和參數
%pre
echo This is pre for %{version}-%{release}: arg=$1
%post
echo This is post for %{version}-%{release}: arg=$1
%preun
echo This is preun for %{version}-%{release}: arg=$1
%postun
echo This is postun for %{version}-%{release}: arg=$1

這裏是另一個示例,這次正確地處理升級過程:


清單 3. indent-5.spec
Summary: GNU indent
Name: indent
Version: 2.2.6
        
          Release: 5
        
Source0: %{name}-%{version}.tar.gz
License: GPL
Group: Development/Tools
BuildRoot: %{_builddir}/%{name}-root
%description
The GNU indent program reformats C code to any of a variety of
formatting standards, or you can define your own.
%prep
%setup -q
%build
./configure
make
%install
rm -rf $RPM_BUILD_ROOT
make DESTDIR=$RPM_BUILD_ROOT install
%post
        
          if [ "$1" = "1" ] ; then  # first install
        
 if [ -x /sbin/install-info ]; then
   /sbin/install-info /usr/local/info/indent.info /usr/local/info/dir
 fi
        
          fi
        
%preun
        
          if [ "$1" = "0" ] ; then # last uninstall
        
 if [ -x /sbin/install-info ]; then
   /sbin/install-info --delete /usr/local/info/indent.info /usr/local/info/dir
 fi
        
          fi
        
%clean
rm -rf $RPM_BUILD_ROOT
%files
%defattr(-,root,root)
/usr/local/bin/indent
%doc /usr/local/info/indent.info
%doc %attr(0444,root,root) /usr/local/man/man1/indent.1
%doc COPYING AUTHORS README NEWS
      

現在,僅當完全刪除這個包時纔會除去 info 鏈接。

觸發器 — 在安裝或卸載其它包時運行腳本

假設在安裝或卸載 其它包時要運行您包中的一些代碼。 可以用 觸發器(trigger)腳本來完成這一任務。

您爲什麼要這樣做?通常是因爲您的包使用一個或多個其它包的服務,或者提供服務給一個或多個其它包。

這裏有一個示例。假設您正在爲 Emacs 和 Xemacs 編輯器打包一個極好的附加工具。它可以與其中任何一個或兩個編輯器一起工作, 但根據所安裝的編輯器,需要做一些少量的配置。

安裝時,可以對 Emacs 和 Xemacs 進行測試,並配置您的工具以使可用編輯器可以訪問它。 但是,如果用戶稍後安裝 Xemacs,那麼會發生什麼情況呢?您的工具在 Xemacs 中不可用,除非用戶卸載並重新安裝您的工具。 如果您的包可以告訴 RPM,“讓我知道是否安裝了 Xemacs”,這是否會更好呢?

這是觸發器腳本的思想。可以將它添加到 .spec 文件中:


清單 4. 觸發器示例
%triggerin -- emacs
# Insert code here to run if your package is already installed,
# then emacs is installed,
# OR if emacs is already installed, then your package is installed
%triggerin -- xemacs
# Insert code here to run if your package is already installed,
# then xemacs is installed,
# OR if xemacs is already installed, then your package is installed
%triggerun -- emacs
# insert code here to run if your package is already installed,
# then emacs is uninstalled
%triggerun -- xemacs
# insert code here to run if your package is already installed,
# then xemacs is uninstalled
%postun
# Insert code here to run if your package is uninstalled

觸發器腳本被傳遞了 兩個參數。第一個參數是當觸發器腳本完成運行時將安裝的 您的包的實例數。第二個參數是當觸發器腳本完成運行時將安裝的 要觸發的包的實例數。

這裏是 RPM 升級期間腳本執行和文件安裝及卸載的完整順序,它來自 RPM 分發版中的 triggers 文件:


清單 5. 腳本順序
 new-%pre  for new version of package being installed
  ...       (all new files are installed)
  new-%post for new version of package being installed
  any-%triggerin (%triggerin from other packages set off by new install)
  new-%triggerin
  old-%triggerun
  any-%triggerun (%triggerun from other packages set off by old uninstall)
  old-%preun    for old version of package being removed
  ...       (all old files are removed)
  old-%postun   for old version of package being removed
  old-%triggerpostun
  any-%triggerpostun (%triggerpostun from other packages set off by old un
        install)

高級腳本編制

備用解釋器

通常,所有安裝時腳本和觸發器腳本都是使用 /bin/sh shell 程序運行的。如果您更喜歡另一個腳本語言,比方說 Perl,那麼可以通過將 -p interpreter 添加到腳本行來告訴 RPM 應該使用另一種解釋器運行您的腳本。例如:


清單 6. 備用解釋器示例
%post -p /usr/bin/perl
# Perl script here
%triggerun -p /usr/bin/perl -- xemacs
# Another Perl script here

請注意,這 適用於 RPM 的構建時腳本,如 %install 。

RPM 變量

RPM 在將 RPM 變量存儲到 RPM 包文件之前先在您的腳本中擴充它們,有時候這是有用的。 例如,可以在 .spec 文件頂部附近定義您自己的參數,然後在整個 .spec 文件 ― 甚至在您的腳本中使用 %{variable_name} 引用它們:


清單 7. RPM 變量示例
...
%define foo_dir /usr/lib/foo
...
%install
cp install.time.message $RPM_BUILD_ROOT/%{foo_dir}
%files
%{foo_dir}/install.time.message
%post
/bin/cat %{foo_dir}/install.time.message

要避免的事情

您可能會在安裝時試圖做一些事情,但結果會證明這是一個壞主意。 例如,與用戶交互的任何嘗試或許不能很好地工作。RPM 被設計成在無需用戶出現的情況下允許進行批處理安裝。 如果在安裝期間 RPM 包停下來並提出問題,而沒有人看到這個問題,那麼安裝將一直掛起。

您可能要避免的另一件事情是啓動任何服務。 在完整安裝期間,您不能確定程序所需的每樣東西是否已經在那裏(例如,可能還沒有任何網絡); 另外,如果在完整操作系統安裝期間每個 RPM 服務都嘗試啓動,那麼整個安裝過程大概會花很長時間。 這種情況下您可以做的就是打印消息,告訴用戶有關任何所需配置或需要啓動的服務的信息。 如果用戶正在手工安裝您的 RPM 包, 那麼他或她將看到這些消息;如果它是較大批處理安裝的一部分,那麼它不會損害任何東西, 機器幾乎肯定在結束時重新引導,啓動您的服務。

要記住的事情

如果您的包安裝了 init 腳本,則可以使用 chkconfig 來安排將在適當運行級別上啓動和停止的服務。 雖然可以通過將必需的符號鏈接直接安裝爲包的一部分來實現同一件事情, 但要使它們恢復正常會有很多麻煩,您可能寧願使用 chkconfig 。

爲了安全起見,許多服務在一個特定的用戶標識下運行; 如果您的服務是這樣的話,當系統上不存在該用戶時,您需要在系統上創建它。

如果您的包安裝了任何 GNU info 文件,那麼在 Info 目錄中將看不到它們,除非在安裝時使用 install-info 工具添加它們。

當然,在卸載之前,必須試圖停止您的包可能正在運行的任何服務(但如果服務不在運行,請確保卸載不會失敗)。

當然,在卸載時,應該將您在安裝時可能對系統做的大多數更改恢復成原來狀態。 但稍微考慮一下您的操作;例如,卸載 RPM 包時不應該意外地刪除任何用戶創建的文件。 所以,請不要嘗試除去用戶標識或刪除整個目錄樹,這樣可能會好一些。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章