分享一個前幾天幫忙排除問題的趣事,事後看覺得再明顯不過,發生當時卻讓人困惑了一下。前幾天要下班時,發現同事還在奮戰,所以就過去湊一下熱鬧。原來是他正在為某個平台建立 cross-compile 環境時碰到麻煩。

這裡說明一下,我們團隊的「cross-compile 環境」其實就是一個個 chroot,預先裝好了目標平台的 cross-toolchain、程式庫,並設定好環境變數。通常這些 cross-compile 都是由熟悉 Linux 環境的人事先打包好,新加入者只要按照產品型號下載對應的編譯環境,就可以立即上手建置專案。當硬體廠商更新驅動程式、或者程式庫 推出安全性修正時,負責的同事必須要更新編譯環境,有時候重大的調整會需要整個砍掉重頭建起。

同事在這方面相當有經驗,按標準流程很快就建好了一個理論上可以用的環境,可是試編譯某個大型專案卻老是編不過,CMake 一直抱怨「找不到 pthread.h」。由於我對 CMake 比較有經驗,所以就留下來幫忙診斷問題。

雖說 cross-compile 有時候就是會有一堆奇奇怪怪的路徑、環境變數問題,以至於工具跑到錯誤的地方去找東西,但是像 pthread 這種基本的東西實在沒理由出錯。同事已經把 pthread 相關的套件重裝好幾次,檢查路徑也看不出問題出在哪裡。用最簡單的 Makefile 直接建小程式並沒有問題,所以他強烈懷疑是 CMake 的 bug,但是換過了幾個 CMake 2.8.x 子版本還是沒有解決。

我就是在這個時候介入的。因為他拿來試編譯的專案頗複雜,我建議他寫一個極簡的 CMakeLists 來測試,只要 find_package(Threads REQUIRED) 就好,其他的事情都不要做,結果確實找得到 pthread.h。所以很明顯 CMake 程式本身是沒有問題的,而是這個專案不知道改動了什麼東西,所以才找不到 pthread.h。

之所以一開始沒往這個方向找,我想主要是因為同事從下午以來思維模式就停留在「我正在測試剛做的東西能不能用」的狀態,可能因此侷限了思考的範圍。而且試編譯用的專案是一個開發活躍的專案,在別的編譯環境都沒遇到編譯問題,最可疑的自然就是這個剛剛才弄出

來的環境。

根據 CMake 輸出的線索,我找到發出訊息的 FindThreads 模組行號,大致上知道 FindThreads 是用 CHECK_INCLUDE_FILE() 去檢查 pthread.h 是否存在。我對建置工具找檔案的慣用技倆略懂一二,這給了我一點想法,但還需要進一步證實。

我大略瀏覽了一下該專案的 CMakeLists,試著搜尋可能的關鍵字,卻不得要領。因為這個專案我平常沒有什麼接觸,對於其 CMakeLists 的架構完全陌生,沒辦法用經驗法則猜測問題所在。

於是我建議同事將最外層的 CMakeLists 分成幾個區塊來看,用「二分搜尋法」的策略插入 find_package(Threads REQUIRED) 然後執行 CMake,一次檢查一個點(因為 CMake 會快取上次的結果,所以每次都要砍掉),直到找到使 CMake 發出「找不到 pthread.h」的分界點為止。同事忍不住說,沒想到他畢業以來第一次使用二分搜尋法竟然是在這個情境。

同時我則是去查詢 CHECK_INCLUDE_FILE() 的行為,驗證自己的假設。由於二分搜尋法的效率,同事很快就定位出造成問題的所在行,很幸運落在最外層的 CMakeLists 裡,不用再往下層追。而我也確認了 CHECK_INCLUDE_FILE() 的行為,兩方面資訊一比對,問題的來龍去脈就很明顯了。

CMake 的 CHECK_INCLUDE_FILE() 採用的方法和許多建置工具一樣,會生成一段小程式碼去 #include 待測的標頭檔,以編譯成敗來判別標頭檔是否存在。這個方法的盲點在於,找不到標頭檔固然會導致編譯失敗,但編譯失敗的原因卻不一定是找不到標頭檔。同事定位的的問題點就是 CMAKE_CXX_FLAGS 被設錯而導致編譯失敗,原因是該專案有一些針對 Arm 平台設定的 CMAKE_CXX_FLAGS,雖然現在這個平台也是 Arm 家族,但因編譯器並不支援其中某些選項而導致編譯失敗,把那段 if 註解掉一切就順利了。

所以到頭來 CMake 輸出的錯誤訊息「找不到 pthread.h」完全不是問題所在,這樣的提示甚至會引導人往錯誤的方向找。遇到這種情境或許應該試著跳出先入為主的思考陷阱,廣泛蒐集線索。

為什麼 CMake (以及許多建置工具)會使用產生測試程式 #include 的方式來找檔案呢?另一種找檔案的方法,是讓建置工具到系統預設和使用者提示的路徑清單裡找東西,CMake 的 FIND_FILE() 就屬這類,但是建置工具以及建置腳本撰寫者很可能無法完全掌握編譯器所能見到的路徑,所以可能會有建置工具回報找不到、但編譯器實際上看得到檔案的情形。而試編譯的方法不管使用者用多旁門左道的方式設定路徑,一切都以編譯器當下能否實際看到檔案為準,理論上應該最符合編譯實況。

arrow
arrow
    文章標籤
    cmake
    全站熱搜

    novus 發表在 痞客邦 留言(0) 人氣()