簡體   English   中英

當從 Azure 管道生成 Xamarin-iOS nuget 時出現奇怪的“本機鏈接失敗,未定義 Objective-C 類”(但在本地構建時不會)

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM