
簡介gflags-2.1.1 是 Google 開源命令行標(biāo)志解析庫的穩(wěn)定歷史版本面向需在 C 程序中動態(tài)管理運行參數(shù)的開發(fā)者尤其是仍在維護舊版 Caffe 框架的工程人員。它將參數(shù)定義、命令行解析、配置文件讀取與 --help 幫助輸出集于一體使學(xué)習(xí)率、批處理大小等訓(xùn)練參數(shù)無需改代碼重編譯即可調(diào)整。壓縮包共 50 個文件、約 100KB以 9 個 cc 源文件與 4 個 h 頭文件承載核心實現(xiàn)輔以 7 個 cmake 構(gòu)建腳本和 10 個 in 配置模板說明編譯安裝流程另有 txt 文檔、flagfile 示例與單元測試用例可完整走通配置到驗證的鏈路。目前已有 222 人學(xué)習(xí)下載。讀者可獲取可直接編譯的完整源碼樹、覆蓋解析與標(biāo)志注冊的測試用例、flagfile 配置樣例及 CMake 集成參考便于在舊項目中復(fù)現(xiàn)穩(wěn)定行為或借此理解 gflags 的標(biāo)志注冊、參數(shù)校驗與多語言接口設(shè)計。1. 版本先定死為什么 Caffe 這條鏈上偏偏要 gflags 2.1.1裝 gflags 2.1.1 這件事本身不復(fù)雜復(fù)雜的是它在 Caffe 編譯鏈里的位置。很多人第一反應(yīng)是去拉最新版結(jié)果 Caffe 一編譯就是滿屏的ParseCommandLineFlags is not a member of google——因為 2.2 之后默認(rèn)命名空間從google換成了gflags老代碼里寫死的google::前綴集體失效。反過來把 2.1.1 老老實實裝進 /usr/local卻忘了執(zhí)行l(wèi)dconfig跑起來又是libgflags.so.2.1: cannot open shared object file。這套環(huán)境在復(fù)現(xiàn)老 Caffe 工程、跑只認(rèn) 2.1.x 的推理腳本、給舊版訓(xùn)練代碼補編譯依賴時特別常見裝之前把版本、prefix、命名空間三件事定死比裝完再救省事得多。2. 裝前清場編譯器、CMake 與系統(tǒng)里那幾份 gflags2.1 工具鏈版本下限與一條命令自檢gflags 2.1.1 的 CMakeLists.txt 里寫的是cmake_minimum_required(VERSION 2.8.12)也就是說 CMake 已經(jīng)低到幾乎不用挑它本身還是 C98 兼容的代碼不像 2.2.2 那樣硬性要求 C11。真正卡人的是它下游的 CaffeCaffe 要 C11所以編譯器版本反而成了實際下限。我這邊常用的一套組合在下表里Ubuntu 16.04 到 20.04 都能照這個范圍配。組件最低可用我這邊實際說明GCC / G4.87.5.0Caffe 需要 C11gflags 2.1.1 自身不需要CMake2.8.123.10.22.1.1 的構(gòu)建腳本按老版本寫新版 CMake 也能跑make3.814.1并行編譯-j依賴它系統(tǒng)Ubuntu 16.04Ubuntu 18.04只影響包名不影響源碼編譯進機器第一件事不是解壓源碼而是把這三條敲一遍確認(rèn)沒有多版本混裝# 確認(rèn)編譯器與構(gòu)建工具的真實指向不要只看 which gcc --version | head -1 g --version | head -1 cmake --version | head -1 make --version | head -1這里的關(guān)鍵是head -1。有些機器上/usr/bin/gcc是 7.x但 PATH 前面掛了個 conda 或某個自編譯工具鏈gcc --version會打出另一套版本。Caffe 編譯時用的是 Makefile 里展開的$(CC)和你手動敲的未必是同一個先看清楚再往下走。提示如果你打算在同一臺機器上同時維護兩套 Caffe一套老版、一套新版建議把 gflags 裝到獨立 prefix比如/opt/gflags-2.1.1別急著塞進 /usr/local后面第 4 章會講怎么在 Makefile.config 里指過去。2.2 把系統(tǒng)自帶的 gflags 揪出來并決定怎么處理這一步是整套安裝里最容易被跳過、也最容易在鏈接期爆炸的環(huán)節(jié)。Ubuntu 的libgflags-dev裝的是 2.1.2 或 2.2.x頭文件在/usr/include/gflags/庫在/usr/lib/x86_64-linux-gnu/和你要裝的 2.1.1 完全平行。兩份頭文件、兩份 so 同時存在編譯器先看到哪一份取決于-I的順序鏈接器先找到哪一份取決于-L的順序和ld.so.cache——這就是玄學(xué)感的來源。先查清楚現(xiàn)場# 包管理器層面有沒有 apt 裝的 gflags dpkg -l | grep -i gflags # 文件層面三處常見位置分別看一眼 ls -l /usr/lib/x86_64-linux-gnu/libgflags* 2/dev/null ls -l /usr/local/lib/libgflags* 2/dev/null ls -l /usr/include/gflags/ 2/dev/null # 動態(tài)鏈接器緩存里注冊了哪個版本 ldconfig -p | grep gflags如果dpkg -l有輸出說明系統(tǒng)級已經(jīng)有一份。三種處理策略按你的實際情況選第一種直接卸掉sudo apt remove libgflags-dev最干凈但要注意它可能被別的包依賴apt remove會提示此時改用第二種。第二種保留但讓 /usr/local 優(yōu)先Caffe 的 Makefile.config 里INCLUDE_DIRS默認(rèn)就把/usr/local/include放在前面鏈接時-L/usr/local/lib也排在/usr/lib前面只要不動默認(rèn)順序就不會串。第三種裝到獨立 prefix在 Caffe 里顯式指定路徑適合多人共用一臺機器、誰也不敢動系統(tǒng)庫的場景。還有一種藏得深的conda 環(huán)境。echo $CONDA_PREFIX如果有值去看$CONDA_PREFIX/lib/libgflags*和$CONDA_PREFIX/include/gflags/。conda 裝的 gflags 常常是 2.2.x而且它的include目錄會被某些構(gòu)建腳本自動加進搜索路徑。我一般會在編譯 Caffe 前conda deactivate把變量環(huán)境清干凈再動手省得后面拿ldd一層層倒查。2.3 源碼包落盤與目錄約定目錄約定看著是小事實際上決定了后面出問題時好不好回滾。我的習(xí)慣是源碼統(tǒng)一放/opt/src構(gòu)建目錄和源碼目錄分離安裝 prefix 用/usr/local——因為 Caffe 的 Makefile.config 默認(rèn)就去這兩個地方找不用改任何路徑。# 源碼統(tǒng)一存放避免散落在 home 目錄里找不回來 sudo mkdir -p /opt/src cd /opt/src # 解壓后頂層目錄就是 gflags-2.1.1 sudo tar -zxvf gflags-2.1.1.tar.gz cd gflags-2.1.1 # 確認(rèn)包是完整的這幾個文件缺一個都別往下走 ls CMakeLists.txt configure.ac include/gflags/ src/解開后應(yīng)該能看到CMakeLists.txt、configure.ac、Makefile.am、include/gflags/、src/、cmake/這幾項。include/gflags/下有三個頭文件gflags.h、gflags_declare.h、gflags_completions.h其中g(shù)flags_completions.h是 2.1.x 時代的東西2.2 之后被移除了后面第 5 章講升級翻車時會用到這個細節(jié)。configure.ac和Makefile.am同時存在說明 2.1.1 還保留了 autotools 那一套這一點和后來的版本不一樣第 3 章末尾會單獨說什么時候才值得走那條路。注意解壓時用tar -zxvf而不是直接雙擊解壓一是保證權(quán)限位正確二是避免某些歸檔工具把符號鏈接解成普通文件后面make會報一些莫名其妙的錯。3. 編譯安裝gflags 2.1.1 的 CMake 參數(shù)逐個過3.1 配置階段五個開關(guān)決定后面會不會翻車gflags 2.1.1 的 CMake 默認(rèn)值有幾個和直覺相反的地方不顯式覆蓋就會在后面某個環(huán)節(jié)找你麻煩。最典型的是BUILD_SHARED_LIBS默認(rèn) OFF——默認(rèn)只產(chǎn)出靜態(tài)庫libgflags.a而 Caffe 編出來的是libcaffe.so動態(tài)庫鏈接沒有開 PIC 的靜態(tài)庫直接報relocation R_X86_64_32 ... can not be used when making a shared object。我用的配置命令是這樣每一條都有理由# 構(gòu)建目錄和源碼分離改參數(shù)時直接刪掉重建不留緩存 cd /opt/src/gflags-2.1.1 rm -rf build mkdir build cd build cmake .. \ -DCMAKE_BUILD_TYPERelease \ -DCMAKE_INSTALL_PREFIX/usr/local \ -DBUILD_SHARED_LIBSON \ -DCMAKE_POSITION_INDEPENDENT_CODEON \ -DBUILD_TESTINGOFF \ -DGFLAGS_NAMESPACEgoogleCMAKE_BUILD_TYPERelease2.1.1 不設(shè)這個變量時構(gòu)建類型是空的編譯器不加優(yōu)化產(chǎn)出的庫性能明顯偏低。Caffe 前向推理里參數(shù)解析不是瓶頸但沒理由留個 O0 的庫在系統(tǒng)里。CMAKE_INSTALL_PREFIX/usr/local這是 Caffe 默認(rèn)搜索路徑。改成別的目錄不是不行但后面 Makefile.config 的INCLUDE_DIRS和LIBRARY_DIRS都得跟著改多一處手工維護就多一處出錯的地方。BUILD_SHARED_LIBSON產(chǎn)出libgflags.so。如果某個項目非要靜態(tài)鏈接2.1.1 在同一個構(gòu)建里也會生成libgflags_nothreads.a之類夠用。反過來只編靜態(tài)庫再想補動態(tài)庫就得整個重編。CMAKE_POSITION_INDEPENDENT_CODEON即使這次編的是動態(tài)庫打開它也保險。將來有人把這套 gflags 改成靜態(tài)庫鏈接進別的 .so不會因為 PIC 再翻一次車。這一條屬于后悔藥成本幾乎為零。BUILD_TESTINGOFF不關(guān)的話2.1.1 會把gflags_unittest一起編出來多花幾十秒到幾分鐘。第一次裝建議留著跑一遍單測確認(rèn)庫本身沒問題跑完再改 OFF 重編一次或者干脆留著反正不影響安裝產(chǎn)物。GFLAGS_NAMESPACEgoogle這條最容易被忽略。2.1.1 的默認(rèn)值本來就是google顯式寫出來的意義在于萬一以后有人在這份 CMakeCache 上改成gflags或者你把這套參數(shù)抄到別的版本上差異會立刻暴露而不是在 Caffe 鏈接階段才以undefined reference的形式出現(xiàn)。配置跑完別急著make先確認(rèn)緩存里的值真的是你寫進去的# 確認(rèn)關(guān)鍵變量落地防止命令行拼錯被 CMake 靜默忽略 grep -E GFLAGS_NAMESPACE|BUILD_SHARED_LIBS|CMAKE_INSTALL_PREFIX CMakeCache.txt輸出里應(yīng)該能看到GFLAGS_NAMESPACE:STRINGgoogle、BUILD_SHARED_LIBS:BOOLON、CMAKE_INSTALL_PREFIX:PATH/usr/local。如果某個變量不見了多半是拼寫問題——CMake 對不認(rèn)識的-D變量不報錯只在緩存里留下一個 unused 警告很容易看漏。3.2 編譯安裝與 ldconfig并行度、DESTDIR 和 sudo 的邊界配置對了編譯本身沒什么懸念但有兩個細節(jié)值得說并行度和安裝后的動態(tài)鏈接器緩存。# 并行編譯核數(shù)用 nproc 拿別拍腦袋寫 -j8 make -j$(nproc) # 安裝到 prefix 指定的位置需要寫 /usr/local 所以要 sudo sudo make install # 刷新動態(tài)鏈接器緩存這一步不做運行時就找不到 so sudo ldconfigmake -j$(nproc)在小內(nèi)存機器上要小心。gflags 2.1.1 的源碼量不大一般不會因為并行度過高把內(nèi)存打爆但如果你的機器同時還跑著別的構(gòu)建任務(wù)-j開到核數(shù)容易觸發(fā) OOM Killer表現(xiàn)是make突然被 kill、沒有任何報錯。穩(wěn)妥做法是-j$(($(nproc)/2))慢一點但不會中斷。sudo ldconfig是必須的。/usr/local/lib在多數(shù)發(fā)行版里已經(jīng)寫進/etc/ld.so.conf.d/的某個配置文件中l(wèi)dconfig會把它掃進緩存程序運行時按 SONAME 找?guī)炀湍苊?。少了這一步make install明明成功ldd卻告訴你libgflags.so.2.1 not found很多人到這一步開始懷疑人生。裝完立刻驗證產(chǎn)物別等到 Caffe 編譯失敗才回頭查# 1. 庫文件應(yīng)該有實體文件和兩條符號鏈接 ls -l /usr/local/lib/libgflags* # 2. 頭文件三個 .h 都在 ls -l /usr/local/include/gflags/ # 3. CMake 配置文件供 find_package 使用 ls -l /usr/local/lib/cmake/gflags/ # 4. pkg-config 文件 ls -l /usr/local/lib/pkgconfig/gflags.pc預(yù)期看到libgflags.so.2.1.1是實體libgflags.so.2.1和libgflags.so是指向它的符號鏈接。SONAME 是libgflags.so.2.1所以運行時找的是中間那條鏈接這也是為什么后面報錯信息里總是出現(xiàn)libgflags.so.2.1而不是2.1.1。/usr/local/lib/cmake/gflags/下的幾個文件決定了find_package(gflags)能不能走 config 模式Caffe 的 CMake 構(gòu)建會用到第 4 章展開。gflags.pc則是 pkg-config 那條路的入口注意它裝在/usr/local/lib/pkgconfig這個路徑默認(rèn)不在pkg-config的搜索列表里需要手動加環(huán)境變量。如果這次裝完想推倒重來2.1.1 的 Makefile 里保留了卸載規(guī)則# 卸載依賴安裝時生成的 install_manifest.txt必須在 build 目錄里執(zhí)行 cd /opt/src/gflags-2.1.1/build sudo make uninstallmake uninstall只刪它自己記錄過的文件。如果你中間手動往/usr/local/include/gflags/里拷過東西或者裝過另一個版本覆蓋了同名文件卸載是不徹底的得手工比對install_manifest.txt清理。3.3 autotools 那條老路什么時候才值得走2.1.1 的源碼包里configure.ac和Makefile.am都還在理論上可以走./configure make make install那條經(jīng)典路徑# autotools 方式僅在 CMake 不可用時考慮 cd /opt/src/gflags-2.1.1 ./configure --prefix/usr/local --enable-shared make -j$(nproc) sudo make install命令能跑通但產(chǎn)物和 CMake 方式不一樣autotools 不會生成/usr/local/lib/cmake/gflags/那一套 config 文件。如果你的下游是 Caffe 的 CMake 構(gòu)建它走find_package(gflags)時會退回FindGFlags.cmake里的手工查找邏輯能找到庫但版本判斷和 include 路徑的推斷不如 config 模式精確。我的取舍是只要機器上有 CMake 3.x一律走 CMake。autotools 這條路留給兩種情況——一是目標(biāo)環(huán)境是某種精簡系統(tǒng)CMake 裝不上二是你要做交叉編譯需要--host參數(shù)那套工具鏈傳遞機制。除此之外沒有理由繞遠路。提示兩條路不要混著走。先用 autotools 裝一遍再用 CMake 裝一遍覆蓋/usr/local/lib下容易出現(xiàn)同名但來源不同的 .soldconfig之后到底加載哪個取決于文件名排序和時間戳排查起來非常費勁。要么徹底make uninstall再換要么一開始就選對。4. 接進 Caffe路徑怎么指、庫名怎么給4.1 Makefile.config 三個變量的填法Caffe 用 Makefile 構(gòu)建時頭文件搜索路徑和庫搜索路徑全在Makefile.config里三個變量決定 gflags 能不能被找到# Makefile.config 片段路徑類變量的默認(rèn)值已經(jīng)包含 /usr/local INCLUDE_DIRS : $(PYTHON_INCLUDE) /usr/local/include LIBRARY_DIRS : $(PYTHON_LIB) /usr/local/lib /usr/lib # 是否用 pkg-config 找依賴默認(rèn)關(guān)閉 USE_PKG_CONFIG : 0INCLUDE_DIRS展開后變成一堆-I參數(shù)LIBRARY_DIRS展開成一堆-L參數(shù)。因為默認(rèn)值里已經(jīng)有/usr/local/include和/usr/local/libgflags 裝在默認(rèn) prefix 時這三行一個字都不用改這也是第 3 章堅持用/usr/local的原因。如果你把 gflags 裝到了別的目錄比如/opt/gflags-2.1.1就要在這兩行前面各加一項順序放在最前面保證優(yōu)先命中# 自定義 prefix 時放在最前面強制優(yōu)先 INCLUDE_DIRS : /opt/gflags-2.1.1/include $(PYTHON_INCLUDE) /usr/local/include LIBRARY_DIRS : /opt/gflags-2.1.1/lib $(PYTHON_LIB) /usr/local/lib /usr/lib順序不能反。C 編譯器的-I是按出現(xiàn)順序查找的鏈接器的-L同理誰在前誰生效。系統(tǒng)里同時存在/usr/include/gflags/gflags.h2.1.2 的和/usr/local/include/gflags/gflags.h2.1.1 的時順序?qū)懛淳蜁庍M另一份頭文件然后鏈接時庫又是另一份版本錯位的經(jīng)典現(xiàn)場。USE_PKG_CONFIG這個開關(guān)打開后會走 pkg-config 找 gflags、glog、protobuf 等依賴。用它能省掉手寫路徑但前提是gflags.pc在pkg-config的搜索路徑里。前面說過/usr/local/lib/pkgconfig默認(rèn)不在列表內(nèi)所以要配合環(huán)境變量# 讓 pkg-config 能找到 /usr/local 下安裝的 .pc 文件 export PKG_CONFIG_PATH/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH # 驗證版本號應(yīng)該輸出 2.1.1 pkg-config --modversion gflags pkg-config --cflags --libs gflags--modversion輸出2.1.1才算找對了。如果輸出的是2.1.2或者2.2.2說明命中的是系統(tǒng)包的那份.pc文件檢查PKG_CONFIG_PATH有沒有生效、/usr/lib/x86_64-linux-gnu/pkgconfig/gflags.pc是不是還在。4.2 CMake 構(gòu)建 Caffe 時 FindGFlags 的查找順序換到 CMake 構(gòu)建 Caffe找 gflags 的邏輯在cmake/Modules/FindGFlags.cmake里。它大致分兩步先嘗試 config 模式也就是找gflags-config.cmake找不到再退回手工find_pathfind_library在標(biāo)準(zhǔn)路徑和GFlags_ROOT之類的提示變量里搜。判斷有沒有找對看 CMake 配置階段的這一行輸出# 在 Caffe 的 build 目錄里執(zhí)行從輸出里撈出 gflags 那一行 cmake .. 21 | grep -i gflags正確的結(jié)果長這樣include 和 library 都指向/usr/localFound gflags (include: /usr/local/include, library: /usr/local/lib/libgflags.so)如果 library 指向/usr/lib/x86_64-linux-gnu/libgflags.so說明系統(tǒng)那套搶先了如果 include 指向/usr/include而 library 指向/usr/local/lib就是典型的兩份混用必須停下來處理不能帶著這個狀態(tài)往下編譯。這種組合在鏈接期不一定報錯但運行時行為會非常詭異比如某些 flag 解析不出來、--help輸出缺項。實在要強制指定可以用這兩個變量覆蓋# 顯式指定繞過 FindGFlags 的自動搜索 cmake .. \ -DGFlags_INCLUDE_DIR/usr/local/include \ -DGFlags_LIBRARIES/usr/local/lib/libgflags.soCMake 的緩存機制在這里也是個坑。第一次配置時找錯了改完環(huán)境變量重新cmake ..它可能還用緩存里的舊值。穩(wěn)妥做法是把build/CMakeCache.txt刪掉重新配置而不是反復(fù)cmake ..試運氣。4.3 用 pkg-config 和一段 CMake 腳本先解耦驗證在把 Caffe 牽扯進來之前最好單獨確認(rèn) gflags 的兩種查找方式都通。pkg-config 那條剛才已經(jīng)驗過CMake 這邊寫個最小工程幾行就夠# /tmp/check_gflags/CMakeLists.txt cmake_minimum_required(VERSION 3.5) project(check_gflags CXX) # 走 config 模式找 /usr/local/lib/cmake/gflags/gflags-config.cmake find_package(gflags REQUIRED) message(STATUS gflags_INCLUDE_DIR ${gflags_INCLUDE_DIR}) message(STATUS gflags_LIBRARIES ${gflags_LIBRARIES})# 配置并觀察輸出不編譯任何東西 mkdir -p /tmp/check_gflags/build cd /tmp/check_gflags/build cmake .. 21 | grep gflagsfind_package(gflags REQUIRED)優(yōu)先走 config 模式2.1.1 安裝的那套gflags-config.cmake會把gflags_INCLUDE_DIR和gflags_LIBRARIES兩個變量填好。如果輸出里這兩個值都是空的但也沒報Could not find的錯誤說明命中了系統(tǒng)里某個不完整的 config 文件ls /usr/lib/x86_64-linux-gnu/cmake/下一層層找就能定位。這一步的價值在于把問題隔離開。gflags 自己的查找鏈路通了再去編 Caffe出問題就只可能是 Caffe 的路徑配置或依賴順序不用在兩個項目之間來回懷疑。5. 踩坑記錄五個把編譯卡死的現(xiàn)場5.1 編譯鏈接階段的三次翻車坑一relocation R_X86_64_32 against symbol ... can not be used when making a shared object現(xiàn)象Caffe 的make all走到鏈接libcaffe.so那一步報錯錯誤信息很長關(guān)鍵詞就是recompile with -fPIC。原因是 gflags 2.1.1 的 CMake 默認(rèn)BUILD_SHARED_LIBSOFF產(chǎn)出的libgflags.a沒開位置無關(guān)代碼往動態(tài)庫里塞就炸。解決回第 3 章的配置命令加-DBUILD_SHARED_LIBSON和-DCMAKE_POSITION_INDEPENDENT_CODEON刪掉 build 目錄重編重裝。已經(jīng)裝過靜態(tài)版的先把/usr/local/lib/libgflags.a和對應(yīng)的 cmake 配置文件清掉再裝一次別讓兩份產(chǎn)物并存??佣ndefined reference to google::ParseCommandLineFlags(int*, char***, bool)現(xiàn)象編譯能過鏈接階段報符號找不到而且符號名里帶著google::。原因多半是頭文件和庫版本不匹配——頭文件來自 2.1.2/usr/include庫里導(dǎo)出的是別的命名空間或者鏈接順序有問題-lgflags排在-lcaffe前面靜態(tài)庫的符號沒被拉進來。解決先ldd .build_release/lib/libcaffe.so | grep gflags看實際鏈到了哪個路徑再nm -D /usr/local/lib/libgflags.so | grep ParseCommandLineFlags確認(rèn)符號在不在。Caffe 的 Makefile 里庫順序是寫死的真遇到順序問題改用 -Wl,--start-group ... -Wl,--本文還有配套的精品資源點擊獲取