Xcode:在每次构建前运行直接修改源代码的脚本

62

我的做法:

我有一个脚本,它

  1. 读取一些配置文件以生成源代码片段
  2. 查找相关的Objective-C源文件并
  3. 用步骤1中生成的代码替换源代码的一些部分。

还有一个Makefile,它有一个特殊的时间戳文件作为make目标和配置文件作为目标源:

SRC = $(shell find ../config -iname "*.txt")
STAMP = $(PROJECT_TEMP_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME).stamp
$(STAMP): $(SRC)
    python inject.py
    touch $(STAMP)

我将这个 Makefile 添加为项目目标构建阶段的顶部的“运行脚本构建阶段”。

发生了什么:

在编译源代码之前,脚本构建阶段被执行。

然而,由于脚本在执行期间修改了源代码,因此我需要构建两次以获取最新版本的构建产品。这是我想象中正在发生的事情:

  1. 第一次运行:Xcode 收集依赖信息 ---> 没有更改
  2. 第一次运行:Xcode 运行“运行脚本构建阶段”---> 在 Xcode 的背后更改源文件
  3. 第一次运行:Xcode 完成构建,认为不需要更新任何东西
  4. 第二次运行:Xcode 收集依赖信息--->源已更改,需要重新构建!
  5. 第二次运行:Xcode 运行“运行脚本构建阶段”---> 一切都是最新的
  6. 第二次运行:Xcode 继续编译

在阅读关于构建阶段的 Xcode 文档之后,我尝试将一个已知每次运行脚本时都会更新的源文件添加为“运行脚本构建阶段”的输出,但没有任何更改。由于项目中可能会有不同数量的配置文件,我不想指定每个输入和输出文件。

问题:

如何让 Xcode 意识到“运行脚本构建阶段”期间对源文件所做的更改?

编辑:

  • 添加了我将脚本构建阶段放在其他构建阶段之前的信息。

3
跟进问题:是否可能只针对编译器重写代码,而不更新实际文件? - Hari Honor
7个回答

104

到目前为止,提到的所有技术都是过度复杂的。为了更好地展示,再次引用Steve Kim的评论:

在构建阶段选项卡中,只需将“运行脚本”步骤拖动到较高的位置(例如,在“编译源代码”之前)。

在Xcode 6上测试通过。


2
这对于Xcode 7很有效。 需要注意的一件事是,我无法将该阶段拖动到上方。 我必须将其他阶段向下拖动。 - Tokuriku
1
我为此苦苦挣扎了一个多小时,不明白为什么我的构建每次都失败。谢谢 - 这也适用于Xcode 6。 - rcat24
3
您可以向上拖动,但如果尝试在“目标依赖项”上方或其后面拖动,则不起作用。您需要在其正下方拖动。 - Rick Smith
5
根据你想要修改的文件不同,这个解决方案可能不适用。例如,如果你想要更新xcconfig文件或Info.plist文件,似乎这些文件在任何构建阶段运行之前就已经被使用了,所以在脚本中进行的更改不会出现在构建中。 - plowman
1
在Xcode 11中进行的源文件修改直到第二次运行才会生效。 - xi.lin
显示剩余3条评论

31

这个解决方案可能已经过时了。请查看更高票的答案;我不再积极使用Xcode并且没有资格审核解决方案。


使用“外部目标”:

  1. 从菜单中选择“项目”>“新建目标”
  2. 选择“Mac OS X”>“其他”>“外部目标”并将其添加到您的项目中
  3. 打开其设置并填写您的脚本设置
  4. 打开主目标设置的“常规”选项卡,并将新目标添加为其直接依赖项

现在,新的“外部目标”会在主目标开始收集依赖信息之前运行,因此在脚本执行期间进行的任何更改都应包含在构建中。


@Abizern,我认为您没有理解 Stack Overflow 的重点。为什么要阻止别人添加有效答案呢?他是否是提问者又有什么关系呢? - Undistraction
2
你可以在构建阶段之前拖放一个“运行脚本”。 - steve kim
这太聪明了!在Cocoapods编译之前运行脚本非常有用(尤其是在存档时需要很长时间)。根据@respectTheCode的建议使用“iOS构建的聚合目标”(在Xcode 5上运行得非常好)。 - Ricardo Sanchez-Saez
更新:这个解决方案似乎不再起作用了。 - dwlz
@Dan,https://dev59.com/kXNA5IYBdhLWcg3wZ8_U#26389328 对你有用吗?如果是的话,我会接受那个答案。 - ento
显示剩余5条评论

4
有另一种稍微简单的选项,不需要单独设置目标,但只有在脚本每次修改相同的源文件时才可行。
首先,对于那些为什么Xcode有时需要构建两次(或执行清除构建)才能在目标应用程序中看到某些更改反映的人,这里是一个简要说明。如果对象文件缺失,或者对象文件的最后修改日期早于源文件的最后修改日期在第一构建阶段的开始时,Xcode会编译源文件。如果您的项目运行一个在预编译构建阶段修改源文件的脚本,则Xcode不会注意到源文件的最后修改日期已更改,因此它不会重新编译它。只有当您第二次构建项目时,Xcode才会注意到日期更改并重新编译该文件。
如果您的脚本恰好每次修改相同的源文件,则有一个简单的解决方案。只需像这样在构建过程的末尾添加一个运行脚本构建阶段:
touch Classes/FirstModifiedFile.m Classes/SecondModifiedFile.m
exit $?

在构建过程结束时对这些源文件运行touch,可以保证它们的最后修改日期始终晚于它们的目标文件,因此Xcode将每次重新编译它们。


你能解释一下“在构建过程的末尾添加一个运行脚本构建阶段”的确切含义吗?谢谢。 - user1244109
这应该会有所帮助:https://developer.apple.com/library/ios/recipes/xcode_help-project_editor/Articles/AddingaRunScriptBuildPhase.html - cduhn

2
我也曾经长时间苦恼于这个问题。答案是使用ento的“外部目标”解决方案。他解释了为什么会出现这个问题以及我们如何在实践中使用它...
Xcode 4的构建步骤直到plist编译完成后才执行,这显然很愚蠢,因为这意味着任何修改plist的预构建步骤都不会生效。但是如果你仔细想想,它们实际上确实会生效...在下一次构建时。这就是为什么有些人会谈论“缓存”plist值或者“我必须进行两次构建才能使其工作”的原因。发生的情况是plist被构建,然后运行你的脚本。下一次构建时,plist将使用你修改过的文件进行构建,因此需要第二次构建。
ento的解决方案是我找到的唯一真正的预构建步骤。不幸的是,我还发现它不会导致plist在没有清理构建的情况下更新,所以我做了修复。以下是我们如何在plist中拥有数据驱动的用户值:
  1. 添加一个指向python脚本并传递一些参数的外部构建系统项目
  2. 将用户定义的构建设置添加到构建中。这些是你传递给python的参数(稍后我们会讲到为什么这样做)
  3. python脚本读取一些输入JSON文件并构建一个plist预处理器头文件,然后触摸主应用程序plist
  4. 主项目已开启“预处理plist文件”,并指向了这个预处理器文件
在主应用程序plist文件上使用touch命令导致主目标每次都生成plist。我们将构建设置作为参数传递的原因是可以通过命令行构建来覆盖设置:
  1. 在预构建项目中添加一个用户定义变量“foo”。
  2. 在你的预构建中,你可以使用$(foo)将值传递到python脚本中。
  3. 在命令行中,你可以添加foo=test来传递一个新值。
python脚本使用基本设置文件,并允许用户定义的设置文件覆盖默认设置。你进行更改后,它会立即出现在plist中。我们只在必须出现在plist中的设置中使用它。对于其他任何东西,它都是浪费时间...生成一个json文件或类似的东西,然后在运行时加载:)
希望这有所帮助...我花了几天时间才解决这个问题。

2
自Xcode 4以来,如果您将生成的文件添加到构建阶段的输出部分,则它将遵循该设置,而不会生成“...已自预编译头文件构建以来已修改”错误消息。如果您的脚本每次只生成少量文件,则这是一个不错的选择。

1
@ento的外部目标解决方案在Xcode 11.5中已经失效。解决方案是在运行脚本中添加所有将被更改的文件到输出文件中。

不要选择外部目标,而是选择使用静态库,你可以做同样的事情。 - Zane

0
另一个选项是创建一个子项目框架,将您的脚本添加为所有目标的依赖项。该子项目的阶段脚本现在应该在所有目标之前执行。

enter image description here


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接