clang在編譯時指定目標檔案所需的最低macOS版本

2022-11-05 21:00:34

調研這個的原因,是因為有個同事在macOS 12.2上打包好的程式,放在macOS 10.15上執行時報錯:

Dyld Error Message:
  Symbol not found: __ZNKSt3__115basic_stringbufIcNS_11char_traitsIcEENS_9allocatorIcEEE3strEv
  Referenced from: /Library/Application Support/XXX.dylib (which was built for Mac OS X 12.2)
  Expected in: /usr/lib/libc++.1.dylib

調研了一番,發現這個是因為Apple的SDK使用weak linking來支援不同版本的macOS。

目標檔案所需的最低macOS版本有個專有名稱「deployment target」。
 
檢視庫的deployment target
檢視*.dylib、*.a或者主程式的deployment target屬性、sdk版本:
otool -l 7z.dylib | grep -E "(minos|sdk)"
find . -name "*.a" | xargs otool -l | grep -E "(minos|sdk)"

通過命令列引數設定deployment target
clang/clang++在編譯時指定deployment target的命令列引數是-mmacos-version-min,比如:-mmacos-version-min=10.15,指定最低為Catalina的最後一個版本10.15。
針對iOS等有類似的開關如-miphoneos-version-min、-mtvos-version-min、 -mwatchos-version-min。
-mmacos-version-min有個別名-mmacosx-version-min,別名只是為了相容,儘量不使用這個別名。
clang會根據這個命令列引數來定義編譯器內建宏MAC_OS_X_VERSION_MIN_REQUIRED等,而SDK標頭檔案<AvailabilityMacros.h>、<Availability.h>中有對這些宏的檢查,根據宏決定哪些符號採用weak linking。weak linking的符號在編譯時不會報錯,dylib載入時也不會報錯,在用到對應的符號時如果不存在才會報錯。
 
通過環境變數設定deployment target
clang的這個引數也可通過環境變數來設定。命令列引數優先於環境變數。
MACOSX_DEPLOYMENT_TARGET
IPHONEOS_DEPLOYMENT_TARGET
TVOS_DEPLOYMENT_TARGET
WATCHOS_DEPLOYMENT_TARGET
DRIVERKIT_DEPLOYMENT_TARGET
 
我們的工程以及我們的工程所直接/間接依賴的所有靜態庫/動態庫,都需要在編譯時指定相同的deployment target。
省事的辦法是通過環境變數來統一設定,在開始整個打包之前設定一下
其次是命令列開關。不同工程型別的命令列開關設定方法有差異。以下是命令列開關的設定。
 
通過vcpkg install編譯的庫
需要更改vcpkg/triplets/x64-osx.cmake檔案的內容,增加下面三行(理論上只要第一行即可,但vcpkg目前貌似有bug,導致VCPKG_OSX_DEPLOYMENT_TARGET只對CMake工程生效,對其他型別的工程不生效。所以需要第二行、第三行):
set(VCPKG_OSX_DEPLOYMENT_TARGET "10.15")
set(VCPKG_C_FLAGS -mmacosx-version-min=10.15)
set(VCPKG_CXX_FLAGS -mmacosx-version-min=10.15)
也可以複製這個檔案到某個目錄下,在複製出來的檔案中增加上面這三行,然後給vcpkg install傳遞--overlay-triplets引數以使用這個修改過的triplet檔案。這樣通過vcpkg install安裝的所有庫的deployment target都是10.15。
 
autoconf型別的工程
比如libiconv庫是手動執行autoconf編譯的,需要在configure時增加引數:
./configure CFLAGS=-mmacos-version-min=10.15 CPPFLAGS=-mmacos-version-min=10.15
 
CMake工程
在CMakeLists.txt裡的project()語句之前增加一句:
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15")
或者
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "" FORCE)
 
Xcode工程
在介面的工程屬性中可以設定deployment target。
 
Makefile工程
在makefile裡自行給clang傳遞引數-mmacos-version-min=10.15即可。