[英]Weird 'Native linking failed, undefined Objective-C class' when Xamarin-iOS nuget is generated from Azure Pipeline (but not when it's built locally)
我有 Xamarin-iOS 綁定項目,它在構建時生成一個 nuget。當且僅當我在我的 Mac 上構建它時,所說的 nuget 在 Xamarin-iOS 應用程序中按預期工作。
但是,當我使用 MacOS-12 作為主機(+ iphone16.2 sdk + sharpie 3.5.61 + clang-1400.0.29.202 與我的 localdev Mac 完全一樣)通過 Azure 管道構建此 nuget 時,即使構建成功生成了 88361998985從某種意義上說,在嘗試使用它構建 Xamarin 應用程序時,我收到以下錯誤:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -framework CoreFoundation -framework Security -framework VisionKit -framework UserNotificationsUI -framework UniformTypeIdentifiers -framework ThreadNetwork -framework WatchConnectivity [...] -u _BrotliEncoderHasMoreOutput -u _BrotliEncoderDestroyInstance -u _BrotliEncoderCompress -u _mono_pmip
Undefined symbols for architecture arm64:
"_OBJC_CLASS_$__TtC17McuMgrBindingsiOS17IOSDeviceResetter", referenced from:
objc-class-ref in registrar.o
"_OBJC_CLASS_$__TtC17McuMgrBindingsiOS17IOSFirmwareEraser", referenced from:
objc-class-ref in registrar.o
"_OBJC_CLASS_$__TtC17McuMgrBindingsiOS19IOSFirmwareUpgrader", referenced from:
objc-class-ref in registrar.o
ld: symbol(s) not found for architecture arm64
我已經檢查了生成的dll,它們都生活在Nugets內部和符號“ TTC17MCUMGRBINDINSIOS17IOSDEVICERESETTETER','ttc17mcumgrbindingsios17ios17ios1findsios17iosfines17iosfirm of'ttc17mcumgrbind888888888888888888888888888888888。
托管在 MacOS-12 上的 azure 管道似乎正在使用 Mono 版本。 16.10.1 用於構建,這正是我的 localdev 所擁有的。
我注意到 Azure 中的 'clang' 目標是 x86 而不是 arm64 - 也許這與以某種方式觀察到的錯誤有關?
(localdev)
Apple clang version 14.0.0 (clang-1400.0.29.202)
Target: arm64-apple-darwin21.6.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
(azure)
Apple clang version 14.0.0 (clang-1400.0.29.102)
Target: x86_64-apple-darwin21.6.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
我用來調用 xcodebuild、sharpie 和 lipo 的構建腳本是這個:
#!/usr/bin/env bash
# Builds a fat library for a given xcode project (framework)
#
# Derived from https://github.com/xamcat/xamarin-binding-swift-framework/blob/master/Swift/Scripts/build.fat.sh#L3-L14
IOS_SDK_VERSION="${IOS_SDK_VERSION:-16.2}" # xcodebuild -showsdks
SWIFT_PROJECT_NAME="McuMgrBindingsiOS"
SWIFT_BUILD_PATH="./$SWIFT_PROJECT_NAME/build"
SWIFT_OUTPUT_PATH="./VendorFrameworks/swift-framework-proxy"
SWIFT_BUILD_SCHEME="McuMgrBindingsiOS"
SWIFT_PROJECT_PATH="./$SWIFT_PROJECT_NAME/$SWIFT_PROJECT_NAME.xcodeproj"
SWIFT_PACKAGES_PATH="./packages"
SWIFT_BUILD_CONFIGURATION="Release"
XAMARIN_BINDING_PATH="Xamarin/SwiftFrameworkProxy.Binding"
function print_macos_sdks() {
xcodebuild -showsdks
}
function build() {
echo "** Build iOS framework for simulator and device"
echo "**** (Build 1/5) Cleanup any possible traces of previous builds"
rm -Rf "$SWIFT_BUILD_PATH"
rm -Rf "$SWIFT_PACKAGES_PATH"
rm -Rf "$XAMARIN_BINDING_PATH"
echo "**** (Build 2/5) Restore packages for 'iphoneos$IOS_SDK_VERSION'"
xcodebuild \
-sdk "iphoneos$IOS_SDK_VERSION" \
-arch arm64 \
-scheme "$SWIFT_BUILD_SCHEME" \
-project "$SWIFT_PROJECT_PATH" \
-configuration "$SWIFT_BUILD_CONFIGURATION" \
-clonedSourcePackagesDirPath "$SWIFT_PACKAGES_PATH" \
-resolvePackageDependencies
if [ $? -ne 0 ]; then
echo "** [FAILED] Failed to download dependencies for 'iphoneos$IOS_SDK_VERSION'"
exit 1
fi
echo "**** (Build 3/5) Build for 'iphoneos$IOS_SDK_VERSION'"
# https://stackoverflow.com/a/74478244/863651
xcodebuild \
-sdk "iphoneos$IOS_SDK_VERSION" \
-arch arm64 \
-scheme "$SWIFT_BUILD_SCHEME" \
-project "$SWIFT_PROJECT_PATH" \
-configuration "$SWIFT_BUILD_CONFIGURATION" \
-derivedDataPath "$SWIFT_BUILD_PATH" \
-clonedSourcePackagesDirPath "$SWIFT_PACKAGES_PATH" \
CODE_SIGN_IDENTITY="" \
CODE_SIGNING_ALLOWED=NO \
CODE_SIGNING_REQUIRED=NO
if [ $? -ne 0 ]; then
echo "** [FAILED] Failed to build 'iphoneos$IOS_SDK_VERSION'"
exit 1
fi
echo "**** (Build 4/5) Restore packages for 'iphonesimulator$IOS_SDK_VERSION'"
xcodebuild \
-sdk "iphonesimulator$IOS_SDK_VERSION" \
-arch arm64 \
-scheme "$SWIFT_BUILD_SCHEME" \
-project "$SWIFT_PROJECT_PATH" \
-configuration "$SWIFT_BUILD_CONFIGURATION" \
-clonedSourcePackagesDirPath "$SWIFT_PACKAGES_PATH" \
-resolvePackageDependencies
if [ $? -ne 0 ]; then
echo "** [FAILED] Failed to download dependencies for 'iphonesimulator$IOS_SDK_VERSION'"
exit 1
fi
echo "**** (Build 5/5) Build for 'iphonesimulator$IOS_SDK_VERSION'"
# https://stackoverflow.com/a/74478244/863651
# https://stackoverflow.com/a/64026089/863651
xcodebuild \
-sdk "iphonesimulator$IOS_SDK_VERSION" \
-scheme "$SWIFT_BUILD_SCHEME" \
-project "$SWIFT_PROJECT_PATH" \
-configuration "$SWIFT_BUILD_CONFIGURATION" \
-derivedDataPath "$SWIFT_BUILD_PATH" \
-clonedSourcePackagesDirPath "$SWIFT_PACKAGES_PATH" \
EXCLUDED_ARCHS="arm64" \
CODE_SIGN_IDENTITY="" \
CODE_SIGNING_ALLOWED=NO \
CODE_SIGNING_REQUIRED=NO
if [ $? -ne 0 ]; then
echo "** [FAILED] Failed to build 'iphonesimulator$IOS_SDK_VERSION'"
exit 1
fi
}
function create_fat_binaries() {
echo "** Create fat binaries for Release-iphoneos and Release-iphonesimulator configuration"
echo "**** (FatBinaries 1/8) Copy one build as a fat framework"
cp \
-R \
"$SWIFT_BUILD_PATH/Build/Products/Release-iphoneos" \
"$SWIFT_BUILD_PATH/Release-fat"
if [ $? -ne 0 ]; then
echo "** [FAILED] Failed to copy"
exit 1
fi
echo "**** (FatBinaries 2/8) Combine modules from another build with the fat framework modules"
cp \
-R \
"$SWIFT_BUILD_PATH/Build/Products/Release-iphonesimulator/$SWIFT_PROJECT_NAME.framework/Modules/$SWIFT_PROJECT_NAME.swiftmodule/" \
"$SWIFT_BUILD_PATH/Release-fat/$SWIFT_PROJECT_NAME.framework/Modules/$SWIFT_PROJECT_NAME.swiftmodule/"
if [ $? -ne 0 ]; then
echo "** [FAILED] Failed to copy"
exit 1
fi
echo "**** (FatBinaries 3/8) Combine iphoneos + iphonesimulator configuration as fat libraries"
lipo \
-create \
-output "$SWIFT_BUILD_PATH/Release-fat/$SWIFT_PROJECT_NAME.framework/$SWIFT_PROJECT_NAME" \
"$SWIFT_BUILD_PATH/Build/Products/Release-iphoneos/$SWIFT_PROJECT_NAME.framework/$SWIFT_PROJECT_NAME" \
"$SWIFT_BUILD_PATH/Build/Products/Release-iphonesimulator/$SWIFT_PROJECT_NAME.framework/$SWIFT_PROJECT_NAME"
if [ $? -ne 0 ]; then
echo "** [FAILED] Failed to combine configurations"
exit 1
fi
echo "**** (FatBinaries 4/8) Verify results"
lipo \
-info \
"$SWIFT_BUILD_PATH/Release-fat/$SWIFT_PROJECT_NAME.framework/$SWIFT_PROJECT_NAME"
if [ $? -ne 0 ]; then
echo "** [FAILED] Failed to verify results"
exit 1
fi
echo "**** (FatBinaries 5/8) Copy fat frameworks to the output folder"
rm -Rf "$SWIFT_OUTPUT_PATH" &&
mkdir -p "$SWIFT_OUTPUT_PATH" &&
cp -Rf \
"$SWIFT_BUILD_PATH/Release-fat/$SWIFT_PROJECT_NAME.framework" \
"$SWIFT_OUTPUT_PATH"
if [ $? -ne 0 ]; then
echo "** [FAILED] Failed to copy fat frameworks"
exit 1
fi
echo "**** (FatBinaries 6/8) Generating binding api definition and structs"
sharpie \
bind \
--sdk="iphoneos$IOS_SDK_VERSION" \
--scope="$SWIFT_OUTPUT_PATH/$SWIFT_PROJECT_NAME.framework/Headers/" \
--output="$SWIFT_OUTPUT_PATH/XamarinApiDef" \
--namespace="$SWIFT_PROJECT_NAME" \
"$SWIFT_OUTPUT_PATH/$SWIFT_PROJECT_NAME.framework/Headers/$SWIFT_PROJECT_NAME-Swift.h"
if [ $? -ne 0 ]; then
echo "** [FAILED] Failed to generate binding api definitions and structs"
exit 1
fi
echo "**** (FatBinaries 7/8) Replace existing metadata with the updated"
mkdir -p "$XAMARIN_BINDING_PATH/" &&
cp \
-Rf \
"$SWIFT_OUTPUT_PATH/XamarinApiDef/." \
"$XAMARIN_BINDING_PATH/"
if [ $? -ne 0 ]; then
echo "** [FAILED] Failed to replace existing metadata with the updated"
exit 1
fi
echo "**** (FatBinaries 8/8) Replace NativeHandle -> IntPtr in the generated c# files"
# replace nativehandle -> intptr
find \
"$XAMARIN_BINDING_PATH/" \
-type f \
-exec sed -i.bak "s/NativeHandle[ ]/IntPtr /gi" {} \;
# also need to get rid of stupid autogenerated [verify(...)] attributes which are intentionally placed there
# by sharpie to force manual verification of the .cs files that have been autogenerated
#
# https://learn.microsoft.com/en-us/xamarin/cross-platform/macios/binding/objective-sharpie/platform/verify
find \
"$XAMARIN_BINDING_PATH/" \
-type f \
-exec sed -i.bak 's/\[Verify\s*\(.*\)\]//gi' {} \;
# adding [model] to the interfaces seems to be mandatory for the azure pipelines to generate a valid nuget for ios if we
# omit adding this attribute then the nuget generated by the azure pipelines gets poisoned and it causes a very cryptic runtime error
# so I'm not 100% sure why the [model] attribute does away with the observed error but it does the trick of solving the problem somehow
#
# find \
# "$XAMARIN_BINDING_PATH/" \
# -type f \
# -exec sed -i.bak 's/interface IOSDeviceResetter/[Model] interface IOSDeviceResetter/gi' {} \;
# find \
# "$XAMARIN_BINDING_PATH/" \
# -type f \
# -exec sed -i.bak 's/interface IOSFirmwareEraser/[Model] interface IOSFirmwareEraser/gi' {} \;
# find \
# "$XAMARIN_BINDING_PATH/" \
# -type f \
# -exec sed -i.bak 's/interface IOSFirmwareUpgrader/[Model] interface IOSFirmwareUpgrader/gi' {} \;
# https://stackoverflow.com/a/49477937/863651 its vital to add [BaseType] to the interface otherwise compilation will fail
find \
"$XAMARIN_BINDING_PATH/" \
-type f \
-exec sed -i.bak 's/interface IOSListenerForDeviceResetter/[BaseType(typeof(NSObject))] [Model] interface IOSListenerForDeviceResetter/gi' {} \;
find \
"$XAMARIN_BINDING_PATH/" \
-type f \
-exec sed -i.bak 's/interface IOSListenerForFirmwareEraser/[BaseType(typeof(NSObject))] [Model] interface IOSListenerForFirmwareEraser/gi' {} \;
find \
"$XAMARIN_BINDING_PATH/" \
-type f \
-exec sed -i.bak 's/interface IOSListenerForFirmwareUpgrader/[BaseType(typeof(NSObject))] [Model] interface IOSListenerForFirmwareUpgrader/gi' {} \;
}
function main() {
print_macos_sdks
build
create_fat_binaries
echo "** Done!"
}
main "$@"
如果你願意,我可以為你提供工作和非工作的 nugets,讓你自己比較它們 - 也許更有經驗的雙眼可以發現我無法發現的東西。
PS:我嘗試在“ApiDefinition.cs”中每個生成的接口之前添加 [Protocol] 但即使這解決了原始問題,它也導致了另一個問題:
在嘗試調用實例化 class 的任何方法時,我現在得到一個異常 ala 'Foundation.You_Should_Not_Call_base_In_This_Method'
我發現出了什么問題。 事實證明,Azure 管道中的 swift 編譯器表現得像個聰明人,它會剝離任何和所有要導出的公共類,如果它們沒有在 package 中使用的話(愚蠢的地獄,但你有它伙計們). 我所做的只是添加一個虛擬的 class 來“說服”編譯器這些公共類不應被剝離:
import Foundation
// to future maintainers keep this dummy class around so as to have it reference the exported classes
// to future maintainers
// to future maintainers omitting this one causes the build environment of the azure pipelines to go completely smart-assinine and strip the
// to future maintainers public classes thinking that they are not being used anywhere
public class DummyPlaceholder {
public func Foobar() {
let _ = SomeClassHere(nil)
let _ = SomeOtherClassHere(nil, nil)
...
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.