CocoaPods 对 Xcode Assets 打包的诡异问题

栏目: IOS · 发布时间: 7年前

内容简介:好久没有写博客了,有一年多了吧,想想那些能够安心码字的日子还甚是怀念,于是今晚无论外界条件怎样的恶劣,这一篇是一定要更新的。想必 CocoaPods 和 Carthage 对于 iOS 开发者而言都不会陌生,今天的这一篇我们就来看看混合使用这两者,以及多个去年下半年,我们内部搭建了一个私有的 CI 平台,主要用于跑一些自动化和日常的测试打包。由于机器性能很强悍,很快成了大家构建新包的首选,原本应该是个愉快的事情,可是在系统这样跑了几个月后,恼人的问题突然就出现了:大家在自己的机器上编译没任何问题,而在 CI

好久没有写博客了,有一年多了吧,想想那些能够安心码字的日子还甚是怀念,于是今晚无论外界条件怎样的恶劣,这一篇是一定要更新的。

想必 CocoaPods 和 Carthage 对于 iOS 开发者而言都不会陌生,今天的这一篇我们就来看看混合使用这两者,以及多个 .xcassets 的情况下,一些莫名其妙的问题。

问题的出现

去年下半年,我们内部搭建了一个私有的 CI 平台,主要用于跑一些自动化和日常的测试打包。由于机器性能很强悍,很快成了大家构建新包的首选,原本应该是个愉快的事情,可是在系统这样跑了几个月后,恼人的问题突然就出现了:大家在自己的机器上编译没任何问题,而在 CI 里发起构建,每次都会失败在 Build Phases 中的 [CP] Copy Pods Resources 这一步,查看详细日志的话,主要是下面的这段错误:

error: None of the input catalogs contained a matching stickers icon set or app icon set named  "AppIcon".

我们的主工程比较大,代码和资源文件都很多,技术选型上是采用了 CocoaPods ( 1.5.x 版本 ) 来做模块化,模块拥有各自的 .xcassets 来存放资源,所以会有多个 .xcassets 文件。在此基础上,我们还依赖了一些 Swift 开源库,所以也使用了 Carthage 来管理依赖。

第一次解决问题

问题必须解决,但我们的 .xcassets 中,肯定是有一个含有 “AppIcon” 的,于是我找了一个时间,仔细分析了下错误的详细日志,发现里面有几处这样的警告:

warning: The app icon set name "AppIcon" is used by multiple app icon sets.

这个警告提示我们 “AppIcon” 冲突了,然后看了下冲突的路劲,尽然都是在 Carthage/Checkouts 目录下。由于我们依赖的 Carthage 库是以源码编译成 Framework 的,而这些源码中有示例和测试项目,其中包括了一些 .xcassets ,最主要的是 CocoaPods 把这些目录下的 .xcassets 都编译到了最终输出的目标中。

相关 issue: https://github.com/CocoaPods/CocoaPods/issues/6159#issuecomment-296698412

不查不知道,一查吓一跳啊,有种“我们 App 被偷偷植入了一些莫名其妙的资源”的感觉,也很庆幸以往的 AppIcon 能正常显示,甚至是很意外它尽然能正常显示。与此同时,错误日志中还有一些其他资源名称冲突的警告,我把这些资源名称和它对应的 .xcassets 名称一一对应的提取了出来,然后在模块间查找、对比,发现尽然存在名称一致但长相完全不同的图片,庆幸的是较新的图片得到了显示。捏了一把冷汗,在手动处理完所有名称冲突后,我把 CI 服务中的构建脚本修改了下,在编译前执行了下面命令:

rm -rf Carthage/Checkouts/**/*.xcassets || :

删除了这些不相干、也没任何作用的 .xcassets 。做完这一切,我在 CI 上发起了一个构建,然后真的就成功了。可这没法解释为啥原先本地没问题,隐隐觉得还没有找到问题的主线,这次只是完成了一个支线任务。

大家又开始愉快地使用 CI 了。

第二次解决问题

时间飞快,好景不长,过了一个月左右,相同的问题、相同的错误信息再一次出现在我面前,而我再一次翻起那详细日志时,里面已没有了任何重复冲突的警告。这一次,我开始认真思考:本地发起构建和通过 CI 发起构建到执行 [CP] Copy Pods Resources 这一步,到底有什么区别?通过一系列测试,发现 Shell 的环境不同,但无法确定环境中哪些会影响到这一步执行,没办法,只能细看下 CocoaPods 这一步自动生成的脚本了:

# 其中 #{Target Name} 为主工程输出目标名称
Pods/Target Support Files/Pods-#{Target Name}/Pods-#{Target Name}-resources.sh

这个脚本主要就是拷贝和编译通过 CocoaPods 所依赖的资源文件,和 .xcassets 相关的主要是 XCASSET_FILES 这个变量,以及最后的这段脚本:

if [ -z ${ASSETCATALOG_COMPILER_APPICON_NAME+x} ]; then
    printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
else
    printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" --app-icon "${ASSETCATALOG_COMPILER_APPICON_NAME}" --output-partial-info-plist "${TARGET_TEMP_DIR}/assetcatalog_generated_info_cocoapods.plist"
fi

所有的 .xcassets 文件都存在了 XCASSET_FILES 这个数组里面了,然后这个数组传递给 actool 来进行编译成 Assets.car 文件。脚本看完后,通过在文件头部加上 set -x 开启了它的调试,在茫茫的输出中,我死盯着 XCASSET_FILES 这个变量,然后惊人的发现这个变量中所有依赖而来的 .xcassets 都变成了两份!真心不知道 actool 在面对这些重复的路劲是怎么的处理,又捏了一把冷汗,于是乎在 Podfile 中加上了这样一些代码:

post_install do |installer|
   # 其中 #{Target Name} 为主工程输出目标名称
    copy_pods_resources_path = "Pods/Target Support Files/Pods-#{Target Name}/Pods-#{Target Name}-resources.sh"
    
    str1 = 'printf "%s\0" "${XCASSET_FILES[@]}"'
    str2 = 'printf "%s\n" "${XCASSET_FILES[@]}" | sort -u | tr \'\n\' \'\\\\0\' '
    
    text = File.read(copy_pods_resources_path)
    new_contents = text.gsub(str1, str2)
    File.open(copy_pods_resources_path, "w") {|file| file.puts new_contents }
end

通过对 CocoaPods 自动生成文件内容进行替换,我们插入了一段脚本,最终对 XCASSET_FILES 中的条目进行了去重。做完这一切,我又在 CI 上发起了一个构建,然后它又成功了,然后还是没法解释为啥本地没问题,所以,注定了这还是一个支线任务。

可是,大家再一次愉快地使用 CI 了。

第三次解决问题

过了很长一段时间,长到我都以为这个问题真的彻底解决了,但冷不丁的就在前几天,这个问题又出现了。都说事不过三,这问题一次又一次的反复,也实在是让我颜面尽失,大过年的,你这该死又淘气的 CocoaPods。按捺住心中的烦躁不安,我又一次仔细地把那自动生成的脚本撸了一遍,可能是内心足够安静了吧,这一次我尽然只凭理论分析,就找到了罪魁祸首 xargs

xargs 不仅能正确处理空格之类的转义,还会在超过一定的限制后,把传递给它的参数 分批 传递给后续的命令, XCASSET_FILES 中就是我们所存储的参数。 xargs 分批传递的限制主要是两个参数: -n 的条目限制和 -s 的大小限制,其中 -s 的大小限制受环境变量 ARG_MAX 影响。

所以,一旦我们的 XCASSET_FILES 被分批传递给了 actool ,其中只有某一批里面有“AppIcon”,其它的自然会报错。由于环境不同, ARG_MAX 值不一致,这也解释了一直没法解释的那个问题。前面的两次修复,都不经意间缩减了 XCASSET_FILES 中的内容,而我们的 .xcassets 文件在慢慢增多,一旦突破了限制,问题就又出现了。

感觉找打了主线,于是修改了下 Podfile 中的代码:

post_install do |installer|
   # 其中 #{Target Name} 为主工程输出目标名称
    copy_pods_resources_path = "Pods/Target Support Files/Pods-#{Target Name}/Pods-#{Target Name}-resources.sh"
    
    str1 = 'printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0'
    str2 = 'printf "%s\n" "${XCASSET_FILES[@]}" | sort -u | tr \'\n\' \'\\\\0\' | xargs -0 -s 20480 -n 100'
    
    text = File.read(copy_pods_resources_path)
    new_contents = text.gsub(str1, str2)
    File.open(copy_pods_resources_path, "w") {|file| file.puts new_contents }
end

搞完后,我在 CI 上再一次发起了构建,如预期的一样,再一次的成功了,我相信这问题不会再出现了。

大家又开始愉快地使用 CI 了。

总结一下

这个问题让我纠结了大半年时间,终于在这新春佳节里给彻底解决了,大体来说就是这样:

  • 如果你混合使用了 CocoaPods 和 Carthage,确认下 Carthage 的所有目录里是否有 .xcassets ,如果有的话,确认下是否被打包到你最后的 App 里了
  • 注意 CocoaPods 生成的脚本中, XCASSET_FILES 里的条目有重复
  • CocoaPods 生成的脚本中,最终传递给 actool 编译的参数,一定不能被 xargs 分批传递

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Approximation Algorithms

Approximation Algorithms

Vijay V. Vazirani / Springer / 2001-07-02 / USD 54.95

'This book covers the dominant theoretical approaches to the approximate solution of hard combinatorial optimization and enumeration problems. It contains elegant combinatorial theory, useful and inte......一起来看看 《Approximation Algorithms》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

随机密码生成器
随机密码生成器

多种字符组合密码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具