DEV Community

Cover image for Xcode Build Phase 文件类型谜团
Ting
Ting

Posted on • Edited on

Xcode Build Phase 文件类型谜团

如果在构建编译过程中,引用了不存在的文件, Xcode 会给出如下错误:

Build input file cannot be found: '{path to some file}'. Did you forget to declare this file as an output of a script phase or custom build rule which produces it?

第一次遇到这个问题是在 Xcode 10 升级, Legacy Build System 升级为 Modern Build System 后,因为编译过程并行化,导致在前序的 Build Phase 步骤中下载的文件,无法被后面的步骤检测到。表现出来的错误的情形是:第一次构建时 Xcode 会报错。再按一次 Build 按钮,编译顺利通过。

我们的情况

在编译项目过程中,我们需要用到一些从服务器下载下来的文件。随着项目变大,需要下载的文件也逐渐变多。没有选择直接放在项目仓库里一是因为这些文件比较大,直接存在 GitHub 上浪费空间,二是因为二进制文件不适合版本管理。因此,我们通过运行一个 Run Script 的 Build Phase 来管理、下载这些文件。在项目每次编译时,首先执行这个步骤,从而确保本地的二进制文件是最新的。

因此,这个脚本需要从一个项目文件读取一些信息,然后从服务器下载对应的个数不定的文件,而这些文件后面会作为 On-Demand Resources 打包进 app bundle。

编译系统

WWDC22: Demystify parallelization in Xcode builds | Apple 介绍了新的现代构建系统通过一些巧妙的办法,最大程度并行化编译过程,以节省时间。不得不说,看了视频里展示类似于火焰图的性能图表,所有构建流程都紧凑地分散在不同的物理核心上时,令人相当心满意足。

视频中提到了两个设置

  • Run Build Script Phases in Parallel:设置是否并行执行编译过程
  • User Script Sandboxing:是否将每一个脚本步骤的输入输出隔离,以作为计算编译过程依赖的图结构的标准

经过我的测试,无论是否并行执行,我遇到的上述错误依旧存在;而如果运行存在文件系统输入输出的脚本,sandboxing 设置是需要关闭的。

我的问题

因为我们的下载文件的脚本是一个 Run Script Phase ,根据视频中的介绍,如果有文件系统的输入输出,理论上必须定义输入和输出的文件/文件列表。这样,编译系统才能按照定义的文件路径,来检测这个步骤是否完成,从而消除并行编译过程中的 race condition。

然而,根据我们自己的测试,在不指定输入输出文件的时候,有时 Build Phase 也能正确完成而不抛出错误。因为这个问题只出现在某些文件上,最初遇到时我们百思不得其解,尝试了许多方法去解决这个问题,包括:

  • 尝试 debug 下载文件的脚本,看是不是因为产生了 race condition 或者线程问题,导致编译失败。结论是脚本没有这方面的问题。
  • 尝试把下载文件的步骤移到 Pre build action 里面,即 xcscheme 设置里面。这个功能大致可以理解为在每次编译之前时,运行一个任意的脚本执行一些功能,和编译本身没有依赖。我们讨论了这个选项,但决定每次编译之前运行这个脚本的成本有点高,每个开发者都要在上面浪费零点几秒到几秒不等,日积月累也是不小的开销。所以决定不用这个功能。
  • 尝试把所有输出文件的列表加到 Output Files 里面。因为需要下载的文件数量随着项目成长是逐渐变多的,每次新加文件需要手动添加到列表很麻烦,也容易出错,所以决定不是万不得已还是不用这个方法。
  • 尝试通过一个脚本生成一个 xcfilelist 文件来列出所有下载的文件。同上,如果有别的解决方法,就想先不用这个方法。

真正的问题

经过很长时间的尝试,我偶然发现,问题并非来自随机的文件,而是固定的几个文件。进而观察到,每次报错都会涉及到几个和 raster 栅格化底图相关的示例程序。顺藤摸瓜,居然发现罪魁祸首是文件类型!

当我删掉这几个下载的 raster 文件夹,其中包括 tif 和 xml 格式的文件时,发现项目可以成功编译了。而把这些有问题的文件放到一个实际文件夹里面,而非引用逻辑文件夹时,也能解决问题。

因此,在这个 Pull Request 里面,我把受影响的文件都放到了文件系统里的文件夹里,问题迎刃而解。

适合你的解法

写这篇笔记的原因是时隔四五年后再次遇到这个问题。

这次我们需要在编译开始之前,下载一些账号密码和密钥文件。再次遇到 “Build input file cannot be found” 报错时有点摸不到头脑,尝试了一圈以后发现还是文件类型的问题。密钥存储在 plist 文件里时就会报错,移到一个 json 文件里就不报错了。

然而,对于这种下载固定个数的文件的情况,其实我更推荐以下两种解法

  • Pre Actions,即前文所说的构建前运行脚本。因为密钥文件大小很小,几毫秒就能下载完,对于项目而言也是一个只读的文件,因此用这个功能完全够用。
  • 指定输出文件路径。因为密钥文件数量固定,只要把输出路径设置好,后期很少需要调整。

而通过用别的文件类型以避免错误的方法,我在网上查了半天也没找到权威文档对于这个行为的解释,因此可以看作未来 Xcode 有可能悄没声就改了的行为。如果随便猜的话,或许是因为 Xcode 对某些特定类型的文件有特别的处理,而一些 Xcode 无法处理的文件类型则全当作任意文件处理。

写下这篇笔记时,目前这个行为在 Xcode 11 - Xcode 16 之间仍然存在。

且用且珍惜吧。🤷

See this PR as an example.

[Fix] Enclose the tiff file in a folder when the ODR is a standalone raster #757

Description

This PR fixes a recent daily build error introduced by #752 . The sample needed an "arran.tif" raster file via ODR, that was incorrectly handled by Xcode, causing a build error of "Build input file cannot be found".

This type of problem affects all tiff files.

  • To mitigate the problem specifically for a standalone tiff file, I added a logic to the download script, to enclose the tiff file in a folder when it is downloaded from AGOL.
  • For rasters that contain more than just 1 tiff file, because typically they already come as a folder, I didn't make any change to handle them.
    • For instance, "Shasta.tif" from "Add raster from file" sample is already enclosed in a folder, so its folder isn't affected by this fix.

Upon a quick search, the only other same case was "srtm.tiff" used by "Apply hillshade renderer to raster" sample, which was fixed in #604. I've reverted that fix.

Backstory

More backstory here

This dates back all the way to https://github.com/Esri/arcgis-runtime-samples-ios/pull/1036 . When Xcode 10 deprecated the legacy build system, there were some unclear changes to how Xcode handle the files that are tracked by the xcodeproj, but aren't part of a build phase's input/output. Later I discovered this undisclosed Xcode behavior that, for certain file types, Xcode reports the following error, even if they are added the same way as other files

[!CAUTION] error: Build input file cannot be found: 'path_to_build/arcgis-maps-sdk-swift-samples/Portal Data/aa97788593e34a32bcaae33947fdc271/arran.tif'. Did you forget to declare this file as an output of a script phase or custom build rule which produces it? (in target 'Samples' from project 'Samples')

So far, file types that are known to be affected are: .tif/.tiff, .plist

Linked Issue(s)

Slack chat reported by swift-devops.

How To Test

  1. Have a clean build of this branch. Either download a zip archive of the branch, or clear your portal data folder before starting the test
  2. Build the project. No download error should be reported
  3. Run the app, open "Apply hillshade renderer to raster" and "Apply map algebra" samples.
  4. The ODR progress bar shows for both samples, and the samples run correctly.
</div>
<div class="gh-btn-container"><a class="gh-btn" href="https://github.com/Esri/arcgis-maps-sdk-swift-samples/pull/757">View on GitHub</a></div>
Enter fullscreen mode Exit fullscreen mode


Top comments (0)