如何自动设置 Watchkit 应用程序目标的版本和内部版本号
How to automatically set the version and build number of a Watchkit app target
Watchkit 应用程序和扩展程序的版本号和内部版本号(或版本号和短版)必须设置为与包含的应用程序相同的值。
我在构建时使用环境变量在 Info.plist
中动态设置应用程序版本。这也适用于 Watchkit 扩展,但不适用于 Watchkit 应用程序。
我使用的环境变量必须在没有 ${}
的主应用程序和扩展的 plist 中提供(对于变量 ${VERSION}
我设置 VERSION
)。
如果我对 Watchkit 应用程序执行相同的操作,它会获取字符串本身,而不是值。如果我为它提供美元和括号,则变量中没有数据。
知道如何为 Watchkit 应用程序设置变量吗?
好吧,如果它不能像这样工作,那就用 Run Script Build Phase
来做吧。做这样的事情:
#!/bin/sh
INFOPLIST="${TARGET_BUILD_DIR}/${INFOPLIST_PATH}"
echo "writing to $INFOPLIST"
PLISTCMD="Set :CFBundleVersion $(git rev-list --all|wc -l)"
echo -n "$INFOPLIST" | xargs -0 /usr/libexec/PlistBuddy -c "$PLISTCMD"
我没有你的 WatchKit 应用程序的正确路径,所以你必须自己更改它。
stk 的回答是正确的,但我也想补充一下我的发现。
解决问题的一种方法是使用 agvtools
:
在 OSX > 其他 > 外部构建系统中创建一个新目标
添加类似于此的 运行 脚本:
#!/bin/bash
#read vesion number from version.txt in project root
VERSION=$(head -n 1 version.txt)
BUILD=`git rev-list $(git rev-parse --abbrev-ref HEAD) | wc -l | awk '{ print }'`
echo "${VERSION} (${BUILD})"
agvtool new-marketing-version ${VERSION}
agvtool new-version -all ${BUILD}
exit 0
我有一个 version.txt
文件,其中只有我的版本号(营销版本 或 短捆绑版本)可以通过任何 CI 系统轻松调整,并使用我的 git SHA 的数量作为内部版本号( 捆绑版本 )
调整 VERSION
和 BUILD
的来源以满足您的要求
运行 在您 build/archiving.
之前为新目标创建的方案
如果您需要将其作为主要目标的依赖项 - 这将失败,因为它将停止执行以下目标(如果有人知道如何防止这种情况发生,我将不胜感激) )
但是您仍然可以通过为每个 plist 执行如下脚本(类似于 stk 提供的脚本)来实现这一点:
#!/bin/sh
#
# usage:
# set-version-in-plist.sh LIST VERSION BUILD
# LIST: Info.plist path & name
# VERSION: version number xxx.xxx.xxx
# BUILD: build number xxxxx
#
# Location of PlistBuddy
PLISTBUDDY="/usr/libexec/PlistBuddy"
${PLISTBUDDY} -c "Set :CFBundleShortVersionString " "";
${PLISTBUDDY} -c "Set :CFBundleVersion " "";
将此脚本另存为文件,使其可执行 (chmod +x SCRIPTNAME
)
然后为所有 plists
使用提到的参数执行它
此解决方案不如 agvtools
解决方案方便,但在依赖项中使用时不应停止构建...
我用它来更新所有目标:
#!/bin/bash
buildNumber=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "$INFOPLIST_FILE")
buildNumber=$(($buildNumber + 1))
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "$INFOPLIST_FILE"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "$SRCROOT/Great WatchKit App/Info.plist"
我的 CFBundleVersion
是 git 仓库中我的 master
分支的提交数。
在我的主要应用程序目标上,在 Build Phases
> + New Run Script Phase
我添加了这个脚本:
# Set the build number to the count of Git commits
buildNumber=$(git rev-list --count HEAD)
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "${PROJECT_DIR}/${INFOPLIST_FILE}"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "$SRCROOT/app WatchKit Extension/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "$SRCROOT/app WatchKit App/Info.plist"
从 app WatchKit App
开始,app
应该是您应用的名称,但请检查确切的路径。
我有一个 运行 脚本附加到我的主要应用程序目标。它将在构建应用程序时传播 WatchKit 扩展和 WatchKit 应用程序。
它是完全可重复使用的。享受吧!
buildNumber=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "${PROJECT_DIR}/${INFOPLIST_FILE}")
buildNumberDec=$(($buildNumber + 1))
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumberDec" "${PROJECT_DIR}/${INFOPLIST_FILE}"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumberDec" "$SRCROOT/${PRODUCT_NAME} WatchKit Extension/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumberDec" "$SRCROOT/${PRODUCT_NAME} WatchKit App/Info.plist"
您可以在没有构建脚本的情况下更新所有目标的构建版本。 (您也可以使用它来更新营销/短构建版本;在这种情况下忽略对 CFBundleVersion 的更改)。
打开项目设置并将 CURRENT_PROJECT_VERSION(当前项目版本)设置为所需的版本号。在所有目标中确保 CURRENT_PROJECT_VERSION 为空(以便其值从项目继承)。然后在所有 Info.plist 文件中将 CFBundleShortVersionString(Bundle 版本字符串,短)和 CFBundleVersion(Bundle 版本/构建版本)设置为 $(CURRENT_PROJECT_VERSION).
您可以选择区分构建版本和“营销”版本(如果构建版本是自动生成的,这很有用)。为此,在您的项目设置中,将 MARKETING_VERSION(营销版本)设置为所需的版本号。和以前一样,确保所有目标都继承项目的值。最后,在所有 Info.plist 文件中将 CFBundleShortVersionString(Bundle versions string,short)设置为 $(MARKETING_VERSION).
如果你想在每次构建时增加你的 CFBundleVersion(或者让它反映你的 git SHA)。按照 dogsgod 的描述使用 agvtool 或参见 https://developer.apple.com/library/ios/qa/qa1827/_index.html.
如果用我自己的个人经验补充其他答案会有用。这些以 ValidateEmbeddedBinary
.
引起的构建失败为中心
ValidateEmbeddedBinary
如果 CFBundleVersion
在嵌入式 WatchKit 应用程序和父应用程序中不相同,将失败。
错误类似于:
(null): error: The value of CFBundleVersion in your WatchKit app's
Info.plist (1234) does not match the value in your companion app's
Info.plist (7931). These values are required to match.
工作在XCode7.3,下面先更新父应用的plist。然后它会在 PBXCp
执行之前更新 Debug 或 Release WatchKit 应用程序以将其复制到父应用程序目录:
#!/bin/sh
git=`sh /etc/profile; which git`
appBuild=`"$git" rev-list HEAD --count`
appPlistPath="${TARGET_BUILD_DIR}/${INFOPLIST_PATH}"
watchKitPlistPath="${BUILT_PRODUCTS_DIR}/../${CONFIGURATION}-watchos/${PRODUCT_NAME} WatchKit App.app/Info.plist"
echo "Setting App CFBundleVersion $appBuild at info plist path at ${appPlistPath}"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $appBuild" "${appPlistPath}"
echo "Setting WatchKit App CFBundleVersion $appBuild at ${watchKitPlistPath}"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $appBuild" "${watchKitPlistPath}"
以上使用 git 的提交计数作为 CFBundleVersion
。
为了扩展这个线程,如果有人遇到与我类似的问题,希望下面的脚本能有所帮助。
例如,我的项目中有 watch target
、watch extension
和一个 app share extension
。
我使用 运行 阶段按照建议更新项目的 plist,如果我构建项目,.plist 文件都按预期更新。
然而问题是当你归档应用程序时(假设所有目标都有不同的内部版本号),归档项目中的信息 plist 没有更新。
经过几次尝试,我发现扩展的 plist 文件在 运行 阶段之前被复制,然后 运行 阶段脚本(更新项目的 plist)将无法帮助归档 plist。
所以我最终更改了脚本以更新已编译目标的 plist,它按我预期的那样工作,我对应用程序中的所有目标都有相同的内部版本号。
这是我的做法:
将此脚本添加到每个目标的构建阶段:
infoPlistPath="${TARGET_BUILD_DIR}/${EXECUTABLE_FOLDER_PATH}/Info.plist"
PLISTBUDDY="/usr/libexec/PlistBuddy"
buildNumber=$(git rev-list HEAD | wc -l | tr -d ' ')
$PLISTBUDDY -c "Set :CFBundleVersion $buildNumber" "${infoPlistPath}"
对于不同的目标,这个EXECUTABLE_FOLDER_PATH
是不同的,它会更新编译目标的信息plist,而不是项目的信息plist。
请注意,我也检查了 "Run script only when installing",因为我只需要将其 运行 用于存档
您也可以简单地将相同的构建阶段添加到 EACH 个目标。
#Update build number with number of git commits if in release mode
if [ ${CONFIGURATION} == "Release" ]; then
buildNumber=$(git rev-list HEAD | wc -l | tr -d ' ')
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "${PROJECT_DIR}/${INFOPLIST_FILE}"
fi;
因此,如果您有一个 Watch App、一个 Watch App Extension、一个 Widget Extension 或任何其他扩展,请转到它们的每个构建阶段,添加一个 New 运行 Script Phase 并粘贴上面的内容。
Watchkit 应用程序和扩展程序的版本号和内部版本号(或版本号和短版)必须设置为与包含的应用程序相同的值。
我在构建时使用环境变量在 Info.plist
中动态设置应用程序版本。这也适用于 Watchkit 扩展,但不适用于 Watchkit 应用程序。
我使用的环境变量必须在没有 ${}
的主应用程序和扩展的 plist 中提供(对于变量 ${VERSION}
我设置 VERSION
)。
如果我对 Watchkit 应用程序执行相同的操作,它会获取字符串本身,而不是值。如果我为它提供美元和括号,则变量中没有数据。
知道如何为 Watchkit 应用程序设置变量吗?
好吧,如果它不能像这样工作,那就用 Run Script Build Phase
来做吧。做这样的事情:
#!/bin/sh
INFOPLIST="${TARGET_BUILD_DIR}/${INFOPLIST_PATH}"
echo "writing to $INFOPLIST"
PLISTCMD="Set :CFBundleVersion $(git rev-list --all|wc -l)"
echo -n "$INFOPLIST" | xargs -0 /usr/libexec/PlistBuddy -c "$PLISTCMD"
我没有你的 WatchKit 应用程序的正确路径,所以你必须自己更改它。
stk 的回答是正确的,但我也想补充一下我的发现。
解决问题的一种方法是使用 agvtools
:
在 OSX > 其他 > 外部构建系统中创建一个新目标 添加类似于此的 运行 脚本:
#!/bin/bash
#read vesion number from version.txt in project root
VERSION=$(head -n 1 version.txt)
BUILD=`git rev-list $(git rev-parse --abbrev-ref HEAD) | wc -l | awk '{ print }'`
echo "${VERSION} (${BUILD})"
agvtool new-marketing-version ${VERSION}
agvtool new-version -all ${BUILD}
exit 0
我有一个 version.txt
文件,其中只有我的版本号(营销版本 或 短捆绑版本)可以通过任何 CI 系统轻松调整,并使用我的 git SHA 的数量作为内部版本号( 捆绑版本 )
调整 VERSION
和 BUILD
的来源以满足您的要求
运行 在您 build/archiving.
之前为新目标创建的方案如果您需要将其作为主要目标的依赖项 - 这将失败,因为它将停止执行以下目标(如果有人知道如何防止这种情况发生,我将不胜感激) )
但是您仍然可以通过为每个 plist 执行如下脚本(类似于 stk 提供的脚本)来实现这一点:
#!/bin/sh
#
# usage:
# set-version-in-plist.sh LIST VERSION BUILD
# LIST: Info.plist path & name
# VERSION: version number xxx.xxx.xxx
# BUILD: build number xxxxx
#
# Location of PlistBuddy
PLISTBUDDY="/usr/libexec/PlistBuddy"
${PLISTBUDDY} -c "Set :CFBundleShortVersionString " "";
${PLISTBUDDY} -c "Set :CFBundleVersion " "";
将此脚本另存为文件,使其可执行 (chmod +x SCRIPTNAME
)
然后为所有 plists
此解决方案不如 agvtools
解决方案方便,但在依赖项中使用时不应停止构建...
我用它来更新所有目标:
#!/bin/bash
buildNumber=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "$INFOPLIST_FILE")
buildNumber=$(($buildNumber + 1))
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "$INFOPLIST_FILE"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "$SRCROOT/Great WatchKit App/Info.plist"
我的 CFBundleVersion
是 git 仓库中我的 master
分支的提交数。
在我的主要应用程序目标上,在 Build Phases
> + New Run Script Phase
我添加了这个脚本:
# Set the build number to the count of Git commits
buildNumber=$(git rev-list --count HEAD)
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "${PROJECT_DIR}/${INFOPLIST_FILE}"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "$SRCROOT/app WatchKit Extension/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "$SRCROOT/app WatchKit App/Info.plist"
从 app WatchKit App
开始,app
应该是您应用的名称,但请检查确切的路径。
我有一个 运行 脚本附加到我的主要应用程序目标。它将在构建应用程序时传播 WatchKit 扩展和 WatchKit 应用程序。
它是完全可重复使用的。享受吧!
buildNumber=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "${PROJECT_DIR}/${INFOPLIST_FILE}")
buildNumberDec=$(($buildNumber + 1))
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumberDec" "${PROJECT_DIR}/${INFOPLIST_FILE}"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumberDec" "$SRCROOT/${PRODUCT_NAME} WatchKit Extension/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumberDec" "$SRCROOT/${PRODUCT_NAME} WatchKit App/Info.plist"
您可以在没有构建脚本的情况下更新所有目标的构建版本。 (您也可以使用它来更新营销/短构建版本;在这种情况下忽略对 CFBundleVersion 的更改)。
打开项目设置并将 CURRENT_PROJECT_VERSION(当前项目版本)设置为所需的版本号。在所有目标中确保 CURRENT_PROJECT_VERSION 为空(以便其值从项目继承)。然后在所有 Info.plist 文件中将 CFBundleShortVersionString(Bundle 版本字符串,短)和 CFBundleVersion(Bundle 版本/构建版本)设置为 $(CURRENT_PROJECT_VERSION).
您可以选择区分构建版本和“营销”版本(如果构建版本是自动生成的,这很有用)。为此,在您的项目设置中,将 MARKETING_VERSION(营销版本)设置为所需的版本号。和以前一样,确保所有目标都继承项目的值。最后,在所有 Info.plist 文件中将 CFBundleShortVersionString(Bundle versions string,short)设置为 $(MARKETING_VERSION).
如果你想在每次构建时增加你的 CFBundleVersion(或者让它反映你的 git SHA)。按照 dogsgod 的描述使用 agvtool 或参见 https://developer.apple.com/library/ios/qa/qa1827/_index.html.
如果用我自己的个人经验补充其他答案会有用。这些以 ValidateEmbeddedBinary
.
ValidateEmbeddedBinary
如果 CFBundleVersion
在嵌入式 WatchKit 应用程序和父应用程序中不相同,将失败。
错误类似于:
(null): error: The value of CFBundleVersion in your WatchKit app's Info.plist (1234) does not match the value in your companion app's Info.plist (7931). These values are required to match.
工作在XCode7.3,下面先更新父应用的plist。然后它会在 PBXCp
执行之前更新 Debug 或 Release WatchKit 应用程序以将其复制到父应用程序目录:
#!/bin/sh
git=`sh /etc/profile; which git`
appBuild=`"$git" rev-list HEAD --count`
appPlistPath="${TARGET_BUILD_DIR}/${INFOPLIST_PATH}"
watchKitPlistPath="${BUILT_PRODUCTS_DIR}/../${CONFIGURATION}-watchos/${PRODUCT_NAME} WatchKit App.app/Info.plist"
echo "Setting App CFBundleVersion $appBuild at info plist path at ${appPlistPath}"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $appBuild" "${appPlistPath}"
echo "Setting WatchKit App CFBundleVersion $appBuild at ${watchKitPlistPath}"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $appBuild" "${watchKitPlistPath}"
以上使用 git 的提交计数作为 CFBundleVersion
。
为了扩展这个线程,如果有人遇到与我类似的问题,希望下面的脚本能有所帮助。
例如,我的项目中有 watch target
、watch extension
和一个 app share extension
。
我使用 运行 阶段按照建议更新项目的 plist,如果我构建项目,.plist 文件都按预期更新。
然而问题是当你归档应用程序时(假设所有目标都有不同的内部版本号),归档项目中的信息 plist 没有更新。 经过几次尝试,我发现扩展的 plist 文件在 运行 阶段之前被复制,然后 运行 阶段脚本(更新项目的 plist)将无法帮助归档 plist。 所以我最终更改了脚本以更新已编译目标的 plist,它按我预期的那样工作,我对应用程序中的所有目标都有相同的内部版本号。 这是我的做法: 将此脚本添加到每个目标的构建阶段:
infoPlistPath="${TARGET_BUILD_DIR}/${EXECUTABLE_FOLDER_PATH}/Info.plist"
PLISTBUDDY="/usr/libexec/PlistBuddy"
buildNumber=$(git rev-list HEAD | wc -l | tr -d ' ')
$PLISTBUDDY -c "Set :CFBundleVersion $buildNumber" "${infoPlistPath}"
对于不同的目标,这个EXECUTABLE_FOLDER_PATH
是不同的,它会更新编译目标的信息plist,而不是项目的信息plist。
请注意,我也检查了 "Run script only when installing",因为我只需要将其 运行 用于存档
您也可以简单地将相同的构建阶段添加到 EACH 个目标。
#Update build number with number of git commits if in release mode
if [ ${CONFIGURATION} == "Release" ]; then
buildNumber=$(git rev-list HEAD | wc -l | tr -d ' ')
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "${PROJECT_DIR}/${INFOPLIST_FILE}"
fi;
因此,如果您有一个 Watch App、一个 Watch App Extension、一个 Widget Extension 或任何其他扩展,请转到它们的每个构建阶段,添加一个 New 运行 Script Phase 并粘贴上面的内容。