天天看點

Python多版本編譯安裝+Ubuntu18.04

這幾天都在為新安裝的系統Ubuntu18.04裝東西,這些東西其實都很容易,但發現每次重新裝了系統後都要重新整理,時間一長完全想不起怎麼回事,好記性不如爛筆頭,是以寫到這裡是給自己作個備注。

編譯安裝前的準備

首先是要安裝一些包,如果沒有這些包,一般會在最後make時報出來,大概是這個樣子,

Python build finished successfully!
The necessary bits to build these optional modules were not found:
_bz2                  _curses               _curses_panel      
_dbm                  _gdbm                 _hashlib           
_lzma                 _sqlite3              _ssl               
_tkinter              _uuid                 readline           
To find the necessary bits, look in setup.py in detect_modules() for the module's name.

The following modules found by detect_modules() in setup.py, have been
built by the Makefile instead, as configured by the Setup files:
_abc                  atexit                pwd                
time                                                           

Could not build the ssl module!
Python requires an OpenSSL 1.0.2 or 1.1 compatible libssl with X509_VERIFY_PARAM_set1_host().
LibreSSL 2.6.4 and earlier do not provide the necessary APIs, 
https://github.com/libressl-portable/portable/issues/381      

是以一般要安裝這些包(Ubuntu18.04上驗證OK,裡面可能有重複的),

libbz2-dev

libc6-dev

libffi-dev

libgdbm-dev

libgdbm-compat-dev

liblzma-dev

libncurses5-dev

libncursesw5-dev (optional)

libreadline-gplv2-dev (optional?)

libreadline-dev

libsqlite3-dev

libssl-dev

openssl

sqlite3

tcl-dev (tcl8.6-dev)

tk-dev (tk8.6-dev)

uuid-dev

zlib1g-dev

軟體包可以到這裡去搜尋,參考連結,

​​​https://packages.ubuntu.com/search?keywords=lzma​​​

參考下表

子產品 依賴 說明
_bsddb bsddb Interface to Berkeley DB library。Berkeley資料庫的接口
_curses ncurses Terminal handling for character-cell displays。
_curses_panel ncurses A panel stack extension for curses。
_sqlite3 sqlite DB-API 2.0 interface for SQLite databases。SqlLite,CentOS可以安裝sqlite-devel
_ssl openssl-devel TLS/SSL wrapper for socket objects。
_tkinter N/A a thin object-oriented layer on top of Tcl/Tk。如果不使用桌面程式可以忽略TKinter
lzma liblzma-dev 老的bsddb子產品,可忽略。
bz2 bzip2-devel Compression compatible with bzip2。bzip2-devel
dbm
dl N/A Call C functions in shared objects.Python2.6開始,已經棄用。
gdbm gdbm-devel.i686 GNU’s reinterpretation of dbm
imageop N/A Manipulate raw image data。已經棄用。
readline readline-devel GNU readline interface
sunaudiodev N/A Access to Sun audio hardware。這個是針對Sun平台的,CentOS下可以忽略
zlib Zlib Compression compatible with gzip

安裝

如果是自己下載下傳編譯安裝,

首先去python官網下載下傳python3的源碼包,網址:https://www.python.org/

解壓到某個檔案夾(我這裡假設是python372)後,進去就可以編譯安裝了,代碼非常簡單,如下

注:在編譯之前,建議先看一下後面的問題2,取決于你的實際系統狀态,有可能需要修改Setup

./configure --prefix=/usr/local --with-pydebug --enable-shared CFLAGS=-fPIC
make -j8 -s  (-j means jobs for multiprocessing  -s means silent)
sudo make install      

說明:其中/usr/local是安裝目錄,當然你完全可以選其他的地方,如果你需要學習Python源碼,那就要調試版,此時要加上--with-pydebug,更詳細的過程可以參考官方說明:ref ​​​https://devguide.python.org/​​​

補充:這裡加上--enable-shared和-fPIC之後可以将python3的動态連結庫編譯出來,預設情況編譯完lib下面隻有python3.xm.a這樣的檔案,python本身可以正常使用,但是如果編譯第三方庫需要python接口的比如caffe等,則會報錯;是以這裡建議按照上面的方式配置,另外如果openssl不使用系統yum安裝的,而是使用自己編譯的比較新的版本可以使用--with-openssl=/usr/local/openssl這種方式指定,後面目錄為openssl實際安裝的目錄,另外編譯完還要将openssl的lib目錄加入ld運作時目錄中即可. 

具體關于fPIC這個參數,請看後面的說明。

多個Python版本

下面講一下如何讓兩個Python共存,實際上,我的系統裡往往遠遠不止2個Python版本。

根據前面的指令,安裝成功之後,安裝目錄就在/usr/local/

安裝完成之後要簡單做一下配置:

方法1:

即将python庫路徑添加到/etc/ld.so.conf配置中,然後執行ldconfig生效;

$ sudo gedit /etc/ld.so.conf      

此指令會打開ld.so.conf檔案,正常情況下,裡面會包含一句,

include /etc/ld.so.conf.d/*.conf

你可以在這個檔案中直接修改,也可以

$ sudo gedit /etc/ld.so.conf.d/python372.conf      

檔案内容寫入

/usr/local/python372/lib  

并儲存,注意這個路徑是你的libpython3.7dm.so檔案所在的位置,對非調試版是libpython3.7m.so,如果是靜态庫,則是libpython3.7dm.a檔案;我也不知道為什麼,位置貌似不是固定的,也有可能是/usr/local/lib 或 /usr/local/lib/python3.7/config-3.7dm-x86_64-linux-gnu等檔案夾下面。然後,

$ sudo ldconfig      

方法2:

或者添加到$LD_LIBRARY_PATH中,這樣在接下來運作python3是就不會報找不到庫檔案的錯誤了。具體做法請參考後面的資料。

系統中原來的python在/usr/bin/python,通過$ls -l /usr/bin/p*可以看到具體情況,通過$which python可以查找是目前python指令是哪個。

這裡不要對原來預設的環境做任何修改,隻建立一個python372的軟連結即可,這裡建立有關的軟連結如下:

$sudo ln -s /usr/local/python372/bin/python3   /usr/bin/python372
$sudo ln -s /usr/local/python372/bin/pip3    /usr/bin/pip372      

同樣要注意,這個目錄也是變動的,和系統配置有關,比如我兩次不同的配置(具體有哪些不同我自己也不記得了),另一個結果為

$sudo ln -s /usr/local/bin/python3 /usr/bin/python372
$sudo ln -s /usr/local/bin/pip3    /usr/bin/pip372      

如果不報錯就表示建好了,以後直接執行python372指令就可以調用該版本的python3了,執行pip372可以安裝需要的python372的子產品;另外如果仔細看python安裝目錄下的bin目錄,實際上python3也是個軟連結,連結到python3.7.2,這樣多次連結也是為了多個版本的管理更加友善,

總結

整個過程其實還是很簡單的,這裡做一個總結。為了給出多版本的特性,我這裡用另一個版本,安裝的地點也換成了/opt/cpython380,如果你根據我這裡的做法,同時安裝python372和python380,就會發現可以完全沒有沖突了

git clone https://github.com/SpaceView/cpython.git
# you can go https://github.com/python/cpython.git if you don't need a fork

cd cpython
mkdir build
cd build

../configure --prefix=/opt/cpython380 --with-pydebug --enable-shared CFLAGS=-fPIC
# --prefix= the place where you need to install the cpython

make -j8 #, or make -j8 -s  (-j means jobs for multiprocessing  -s means silent)

# make test, if you wanna see something testing

sudo -H make install

sudo gedit /etc/ld.so.conf.d/python380.conf
# (write "/opt/cpython380/lib" in the file and save it)
sudo ldconfig

# --- now close the current terminal ----

# --- open a new terminal to let the above configure take effect ---
sudo ln -s /opt/cpython380/bin/python3 /usr/bin/python380

# --- install the latest pip version----
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
sudo -H python380 get-pip.py

sudo ln -s /opt/cpython380/bin/pip3 /usr/bin/pip380      

--------------------------------------------華麗的分割線---------------------------------------------------

編譯時的報錯處理

-----------------------------------------------------------------------------

No.1

~/dev/python372/Modules/_cursesmodule.c: In function ‘PyCurses_setupterm’:

~/dev/python372/Modules/_cursesmodule.c:2564:35: error: implicit declaration of function ‘setupterm’; did you mean ‘set_term’? [-Werror=implicit-function-declaration]

     if (!initialised_setupterm && setupterm(termstr,fd,&err) == ERR) {

                                   ^~~~~~~~~

                                   set_term

cc1: some warnings being treated as errors

解決方式:

sed -i "s/Werror=implicit-function-declaration/Wno-error/g" configure(修改configure檔案)

sudo make clean

sudo make

-----------------------------------------------------------------------------

No.2

pip is configured with locations that require TLS/SSL, however the ssl module in Python is not available.

這個問題比較頑固,先給出正确答案,

cd ~/python372/Module/
vi Setup
或者
gedit ~/dev/python372/Modules/Setup


找到下面這三行,取消前面的注釋,讓其起作用,然後儲存退出
ssl _ssl.c \
    -DUSE_SSL -I$(SSL)/include -I$(SSL)/include/openssl \
    -L$(SSL)/lib -lssl -lcrypto

make
sudo make install      

參考:​​https://stackoverflow.com/questions/49094768/ssl-module-in-python-is-not-available​​

Python3.6以前的版本貌似又不一樣,具體可以參考(這裡邊所有的資訊對我都沒有用):

​​​https://stackoverflow.com/questions/41328451/ssl-module-in-python-is-not-available-when-installing-package-with-pip3​​​

-----------------------------------------------------------------------------

No.3

Failed to build these modules: _ctypes   

請仔細檢查,一般是缺少庫檔案引起的,比如缺少libffi-dev,libc6等庫。

編譯參數說明

gcc編譯參數-fPIC的一些問題

​​

-fPIC 作用于編譯階段,告訴編譯器産生與位置無關代碼(Position-Independent Code),

則産生的代碼中,沒有絕對位址,全部使用相對位址,故而代碼可以被加載器加載到記憶體的任意位置,都可以正确的執行。這正是共享庫所要求的,共享庫被加載時,在記憶體的位置不是固定的。

gcc -shared -fPIC -o 1.so 1.c

這裡有一個-fPIC參數PIC就是position independent code,PIC使.so檔案的代碼段變為真正意義上的共享

如果不加-fPIC,則加載.so檔案的代碼段時,代碼段引用的資料對象需要重定位, 重定位會修改代碼段的内容,這就造成每個使用這個.so檔案代碼段的程序在核心裡都會生成這個.so檔案代碼段的copy.每個copy都不一樣,取決于 這個.so檔案代碼段和資料段記憶體映射的位置.

不加fPIC編譯出來的so,是要再加載時根據加載到的位置再次重定位的.(因為它裡面的代碼并不是位置無關代碼)

如果被多個應用程式共同使用,那麼它們必須每個程式維護一份so的代碼副本了.(因為so被每個程式加載的位置都不同,顯然這些重定位後的代碼也不同,當然不能共享)

我們總是用fPIC來生成so,也從來不用fPIC來生成a.

fPIC與動态連結可以說基本沒有關系,libc.so一樣可以不用fPIC編譯,隻是這樣的so必須要在加載到使用者程式的位址空間時重定向所有表目.

是以,不用fPIC編譯so并不總是不好,如果你滿足以下4個需求/條件:

1.該庫可能需要經常更新

2.該庫需要非常高的效率(尤其是有很多全局量的使用時)

3.該庫并不很大.

4.該庫基本不需要被多個應用程式共享

如果用沒有加這個參數的編譯後的共享庫,也可以使用的話,可能是兩個原因:

1:gcc預設開啟-fPIC選項

2:loader使你的代碼位置無關

從GCC來看,shared應該是包含fPIC選項的,但似乎不是是以系統都支援,是以最好顯式加上fPIC選項。參見如下

`-shared'

     Produce a shared object which can then be linked with other

     objects to form an executable.  Not all systems support this

     option.  For predictable results, you must also specify the same

     set of options that were used to generate code (`-fpic', `-fPIC',

     or model suboptions) when you specify this option.(1)

-fPIC 的使用,會生成 PIC 代碼,.so 要求為 PIC,以達到動态連結的目的,否則,無法實作動态連結。

non-PIC 與 PIC 代碼的差別主要在于 access global data, jump label 的不同。

比如一條 access global data 的指令,

non-PIC 的形勢是:ld r3, var1

PIC 的形式則是:ld r3, ​​var1-offset@GOT​​,意思是從 GOT 表的 index 為 var1-offset 的地方處

訓示的位址處裝載一個值,即​​var1-offset@GOT​​處的4個 byte 其實就是 var1 的位址。這個位址隻有在運作的時候才知道,是由 dynamic-loader(ld-linux.so) 填進去的。

再比如 jump label 指令

non-PIC 的形勢是:jump printf ,意思是調用 printf。

PIC 的形式則是:jump ​​printf-offset@GOT​​,

意思是跳到 GOT 表的 index 為 printf-offset 的地方處訓示的位址去執行,這個位址處的代碼擺放在 .plt section,

每個外部函數對應一段這樣的代碼,其功能是呼叫dynamic-loader(ld-linux.so) 來查找函數的位址(本例中是 printf),然後将其位址寫到 GOT 表的 index 為 printf-offset 的地方,

同時執行這個函數。這樣,第2次呼叫 printf 的時候,就會直接跳到 printf 的位址,而不必再查找了。

GOT 是 data section, 是一個 table, 除專用的幾個 entry,每個 entry 的内容可以再執行的時候修改;

PLT 是 text section, 是一段一段的 code,執行中不需要修改。

每個 target 實作 PIC 的機制不同,但大同小異。比如 MIPS 沒有 .plt, 而是叫 .stub,功能和 .plt 一樣。

可見,動态連結執行很複雜,比靜态連結執行時間長;但是,極大的節省了 size,PIC 和動态連結技術是計算機發展史上非常重要的一個裡程碑。

gcc manul上面有說

-fpic        If the GOT size for the linked executable exceeds a machine-specific maximum size, you get an error message from the linker indicating that -fpic does not work; in that case, recompile with -fPIC instead. (These maximums are 8k on the SPARC and 32k on the m68k and RS/6000. The 386 has no such limit.)

-fPIC       If supported for the target machine, emit position-independent code, suitable for dynamic linking and avoiding any limit on the size of the global offset table. This option makes a difference on the m68k, PowerPC and SPARC. Position-independent code requires special support, and therefore works only on certain machines.

關鍵在于GOT全局偏移量表裡面的跳轉項大小。

intel處理器應該是統一4位元組,沒有問題。

powerpc上由于彙編碼或者機器碼的特殊要求,是以跳轉項分為短、長兩種。

-fpic為了節約記憶體,在GOT裡面預留了“短”長度。

而-fPIC則采用了更大的跳轉項。

動态庫的配置

第一種,通過ldconfig指令

    ldconfig指令的用途, 主要是在預設搜尋目錄(/lib和/usr/lib)以及動态庫配置檔案/etc/ld.so.conf内所列的目錄下, 搜尋出可共享的動态連結庫(格式如lib*.so*), 進而建立出動态裝入程式(ld.so)所需的連接配接和緩存檔案. 緩存檔案預設為/etc/ld.so.cache, 此檔案儲存已排好序的動态連結庫名字清單. 

    這裡面涉及到的關鍵内容有指令:ldconfig,配置檔案目錄:/etc/ld.so.conf.d,配置檔案在/etc/ld.so.conf内容由使用者編輯,緩沖檔案/etc/ld.so.cache。下面舉個例子,比如你在部署軟體時,有些動态庫安裝在exe目錄下,可以通過如下方法實作,

1)配置exe.conf檔案,裡面加一行~/exe,然後将該檔案放到/etc/ld.so.conf.d目錄下;

2)在/etc/ld.so.conf檔案中增加一行include ld.so.conf.d/exe.conf

3)執行 ldconfig指令

之後程式運作時,會自動增加在exe目錄中搜尋動态庫。

第二種,通過LD_LIBRARY_PATH環境變量

可以通過在.bashrc或者.cshrc中配置該環境變量,LD_LIBRARY_PATH的意思是告訴loader在哪些目錄中可以找到共享庫. 可以設定多個搜尋目錄, 這些目錄之間用冒号分隔開.

同樣是上面的例子,可以通過以上的方法來實作

在.bashrc或.cshrc中增加一行,export LD_LIBRARY_PATH = ~/exe:$LD_LIBRARY_PATH即可。

第三種,通過編譯選項-Wl, -rpath指定動态搜尋的路徑

    -Wl選項告訴編譯器将後面的參數傳遞給連結器。

    通過上面的介紹,對/etc/ld.so.conf.d/的作用就比較清晰了。

現在我們知道了動态與靜态函數庫,也知道了目前的Linux大多是将函數庫做成動态函數庫,下面來讨論增加函數庫讀取性能的方法。我們知道,記憶體的通路速度是硬碟的好幾倍,是以,如果将常用的動态函數庫加載到記憶體中(高速緩存,cache),當軟體套件要采用動态函數庫時,就不需要重新從硬碟裡讀出,這樣就可以提高動态函數庫的讀取速度。這個時候需要ldconfig與 /etc/ld.so.conf的幫助。

将動态函數庫加載到高速緩存(cache)中的過程如下:

1. 首先,要在 /etc/ld.so.conf中寫下“想要讀入高速緩存中的動态函數庫所在的目錄”,注意,是目錄而不是檔案。

2. 利用ldconfig執行檔案将 /etc/ld.so.conf的資料讀入高速緩存中。

3. 同時在 /etc/ld.so.cache檔案中記錄資料。

範例一:假設MySQL資料庫函數庫在 /usr/lib/mysql中,如何讀入高速緩存?

[root@linux ~]# vi /etc/ld.so.conf

include ld.so.conf.d/*.conf

/usr/lib/mysql   <==這一行是新增的。

[root@linux ~]# ldconfig

# 畫面上不會顯示任何資訊,正常。

[root@linux ~]# ldconfig -p

928 libs found in cache `/etc/ld.so.cache'

        libz.so.1 (libc6) => /usr/lib/libz.so.1