【问题标题】:Xcode Build Phase Input FolderXcode 构建阶段输入文件夹
【发布时间】:2022-03-05 09:57:46
【问题描述】:

我有一个自定义的“运行脚本”构建阶段,它根据文件夹 A 中包含的文件生成一些源文件。构建阶段在“编译源”构建阶段之前执行。由于生成了源文件,并且我想避免在每次运行时触发完整的项目重建,我想利用“输入文件”和“输出文件”部分,as described in this blog post

很遗憾,文件夹A 是文件夹而不是文件。当我将文件夹A 的路径作为“输入文件”提供时,当文件夹中没有任何更改时,构建阶段不会正确执行。当我重命名一些文件时,构建阶段也会正确执行。 但是当更改文件夹 A 内的文件中的某些内容时,不会执行构建阶段

我尝试提供不带* 的文件夹名称,最后还提供***

当给定输入文件夹中的任何文件发生更改(添加、删除、内容修改)时,如何执行构建阶段?

【问题讨论】:

  • xcassets 与源文件(准确地说是源文件)有什么关系? xcassets 是一种与应用捆绑文件的方法,与构建过程无关。
  • 我使用 objc-codegenutils 是因为我鄙视代码中的字符串类型引用
  • 好吧,我什么都不懂(也不想),但是你没有回答我的问题。为什么这些源文件是xcassets 的一部分?它们可以存在于项目文件夹中的任何位置,并且不会成为 app bundle 的一部分,那么为什么要使用 xcassets
  • 我还使用了来自 objc-codegenutils 的 objc-assetgen,我从问题中编辑了它以更简单
  • 我们能不能停下来讨论一下xcassets,这不是这个问题的本质。我将编辑问题。

标签: ios xcode


【解决方案1】:

有两个极端:喜欢在任何事情上都使用 Xcode 的人,以及倾向于避免使用 Xcode 的人,除了只能通过它完成的工作。这是一个不喜欢所有 Xcode 的非 IDE 功能的开发人员的答案:我不知道你的确切问题的答案,我什至不想知道,因为我尽量避免将 Xcode 用于超出它的任何东西默认功能。

就我收到您的问题而言,您有一些 IN 内容,您想对其进行一些处理,以便处理会产生一些 OUT 内容。我们称之为Subproject:包含IN、OUT 和处理的东西:IN -> OUT。并且您希望此处理是增量的:如果已经完成,请不要重复相同的工作,即如果 OUT 存在且 IN 未修改,请不要执行处理 IN -> OUT,因为结果已经存在。

对于这种任务,我使用 Make(任何其他 构建系统 都可以,例如 Ninja),这正是完成这项工作的工具:你描述你需要什么,描述你拥有什么,然后转换过程,它可以工作并为您提供增量处理。

无需在运行脚本阶段修改 Xcode 的构建阶段,您只需输入 cd Subproject; make 并通过编写正确的 Makefile 将与您的 IN -> OUT 处理相关的所有内容委托给 Make。 Xcode 中唯一保留的内容是 Run Script 阶段,该阶段将您的构建规则委托给 Make。

我创建了解决您的问题的示例,并演示了将基于 Make 的子项目集成到 Xcode 项目中:Xcode and Make

├── README.md
├── Subproject
│   ├── Generated-Code
│   │   ├── file1.out
│   │   ├── file2.out
│   │   └── file3.out
│   ├── Makefile
│   └── Source
│       ├── file1.in
│       ├── file2.in
│       └── file3.in
└── Xcode-and-Make
    ├── Xcode-and-Make
    │   └── main.m
    └── Xcode-and-Make.xcodeproj
        └── project.pbxproj

自定义的东西被隔离在子项目文件夹中。此文件夹包含在 Xcode 项目中: Generated-Code .out 文件包含在项目的主要目标中,*.in 文件不包含,Makefile 也不包含。

在项目的运行脚本阶段,只有对 Make 的调用:

cd ../Subproject
make

所有Source/*.in -> Generated-Code/*.out 处理都在 Makefile 中完成,其编写如下:

IN_PATH=./Source
IN_FILES=$(wildcard $(IN_PATH)/*.in)

OUT_PATH=./Generated-Code

OUT_FILES := $(patsubst %, $(OUT_PATH)/%, $(notdir $(IN_FILES)))
OUT_FILES := $(patsubst %.in, %.out, $(OUT_FILES))

default: generate

generate: $(OUT_FILES)

clean:
    rm -rf $(OUT_PATH)

$(OUT_PATH)/%.out: $(OUT_PATH) $(IN_PATH)/%.in
    cp -v $(IN_PATH)/$*.in $(OUT_PATH)/$*.out

$(OUT_PATH):
    mkdir -p $(OUT_PATH)

如果您是 Make 新手,所有这些符号一开始可能会令人困惑,但在阅读有关 Make 的教程一天后,您将对 Make 的工作原理有基本的了解。

编写此Makefile 是为了给您提供增量处理:cp 只是一个简单的演示操作,它将*.in 复制到*.out - 在实际应用中它可以是编译器或其他一些工具。

当您在Subproject 文件夹中您第一次看到make 时:

Subproject$ make
mkdir -p ./Generated-Code
cp -v ./Source/file1.in ./Generated-Code/file1.out
./Source/file1.in -> ./Generated-Code/file1.out
cp -v ./Source/file2.in ./Generated-Code/file2.out
./Source/file2.in -> ./Generated-Code/file2.out
cp -v ./Source/file3.in ./Generated-Code/file3.out
./Source/file3.in -> ./Generated-Code/file3.out

但在第二次运行时你会得到:

Subproject$ make
make: Nothing to be done for `default'.

这是因为 Make 很聪明地理解您没有对任何 *.in 文件进行任何修改,因此要使 Make 再次执行其处理,您需要在 *.in 文件中实际执行一些更改,这是正是您最初的问题。

我认为示例项目是最好的展示,所以请随意决定这偏离 Xcode 的默认设置是否会让您感到舒服,如果您有任何其他问题,请随时询问。

免责声明:我已经使用 Make 2 年了(几乎)所有涉及构建 Xcode 无法处理的东西,所以我强烈建议您学习 Make 的基础知识:它可以是构建脚本的非常强大的工具,其中增量处理很高兴拥有。

【讨论】:

  • Plus 1 是一种有趣的开箱即用方法
  • 您的评论描述并将人们分为极端类别,这给人以居高临下的印象。我们不应该采取像使用 MAKEFILES 这样的严厉措施; Xcode 应该是一个独立的解决方案。如果它不能满足某些需求,并不意味着需求不对,只是意味着它的功能集不足或存在错误。人们应该简单地通过 FeedbackAssistant 向 Apple 报告这个问题,与此同时,还有比实施不可维护的 MAKEFILE 更好的选择。
【解决方案2】:

我们可以使用cksum 命令来检查文件是否被更改,因此我们将能够运行diff 命令来检查更改。

所以,首先,我们需要列出我们想要校验和的所有文件并将其放入一个文件中:

find directory_to_search_in -type f -name "*.storyboard" -or -name "*.xib" > $FILE_PATHS

然后我们检查我们是否曾经运行过这个脚本,如果是,那么我们将获得包含我们找到的文件的初始校验和的文件,作为下一步 - 我们创建当前文件校验和的新文件并进行比较他们俩都为了改变。万一他们不一样 - 我们运行脚本。
如果此文件不存在 - 我们将创建它并运行您的脚本。 此外,我们在开始时添加了一个检查以删除我们用来存储文件路径和当前文件校验和的文件,以防它们构建被中断并且脚本没有正确结束,因为我们不需要这些文件挂起,我们只需要它们来进行检查。这些文件将被删除。

结果如下:

if [ -f $FILE_PATHS ]; then
rm $FILE_PATHS
fi
if [ -f $rm $CURRENT_CHECKSUMS ]; then
rm $CURRENT_CHECKSUMS
fi


find App/Source/UI/ -type f -name "*.storyboard" -or -name "*.xib" > $FILE_PATHS

if [ ! -f $INITIAL_CHECKSUMS ]; then
while read file; do
cksum $file >> $INITIAL_CHECKSUMS
done < $FILE_PATHS

your_generate_script_function_call
else
while read file; do
cksum $file >> $CURRENT_CHECKSUMS
done < $FILE_PATHS
if ! diff $INITIAL_CHECKSUMS $CURRENT_CHECKSUMS > /dev/null ; then
your_generate_script_function_call
cat $CURRENT_CHECKSUMS > $INITIAL_CHECKSUMS
fi

rm $CURRENT_CHECKSUMS
fi

rm $FILE_PATHS

【讨论】:

    【解决方案3】:

    Xcode 要求输入文件在 $SRCROOT 目录中,输出文件在 $DERIVED_FILE_DIR 目录中,否则它将在每次构建时运行脚本。

    遗憾的是,您不能对输入文件使用递归路径。 (请向 Apple 提交 FeedbackAssistant 票以请求此操作。)

    您必须使用 .xcfilelist,并保持更新。为了不必手动更新它,您需要一些在预构建方案脚本期间运行的其他脚本,该脚本检查每个目标的当前 .xcfilelist 是否准确地反映了您希望脚本定位的所有文件。显然,如果您有很多方案,这是一种非常烦人的解决方案。

    所以我采用了一种解决方法,即:

    • 指定$(SRCROOT)/$(INFOPLIST_FILE) 作为唯一的输入文件
    • $(DERIVED_FILE_DIR)/X.plist指定为唯一的输出文件
    • 在脚本开头将输入文件复制到输出文件
    • 然后执行脚本的主要工作

    例如:

    set -ex
    
    if [ -f "$SCRIPT_OUTPUT_FILE_0" ]; then
        echo "$SCRIPT_OUTPUT_FILE_0 exists. This script should not even be running"
        exit 0
    else
        cp -a $SCRIPT_INPUT_FILE_0 $SCRIPT_OUTPUT_FILE_0
    fi
    

    这可确保脚本仅在干净的构建(即清理构建文件夹或擦除 DerivedData/YourWorkspace/Build 后的第一个构建)上运行。

    不幸的是,这意味着脚本永远不会在增量构建上运行,即使您更改了它用作输入的某些文件。然而,这是我能找到的最好的妥协(到目前为止)。这似乎比诉诸 MAKEFILE 等要好得多。

    更好的选择可能是使用构建规则。

    【讨论】:

      猜你喜欢
      • 2013-11-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-25
      • 1970-01-01
      • 1970-01-01
      • 2022-10-14
      相关资源
      最近更新 更多