Xcode:在每次直接修改源代码的构建之前运行一个脚本
Posted
技术标签:
【中文标题】Xcode:在每次直接修改源代码的构建之前运行一个脚本【英文标题】:Xcode: Running a script before every build that modifies source code directly 【发布时间】:2010-11-01 20:45:23 【问题描述】:我做了什么:
我有一个脚本
-
读取一些配置文件生成源代码sn-ps
查找相关的 Objective-C 源文件并
将源代码的某些部分替换为步骤 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 添加为项目目标的构建阶段堆栈顶部的“运行脚本构建阶段”。
发生了什么:
脚本构建阶段在编译源代码之前运行。
但是,由于脚本在执行过程中会修改源代码,因此我需要构建 两次 才能获得最新版本的构建产品。以下是我想象中会发生的事情:
-
第一次运行:Xcode 收集依赖信息 ---> 没有变化
第一次运行:Xcode 运行“运行脚本构建阶段”---> 源代码在 Xcode 背后更改
第一次运行:Xcode 完成构建,认为不需要更新任何内容
第二次运行:Xcode 收集依赖信息 ---> 源已更改,需要重新构建!
第二次运行:Xcode 运行 Run Script Build Phase" ---> 一切都是最新的
第二次运行:Xcode 继续编译
在阅读Xcode documentation on Build Phases 之后,我尝试添加一个源文件,该源文件在每次运行脚本时都会被更新为“运行脚本构建阶段”的输出,但没有任何改变。由于我的项目中配置文件的数量可能会有所不同,因此我不想指定每个输入和输出文件。
问题:
如何让 Xcode 了解在“运行脚本构建阶段”期间所做的源文件更改?
编辑:
补充说我将脚本构建阶段放在其他构建阶段之前【问题讨论】:
后续问题:是否可以只为编译器重写代码而不更新实际文件? 【参考方案1】:另一种选择是使用您的脚本创建一个子项目框架,并将其作为依赖项添加到所有目标。这个子项目的阶段脚本现在应该在所有目标之前执行。
【讨论】:
【参考方案2】:我也为此苦苦挣扎了很长时间。答案是使用 ento 的“外部目标”解决方案。他是为什么会出现这个问题以及我们如何在实践中使用它......
在 plist 编译完成之前,Xcode 4 构建步骤不会执行。当然,这很愚蠢,因为这意味着任何修改 plist 的预构建步骤都不会生效。但如果你仔细想想,它们实际上确实会生效……在 NEXT 版本中。这就是为什么有些人谈论 plist 值的“缓存”或“我必须进行 2 次构建才能使其工作”的原因。会发生什么是 plist 被构建,然后你的脚本运行。下次构建时,plist 会使用您修改的文件构建,因此是第二次构建。
ento 的解决方案是我发现实际执行真正的预构建步骤的一种方法。不幸的是,我还发现它不会导致 plist 在没有干净构建的情况下更新,我修复了这个问题。以下是我们如何在 plist 中拥有数据驱动的用户值:
-
添加一个指向 python 脚本并传递一些参数的外部构建系统项目
将用户定义的构建设置添加到构建中。这些是你传递给 python 的参数(更多关于为什么我们稍后会这样做)
python 脚本读取一些输入 JSON 文件并构建 plist 预处理器头文件并触及主应用 plist
主项目已打开“预处理 plist 文件”并指向此预处理器文件
在主应用 plist 文件上使用 touch 会导致主目标每次都生成 plist。我们将构建设置作为参数传递的原因是我们的命令行构建可以覆盖设置:
-
将用户定义的变量“foo”添加到预编译项目中。
在您的预构建中,您可以使用 $(foo) 将值传递给 python 脚本。
您可以在命令行中添加 foo=test 以传入新值。
python 脚本使用基本设置文件并允许用户定义的设置文件覆盖默认值。您进行更改后,它会立即出现在 plist 中。我们仅将其用于必须在 plist 中的设置。对于其他任何事情都是浪费精力....生成一个 json 文件或类似的东西并在运行时加载它:)
我希望这会有所帮助...搞清楚这个问题已经好几天了。
【讨论】:
【参考方案3】:到目前为止提到的每一种技术都是矫枉过正的。转载steve kim的评论以提高知名度:
在构建阶段选项卡中,只需将“运行脚本”步骤拖到更高的位置(例如,在“编译源”之前)。
在 Xcode 6 上测试
【讨论】:
这适用于 Xcode 7。需要注意的一点是,我无法将阶段向上拖动。我不得不向下拖动其他人。 我为此苦苦挣扎了一个多小时,不明白为什么我的构建每次都失败。谢谢 - 也适用于 xcode 6。 @Tokuriku 您可以向上拖动,但如果您尝试拖动或超过“目标依赖项”,它将不起作用。您需要在它下方拖动。 根据您要更改的文件,此解决方案可能不起作用。例如,如果您正在更新 xcconfig 文件或 Info.plist 文件,它们似乎在任何构建阶段运行之前已经被使用,因此脚本内发生的更改不会显示在构建中。 Xcode 11 中的源文件修改要到第二次运行才能生效。【参考方案4】:这个解决方案可能已经过时了。请查看投票率较高的答案;我不再积极使用 Xcode,也没有资格审查解决方案。
使用“外部目标”:
-
从菜单中选择“项目”>“新目标...”
选择“Mac OS X”>“其他”>“外部目标”并将其添加到您的项目中
打开其设置并填写您的脚本设置
打开主目标设置的“常规”选项卡并添加新目标,因为它是直接依赖项
现在新的“外部目标”运行之前主目标甚至开始收集依赖信息,因此脚本执行期间所做的任何更改都应包含在构建中。
【讨论】:
您还可以在单个目标中添加“运行脚本”构建阶段,将该脚本构建阶段置于其他目标构建阶段之前。 @Barry 我确实将脚本构建阶段放在了构建阶段堆栈的顶部,但我仍然需要构建两次。我正在将其编辑到问题中。谢谢。 @ento 是否可以通过这种方式将构建设置传递给您的脚本?如果是这样,我无法弄清楚如何。外部目标有一个“构建设置”部分,但我希望我的构建脚本使用“外部目标”(嵌入外部目标的那个)的构建设置。我看不出方法,而且关于这个问题的文档似乎不存在。 @Abizern 我认为您错过了 Stack Overflow 的重点。为什么不鼓励某人添加有效答案。谁在乎这是否是他自己的问题? 您可以在构建阶段之前拖放“运行脚本”。【参考方案5】:@ento 的外部目标解决方案从 Xcode 11.5 开始不再有效。解决方法是在Run Script中添加Output Files
下所有将要更改的文件。
【讨论】:
替代外部目标,选择使用静态库,您可以做同样的事情。【参考方案6】:从 Xcode 4 开始,看起来如果您将生成的文件添加到构建阶段的输出部分,它将遵守该设置,并且不会生成 ... has been modified since the precompiled header was built
错误消息。
如果您的脚本每次只生成少量文件,这是一个不错的选择。
【讨论】:
【参考方案7】:还有另一个稍微简单的选项,不需要单独的目标,但只有当您的脚本倾向于每次修改相同的源文件时才可行。
首先,对于那些对为什么 Xcode 有时要求您构建两次(或进行一次干净的构建)以查看目标应用程序中反映的某些更改感到困惑的人,这里有一个简短的解释。如果 Xcode 生成的目标文件丢失,或者如果目标文件的最后修改日期早于源文件的最后修改日期在第一个构建阶段的开始,Xcode 会编译源文件。如果您的项目在预编译构建阶段运行修改源文件的脚本,Xcode 不会注意到源文件的最后修改日期已更改,因此它不会费心重新编译它。只有当您第二次构建项目时,Xcode 才会注意到日期更改并重新编译文件。
如果您的脚本每次都修改相同的源文件,这是一个简单的解决方案。只需在构建过程结束时添加一个运行脚本构建阶段,如下所示:
touch Classes/FirstModifiedFile.m Classes/SecondModifiedFile.m
exit $?
在构建过程结束时对这些源文件运行 touch
可确保它们的最后修改日期总是比其目标文件晚,因此 Xcode 每次都会重新编译它们。
【讨论】:
您能解释一下“在构建过程结束时添加运行脚本构建阶段”的确切含义吗?谢谢 这应该会有所帮助:developer.apple.com/library/ios/recipes/…以上是关于Xcode:在每次直接修改源代码的构建之前运行一个脚本的主要内容,如果未能解决你的问题,请参考以下文章