Xcode:在每次直接修改源代码的构建之前运行脚本
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/976454/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Xcode: Running a script before every build that modifies source code directly
提问by ento
What I did:
我做了什么:
I have a script that
我有一个脚本
- Read some configuration files to generate source code snippets
- Find relevant Objective-C source files and
- Replace some portions of the source code with the generated code in step 1.
- 读取一些配置文件以生成源代码片段
- 找到相关的 Objective-C 源文件和
- 用步骤 1 中生成的代码替换源代码的某些部分。
and a Makefile that has a special timestamp file as a make target and the configuration files as target sources:
和一个 Makefile,它有一个特殊的时间戳文件作为 make 目标,配置文件作为目标源:
SRC = $(shell find ../config -iname "*.txt")
STAMP = $(PROJECT_TEMP_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME).stamp
$(STAMP): $(SRC)
python inject.py
touch $(STAMP)
I added this Makefile as a "Run Script Build Phase" on top of the stack of build phases for the project target.
我将此 Makefile 添加为项目目标的构建阶段堆栈顶部的“运行脚本构建阶段”。
What happened:
发生了什么:
The script build phase was run before compiling the source.
脚本构建阶段在编译源代码之前运行。
However, since the script modifies source code during its execution, I needed to build twiceto get the most recent version of the build product. Here is what I imagine to be happening:
但是,由于脚本在执行期间修改了源代码,我需要构建两次才能获得构建产品的最新版本。这是我想象发生的事情:
- 1st run: Xcode collects dependency information ---> no changes
- 1st run: Xcode runs "Run Script Build Phase" ---> source is changed behind Xcode's back
- 1st run: Xcode finishes build, thinking nothing needs to be updated
- 2nd run: Xcode collects dependency information ---> source has changed, needs rebuild!
- 2nd run: Xcode runs Run Script Build Phase" ---> everything is up-to-date
- 2nd run: Xcode proceeds to compilation
- 第一次运行:Xcode 收集依赖信息 ---> 没有变化
- 第一次运行:Xcode 运行“Run Script Build Phase”---> 源代码在 Xcode 背后更改
- 第一次运行:Xcode 完成构建,认为不需要更新
- 第二次运行:Xcode 收集依赖信息 ---> 源已更改,需要重建!
- 第二次运行:Xcode 运行运行脚本构建阶段”---> 一切都是最新的
- 第二次运行:Xcode 进行编译
After reading Xcode documentation on Build Phases, I tried adding a source file which is known to be updated every time the script is run as the output of "Run Script Build Phases", but nothing changed. Since the number of configuration files may vary in my project, I don't want to specify every input and output file.
在阅读了关于 Build Phases 的 Xcode 文档后,我尝试添加一个源文件,该文件在每次运行脚本时都会更新为“Run Script Build Phases”的输出,但没有任何改变。由于我的项目中配置文件的数量可能会有所不同,因此我不想指定每个输入和输出文件。
Question:
题:
How do I make Xcode aware of source file changes made during "Run Script Build Phase"?
如何让 Xcode 知道在“运行脚本构建阶段”期间所做的源文件更改?
Edit:
编辑:
- Added that I placed the script build phase before the other build phases
- 补充说我将脚本构建阶段放在其他构建阶段之前
回答by marinosb
Every technique mentioned so far is an overkill. Reproducing steve kim's comment for visibility:
到目前为止提到的每一种技术都是矫枉过正的。复制史蒂夫金的可见性评论:
In the build phases tab, simply drag the "Run Script" step to a higher location (e.g. before "Compile Sources").
在构建阶段选项卡中,只需将“运行脚本”步骤拖到更高的位置(例如在“编译源”之前)。
Tested on XCode 6
在 XCode 6 上测试
回答by ento
This solution is probably outdated. See the higher voted answer instead.
此解决方案可能已过时。请参阅更高投票的答案。
Use "External Target":
使用“外部目标”:
- Select "Project" > "New Target..." from the menu
- Select "Mac OS X" > "Other" > "External Target" and add it to your project
- Open its settings and fill in your script setup
- Open the "General" tab of the main target's settings and add the new target as it's direct dependency
- 从菜单中选择“项目”>“新建目标...”
- 选择“Mac OS X”>“其他”>“外部目标”并将其添加到您的项目中
- 打开其设置并填写您的脚本设置
- 打开主目标设置的“常规”选项卡并添加新目标,因为它是直接依赖项
Now the new "External Target" runs beforethe main target even starts gathering dependency information, so that any changes made during the script execution should be included in the build.
现在,新的“外部目标”在主目标甚至开始收集依赖信息之前运行,因此脚本执行期间所做的任何更改都应包含在构建中。
回答by cduhn
There is another, slightly simpler option that doesn't require a separate target, but it's only viable if your script tends to modify the same source files every time.
还有另一个稍微简单的选项,不需要单独的目标,但只有当您的脚本每次都倾向于修改相同的源文件时,它才可行。
First, here's a brief explanation for anyone who's confused about why Xcode sometimes requires you to build twice (or do a clean build) to see certain changes reflected in your target app. Xcode compiles a source file if the object file it produces is missing, or if the object file's last-modified date is earlier than the source file's last-modified date was at the beginning of the first build phase. If your project runs a script that modifies a source file in a pre-compilation build phase, Xcode won't notice that the source file's last-modified date has changed, so it won't bother to recompile it. It's only when you build the project a second time that Xcode will notice the date change and recompile the file.
首先,这里有一个简短的解释给那些对为什么 Xcode 有时需要您构建两次(或执行一次干净构建)以查看您的目标应用程序中反映的某些更改感到困惑的人。如果 Xcode 生成的目标文件丢失,或者目标文件的最后修改日期早于源文件的最后修改日期在第一个构建阶段开始时,则Xcode 会编译源文件。如果您的项目在预编译构建阶段运行修改源文件的脚本,Xcode 不会注意到源文件的最后修改日期已更改,因此不会费心重新编译它。只有当您第二次构建项目时,Xcode 才会注意到日期更改并重新编译文件。
Here's a simple solution if your script happens to modify the same source files every time. Just add a Run Script build phase at the end of your build processlike this:
如果您的脚本每次都修改相同的源文件,这里有一个简单的解决方案。只需在构建过程结束时添加一个运行脚本构建阶段,如下所示:
touch Classes/FirstModifiedFile.m Classes/SecondModifiedFile.m
exit $?
Running touch
on these source files at the end of your build process guarantees that they will always have a later last-modified date than their object files, so Xcode will recompile them every time.
touch
在构建过程结束时在这些源文件上运行可确保它们的最后修改日期始终比它们的目标文件晚,因此 Xcode 每次都会重新编译它们。
回答by Senseful
As of Xcode 4, it looks like if you add the generated files to the output section of the build phase, it will respect that setting, and not generate the ... has been modified since the precompiled header was built
error messages.
从 Xcode 4 开始,看起来如果您将生成的文件添加到构建阶段的输出部分,它将遵循该设置,并且不会生成... has been modified since the precompiled header was built
错误消息。
This is a good option if your script is only generating a handful of files each time.
如果您的脚本每次只生成少量文件,这是一个不错的选择。
回答by mstelzer
I as well struggled with this for a long time. The answer is to use ento's "External Target" solution. He is WHY this problem occurs and how we use it in practice...
我也为此挣扎了很长时间。答案是使用 ento 的“外部目标”解决方案。他是为什么会出现这个问题以及我们如何在实践中使用它...
Xcode4 build steps do not execute until AFTER the plist has been compiled. This is silly, of course, because it means that any pre-build steps that modify the plist won't take effect. But if you think about it, they actually DO take effect...on the NEXT build. That's why some people have talked about "caching" of plist values or "I have to do 2 builds to make it work." What happens is the plist is built, then your script runs. Next time you build, the plist builds using your modified files, hence the second build.
Xcode4 构建步骤直到 plist 被编译后才会执行。当然,这很愚蠢,因为这意味着任何修改 plist 的预构建步骤都不会生效。但是,如果您仔细想想,它们实际上确实会在……在 NEXT 版本中生效。这就是为什么有些人谈论 plist 值的“缓存”或“我必须进行 2 次构建才能使其工作”的原因。会发生什么是 plist 被构建,然后你的脚本运行。下次构建时,plist 将使用修改后的文件构建,因此是第二次构建。
ento's solution is the one way I've found to actually do a true pre-build step. Unfortunately I also found that it does not cause the plist to update without a clean build and I fixed that. Here is how we have data-driven user values in the plist:
ento 的解决方案是我发现实际执行真正的预构建步骤的一种方法。不幸的是,我还发现它不会导致 plist 在没有干净构建的情况下更新,我修复了这个问题。以下是我们如何在 plist 中获得数据驱动的用户值:
- Add an External Build System project that points to a python script and passes some arguments
- Add user-defined build settings to the build. These are the arguments that you pass to python (more on why we do this later)
- The python script reads some input JSON files and builds a plist preprocessor header file AND touches the main app plist
- The main project has "preprocess plist files" turned on and points to this preprocessor file
- 添加一个指向 python 脚本并传递一些参数的外部构建系统项目
- 将用户定义的构建设置添加到构建中。这些是您传递给 python 的参数(更多关于我们稍后这样做的原因)
- python 脚本读取一些输入的 JSON 文件并构建一个 plist 预处理器头文件并接触主应用程序 plist
- 主项目开启了“预处理plist文件”并指向这个预处理器文件
Using touch on the main app plist file causes the main target to generate the plist every time. The reason we pass in build settings as parameters is so our command-line build can override settings:
在主应用程序 plist 文件上使用 touch 会导致主目标每次都生成 plist。我们将构建设置作为参数传递的原因是我们的命令行构建可以覆盖设置:
- Add a user-defined variable "foo" to the prebuild project.
- In your prebuild you can use $(foo) to pass the value into the python script.
- On the command-line you can add foo=test to pass in a new value.
- 将用户定义的变量“foo”添加到预构建项目中。
- 在您的预构建中,您可以使用 $(foo) 将值传递到 python 脚本中。
- 在命令行上,您可以添加 foo=test 以传递新值。
The python script uses base settings files and allows for user-defined settings files to override the defaults. You make a change and immediately it ends up in the plist. We only use this for settings that MUST be in the plist. For anything else it's a waste of effort....generate a json file or something similar instead and load it at run-time :)
python 脚本使用基本设置文件并允许用户定义的设置文件覆盖默认值。您进行更改后,它会立即出现在 plist 中。我们只将它用于必须在 plist 中的设置。对于其他任何事情,这都是浪费精力....生成一个 json 文件或类似的文件并在运行时加载它:)
I hope this helps...it's been a couple rough days figuring this out.
我希望这会有所帮助......弄清楚这一点已经过了几天艰难的日子。