解決庫依賴,讓你無需每次都把庫檔案拷貝到系統資料夾。
解決MAC執行時庫依賴報錯問題
本文只探討執行時出錯,編譯不通過請自行google解決。
1、庫依賴報錯有幾種,這裡只介紹動態連線庫 *.dylib、框架*.framwork,其餘的依賴報錯解決辦法類似。
2、找出庫依賴
xcode會報出庫依賴的錯誤,但是可能不夠詳細。
命令列輸入 otool -L <object file>可以查詢可執行檔案所依賴的庫。
例:(為了節省時間,大神們可以只看小標題)
1)生成你的app。
開啟xcode,編譯、執行出錯的工程,由於找不到依賴的庫,執行時會卡住,這時候不管它,先停止執行。
2)找到該app檔案中的可執行檔案。
在xcode
3)查詢依賴的庫
otool -L 該可執行檔案
開啟終端, 輸入: otool -L [空格]
注意, -L後面輸入一個空格,然後把剛剛找到的可執行檔案拖到終端來。
拖完之後,終端顯示類似的資訊:
otool -L /Users/userName/Library/Developer/Xcode/DerivedData/Bluepoint-fapwtpqiamwycgdslbokwedxpcss/Build/Products/Debug/Bluepoint.app/Contents/MacOS/Bluepoint
按回車開始查詢。
例子中找到如下資訊:
/usr/local/lib/libPowerMeterLib.dylib (compatibility version 1.0.0, current version 1.0.0)
/Library/Frameworks/LuCamSDK.framework/Versions/A/LuCamSDK (
@rpath/BeamProfileKit.framework/Versions/A/BeamProfileKit (compatibility version 1.0.0, current version 1.0.0)
libSMC6490_Lib.dylib (compatibility version 0.0.0, current version 0.0.0)
/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (compatibility version 300.0.0, current version 1349.25.0)
/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 307.4.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1238.0.0)
/System/Library/Frameworks/AppKit.framework/Versions/C/AppKit(compatibility version 45.0.0, current version 1504.75.0)
/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation(compatibility version 150.0.0, current version 1348.28.0)
每一行就是一個依賴項,
現在我來科普一下每行的內容。
第一行,/usr/local/lib/libPowerMeterLib.dylib(compatibility version 1.0.0, current version 1.0.0)
,後面括號裡面的內容是版本資訊,暫時不用管它。括號前面是一個檔案的絕對路徑,這個檔案是一個.dylib結尾的檔案,這是一個動態連線庫(類似Windows裡面的 dll )。
哎,問題就在這裡,這個絕對路徑是庫的開發者有意無意設定的,執行時,Xcode會去該路徑查詢該檔案,找不到該檔案就報錯了。聰明的小夥伴們已經想到了,工程裡面是有這個檔案的,(不然編譯的時候極有可能報錯,為什麼不是必然報錯?後面有機會再講 。)既然有這個檔案,我們告訴xcode去哪裡找這個東西就OK了。
第二行,它依賴 /Library/Frameworks/ 下的 LuCamSDK.framework檔案, LuCamSDK.framework下的可執行檔案路徑是/Versions/A/LuCamSDK,
這是一個可執行檔案依賴框架檔案的栗子。
第三行同是一個框架檔案依賴,只是路徑是 @rpath下的 BeamProfileKit.framework檔案。
@rpath 是一個儲存了多個路徑的變數,可以用編譯器指定,這樣在執行時,會遍歷多個路徑,直至找到存放指定檔案為止。
第四行,libSMC6490_Lib.dylib ,木有路徑?這個沒關係,沒有路徑的,預設在/usr/local/lib/,只是修改依賴路徑的時候,old 引數需直接傳 libSMC6490_Lib.dylib
第五行及以下行是系統庫依賴,如果工程沒有對系統庫進行修改,請不要更改依賴路徑,
還有些情況,就是如果用到了跟目前系統版本不同的庫,也可以通過修改庫路徑來相容不同版本的作業系統。
3、修改依賴路徑
MAC下,修改可執行檔案依賴路徑的命令是:
install_name_tool [-change old new] input
舉個栗子:
假如一個可執行檔案e1,e1在電腦上的/pathC(完整路徑是: /pathC/e1 (就是input))
它原來依賴 /usr/local/lib/下面的 A.dylib檔案 ,(完整路徑是: /usr/local/lib/A.dylib(也是old))
可是,我電腦上的A.dylib檔案在目錄/pathB,我要把e1的依賴修改成 /pathB/A.dylib (new)
完整的命令如下:
install_name_tool -change /usr/local/lib/A.dylib /pathB/A.dylib /pathC/e1
改完之後,可以 otool -L /pathC/e1 看一下改好沒有。
下面我們就可以來修改依賴路徑了^_^。
等一下!!!我們每次編譯,都會重新生成.app檔案的,現在修改了依賴路徑,如果重新編譯它會不會(。 ́︿ ̀。)
還有,我們工程裡面有這個檔案,但是編譯之後,我們要把.app安裝到別人的電腦上,別人的電腦沒有這個工程,當然也不會存在這個檔案,會不會執行報錯(。 ́︿ ̀。)
為了解決以上問題,我們可以考慮
1、在每次生成.app之後修改依賴路徑
2、把依賴的檔案,從工程裡拷貝打包到.app檔案裡面。
由於修改路徑需要該被依賴檔案事先存在,我們把順序調換一下:
1、把依賴的檔案,從工程裡拷貝打包到.app檔案裡面。
2、在每次生成.app之後修改依賴路徑。
xcode打包時,可以自動把一些檔案拷貝到.app裡面,具體做法是在 target 的Build Phases裡面點選上方的 +號,選擇 New Copy File Phase,增加一個檔案拷貝欄位,xcode在編譯完成之後,會自動拷貝欄位裡面提到的檔案。
為了規範,我們把需要的框架檔案拷貝到 Contents資料夾下面的Framworks資料夾裡面。
把需要的動態連線庫拷貝到Contents資料夾下面的Dylib資料夾裡面。
1)New Copy File Phase->修改Destination為 Framworks ->點選下方的 +號,選擇需要拷貝的*.framwork檔案。
xcode會自動建立一個 Framworks資料夾,並把需要的檔案都拷貝到裡面來。
2)New Copy File Phase ->修改Destination為 Wrapper, SubPath填Contents/Dylib,->選擇需要拷貝的 *.dylib檔案。
3) New Run Script Phase ->填上如下shell程式碼:
install_name_tool -change /usr/local/lib/libPowerMeterLib.dylib @executable_path/../Dylib/libPowerMeterLib.dylib "$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/MacOS/$PRODUCT_NAME"
install_name_tool -change libSMC6490_Lib.dylib @executable_path/../Dylib/libSMC6490_Lib.dylib "$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/MacOS/$PRODUCT_NAME"
install_name_tool -change /Library/Frameworks/LuCamSDK.framework/Versions/A/LuCamSDK @executable_path/../Frameworks/LuCamSDK.framework/Versions/A/LuCamSDK "$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/MacOS/$PRODUCT_NAME"
install_name_tool -change /Library/Frameworks/LuCamSDK.framework/Versions/A/LuCamSDK @rpath/LuCamSDK.framework/Versions/A/LuCamSDK "$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/Frameworks/BeamProfileKit.framework/Versions/A/BeamProfileKit"
注意,每個引數之間有空格,每句命令之間用回車分隔即可。
4)然後就可以啦,編譯執行,一切正常。
解釋一下:
第一句指令碼,
/usr/local/lib/libPowerMeterLib.dylib 對應 old
@executable_path/../Dylib/libPowerMeterLib.dylib 對應new
"$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/MacOS/$PRODUCT_NAME"對應input
old沒什麼好說的,原來的依賴路徑,
new這裡引用了一個 @executable_path變數,這個是指工程啟動的執行檔案所在的路徑,一般固定為 XXX/*.app/Contents/MacOS/,這個路徑是執行的時候產生的。
input 這裡引用了兩個變數, $TARGET_BUILD_DIR 和 $PRODUCT_NAME 。顧名思義,這兩個變數分別是生成目標(.app)的目錄,和(.app)的名稱。
意思就是把app裡入口可執行檔案的 /usr/local/lib/libPowerMeterLib.dylib依賴路徑改成
入口可執行檔案的上一層目錄的/Dylib/libPowerMeterLib.dylib目錄。 (..)是上一層的意思。
第二第三句沒什麼說的,
第四句, input為"$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/Frameworks/BeamProfileKit.framework/Versions/A/BeamProfileKit"
為什麼要新增這句呢?
因為BeamProfileKit.framework裡面的可執行檔案BeamProfileKit也依賴了LuCamSDK,如果不修改依賴,也會引起報錯。(每個框架檔案裡面也有一個可執行檔案,它有可能依賴別的庫)。
終於寫完了,寫得又長又臭,人家說如果不能用一句話把事情描述清楚的話,可能是你的理解還不到位。好吧,如果用一句話來說,它就是:打包你庫,修改庫依賴路徑。