使用Xcode和SDK 4+构建胖静态库(设备+模拟器)

从理论上讲,我们似乎可以构建一个包含模拟器、iPhone和iPad的单一静态库。

然而,苹果没有我能找到的关于这方面的文档,Xcode的默认模板也没有配置这样做。

我正在寻找一个简单的,可移植的,可重用的技术,可以在Xcode中完成。

一些历史:

  • 在2008年,我们曾经能够制作包含sim卡和设备的单个静态库。苹果禁用了这个功能。
  • 整个2009年,我们做了一对静态库-一个用于sim,一个用于设备。苹果现在也禁用了这个功能。

引用:

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

    • 他的脚本中有一些错误,这意味着它只能在他的机器上工作-他应该使用BUILT_PRODUCTS_DIR和/或BUILD_DIR,而不是“猜测”它们)
    • 苹果最新的Xcode阻止你做他做过的事情——它根本不起作用,因为Xcode处理目标的方式发生了(文档化的)变化。
    • 李< / ul > < / >
    • 另一个SO提问者问如何在没有xcode的情况下做到这一点,并且回答集中在arm6 vs arm7部分-但忽略了i386部分:我如何编译一个静态库(脂肪)的armv6, armv7和i386

      • 由于苹果的最新改动,模拟器部分不再是arm6/arm7的区别了——这是一个不同的问题,见上文)
      • 李< / ul > < / >
137203 次浏览
有一个命令行实用程序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的批量构建功能。

选择:

轻松复制/粘贴最新版本(但安装说明可能会改变-见下文!)

卡尔的图书馆需要更多的努力来设置,但是更好的长期解决方案(它将你的库转换为框架)。

使用它,然后调整它以添加对Archive构建的支持 - c.f. @Frederik的评论下面的变化,他正在使用使此工作与存档模式很好。


< p >最近的变化: 1. 增加了对iOS 10的支持。X(同时保持对旧平台的支持)

  1. 关于如何在一个项目嵌入到另一个项目中使用这个脚本的信息(尽管我强烈建议不要这样做,永远不要-如果你在Xcode 3中相互嵌入项目,苹果在Xcode中有几个显示停止的bug。x到Xcode 4.6.x)

  2. 奖励脚本,让你自动包括包(即包括PNG文件,PLIST文件等从你的库!)-见下文(滚动到底部)

  3. 现在支持iPhone5(使用苹果解决lipo漏洞的方法)。注意:安装说明已经更改(我可能可以通过在将来更改脚本来简化这一点,但现在不想冒险)

  4. “复制标题”部分现在尊重公共标题位置的构建设置(由Frederik Wallner提供)

  5. 增加了SYMROOT的显式设置(可能也需要设置OBJROOT ?),感谢Doug Dickinson


SCRIPT(这是你需要复制/粘贴的东西)

有关使用/安装说明,请参见下面

##########################################
#
# 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. 在“构建设置”选项卡中,将“仅构建活动体系结构”设置为“NO”(用于所有项)
  4. 在“Build Phases”选项卡中,选择“Add…”新的构建阶段…新的运行脚本构建阶段”
  5. 复制/粘贴上面的脚本到方框中

...奖金可选用法:

  1. 可选:如果你的库中有头文件,将它们添加到“复制头文件”阶段
  2. 可选:…并将它们从“项目”部分拖放到“公共”部分
  3. 可选:…并且每次你构建应用程序时,它们都会自动被导出到“debug-universal”目录的子目录中(它们会在usr/local/include中)
  4. 可选:注意:如果你试图拖放你的项目到另一个Xcode项目,这暴露了Xcode 4中的一个错误,如果你在拖放项目中有公共头文件,它不能创建一个。ipa文件。解决方法:不要嵌入xcode项目(苹果代码中有太多bug !)

如果你找不到输出文件,这里有一个变通办法:

  1. 将以下代码添加到脚本的最后(由Frederik Wallner提供):

  2. 苹果会删除200行之后的所有输出。选择你的目标,在运行脚本阶段,你必须取消勾选:“在构建日志中显示环境变量”

  3. 如果你正在为XCode4使用一个自定义的“build output”目录,那么XCode将所有“意外”文件放在错误的位置。

    1. 构建项目
    2. 单击右边的最后一个图标,在Xcode4的左上方区域。
    3. 选择顶部的项目(这是您的“最新构建”。苹果应该自动选择,但他们没有想到这一点)
    4. 在主窗口中,滚动到底部。最后一行应该读为:lipo:为当前配置(调试)创建输出文件:/Users/blah/Library/Developer/Xcode/DerivedData/AppName-ashwnbutvodmoleijzlncudsekyf/Build/Products/Debug-universal/libTargetName.a

    ...这是通用构建的位置。


如何在你的项目中包含“非源代码”文件(PNG, PLIST, XML等)

  1. 以上一切都做了,检查一下是否正常
  2. 在第一个阶段之后创建一个新的运行脚本阶段(复制/粘贴下面的代码)
  3. 在Xcode中创建一个新的目标,类型为bundle
  4. 在你的MAIN PROJECT中,在“Build Phases”中,添加新的bundle作为它“依赖”的东西(顶部部分,点击加号按钮,滚动到底部,找到“”)。bundle"文件在你的产品)
  5. 在你的NEW BUNDLE TARGET中,在“Build Phases”中,添加一个“Copy BUNDLE Resources”部分,并拖放所有的PNG文件等

脚本自动复制构建的包(s)到相同的文件夹作为你的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}"

伟大的工作!我拼凑了一些类似的东西,但必须单独运行。让它成为构建过程的一部分会使构建过程简单得多。

有一点值得注意。我注意到它不会复制任何你标记为公共的包含文件。我把我剧本里的内容改编成了你的,效果相当不错。将以下内容粘贴到脚本的末尾。

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 4项目模板,它可以让你像创建常规库一样轻松地创建一个通用框架。

我已经把它变成了Xcode 4模板,与Karl的静态框架模板相同。

我发现构建静态框架(而不是普通的静态库)会导致LLVM随机崩溃,这是由于一个明显的链接器错误——所以,我猜静态库仍然有用!

我花了很多时间试图构建一个可以在armv7、armv7s和模拟器上工作的大型静态库。最后找到了解决方案

要点是分别构建两个库(一个用于设备,另一个用于模拟器),重命名它们以彼此区分,然后将它们创建到一个库中。

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

我试过了,很管用!

我实际上只是为了这个目的而自己写剧本。它不使用Xcode。 (它基于Gambit Scheme项目中的类似脚本)

基本上,它运行。/configure和make三次(适用于i386、armv7和armv7s),并将每个生成的库合并到一个胖库中。

我需要一个脂肪静态库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 12更新:

如果你运行xcodebuild而不带-arch参数,XCode 12将构建架构为“arm64 x86_64”的模拟器库。为默认。

然后运行xcrun -sdk iphoneos lipo -create -output会发生冲突,因为arm64体系结构存在于模拟器和设备库中。

fork脚本从亚当git和修复它。