作者 | Sylvain Leroux
譯者 | qhwdw
簡介:這篇文章詳細介紹了在 Linux 中怎麼用原始碼安裝程式,以及怎麼去解除安裝用原始碼安裝的程式。
Linux 發行版的一個最大的優點就是它的包管理器和相關的軟體庫。透過它們提供的資源和工具,你才能夠以完全自動化的方式在你的計算機上下載和安裝軟體。
但是,儘管付出了很多的努力,包維護者仍然沒法照顧好每種情況,也不可能將所有的可用軟體都打包進去。因此,仍然存在需要你自已去編譯和安裝一個新軟體的情形。對於我來說,到目前為止,最主要的原因是,我編譯一些軟體是我需要去執行一個特定的版本。或者是我想去修改原始碼或使用一些想要的編譯選項。
如果你也屬於後一種情況,那你已經知道你應該怎麼做了。但是,對於絕大多數的 Linux 使用者來說,第一次從原始碼中編譯和安裝一個軟體看上去像是一個入門儀式:它讓很多人感到恐懼;但是,如果你能剋服困難,你將可能進入一個全新的世界,並且,如果你做到了,那麼你將成為社群中享有特權的一部分人。
A. 在 Linux 中從原始碼開始安裝軟體
這正是我們要做的。因為這篇文章的需要,我要在我的系統上安裝 NodeJS[2] 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[4],git[5] 或者提到的其它 版本管理系統[6]包含了這個軟體的原始碼,以及多年來對該軟體的所有修改的歷史。甚至可以回溯到該軟體的最早版本。對於開發者來說,保留它的歷史版本有很多好處。如今對我來說,其中一個好處是可以得到任何一個給定時間點的專案原始碼。更準確地說,我可以得到我所要的 8.1.1 釋出時的原始碼。即便從那之後他們有了很多的修改。
Choose the v8.1.1 tag in the NodeJS GitHub repository
在 GitHub 上,你可以使用 “branch” (分支)按鈕導航到這個軟體的不同版本。“分支” 和 “標簽” 是 Git 中一些相關的概念[7]。總的來說,開發者建立 “分支” 和 “標簽” 來在專案歷史中對重要事件保持跟蹤,比如當他們啟用一個新特性或者釋出一個新版本時。在這裡先不詳細介紹了,你現在只需要知道我在找被標記為 “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[8] 工具,不用擔心,你可以取代下麵的命令方式:
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 風格的構建系統[9]。這在開源社群中這是一個很流行的選擇。由此開始,你將進入一段精彩的旅程。
寫出和調優一個構建系統是一個非常複雜的任務。但是,作為 “終端使用者” 來說,GNU 風格的構建系統使用兩個工具讓他們免於此難:configure
和 make
。
configure
檔案是個專案專用的指令碼,它將檢查標的系統的配置和可用功能,以確保該專案可以被構建,並最終吻合當前平臺的特性。
一個典型的 configure
任務的重要部分是去構建 Makefile
。這個檔案包含了有效構建專案所需的指令。
另一方面,make
工具[10],這是一個可用於任何類 Unix 系統的 POSIX 工具。它將讀取專案專用的 Makefile
然後執行所需的操作去構建和安裝你的程式。
但是,在 Linux 的世界中,你仍然有一些定製你自己專用的構建的理由。
./configure --help
configure -help
命令將展示你可用的所有配置選項。再強調一下,這是非常的專案專用。說實話,有時候,在你完全理解每個配置選項的作用之前,你需要深入到專案中去好好研究。
不過,這裡至少有一個標準的 GNU 自動化工具選項是你該知道的,它就是眾所周知的 --prefix
選項。它與檔案系統的層次結構有關,它是你軟體要安裝的位置。
第 3 步:檔案系統層次化標準(FHS)
大部分典型的 Linux 發行版的檔案系統層次結構都遵從 檔案系統層次化標準(FHS)[11]。
這個標準說明瞭你的系統中各種目錄的用途,比如,/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/
或者/opt/
目錄中,此處是所說的那個軟體名的名字,而
處是提供者的 LANANA 註冊名字。”(LCTT 譯註:LANANA 是指 The Linux Assigned Names And Numbers Authority[12]。 )
因此,我們將在 /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++ 語言[13] 寫的,我們需要一個 C++ 編譯器[14]。在這裡我將安裝 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[15] 包,它就是這種用作。在基於 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 原始碼上做一個微小改變。然後,我們將看到我們的改變將被納入到軟體的編譯版本中:
用你喜歡的 文字編輯器[17](如,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 行[18] 附近。然後,修改包含 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 定位可執行檔案是透過在環境變數[19] 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
等等? 依賴地獄在哪裡?
作為最終的討論,如果你讀過有關的編譯定製軟體的檔案,你可能聽到關於 依賴地獄[20] 的說法。那是在你能夠成功編譯一個軟體之前,對那種煩人情況的一個別名,你必須首先編譯一個前提條件所需要的庫,它又可能要求其它的庫,而這些庫有可能與你的系統上已經安裝的其它軟體不相容。
發行版的軟體包維護者的部分工作,就是實際去地解決那些依賴地獄,確保你的系統上的各種軟體都使用了相容的庫,並且按正確的順序去安裝。
在這篇文章中,我特意選擇了 NodeJS 去安裝,是因為它幾乎沒有依賴。我說 “幾乎” 是因為,實際上,它 有 依賴。但是,這些原始碼的依賴已經預置到專案的源倉庫中(在 node/deps
子目錄下),因此,在你動手編譯之前,你不用手動去下載和安裝它們。
如果你有興趣瞭解更多關於那個問題的知識和學習怎麼去處理它。請在下麵的評論區告訴我,它將是更高階別的文章的好主題!
作者簡介:
充滿激情的工程師,職業是教師,我的標的是:熱心分享我所教的內容,並讓我的學生自己培養它們的技能。你也可以在我的網站上聯絡到我。
via: https://itsfoss.com/install-software-from-source-code/
作者:Sylvain Leroux[22] 譯者:qhwdw 校對:wxy
本文由 LCTT 原創編譯,Linux中國 榮譽推出