在没有Xcode构建系统的情况下构建iOS应用

(本文最初是为我的博客 vojtastavik.com撰写的

尽管其名称听起来很吓人 ,但是构建系统只是一个常规程序,它知道如何构建其他程序。 作为iOS开发人员,您当然熟悉如何使用Xcode构建项目。 您转到Product菜单,然后选择Build ,或者使用, ⌘B键盘快捷键。

您可能还听说过Xcode命令行工具。 它是一组工具,使您可以使用xcodebuild命令直接从终端构建Xcode项目。 用于自动化流程(例如在CI上)的非常方便的事情。

不管您如何启动它,建筑物本身都是由Xcode的构建系统编排的。

我们是否可以在没有Xcode的构建系统的情况下复制构建过程并“手动”构建应用程序?

是否可以对生成的应用程序进行签名 ? 甚至部署到实际的iOS设备上?

⚠️ 免责声明 1⚠️

这篇文章是关于什么的: 编写一个不可重用的脚本,以最简单的方式构建一个具体的iOS项目。

这篇文章的目的不在于: 编写一个复杂而通用的构建系统。


我让Xcode 10.0使用Single View App模板生成一个新项目,并将其命名为“ ExampleApp”。 这将成为我们将尝试“手动”构建的参考应用程序。 我所做的项目的唯一调整是在主(也是唯一的) ViewController添加带有🎉UILabel

我还在项目的根文件夹中创建了build.bash文件。 我们将使用此文件作为实际的构建脚本。

不要忘记通过在终端中运行以下命令来使文件可执行:

$ chmod +x build.bash


⚠️ 免责声明 2⚠️

其应如何构建应用程序的完整“食谱”包含在其 xcodeproj 文件中。 本文不是关于如何解析和从中检索此信息。

为了本文的目的,我们将忽略项目文件。 为了使我们的生活更轻松, 我们将所有细节(例如项目名称,源文件或构建设置)直接硬编码到构建脚本中。

让我们从一些整理工作开始。 我们需要定义并创建在构建过程中将要使用的一组文件夹。

  ################################################ ########### 
#build.bash
################################################ ########### !! / bin / bash#如果任何命令失败,请立即退出此脚本
set -ePROJECT_NAME = ExampleApp#此脚本的产品。 这是实际的应用程序捆绑包!
BUNDLE_DIR = $ {PROJECT_NAME} .app#用于构建所需临时文件的位置
TEMP_DIR = _BuildTemp
################################################ ###########
echo→步骤1:准备工作文件夹
################################################ ############从以前的版本中删除现有文件夹
rm -rf $ {BUNDLE_DIR}
rm -rf $ {TEMP_DIR} mkdir $ {BUNDLE_DIR}
echo✅创建$ {BUNDLE_DIR}文件夹mkdir $ {TEMP_DIR}
echo✅创建$ {TEMP_DIR}文件夹

运行脚本时,终端应如下所示:

  $ ./build.bash 
→步骤1:准备工作文件夹
✅创建ExampleApp.app文件夹
✅创建_BuildTemp文件夹

注意,该脚本创建了两个文件夹:


您是否知道可以使用swiftc从终端直接调用Swift编译器?

  $ swiftc -h 
概述:Swift编译器用法:swiftc [选项]
...

使用正确的标志,编译所有.swift源文件是非常简单的操作。

  ################################################ ########### 
echo→步骤2:编译Swift文件
################################################ ############项目源的根目录
SOURCE_DIR = ExampleApp#源目录中的所有Swift文件
SWIFT_SOURCE_FILES = $ {SOURCE_DIR} / *。swift#我们要构建的目标体系结构
TARGET = x86_64-apple-ios12.0-simulator#我们要用于编译的SDK的路径
SDK_PATH = $(xcrun --show-sdk-path --sdk iphonesimulator)swiftc $ {SWIFT_SOURCE_FILES} \
-sdk $ {SDK_PATH} \
-target $ {TARGET} \
-emit-executable \
-o $ {BUNDLE_DIR} / $ {PROJECT_NAME} echo✅编译Swift源文件$ {SWIFT_SOURCE_FILES}

我想谈谈-emit-executable标志。 根据文档,使用此标志时,编译器“发出链接的可执行文件”。 当您在详细模式( -v )中运行swiftc时,您实际上可以看到实际情况。 编译器为每个.swift文件创建.o对象,然后调用ld将其链接到最终的可执行文件中。

注意,我们将输出( -o )设置为ExampleApp.app/ExampleApp 。 那就是编译过程中生成的可执行文件。

运行脚本,您应该看到以下内容:

  $ ./build.bash 
→步骤1:准备工作文件夹
✅创建ExampleApp.app文件夹
✅创建_BuildTemp文件夹→步骤2:编译Swift文件
✅编译Swift源文件ExampleApp / AppDelegate.swift ExampleApp / ViewController.swift

您还可以检查是否在ExampleApp.app捆绑包内创建了生成的可执行文件:

  $树ExampleApp.app 
ExampleApp.app
└──ExampleApp

源代码文件不是唯一需要编译的文件。 我们还需要编译所有.storyboard文件。 用于情节ibtool (以及所有其他接口构建器文件)的编译器称为ibtool 。 该过程生成的文件扩展名为.storyboardc

有趣的事实

storyboardc 文件是文件包。 当检查捆绑包的内容时,您会看到,编译的情节提要实际上只是一堆 nib 文件。

ibtool不接受多个文件。 我们需要使用for循环遍历所有情节提要。

  ################################################ ########### 
echo→步骤3:编译情节提要
################################################ ############ Base.lproj目录中的所有情节提要
STORYBOARDS = $ {SOURCE_DIR} /Base.lproj / *。storyboard#编译的故事板的输出文件夹
STORYBOARD_OUT_DIR = $ {BUNDLE_DIR} /Base.lprojmkdir -p $ {STORYBOARD_OUT_DIR}
echo✅在$ {STORYBOARDS}中为storyboard_path创建$ {STORYBOARD_OUT_DIR}文件夹; 做

ibtool $ storyboard_path \
--compilation-directory $ {STORYBOARD_OUT_DIR} echo✅编译$ storyboard_path
完成

让我们运行脚本并验证ExampleApp.app的内容:

  $ ./build.bash 
→步骤1:准备工作文件夹
✅创建ExampleApp.app文件夹
✅创建_BuildTemp文件夹→步骤2:编译Swift文件
✅编译Swift源文件ExampleApp / AppDelegate.swift ExampleApp / ViewController.swift→步骤3:编译情节提要
✅创建ExampleApp.app/Base.lproj文件夹
✅编译ExampleApp / Base.lproj / LaunchScreen.storyboard
✅编译ExampleApp / Base.lproj / Main.storyboard $ tree -L 2 ExampleApp.app
ExampleApp.app
├──Base.lproj
│├──LaunchScreen.storyboardc
│└──Main.storyboardc
└──ExampleApp

有效的应用程序捆绑包必须包含Info.plist 。 不幸的是,我们不能简单地从项目目录中复制一个。 此原始文件包含几个变量,我们需要将其替换为实际值。

  Info.plist变量 
----------------------------------------------
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleName
$(PRODUCT_NAME)

为了轻松处理plist文件,Apple为我们提供了一个名为PlistBuddy的工具。 因为我们不想修改原始的plist我们首先在_BuildTemp文件夹中创建一个临时副本,然后在其中进行修改。 最后,我们将已处理的plist复制到应用程序捆绑包中。

  ################################################ ########### 
echo→步骤4:处理和复制Info.plist
################################################ ############原始Info.plist文件的位置
ORIGINAL_INFO_PLIST = $ {SOURCE_DIR} /Info.plist#用于编辑的临时Info.plist副本的位置
TEMP_INFO_PLIST = $ {TEMP_DIR} /Info.plist#应用程序捆绑包中已处理的Info.plist的位置
PROCESSED_INFO_PLIST = $ {BUNDLE_DIR} /Info.plist#生成的应用程序的捆绑包标识符
APP_BUNDLE_IDENTIFIER = com.vojtastavik。$ {PROJECT_NAME} cp $ {ORIGINAL_INFO_PLIST} $ {TEMP_INFO_PLIST}
echo✅将$ {ORIGINAL_INFO_PLIST}复制到$ {TEMP_INFO_PLIST}
#用于处理plists的命令行工具
PLIST_BUDDY = / usr / libexec / PlistBuddy#设置我们在步骤2中创建的可执行文件的正确名称
$ {PLIST_BUDDY} -c“设置:CFBundleExecutable $ {PROJECT_NAME}” $ {TEMP_INFO_PLIST}
echo✅将CFBundleExecutable设置为$ {PROJECT_NAME}#设置有效的包标识符
$ {PLIST_BUDDY} -c“设置:CFBundleIdentifier $ {APP_BUNDLE_IDENTIFIER}” $ {TEMP_INFO_PLIST}
echo✅将CFBundleIdentifier设置为$ {APP_BUNDLE_IDENTIFIER}#设置正确的捆绑包名称
$ {PLIST_BUDDY} -c“设置:CFBundleName $ {PROJECT_NAME}” $ {TEMP_INFO_PLIST}
echo✅将CFBundleName设置为$ {PROJECT_NAME}#将已处理的Info.plist复制到应用程序包
cp $ {TEMP_INFO_PLIST} $ {PROCESSED_INFO_PLIST}
echo✅将$ {TEMP_INFO_PLIST}复制到$ {PROCESSED_INFO_PLIST}

让我们运行脚本:

  $ ./build.bash 
#...#
→步骤4:处理和复制Info.plist
Example将ExampleApp / Info.plist复制到_BuildTemp / Info.plist
✅将CFBundleExecutable设置为ExampleApp
✅将CFBundleIdentifier设置为com.vojtastavik.ExampleApp
✅将CFBundleName设置为ExampleApp
✅将_BuildTemp / Info.plist复制到ExampleApp.app/Info.plist

我们还应该验证已处理的plist文件在那里,并且其内容正确:

  $ cat ExampleApp.app/Info.plist 




#...#
CFBundleExecutable
ExampleApp
CFBundleIdentifier
com.vojtastavik.ExampleApp
CFBundleInfoDictionaryVersion
6.0
CFBundleName
ExampleApp
#...#


此时,我们应该有一个有效的.app捆绑包。 至少要在iOS模拟器中运行它。 您可以直接从终端窗口打开模拟器:

$ open -a "Simulator.app"

为了与iOS模拟器进行交互,Apple创建了一个名为simctl的工具。 这是将ExampleApp应用程序捆绑包安装到当前正在运行的模拟器的方法。 第二个命令启动已安装的应用程序。

$ xcrun simctl install booted ExampleApp.app

$ xcrun simctl launch booted com.vojtastavik.ExampleApp

我必须承认,当我看到该应用程序正在运行时,我感到非常惊讶!

事实是,我认为还需要再走一步。 Swift尚没有稳定的二进制接口,iOS中不包含Swift运行时。 因此,每个应用程序都必须具有捆绑包中包含的自己的Swift运行时库副本。

我们尚未将运行时库复制到捆绑包中,但该应用程序可以运行!

使用swiftc编译Swift文件时,可以使用-Xlinker -rpath标志指定dylib运行时搜索文件夹。 这样,您就可以让链接器知道在哪里搜索动态库,例如Swift运行时库。 原来,如果您没有像我一样指定它们,则搜索路径默认为:

  /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/iphonesimulator 

这正是Swift运行时库存储在计算机上的地方!

iOS模拟器未沙盒化,可以访问您的所有文件。 这意味着它可以轻松地从任意位置加载运行时库。 我对此一无所知。

只要您以iOS模拟器为目标,就可以创建有效的,功能齐全的Swift iOS应用,而无需在捆绑包中包含Swift运行时库。


如您所见,为Simulator构建代码是相当宽容的。 我们不需要在捆绑包中包含运行时库。 我们也不需要对其进行签名和处理。

更改构建脚本以使其可以生成有效的应用程序捆绑包(可以在iOS设备上执行)是另一种乐趣。

首先,我们需要让脚本知道何时为设备构建。

让我们使用--device标志向脚本发送所需的目标体系结构信号。 我们从步骤1更新代码:

  ################################################ ########### 
#build.bash
################################################ ########### !! / bin / bash#如果任何命令失败,请立即退出此脚本
set -ePROJECT_NAME = ExampleApp#此脚本的产品。 这是实际的应用程序捆绑包!
BUNDLE_DIR = $ {PROJECT_NAME} .app#用于构建所需的任何临时文件的位置
TEMP_DIR = _BuildTemp #检查--device标志
如果[“ $ 1” =“ --device”]; 然后 #
BUILDING_FOR_DEVICE = true;
fi#

#打印当前目标架构
如果[“ $ {BUILDING_FOR_DEVICE}” = true]; 然后 #
回显👍为设备#构建$ {PROJECT_NAME}
其他#
echo👍为模拟器#构建$ {PROJECT_NAME}
fi ################################################ #############
echo→步骤1:准备工作文件夹
################################################ ###########
...

其次,我们需要从步骤2更新代码:

  ################################################ ########### 
echo→步骤2:编译Swift文件
################################################ ############项目源的根目录
SOURCE_DIR = ExampleApp#源目录中的所有Swift文件
SWIFT_SOURCE_FILES = $ {SOURCE_DIR} / *。swift #我们要构建的目标体系结构
TARGET =“” #我们要用于编译的SDK的路径
如果[[$ {BUILDING_FOR_DEVICE}“ = true],则 SDK_PATH =”“ 然后
#建立设备
TARGET = arm64-apple-ios12.0
SDK_PATH = $(xcrun --show-sdk-path --sdk iphoneos) #应用包中的文件夹
#将复制所有必需的dylib
FRAMEWORKS_DIR = Frameworks #为编译器设置其他标志
OTHER_FLAGS =“-Xlinker -rpath -Xlinker @executable_path / $ {FRAMEWORKS_DIR}” 其他
#建立模拟器
TARGET = x86_64-apple-ios12.0-simulator
SDK_PATH = $(xcrun --show-sdk-path --sdk iphonesimulator)
fi #编译源代码
swiftc $ {SWIFT_SOURCE_FILES} \
-sdk $ {SDK_PATH} \
-target $ {TARGET} \
-emit-executable \
$ {OTHER_FLAGS} \
-o $ {BUNDLE_DIR} / $ {PROJECT_NAME} echo✅编译Swift源文件$ {SWIFT_SOURCE_FILES}

我们进行的更改:

  • 我们检查BUILDING_FOR_DEVICE的值,并相应地设置TARGETSDK_PATH变量。
  • 我们介绍了OTHER_FLAGS变量。 对于模拟器构建,此变量为空。 对于设备构建,我们将有关在应用程序包中何处查找动态库的其他信息传递给编译器。 对于设备构建,我们将必须在应用程序包中包含Swift运行时。

最后:

因为仅设备构建才需要执行所有后续步骤,所以如果我们为模拟器构建,则可以在步骤4之后立即退出构建脚本。

让我们将其添加到脚本的末尾:

  ################################################ ########### 
如果[“ $ {BUILDING_FOR_DEVICE}”!= true]; 然后
#如果我们为模拟器构建,则可以在此处退出脚本
echo🎉为模拟器成功构建$ {PROJECT_NAME}! 🎉
出口0
科幻
################################################ ###########

我已经提到过,在为设备构建时,我们需要在应用程序包中包含Swift运行时库。 幸运的是,这里不需要魔术。 我们可以简单地将库复制到捆绑软件中。

  ################################################ ########### 
echo→步骤5:复制Swift运行时库
################################################ ############# Swift运行时库在计算机上的文件夹
SWIFT_LIBS_SRC_DIR = / Applications / Xcode.app / Contents / Developer / Toolchains / XcodeDefault.xctoolchain / usr / lib / swift / iphoneos#我们要在其中复制文件夹的文件夹
SWIFT_LIBS_DEST_DIR = $ {BUNDLE_DIR} / $ {FRAMEWORKS_DIR}#我们要复制的所有库的列表
SWIFT_RUNTIME_LIBS =(libswiftCore.dylib libswiftCoreFoundation.dylib libswiftCoreGraphics.dylib libswiftCoreImage.dylib libswiftDarwin.dylib libswiftDispatch.dylib libswiftFoundation.dylib libswiftMetal.dylib libswiftObjectiveC.dylib libswiftQuartzCore.dylib libswiftSwiftOnoneSupport.dylib libswiftUIKit.dylib libswiftos.dylib)MKDIR -p $ {BUNDLE_DIR} / $ {FRAMEWORKS_DIR}
echo✅在“ $ {SWIFT_RUNTIME_LIBS [@]}”中为library_name创建$ {SWIFT_LIBS_DEST_DIR}文件夹; 做
#复制资料库
cp $ {SWIFT_LIBS_SRC_DIR} / $ library_name $ {SWIFT_LIBS_DEST_DIR} /
echo✅将$ library_name复制到$ {SWIFT_LIBS_DEST_DIR}
完成

让我们运行脚本并验证结果。 不要忘记使用--device

  $ ./build.bash-设备 
device设备的Bulding ExampleApp
#...#
→步骤5:复制Swift运行时库
✅将libswiftCore.dylib复制到ExampleApp.app/Frameworks
✅将libswiftCoreFoundation.dylib复制到ExampleApp.app/Frameworks
✅将libswiftCoreGraphics.dylib复制到ExampleApp.app/Frameworks
✅将libswiftCoreImage.dylib复制到ExampleApp.app/Frameworks
✅将libswiftDarwin.dylib复制到ExampleApp.app/Frameworks
✅将libswiftDispatch.dylib复制到ExampleApp.app/Frameworks
✅将libswiftFoundation.dylib复制到ExampleApp.app/Frameworks
✅将libswiftMetal.dylib复制到ExampleApp.app/Frameworks
✅将libswiftObjectiveC.dylib复制到ExampleApp.app/Frameworks
✅将libswiftQuartzCore.dylib复制到ExampleApp.app/Frameworks
✅将libswiftSwiftOnoneSupport.dylib复制到ExampleApp.app/Frameworks
✅将libswiftUIKit.dylib复制到ExampleApp.app/Frameworks
✅将libswiftos.dylib复制到ExampleApp.app/Frameworks$ tree -L 2 ExampleApp.app
ExampleApp.app
├──Base.lproj
│├──LaunchScreen.storyboardc
│└──Main.storyboardc
├──ExampleApp
├──框架
│├──libswiftCore.dylib
│├──libswiftCoreFoundation.dylib
│├──libswiftCoreGraphics.dylib
│├──libswiftCoreImage.dylib
│├──libswiftDarwin.dylib
│├──libswiftDispatch.dylib
│├──libswiftFoundation.dylib
│├──libswiftMetal.dylib
│├──libswiftObjectiveC.dylib
│├──libswiftQuartzCore.dylib
│├──libswiftSwiftOnoneSupport.dylib
│├──libswiftUIKit.dylib
│└──libswiftos.dylib
└──Info.plist

我花了20多个小时来尝试找出成功构建设备应用程序所需的所有步骤。 我有一半的时间都花在了这一步上。

在我们开始之前,这是给您的小猫的好照片。

6.1供应档案

您已安装的所有配置文件都存储在~/Library/MobileDevice/Provisioning\ Profiles/ 。 第一个挑战是找到要用于此应用的正确应用。

我强烈建议首先在Xcode中解决所有问题。 如果我没记错的话,使用免费的Apple开发人员帐户,您甚至无法使用Web门户创建新的配置文件。 您必须为此使用Xcode。

在这里进行代码签名后,请记下使用了哪个配置文件和签名身份。

为了使应用程序包有效,它必须在其根目录中包含一个名为embedded.mobileprovision的文件。 让我们将正确的配置文件复制到应用程序捆绑,然后重命名:

  ################################################ ########### 
echo→步骤6:代码签名
################################################ ############要使用的配置文件的名称
#⚠️您需要将其更改为您的个人资料️️⚠️
PROVISIONING_PROFILE_NAME = 23a6e9d9-ad3c-4574-832c-be6eb9d51b8c.mobileprovision#应用包中的配置文件位置
EMBEDDED_PROVISIONING_PROFILE = $ {BUNDLE_DIR} /embedded.mobileprovisioncp〜/ Library / MobileDevice / Provisioning \ Profiles / $ {PROVISIONING_PROFILE_NAME} $ {EMBEDDED_PROVISIONING_PROFILE}
echo✅将配置文件$ {PROVISIONING_PROFILE_NAME}复制到$ {EMBEDDED_PROVISIONING_PROFILE}

6.2签署权利

成功签署捆绑软件所需的另一个文件是.xcent文件。 该文件只是另一个plist ,是通过将项目的Entitlements文件与其他签名信息合并而创建的。

我们的项目中没有Entitlements文件,因此合并的这一部分完成了。

我所指的其他签名信息是Apple开发人员团队ID。 该ID必须是您将在下一步中使用的签名身份中的ID。

让我们创建.xcent文件并设置所需的值:

  ################################################ ########### 
echo→步骤6:代码签名
################################################ ########### ... #您的签名身份的团队标识符
#⚠️您需要将其更改为您的ID️️⚠️
TEAM_IDENTIFIER = X53G3KMVA6 #.xcent文件的位置
XCENT_FILE = $ {TEMP_DIR} / $ {PROJECT_NAME} .xcent #文件不存在,但PlistBuddy会自动创建它
$ {PLIST_BUDDY} -c“添加:应用程序标识符字符串$ {TEAM_IDENTIFIER}。$ {APP_BUNDLE_IDENTIFIER}” $ {XCENT_FILE}
$ {PLIST_BUDDY} -c“添加:com.apple.developer.team-identifier字符串$ {TEAM_IDENTIFIER}” $ {XCENT_FILE} echo✅创建$ {XCENT_FILE}

6.3签署

现在,当我们准备好所有必需的零件时,我们将执行实际的签名。 可以帮助我们解决此问题的工具称为codesign

您需要指定用于签名的身份。 您可以使用以下命令列出终端中所有可用的身份:

  $安全性查找身份-v -p代码签名 

为了使应用程序捆绑包有效,我们需要在Frameworks文件夹中签名所有动态库,然后对捆绑包本身进行签名。 我们仅将xcent文件用于最终签名。 让我们完成这个!

  ################################################ ########### 
echo→步骤6:代码签名
################################################ ########### ... #用于签名的身份的ID
IDENTITY = E8C36646D64DA3566CB93E918D2F0B7558E78BAA #签名捆绑中的所有库
用于$ {SWIFT_LIBS_DEST_DIR} / *中的lib;
# 标志
代号\
-力
--timestamp = none \
--sign $ {IDENTITY} \
$ {lib}
回声✅Codesign $ {lib}
已完成 #对捆绑包本身 进行 签名
代号\
-力
--timestamp = none \
--sign $ {IDENTITY} \
--entitlements $ {XCENT_FILE} \
$ {BUNDLE_DIR} echo✅协同设计$ {BUNDLE_DIR}

庆祝!

我们需要编写的脚本的最后一部分是最后一条消息:

  ################################################ ########### 
echo🎉为设备成功完成构建$ {PROJECT_NAME}!🎉
出口0
################################################ ###########

现在,您可以运行脚本,坐下来,享受胜利:

  $ ./build.bash-设备 
👍构建设备的ExampleApp→步骤1:准备工作文件夹
✅创建ExampleApp.app文件夹
✅创建_BuildTemp文件夹→步骤2:编译Swift文件
✅编译Swift源文件ExampleApp / AppDelegate.swift ExampleApp / ViewController.swift→步骤3:编译情节提要
✅创建ExampleApp.app/Base.lproj文件夹
✅编译ExampleApp / Base.lproj / LaunchScreen.storyboard
✅编译ExampleApp / Base.lproj / Main.storyboard→步骤4:处理并复制Info.plist
Example将ExampleApp / Info.plist复制到_BuildTemp / Info.plist
✅将CFBundleExecutable设置为ExampleApp
✅将CFBundleIdentifier设置为com.vojtastavik.ExampleApp
✅将CFBundleName设置为ExampleApp
✅将_BuildTemp / Info.plist复制到ExampleApp.app/Info.plist→步骤5:复制Swift运行时库
✅创建ExampleApp.app/Frameworks文件夹
✅将libswiftCore.dylib复制到ExampleApp.app/Frameworks
✅将libswiftCoreFoundation.dylib复制到ExampleApp.app/Frameworks
✅将libswiftCoreGraphics.dylib复制到ExampleApp.app/Frameworks
✅将libswiftCoreImage.dylib复制到ExampleApp.app/Frameworks
✅将libswiftDarwin.dylib复制到ExampleApp.app/Frameworks
✅将libswiftDispatch.dylib复制到ExampleApp.app/Frameworks
✅将libswiftFoundation.dylib复制到ExampleApp.app/Frameworks
✅将libswiftMetal.dylib复制到ExampleApp.app/Frameworks
✅将libswiftObjectiveC.dylib复制到ExampleApp.app/Frameworks
✅将libswiftQuartzCore.dylib复制到ExampleApp.app/Frameworks
✅将libswiftSwiftOnoneSupport.dylib复制到ExampleApp.app/Frameworks
✅将libswiftUIKit.dylib复制到ExampleApp.app/Frameworks
lib将libswiftos.dylib复制到ExampleApp.app/Frameworks→步骤6:代码签名
✅将配置文件配置文件23a6e9d9-ad3c-4574-832c-be6eb9d51b8c.mobileprovision复制到ExampleApp.app/embedded.mobileprovision
文件不存在,将创建:_BuildTemp / ExampleApp.xcent
✅创建_BuildTemp / ExampleApp.xcent
ExampleApp.app/Frameworks/libswiftCore.dylib:替换现有签名
✅协同设计ExampleApp.app/Frameworks/libswiftCore.dylib
ExampleApp.app/Frameworks/libswiftCoreFoundation.dylib:替换现有签名
✅协同设计ExampleApp.app/Frameworks/libswiftCoreFoundation.dylib
ExampleApp.app/Frameworks/libswiftCoreGraphics.dylib:替换现有签名
✅协同设计ExampleApp.app/Frameworks/libswiftCoreGraphics.dylib
ExampleApp.app/Frameworks/libswiftCoreImage.dylib:替换现有签名
✅协同设计ExampleApp.app/Frameworks/libswiftCoreImage.dylib
ExampleApp.app/Frameworks/libswiftDarwin.dylib:替换现有签名
✅协同设计ExampleApp.app/Frameworks/libswiftDarwin.dylib
ExampleApp.app/Frameworks/libswiftDispatch.dylib:替换现有签名
✅协同设计ExampleApp.app/Frameworks/libswiftDispatch.dylib
ExampleApp.app/Frameworks/libswiftFoundation.dylib:替换现有签名
✅协同设计ExampleApp.app/Frameworks/libswiftFoundation.dylib
ExampleApp.app/Frameworks/libswiftMetal.dylib:替换现有签名
✅协同设计ExampleApp.app/Frameworks/libswiftMetal.dylib
ExampleApp.app/Frameworks/libswiftObjectiveC.dylib:替换现有签名
✅协同设计ExampleApp.app/Frameworks/libswiftObjectiveC.dylib
ExampleApp.app/Frameworks/libswiftQuartzCore.dylib:替换现有签名
✅协同设计ExampleApp.app/Frameworks/libswiftQuartzCore.dylib
ExampleApp.app/Frameworks/libswiftSwiftOnoneSupport.dylib:替换现有签名
✅协同设计ExampleApp.app/Frameworks/libswiftSwiftOnoneSupport.dylib
ExampleApp.app/Frameworks/libswiftUIKit.dylib:替换现有签名
✅协同设计ExampleApp.app/Frameworks/libswiftUIKit.dylib
ExampleApp.app/Frameworks/libswiftos.dylib:替换现有签名
✅协同设计ExampleApp.app/Frameworks/libswiftos.dylib
✅CodesignExampleApp.app🎉设备的构建ExampleApp成功完成! 🎉

不幸的是,将应用程序安装到连接的iOS设备上并没有像模拟器那样简单。 幸运的是,有第三方工具可以使任务更加轻松愉快。

我正在使用 ios-deploy

您可以使用-c列出所有连接的设备,然后复制该设备的标识符。

  $ ios部署-c 
[....]等待5秒钟以连接iOS设备
[....]找到了通过USB连接的'Vojta's iPhone',也就是00008020-xxxxxxxxxxxx(D321AP,D321AP,uknownos,unkarch)。

最后,使用以下命令将应用程序捆绑包安装到设备上:

  $ ios-deploy -i 00008020-xxxxxxxxxxxx -b ExampleApp.app 

这是 带有脚本最终版本 GitHub存储库

在撰写本文时,我学到了很多东西 。 我个人不打算再使用此脚本。 但是,如果有人有兴趣对其进行改进,可以考虑以下几点:

  • 我不需要触摸*.xcassets文件,因为里面没有内容。 但是,这些文件也需要编译。
  • 使增量构建适用于Swift会很好。
  • 该脚本当前仅构建Swift文件。 带有Objective-C,C ++,Objective-C ++,C源代码的项目呢?
  • 我认为解析xcodeproj并从中获取构建参数没有意义。 如何从XcodeGen文件获取构建设置?
  • 那么还需要构建目标依赖项(如自定义嵌入式框架)呢?

特别感谢 Harlan Haskins 通过回答我的问题为我提供帮助。


这篇博客文章花了37个小时来写,其中包括23个小时的研究。 (页脚受Michele Titolo启发)