簡體   English   中英

使用 Xcode 和 SDK 4+ 構建胖靜態庫(設備 + 模擬器)

[英]Build fat static library (device + simulator) using Xcode and SDK 4+

從理論上講,我們似乎可以構建一個包含模擬器、iPhone 和 iPad 的靜態庫。

但是,Apple 沒有我可以找到的關於此的文檔,並且 Xcode 的默認模板未配置為執行此操作。

我正在尋找一種可以在 Xcode 中完成的簡單、便攜、可重用的技術。

一些歷史:

  • 2008 年,我們曾經能夠制作包含 sim 和設備的單個靜態庫。 蘋果禁用了。
  • 整個 2009 年,我們制作了成對的靜態庫——一個用於 sim,一個用於設備。 蘋果現在也禁用了它。

參考:

  1. 這是一個好主意,它是一個很好的方法,但它不起作用: http : //www.drobnik.com/touch/2010/04/universal-static-libraries/

    • 他的腳本中有一些錯誤,這意味着它只能在他的機器上運行——他應該使用 BUILT_PRODUCTS_DIR 和/或 BUILD_DIR 而不是“猜測”它們)
    • Apple 最新的 Xcode 阻止您執行他所做的操作 - 由於 Xcode 處理目標的方式發生了(已記錄的)更改,因此它根本無法工作)
  2. 另一個 SO 提問者詢問如何在沒有 xcode 的情況下做到這一點,並且回答側重於 arm6 與 arm7 部分 - 但忽略了 i386 部分: 如何為 armv6、armv7 和 i386 編譯靜態庫(胖)

    • 由於 Apple 的最新變化,模擬器部分不再與 arm6/arm7 的區別相同 - 這是一個不同的問題,見上文)

備擇方案:

輕松復制/粘貼最新版本(但安裝說明可能會更改 - 見下文!)

Karl 的庫需要更多的努力來設置,但更好的長期解決方案(它將您的庫轉換為框架)。

使用它,然后調整它以添加對存檔構建的支持- 參見下面@Frederik 對他使用的更改的評論,以使其在存檔模式下可以很好地工作。


最近的變化: 1. 添加了對 iOS 10.x 的支持(同時保持對舊平台的支持)

  1. 關於如何將這個腳本與嵌入在另一個項目中的項目一起使用的信息(盡管我強烈建議不要這樣做,但如果您將項目從 Xcode 相互嵌入,Apple 在 Xcode 中有幾個顯示停止錯誤3.x 到 Xcode 4.6.x)

  2. 獎勵腳本讓您自動包含捆綁包(即包含庫中的 PNG 文件、PLIST 文件等!) - 見下文(滾動到底部)

  3. 現在支持 iPhone5(使用 Apple 解決 lipo 中的錯誤的方法)。 注意:安裝說明已更改(我可能可以通過將來更改腳本來簡化此操作,但現在不想冒險)

  4. “復制標題”部分現在尊重公共標題位置的構建設置(由 Frederik Wallner 提供)

  5. 添加了 SYMROOT 的顯式設置(也許還需要設置 OBJROOT?),感謝 Doug Dickinson


腳本(這是您必須復制/粘貼的內容)

有關使用/安裝說明,請參見下文

##########################################
#
# c.f. https://stackoverflow.com/questions/3520977/build-fat-static-library-device-simulator-using-xcode-and-sdk-4
#
# Version 2.82
#
# Latest Change:
# - MORE tweaks to get the iOS 10+ and 9- working
# - Support iOS 10+
# - Corrected typo for iOS 1-10+ (thanks @stuikomma)
# 
# Purpose:
#   Automatically create a Universal static library for iPhone + iPad + iPhone Simulator from within XCode
#
# Author: Adam Martin - http://twitter.com/redglassesapps
# Based on: original script from Eonil (main changes: Eonil's script WILL NOT WORK in Xcode GUI - it WILL CRASH YOUR COMPUTER)
#

set -e
set -o pipefail

#################[ Tests: helps workaround any future bugs in Xcode ]########
#
DEBUG_THIS_SCRIPT="false"

if [ $DEBUG_THIS_SCRIPT = "true" ]
then
echo "########### TESTS #############"
echo "Use the following variables when debugging this script; note that they may change on recursions"
echo "BUILD_DIR = $BUILD_DIR"
echo "BUILD_ROOT = $BUILD_ROOT"
echo "CONFIGURATION_BUILD_DIR = $CONFIGURATION_BUILD_DIR"
echo "BUILT_PRODUCTS_DIR = $BUILT_PRODUCTS_DIR"
echo "CONFIGURATION_TEMP_DIR = $CONFIGURATION_TEMP_DIR"
echo "TARGET_BUILD_DIR = $TARGET_BUILD_DIR"
fi

#####################[ part 1 ]##################
# First, work out the BASESDK version number (NB: Apple ought to report this, but they hide it)
#    (incidental: searching for substrings in sh is a nightmare! Sob)

SDK_VERSION=$(echo ${SDK_NAME} | grep -o '\d\{1,2\}\.\d\{1,2\}$')

# Next, work out if we're in SIM or DEVICE

if [ ${PLATFORM_NAME} = "iphonesimulator" ]
then
OTHER_SDK_TO_BUILD=iphoneos${SDK_VERSION}
else
OTHER_SDK_TO_BUILD=iphonesimulator${SDK_VERSION}
fi

echo "XCode has selected SDK: ${PLATFORM_NAME} with version: ${SDK_VERSION} (although back-targetting: ${IPHONEOS_DEPLOYMENT_TARGET})"
echo "...therefore, OTHER_SDK_TO_BUILD = ${OTHER_SDK_TO_BUILD}"
#
#####################[ end of part 1 ]##################

#####################[ part 2 ]##################
#
# IF this is the original invocation, invoke WHATEVER other builds are required
#
# Xcode is already building ONE target...
#
# ...but this is a LIBRARY, so Apple is wrong to set it to build just one.
# ...we need to build ALL targets
# ...we MUST NOT re-build the target that is ALREADY being built: Xcode WILL CRASH YOUR COMPUTER if you try this (infinite recursion!)
#
#
# So: build ONLY the missing platforms/configurations.

if [ "true" == ${ALREADYINVOKED:-false} ]
then
echo "RECURSION: I am NOT the root invocation, so I'm NOT going to recurse"
else
# CRITICAL:
# Prevent infinite recursion (Xcode sucks)
export ALREADYINVOKED="true"

echo "RECURSION: I am the root ... recursing all missing build targets NOW..."
echo "RECURSION: ...about to invoke: xcodebuild -configuration \"${CONFIGURATION}\" -project \"${PROJECT_NAME}.xcodeproj\" -target \"${TARGET_NAME}\" -sdk \"${OTHER_SDK_TO_BUILD}\" ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO" BUILD_DIR=\"${BUILD_DIR}\" BUILD_ROOT=\"${BUILD_ROOT}\" SYMROOT=\"${SYMROOT}\"

xcodebuild -configuration "${CONFIGURATION}" -project "${PROJECT_NAME}.xcodeproj" -target "${TARGET_NAME}" -sdk "${OTHER_SDK_TO_BUILD}" ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" SYMROOT="${SYMROOT}"

ACTION="build"

#Merge all platform binaries as a fat binary for each configurations.

# Calculate where the (multiple) built files are coming from:
CURRENTCONFIG_DEVICE_DIR=${SYMROOT}/${CONFIGURATION}-iphoneos
CURRENTCONFIG_SIMULATOR_DIR=${SYMROOT}/${CONFIGURATION}-iphonesimulator

echo "Taking device build from: ${CURRENTCONFIG_DEVICE_DIR}"
echo "Taking simulator build from: ${CURRENTCONFIG_SIMULATOR_DIR}"

CREATING_UNIVERSAL_DIR=${SYMROOT}/${CONFIGURATION}-universal
echo "...I will output a universal build to: ${CREATING_UNIVERSAL_DIR}"

# ... remove the products of previous runs of this script
#      NB: this directory is ONLY created by this script - it should be safe to delete!

rm -rf "${CREATING_UNIVERSAL_DIR}"
mkdir "${CREATING_UNIVERSAL_DIR}"

#
echo "lipo: for current configuration (${CONFIGURATION}) creating output file: ${CREATING_UNIVERSAL_DIR}/${EXECUTABLE_NAME}"
xcrun -sdk iphoneos lipo -create -output "${CREATING_UNIVERSAL_DIR}/${EXECUTABLE_NAME}" "${CURRENTCONFIG_DEVICE_DIR}/${EXECUTABLE_NAME}" "${CURRENTCONFIG_SIMULATOR_DIR}/${EXECUTABLE_NAME}"

#########
#
# Added: StackOverflow suggestion to also copy "include" files
#    (untested, but should work OK)
#
echo "Fetching headers from ${PUBLIC_HEADERS_FOLDER_PATH}"
echo "  (if you embed your library project in another project, you will need to add"
echo "   a "User Search Headers" build setting of: (NB INCLUDE THE DOUBLE QUOTES BELOW!)"
echo '        "$(TARGET_BUILD_DIR)/usr/local/include/"'
if [ -d "${CURRENTCONFIG_DEVICE_DIR}${PUBLIC_HEADERS_FOLDER_PATH}" ]
then
mkdir -p "${CREATING_UNIVERSAL_DIR}${PUBLIC_HEADERS_FOLDER_PATH}"
# * needs to be outside the double quotes?
cp -r "${CURRENTCONFIG_DEVICE_DIR}${PUBLIC_HEADERS_FOLDER_PATH}"* "${CREATING_UNIVERSAL_DIR}${PUBLIC_HEADERS_FOLDER_PATH}"
fi
fi

安裝說明

  1. 創建一個靜態庫項目
  2. 選擇目標
  3. 在“Build Settings”選項卡中,將“Build Active Architecture Only”設置為“NO”(對於所有項目)
  4. 在“Build Phases”選項卡中,選擇“Add ... New Build Phase ... New Run Script Build Phase”
  5. 將腳本(上方)復制/粘貼到框中

...獎金可選用法:

  1. 可選:如果您的庫中有標題,請將它們添加到“復制標題”階段
  2. 可選:...並將它們從“項目”部分拖放到“公共”部分
  3. 可選:......每次構建應用程序時,它們都會自動導出到“調試通用”目錄的子目錄中(它們將在 usr/local/include 中)
  4. 可選:注意:如果您嘗試將您的項目拖放到另一個 Xcode 項目中,這會暴露 Xcode 4 中的一個錯誤,如果您的拖放項目中有公共標題,則它無法創建 .IPA 文件。 解決方法:不要嵌入 xcode 項目(Apple 代碼中的錯誤太多!)

如果找不到輸出文件,這里有一個解決方法:

  1. 將以下代碼添加到腳本的最后(由 Frederik Wallner 提供):打開“${CREATING_UNIVERSAL_DIR}”

  2. Apple 刪除 200 行后的所有輸出。 選擇您的目標,在運行腳本階段,您必須取消勾選:“在構建日志中顯示環境變量”

  3. 如果您使用 XCode4 的自定義“構建輸出”目錄,則 XCode 會將所有“意外”文件放在錯誤的位置。

    1. 構建項目
    2. 單擊右側最后一個圖標,位於 Xcode4 的左上角區域。
    3. 選擇頂部項目(這是您的“最新版本”。Apple 應該自動選擇它,但他們沒有想到這一點)
    4. 在主窗口中,滾動到底部。 最后一行應該是: lipo: for current configuration (Debug) 創建輸出文件:/Users/blah/Library/Developer/Xcode/DerivedData/AppName-ashwnbutvodmoleijzlncudsekyf/Build/Products/Debug-universal/libTargetName.a

    ...那是您的通用構建的位置。


如何在項目中包含“非源代碼”文件(PNG、PLIST、XML 等)

  1. 做上面的一切,檢查它是否有效
  2. 在第一個之后創建一個新的運行腳本階段(復制/粘貼下面的代碼)
  3. 在 Xcode 中創建一個新的 Target,類型為“bundle”
  4. 在您的主要項目中,在“構建階段”中,將新包添加為它“依賴”的內容(頂部部分,點擊加號按鈕,滾動到底部,在您的產品中找到“.bundle”文件)
  5. 在您的新捆綁包目標的“構建階段”中,添加一個“復制捆綁包資源”部分,然后將所有 PNG 文件等拖放到其中

將構建的包自動復制到與 FAT 靜態庫相同的文件夾中的腳本:

echo "RunScript2:"
echo "Autocopying any bundles into the 'universal' output folder created by RunScript1"
CREATING_UNIVERSAL_DIR=${SYMROOT}/${CONFIGURATION}-universal
cp -r "${BUILT_PRODUCTS_DIR}/"*.bundle "${CREATING_UNIVERSAL_DIR}"

我花了很多時間試圖構建一個可以在 armv7、armv7s 和模擬器上運行的胖靜態庫。 終於找到了解決辦法

要點是分別構建兩個庫(一個用於設備,一個用於模擬器),重命名它們以區分彼此,然后 lipo -create 它們到一個庫中。

lipo -create libPhone.a libSimulator.a -output libUniversal.a

我試過了,它有效!

我制作了一個XCode 4 項目模板,讓您可以像制作常規庫一樣輕松制作通用框架。

有一個命令行實用程序xcodebuild ,您可以在 xcode 中運行 shell 命令。 因此,如果您不介意使用自定義腳本,此腳本可能會對您有所幫助。

#Configurations.
#This script designed for Mac OS X command-line, so does not use Xcode build variables.
#But you can use it freely if you want.

TARGET=sns
ACTION="clean build"
FILE_NAME=libsns.a

DEVICE=iphoneos3.2
SIMULATOR=iphonesimulator3.2






#Build for all platforms/configurations.

xcodebuild -configuration Debug -target ${TARGET} -sdk ${DEVICE} ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO
xcodebuild -configuration Debug -target ${TARGET} -sdk ${SIMULATOR} ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO
xcodebuild -configuration Release -target ${TARGET} -sdk ${DEVICE} ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO
xcodebuild -configuration Release -target ${TARGET} -sdk ${SIMULATOR} ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO







#Merge all platform binaries as a fat binary for each configurations.

DEBUG_DEVICE_DIR=${SYMROOT}/Debug-iphoneos
DEBUG_SIMULATOR_DIR=${SYMROOT}/Debug-iphonesimulator
DEBUG_UNIVERSAL_DIR=${SYMROOT}/Debug-universal

RELEASE_DEVICE_DIR=${SYMROOT}/Release-iphoneos
RELEASE_SIMULATOR_DIR=${SYMROOT}/Release-iphonesimulator
RELEASE_UNIVERSAL_DIR=${SYMROOT}/Release-universal

rm -rf "${DEBUG_UNIVERSAL_DIR}"
rm -rf "${RELEASE_UNIVERSAL_DIR}"
mkdir "${DEBUG_UNIVERSAL_DIR}"
mkdir "${RELEASE_UNIVERSAL_DIR}"

lipo -create -output "${DEBUG_UNIVERSAL_DIR}/${FILE_NAME}" "${DEBUG_DEVICE_DIR}/${FILE_NAME}" "${DEBUG_SIMULATOR_DIR}/${FILE_NAME}"
lipo -create -output "${RELEASE_UNIVERSAL_DIR}/${FILE_NAME}" "${RELEASE_DEVICE_DIR}/${FILE_NAME}" "${RELEASE_SIMULATOR_DIR}/${FILE_NAME}"

也許看起來效率低下(我不擅長 shell 腳本),但很容易理解。 我配置了一個僅運行此腳本的新目標。 該腳本是為命令行設計的,但未在 :) 中進行測試

核心概念是xcodebuildlipo

我在 Xcode UI 中嘗試了許多配置,但沒有任何效果。 因為這是一種批處理,所以命令行設計更適合,所以蘋果逐漸從 Xcode 中刪除了批處理構建功能。 所以我不希望他們將來提供基於 UI 的批量構建功能。

我需要一個用於 JsonKit 的靜態庫,所以在 Xcode 中創建了一個靜態庫項目,然后在項目目錄中運行這個 bash 腳本。 只要您在關閉“僅構建活動配置”的情況下配置了 xcode 項目,您就應該在一個庫中獲得所有架構。

#!/bin/bash
xcodebuild -sdk iphoneos
xcodebuild -sdk iphonesimulator
lipo -create -output libJsonKit.a build/Release-iphoneos/libJsonKit.a build/Release-iphonesimulator/libJsonKit.a

IOS 10 更新:

我在用 iphoneos10.0 構建 fatlib 時遇到了問題,因為腳本中的正則表達式只需要 9.x 及更低版本,而對於 ios 10.0 則返回 0.0

要解決這個問題,只需更換

SDK_VERSION=$(echo ${SDK_NAME} | grep -o '.\{3\}$')

SDK_VERSION=$(echo ${SDK_NAME} | grep -o '[\\.0-9]\{3,4\}$')

我已經把它變成了一個Xcode 4 模板,與 Karl 的靜態框架模板一樣。

我發現由於明顯的鏈接器錯誤,構建靜態框架(而不是普通靜態庫)會導致 LLVM 隨機崩潰 - 所以,我猜靜態庫仍然有用!

XCode 12 更新:

如果您在沒有-arch參數的情況下運行xcodebuild ,XCode 12 將構建默認架構為“arm64 x86_64”的模擬器庫。

然后運行xcrun -sdk iphoneos lipo -create -output會沖突,因為模擬器和設備庫中存在arm64架構。

從 Adam git 中 fork 腳本並修復它。

很好! 我將類似的東西拼湊在一起,但不得不單獨運行。 將它作為構建過程的一部分使它變得更加簡單。

注意事項之一。 我注意到它不會復制您標記為公開的任何包含文件。 我已經將我的腳本中的內容改編為您的腳本,並且效果很好。 將以下內容粘貼到腳本的末尾。

if [ -d "${CURRENTCONFIG_DEVICE_DIR}/usr/local/include" ]
then
  mkdir -p "${CURRENTCONFIG_UNIVERSAL_DIR}/usr/local/include"
  cp "${CURRENTCONFIG_DEVICE_DIR}"/usr/local/include/* "${CURRENTCONFIG_UNIVERSAL_DIR}/usr/local/include"
fi

我實際上只是為此目的編寫了自己的腳本 它不使用 Xcode。 (它基於 Gambit Scheme 項目中的類似腳本。)

基本上,它運行 ./configure 並 make 三次(對於 i386、armv7 和 armv7s),並將每個生成的庫組合到一個胖庫中。

從 21 年 10 月 22 日起,我采用了最新 Xcode 的公認答案腳本,並添加了對 .framework 和新 .XCFrameworks 的支持,以及更完整的 Objective-C 標頭復制。

https://github.com/Talking-App-Kit/Useful/blob/main/BuildFatPackage.sh

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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