詳解如何用源代碼安裝軟件,如何卸載它?
Linux 發行版的一個最大的優點就是它的包管理器和相關的軟件庫。通過它們提供的資源和工具,你才能夠以完全自動化的方式在你的計算機上下載和安裝軟件。
但是,盡管付出了很多的努力,包維護者仍然沒法照顧好每種情況,也不可能將所有的可用軟件都打包進去。因此,仍然存在需要你自已去編譯和安裝一個新軟件的情形。對于我來說,到目前為止,最主要的原因是,我編譯一些軟件是我需要去運行一個特定的版本。或者是我想去修改源代碼或使用一些想要的編譯選項。
如果你也屬于后一種情況,那你已經知道你應該怎么做了。但是,對于絕大多數的 Linux 用戶來說,第一次從源代碼中編譯和安裝一個軟件看上去像是一個入門儀式:它讓很多人感到恐懼;但是,如果你能克服困難,你將可能進入一個全新的世界,并且,如果你做到了,那么你將成為社區中享有特權的一部分人。
A. 在 Linux 中從源代碼開始安裝軟件
這正是我們要做的。因為這篇文章的需要,我要在我的系統上安裝 NodeJS 8.1.1。它是個完全真實的版本。這個版本在 Debian 倉庫中沒有:
sh$ apt-cache madison nodejs | grep amd64
nodejs | 6.11.1~dfsg-1 | http://deb.debian.org/debian experimental/main amd64 Packages
nodejs | 4.8.2~dfsg-1 | http://ftp.fr.debian.org/debian stretch/main amd64 Packages
nodejs | 4.8.2~dfsg-1~bpo8+1 | http://ftp.fr.debian.org/debian jessie-backports/main amd64 Packages
nodejs | 0.10.29~dfsg-2 | http://ftp.fr.debian.org/debian jessie/main amd64 Packages
nodejs | 0.10.29~dfsg-1~bpo70+1 | http://ftp.fr.debian.org/debian wheezy-backports/main amd64 Packages
第 1 步:從 GitHub 上獲取源代碼
像大多數開源項目一樣,NodeJS 的源代碼可以在 GitHub:https://github.com/nodejs/node 上找到。
所以,我們直接開始吧。
The NodeJS official GitHub repository
如果你不熟悉 GitHub,git 或者提到的其它 版本管理系統包含了這個軟件的源代碼,以及多年來對該軟件的所有修改的歷史。甚至可以回溯到該軟件的最早版本。對于開發者來說,保留它的歷史版本有很多好處。如今對我來說,其中一個好處是可以得到任何一個給定時間點的項目源代碼。更準確地說,我可以得到我所要的 8.1.1 發布時的源代碼。即便從那之后他們有了很多的修改。
Choose the v8.1.1 tag in the NodeJS GitHub repository
在 GitHub 上,你可以使用 “branch” (分支)按鈕導航到這個軟件的不同版本。“分支” 和 “標簽” 是 Git 中一些相關的概念。總的來說,開發者創建 “分支” 和 “標簽” 來在項目歷史中對重要事件保持跟蹤,比如當他們啟用一個新特性或者發布一個新版本時。在這里先不詳細介紹了,你現在只需要知道我在找被標記為 “v8.1.1” 的版本。
The NodeJS GitHub repository as it was at the time the v8.1.1 tag was created
在選擇了 “v8.1.1” 標簽后,頁面被刷新,最顯著的變化是標簽現在作為 URL 的一部分出現。另外,你可能會注意到文件改變日期也有所不同。你現在看到的源代碼樹是創建了 v8.1.1 標簽時的代碼。在某種意義上,你也可以認為像 git 這樣的版本管理工具是一個時光穿梭機,允許你在項目歷史中來回穿梭。
NodeJS GitHub repository download as a ZIP button
此時,我們可以下載 NodeJS 8.1.1 的源代碼。你不要忘記去點那個建議的大的藍色按鈕來下載一個項目的 ZIP 壓縮包。對于我來說,為講解的目的,我從命令行中下載并解壓這個 ZIP 壓縮包。但是,如果你更喜歡使用一個 GUI 工具,不用擔心,你可以取代下面的命令方式:
wget https://github.com/nodejs/node/archive/v8.1.1.zip
unzip v8.1.1.zip
cd node-8.1.1/
下載一個 ZIP 包就可以,但是如果你希望“像個專家一樣”,我建議你直接使用 git
工具去下載源代碼。它一點也不復雜 — 并且如果你是第一次使用該工具,它將是一個很好的開端,你以后將經常用到它:
# first ensure git is installed on your system
sh$ sudo apt-get install git
# Make a shallow clone the NodeJS repository at v8.1.1
sh$ git clone --depth 1 \
--branch v8.1.1 \
https://github.com/nodejs/node
sh$ cd node/
順便說一下,如果你有任何問題,這篇文章的第一部分只是做一個總體介紹而已。后面,為了幫你排除常見問題,我們將基于 Debian 和基于 RedHat 的發行版更詳細地解釋。
不管怎樣,在你使用 git
或者作為一個 ZIP 壓縮包下載了源代碼后,在當前目錄下就有了同樣的源代碼文件:
sh$ ls
android-configure BUILDING.md common.gypi doc Makefile src
AUTHORS CHANGELOG.md configure GOVERNANCE.md node.gyp test
benchmark CODE_OF_CONDUCT.md CONTRIBUTING.md lib node.gypi tools
BSDmakefile COLLABORATOR_GUIDE.md deps LICENSE README.md vcbuild.bat
第 2 步:理解程序的構建系統
構建系統就是我們通常所說的“編譯源代碼”,其實,編譯只是從源代碼中生成一個可使用的軟件的其中一個階段。構建系統是一套工具,用于自動處置不同的任務,以便可以僅通過幾個命令就能構建整個軟件。
雖然概念很簡單,實際上編譯做了很多事情。因為不同的項目或者編程語言也許有不同的要求,或者因為編程者的好惡,或者因為支持的平臺、或者因為歷史的原因,等等等等 … 選擇或創建另外一個構建系統的原因幾乎數不清。這方面有許多種不同的解決方案。
NodeJS 使用一種 GNU 風格的構建系統。這在開源社區中這是一個很流行的選擇。由此開始,你將進入一段精彩的旅程。
寫出和調優一個構建系統是一個非常復雜的任務。但是,作為 “終端用戶” 來說,GNU 風格的構建系統使用兩個工具讓他們免于此難:configure
和 make
。
configure
文件是個項目專用的腳本,它將檢查目標系統的配置和可用功能,以確保該項目可以被構建,并最終吻合當前平臺的特性。
一個典型的 configure
任務的重要部分是去構建 Makefile
。這個文件包含了有效構建項目所需的指令。
另一方面,make
工具,這是一個可用于任何類 Unix 系統的 POSIX 工具。它將讀取項目專用的 Makefile
然后執行所需的操作去構建和安裝你的程序。
但是,在 Linux 的世界中,你仍然有一些定制你自己專用的構建的理由。
./configure --help
configure -help
命令將展示你可用的所有配置選項。再強調一下,這是非常的項目專用。說實話,有時候,在你完全理解每個配置選項的作用之前,你需要深入到項目中去好好研究。
不過,這里至少有一個標準的 GNU 自動化工具選項是你該知道的,它就是眾所周知的 --prefix
選項。它與文件系統的層次結構有關,它是你軟件要安裝的位置。
第 3 步:文件系統層次化標準(FHS)
大部分典型的 Linux 發行版的文件系統層次結構都遵從 文件系統層次化標準(FHS)。
這個標準說明了你的系統中各種目錄的用途,比如,/usr
、/tmp
、/var
等等。
當使用 GNU 自動化工具 和大多數其它的構建系統 時,它會把新軟件默認安裝在你的系統的 /usr/local
目錄中。這是依據 FHS 中 “/usr/local
層級是為系統管理員本地安裝軟件時使用的,它在系統軟件更新覆蓋時是安全的。它也可以用于存放在一組主機中共享,但又沒有放到 /usr 中的程序和數據”,因此,它是一個非常好的選擇。
/usr/local
層級以某種方式復制了根目錄,你可以在 /usr/local/bin
這里找到可執行程序,在 /usr/local/lib
中找到庫,在 /usr/local/share
中找到架構無關的文件,等等。
使用 /usr/local
樹作為你定制安裝的軟件位置的唯一問題是,你的軟件的文件將在這里混雜在一起。尤其是你安裝了多個軟件之后,將很難去準確地跟蹤 /usr/local/bin
和 /usr/local/lib
中的哪個文件到底屬于哪個軟件。它雖然不會導致系統的問題。畢竟,/usr/bin
也是一樣混亂的。但是,有一天你想去卸載一個手工安裝的軟件時它會將成為一個問題。
要解決這個問題,我通常喜歡安裝定制的軟件到 /opt
子目錄下。再次引用 FHS:
“
/opt
是為安裝附加的應用程序軟件包而保留的。包安裝在
/opt
下的軟件包必須將它的靜態文件放在單獨的/opt/<package>
或者/opt/<provider>
目錄中,此處<package>
是所說的那個軟件名的名字,而<provider>
處是提供者的 LANANA 注冊名字。”(LCTT 譯注:LANANA 是指 The Linux Assigned Names And Numbers Authority。 )
因此,我們將在 /opt
下創建一個子目錄,用于我們定制的 NodeJS 安裝。并且,如果有一天我想去卸載它,我只是很簡單地去刪除那個目錄:
sh$ sudo mkdir /opt/node-v8.1.1
sh$ sudo ln -sT node-v8.1.1 /opt/node
# What is the purpose of the symbolic link above?
# Read the article till the end--then try to answer that
# question in the comment section!
sh$ ./configure --prefix=/opt/node-v8.1.1
sh$ make -j9 && echo ok
# -j9 means run up to 9 parallel tasks to build the software.
# As a rule of thumb, use -j(N+1) where N is the number of cores
# of your system. That will maximize the CPU usage (one task per
# CPU thread/core + a provision of one extra task when a process
# is blocked by an I/O operation.
在你運行完成 make
命令之后,如果有任何的除了 “ok” 以外的信息,將意味著在構建過程中有錯誤。當我們使用一個 -j
選項去運行并行構建時,在構建系統的大量輸出過程中,檢索錯誤信息并不是件很容易的事。
在這種情況下,只能是重新開始 make
,并且不要使用 -j
選項。這樣錯誤將會出現在輸出信息的最后面:
sh$ make
最終,編譯結束后,你可以運行這個命令去安裝你的軟件:
sh$ sudo make install
然后測試它:
sh$ /opt/node/bin/node --version
v8.1.1
B. 如果在源代碼安裝的過程中出現錯誤怎么辦?
我上面介紹的大多是你能在文檔完備的項目的“構建指令”頁面上看到。但是,本文的目標是讓你從源代碼開始去編譯你的第一個軟件,它可能要花一些時間去研究一些常見的問題。因此,我將再次重新開始一遍整個過程,但是,這次是在一個最新的、最小化安裝的 Debian 9.0 和 CentOS 7.0 系統上。因此,你可能看到我遇到的錯誤以及我怎么去解決它。
從 Debian 9.0 中 “Stretch” 開始
itsfoss@debian:~$ git clone --depth 1 \
--branch v8.1.1 \
https://github.com/nodejs/node
-bash: git: command not found
這個問題非常容易去診斷和解決。去安裝這個 git
包即可:
itsfoss@debian:~$ sudo apt-get install git
itsfoss@debian:~$ git clone --depth 1 \
--branch v8.1.1 \
https://github.com/nodejs/node && echo ok
[...]
ok
itsfoss@debian:~/node$ sudo mkdir /opt/node-v8.1.1
itsfoss@debian:~/node$ sudo ln -sT node-v8.1.1 /opt/node
現在沒有問題了。
itsfoss@debian:~/node$ ./configure --prefix=/opt/node-v8.1.1/
WARNING: failed to autodetect C++ compiler version (CXX=g++)
WARNING: failed to autodetect C compiler version (CC=gcc)
Node.js configure error: No acceptable C compiler found!
Please make sure you have a C compiler installed on your system and/or
consider adjusting the CC environment variable if you installed
it in a non-standard prefix.
很顯然,編譯一個項目,你需要一個編譯器。NodeJS 是使用 C++ 語言 寫的,我們需要一個 C++ 編譯器。在這里我將安裝 g++
,它就是為這個目的寫的 GNU C++ 編譯器:
itsfoss@debian:~/node$ sudo apt-get install g++
itsfoss@debian:~/node$ ./configure --prefix=/opt/node-v8.1.1/ && echo ok
[...]
ok
itsfoss@debian:~/node$ make -j9 && echo ok
-bash: make: command not found
還差一個其它工具。同樣的癥狀。同樣的解決方案:
itsfoss@debian:~/node$ sudo apt-get install make
itsfoss@debian:~/node$ make -j9 && echo ok
[...]
ok
itsfoss@debian:~/node$ sudo make install
[...]
itsfoss@debian:~/node$ /opt/node/bin/node --version
v8.1.1
成功!
請注意:我將一次又一次地安裝各種工具去展示怎么去診斷編譯問題,以及展示怎么去解決這些問題。但是,如果你搜索關于這個主題的更多文檔,或者讀其它的教程,你將發現,很多發行版有一個 “meta-packages”,它包羅了安裝一些或者全部的用于編譯軟件的常用工具。在基于 Debian 的系統上,你或許遇到過 build-essentials 包,它就是這種用作。在基于 Red Hat 的發行版中,它將是 “Development Tools” 組。
在 CentOS 7.0 上
[itsfoss@centos ~]$ git clone --depth 1 \
--branch v8.1.1 \
https://github.com/nodejs/node
-bash: git: command not found
命令沒有找到?可以用 yum
包管理器去安裝它:
[itsfoss@centos ~]$ sudo yum install git
[itsfoss@centos ~]$ git clone --depth 1 \
--branch v8.1.1 \
https://github.com/nodejs/node && echo ok
[...]
ok
[itsfoss@centos ~]$ sudo mkdir /opt/node-v8.1.1
[itsfoss@centos ~]$ sudo ln -sT node-v8.1.1 /opt/node
[itsfoss@centos ~]$ cd node
[itsfoss@centos node]$ ./configure --prefix=/opt/node-v8.1.1/
WARNING: failed to autodetect C++ compiler version (CXX=g++)
WARNING: failed to autodetect C compiler version (CC=gcc)
Node.js configure error: No acceptable C compiler found!
Please make sure you have a C compiler installed on your system and/or
consider adjusting the CC environment variable if you installed
it in a non-standard prefix.
你知道的:NodeJS 是使用 C++ 語言寫的,但是,我的系統缺少合適的編譯器。Yum 可以幫到你。因為,我不是一個合格的 CentOS 用戶,我實際上是在互聯網上搜索到包含 g++ 編譯器的包的確切名字的。這個頁面指導了我:https://superuser.com/questions/590808/yum-install-gcc-g-doesnt-work-anymore-in-centos-6-4 。
[itsfoss@centos node]$ sudo yum install gcc-c++
[itsfoss@centos node]$ ./configure --prefix=/opt/node-v8.1.1/ && echo ok
[...]
ok
[itsfoss@centos node]$ make -j9 && echo ok
[...]
ok
[itsfoss@centos node]$ sudo make install && echo ok
[...]
ok
[itsfoss@centos node]$ /opt/node/bin/node --version
v8.1.1
再次成功!
C. 從源代碼中對要安裝的軟件做一些改變
從源代碼中安裝一個軟件,可能是因為你的分發倉庫中沒有一個可用的特定版本。或者因為你想去 修改 那個程序。也可能是修復一個 bug 或者增加一個特性。畢竟,開源軟件這些都可以做到。因此,我將抓住這個機會,讓你親自體驗怎么去編譯你自己的軟件。
在這里,我將在 NodeJS 源代碼上做一個微小改變。然后,我們將看到我們的改變將被納入到軟件的編譯版本中:
用你喜歡的 文本編輯器(如,vim、nano、gedit、 … )打開文件 node/src/node.cc
。然后,嘗試找到如下的代碼片段:
if (debug_options.ParseOption(argv[0], arg)) {
// Done, consumed by DebugOptions::ParseOption().
} else if (strcmp(arg, "--version") == 0 || strcmp(arg, "-v") == 0) {
printf("%s\n", NODE_VERSION);
exit(0);
} else if (strcmp(arg, "--help") == 0 || strcmp(arg, "-h") == 0) {
PrintHelp();
exit(0);
}
它在 文件的 3830 行 附近。然后,修改包含 printf
的行,將它替換成如下內容:
printf("%s (compiled by myself)\n", NODE_VERSION);
然后,返回到你的終端。在繼續之前,為了對強大的 Git 支持有更多的了解,你可以去檢查一下,你修改是文件是否正確:
diff --git a/src/node.cc b/src/node.cc
index bbce1022..a5618b57 100644
--- a/src/node.cc
+++ b/src/node.cc
@@ -3828,7 +3828,7 @@ static void ParseArgs(int* argc,
if (debug_options.ParseOption(argv[0], arg)) {
// Done, consumed by DebugOptions::ParseOption().
} else if (strcmp(arg, "--version") == 0 || strcmp(arg, "-v") == 0) {
- printf("%s\n", NODE_VERSION);
+ printf("%s (compiled by myself)\n", NODE_VERSION);
exit(0);
} else if (strcmp(arg, "--help") == 0 || strcmp(arg, "-h") == 0) {
PrintHelp();
在你前面改變的那行之前,你將看到一個 “-” (減號標志)。而在改變之后的行前面有一個 “+” (加號標志)。
現在可以去重新編譯并重新安裝你的軟件了:
make -j9 && sudo make install && echo ok
[...]
ok
這個時候,可能失敗的唯一原因就是你改變代碼時的輸入錯誤。如果就是這種情況,在文本編輯器中重新打開 node/src/node.cc
文件并修復錯誤。
一旦你完成了新修改版本的 NodeJS 的編譯和安裝,就可以去檢查你的修改是否包含到軟件中:
itsfoss@debian:~/node$ /opt/node/bin/node --version
v8.1.1 (compiled by myself)
恭喜你!你對開源程序做出了你的第一個改變!
D. 讓 shell 找到我們定制構建的軟件
到目前為止,你可能注意到,我通常啟動我新編譯的 NodeJS 軟件是通過指定到該二進制文件的絕對路徑。
/opt/node/bin/node
這是可以正常工作的。但是,這樣太麻煩。實際上有兩種辦法可以去解決這個問題。但是,去理解它們,你必須首先明白,你的 shell 定位可執行文件是通過在環境變量 PATH
中指定的目錄里面查找的。
itsfoss@debian:~/node$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
在這個 Debian 系統上,如果你不指定一個精確的目錄做為命令名字的一部分,shell 將首先在 /usr/local/bin
中查找可執行程序;如果沒有找到,然后進入 /usr/bin
中查找;如果沒有找到,然后進入 /bin
查找;如果沒有找到,然后進入 /usr/local/games
查找;如果沒有找到,然后進入 /usr/games
查找;如果沒有找到,那么,shell 將報告一個錯誤,“command not found”。
由此,我們可以知道有兩種方法去確保命令可以被 shell 訪問到:將它(該二進制程序)增加到已經配置好的 PATH
目錄中,或者將包含可執行程序的目錄添加到 PATH
中。
從 /usr/local/bin 中添加一個鏈接
只是從 /opt/node/bin
中 拷貝 NodeJS 二進制可執行文件到 /usr/local/bin
是一個錯誤的做法。因為,如果這么做,該可執行程序將無法定位到在 /opt/node/
中的需要的其它組件。(軟件以它自己的位置去定位它所需要的資源文件是常見的做法)
因此,傳統的做法是去使用一個符號鏈接:
itsfoss@debian:~/node$ sudo ln -sT /opt/node/bin/node /usr/local/bin/node
itsfoss@debian:~/node$ which -a node || echo not found
/usr/local/bin/node
itsfoss@debian:~/node$ node --version
v8.1.1 (compiled by myself)
這一個簡單而有效的解決辦法,尤其是,如果一個軟件包是由好幾個眾所周知的可執行程序組成的,因為,你將為每個用戶調用的命令創建一個符號鏈接。例如,如果你熟悉 NodeJS,你知道應用的 npm
組件,也應該從 /usr/local/bin
做個符號鏈接。我把這個留給你做練習。
修改 PATH
首先,如果你嘗試過前面的解決方案,請先移除前面創建的節點符號鏈接,去從一個干凈的狀態開始:
itsfoss@debian:~/node$ sudo rm /usr/local/bin/node
itsfoss@debian:~/node$ which -a node || echo not found
not found
現在,這里有一個改變你的 PATH
的魔法命令:
itsfoss@debian:~/node$ export PATH="/opt/node/bin:${PATH}"
itsfoss@debian:~/node$ echo $PATH
/opt/node/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
簡單說就是,我用環境變量 PATH
之前的內容前綴了一個 /opt/node/bin
替換了其原先的內容。因此,你可以想像一下,shell 將先進入到 /opt/node/bin
目錄中查找可執行程序。我們也可以使用 which
命令去確認一下:
itsfoss@debian:~/node$ which -a node || echo not found
/opt/node/bin/node
itsfoss@debian:~/node$ node --version
v8.1.1 (compiled by myself)
鑒于 “符號鏈接” 解決方案是永久的,只要創建到 /usr/local/bin
的符號鏈接就行了,而對 PATH
的改變僅影響到當前的 shell。你可以自己做一些研究,如何做到對 PATH
的永久改變。給你一個提示,可以將它寫到你的 “profile” 中。如果你找到這個解決方案,不要猶豫,通過下面的評論區共享給其它的讀者!
E. 怎么去卸載剛才從源代碼中安裝的軟件
因為我們定制編譯的 NodeJS 軟件全部在 /opt/node-v8.1.1
目錄中,卸載它不需要做太多的工作,僅使用 rm
命令去刪除那個目錄即可:
sudo rm -rf /opt/node-v8.1.1
注意:sudo
和 rm -rf
是 “非常危險的雞尾酒”!一定要在按下回車鍵之前多檢查幾次你的命令。你不會得到任何的確認信息,并且如果你刪除了錯誤的目錄它是不可恢復的 …
然后,如果你修改了你的 PATH
,你可以去恢復這些改變。它一點也不復雜。
如果你從 /usr/local/bin
創建了一個符號鏈接,你應該去刪除它們:
itsfoss@debian:~/node$ sudo find /usr/local/bin \
-type l \
-ilname "/opt/node/*" \
-print -delete
/usr/local/bin/node
等等? 依賴地獄在哪里?
作為最終的討論,如果你讀過有關的編譯定制軟件的文檔,你可能聽到關于 依賴地獄 的說法。那是在你能夠成功編譯一個軟件之前,對那種煩人情況的一個別名,你必須首先編譯一個前提條件所需要的庫,它又可能要求其它的庫,而這些庫有可能與你的系統上已經安裝的其它軟件不兼容。
發行版的軟件包維護者的部分工作,就是實際去地解決那些依賴地獄,確保你的系統上的各種軟件都使用了兼容的庫,并且按正確的順序去安裝。
在這篇文章中,我特意選擇了 NodeJS 去安裝,是因為它幾乎沒有依賴。我說 “幾乎” 是因為,實際上,它 有 依賴。但是,這些源代碼的依賴已經預置到項目的源倉庫中(在 node/deps
子目錄下),因此,在你動手編譯之前,你不用手動去下載和安裝它們。
如果你有興趣了解更多關于那個問題的知識和學習怎么去處理它。請在下面的評論區告訴我,它將是更高級別的文章的好主題!
作者簡介:
充滿激情的工程師,職業是教師,我的目標是:熱心分享我所教的內容,并讓我的學生自己培養它們的技能。你也可以在我的網站上聯系到我。