

场景
我们的Xcode项目中有许多计划,每个计划都构建不同的配置。 对于每种配置,我们需要使用唯一的参数,例如API端点,API密钥,分析密钥和其他第三方密钥等。过去,我们在应用程序源代码中使用预处理语句来返回敏感信息的字节数组。 :
func apiKey()->字符串{
让字节:[UInt8] = {
#if MOCK
返回[0x36、0x37、0x35、0x41、0x32,... 0x0]
#elseif分级
返回[0x31、0x36、0x32、0x2d,0x37,... 0x0]
#elseif PROD
返回[0x42、0x43、0x35、0x41、0x2d,... 0x0]
#万一
}()
让数据=数据(字节:字节)
返回字符串(数据:数据,编码:.utf8)!
}
对于非敏感信息,我们为每个构建配置(因此还有Scheme)都有一个Config-${CONFIGURATION}.plist ,该列表在构建时复制到捆绑包中,并在运行时由Config.swift进行解析。


这不是理想的,因为要保持内部版本之间的差异,就意味着如果它是敏感键,则生成并更新字节数组;如果不敏感,则转到Config-Something.plist并更改值。
解决方案
理想情况下,开发人员只需要更新一个文件-其他所有内容都应该自动化。 这是我想要实现的目标的Developer Experience™流程图:


注意事项:在项目的这一阶段,我们已经依靠我们应用程序内的RNCryptor来加密和解密缓存。 在运行脚本阶段,我最喜欢的加密方法是openssl命令,但是经过一些研究,似乎RNCryptor不[轻松地]支持解密openssl加密数据(它的创建者也没有对openssl说的openssl ‘的安全性)。 所以我对自己想:在看到一个 RNCryptor 二进制文件作为工件 RNCryptor 在身边时,为什么不使用它来进行加密呢?
从Shell脚本中运行Swift
实际上,您可以从终端提示中swift代码通过管道发送:
$ echo“ print(\” Hello Swift〜\“)” | 迅速
欢迎使用Apple Swift版本3.1(swiftlang-802.0.53 clang-802.0.42)。 键入:help以获得帮助。
你好斯威夫特〜
但是,第一个消息很烦人。 使用—代替文件名是向命令建议它应使用stdin而不是文件内容的相当标准的方法-让我们尝试一下:
$ echo“ print(\” Hello Swift〜\“)” | 迅速-
你好斯威夫特〜
完善。 好吧,不完全是。 用这种方式,简单的print看起来很整洁,但是我们可能会有很多行代码,因此我们需要一种很好的方式将它们写到文件中。 将以下内容复制到.sh文件中作为测试:
#!/ bin / bashTEST_VAR =“:blush:” swift_code(){
猫<< EOF
print(“ Hello Swift〜”)
print(“ Shell变量在这里也可用:$ TEST_VAR”)EOF
}回显“ $(swift_code)” | 迅速-
这确实符合您的期望:两个EOF标记之间的代码由swift_code函数返回,并因此通过swift swift_code并执行。 但是,我们没有指定要使用的Swift版本。 如果我们希望将其置于“运行脚本”阶段,则需要使所有内容都健壮且明确。
运行脚本阶段化
幸运的是,Xcode在其运行脚本阶段中注入了许多有用的环境变量。 您可以通过在脚本中添加env命令并查看构建日志来查看所有内容。 我在输出中进行搜索,直到找到一些有趣的变量: SRCROOT (您应该已经在使用的经典变量), DEVELOPER_DIR (主机Xcode开发人员目录的路径)和TOOLCHAIN_DIR (可用于定位特定的swift二进制文件,而不是比使用默认的/usr/bin/swift )。 您需要的最后一个swift命令是:
回声“ $(swift_code)” | DEVELOPER_DIR =“ $ DEVELOPER_DIR”“ $ TOOLCHAIN_DIR / usr / bin /”迅速-
因此,您已确保运行脚本阶段使用的Swift版本始终是Xcode项目中使用的版本。
链接到框架
虽然我花了一些时间弄清楚该怎么做,但最后实际上很简单。 我们正在使用迦太基来管理我们的依赖关系,因此默认情况下,它已经RNCryptor为macOS构建RNCryptor 。 我们需要对swift命令进行的最后两个修改是向其显示一个目录,以在其中搜索框架(使用-F标志),并告知其目标macOS(使用xcrun )。
回声“ $(swift_code)” | DEVELOPER_DIR =“ $ DEVELOPER_DIR” xcrun --sdk macosx“ $ TOOLCHAIN_DIR / usr / bin /” swift -F“ $ SRCROOT / Carthage / Build / Mac /”-
现在,我们可以从swift_code函数中import以macOS为目标并驻留在"$SRCROOT/Carthage/Build/Mac/"任何Framework。 精彩。
放在一起
我的最后一个Run Script阶段将相关的Config-xxx.plist转换为JSON(以便我们可以在运行时使用JSONDecoder轻松对其进行解码),以及对其进行加密和Base64编码。 并且,作为额外的好处,它会生成一个Swift扩展,其中包含Config类上的密码字节数组。 此扩展名以及输出Config.json分别添加到XCode的编译源和捆绑资源,但是由于它们是在每次构建之前动态生成的,因此未检入到源代码管理中。
这就是所有的一切!
后记:生成字节数组的更简便方法
当我们在这里时,让我们谈谈如何创建我在本文开头提到的字节数组。 我经常看到人们在谷歌搜索“字符串到十六进制转换器”,粘贴他们的API密钥,然后手动添加一个, 0x分隔符。 让我们学习一个更简单的方法。
您可以使用以下这种单行代码从命令行轻松生成这些字节数组:
$ echo -n“ an-api-key” | xxd -pu -c 9999 | sed's /。\ {2 \} / 0x&,/ g'
0x61、0x6e,0x2d,0x61、0x70、0x69、0x2d,0x6b,0x65、0x79,
echo -n打印出“ an-api-key”而不带换行符。 xxd -p将通过echo传递的字符串转换为十六进制代码( -cx确保仅在x字符之后添加列分隔符(即,对于短API密钥不添加)。 sed然后简单地破坏十六进制表示并添加定界符。 我还建议在任何字节数组的末尾添加一个0x0 ,因为我经历过的应用程序不知道数组的末尾,因此会读入随机内存中……
不可避免地会有人问你“我们用于构建xxx的API密钥是什么” —再次,在终端中转换字节数组比手动删除定界符和为转换器谷歌搜索要快!
$ echo -n“ 0x61、0x6e,0x2d,0x61、0x70、0x69、0x2d,0x6b,0x65、0x79” xxd -p -r
api键
与其搜索在线转换器,不如尝试从命令行搜索如何执行该操作,因为这将节省您将来的时间并增加您的知识。