出于CI的目的,我需要能够在我们的每晚构建中生成XCARCHIVE和IPA文件。 IPA适用于我们的测试人员,使用我们的临时密钥签名,XCARCHIVE将发送给客户端,以便他们可以将其导入Xcode并在他们满意时将其提交到应用商店。 / p>
通过一些谷歌搜索来生成IPA很简单,但是如何生成.XCARCHIVE文件是我的目标。我发现的最接近的是:
xcodebuild -scheme myscheme archive
但是,这会将.xcarchive存储在一些难以找到的文件夹中,例如:
/Users/me/Library/Developer/Xcode/Archives/2011-12-14/MyApp 14-12-11 11.42 AM.xcarchive
有没有办法控制存档的放置位置,名称是什么,以及如何避免重新编译?我想最好的结果是从你执行'xcodebuild build'时生成的DSYM和APP生成xcarchive - 这可能吗?
答案 0 :(得分:53)
Xcode 5现在支持-archivePath
选项:
xcodebuild -scheme myscheme archive -archivePath /path/to/AppName.xcarchive
您现在还可以从刚刚构建的存档中导出已签名的IPA:
xcodebuild -exportArchive -exportFormat IPA -exportProvisioningProfile my_profile_name -archivePath /path/to/AppName.xcarchive -exportPath /path/to/AppName.ipa
答案 1 :(得分:41)
从Xcode 4 Preview 5开始,可以在scheme archive的后期操作中访问三个环境变量。
ARCHIVE_PATH: The path to the archive.
ARCHIVE_PRODUCTS_PATH: The installation location for the archived product.
ARCHIVE_DSYMS_PATH: The path to the product’s dSYM files.
您可以在此处移动/复制存档。我想在CI脚本中对进程进行更多控制,因此我保存了一个临时文件,该文件很容易在我的CI脚本中包含这些值。
BUILD_DIR=$PROJECT_DIR/build
echo "ARCHIVE_PATH=\"$ARCHIVE_PATH\"" > $BUILD_DIR/archive_paths.sh
echo "ARCHIVE_PRODUCTS_PATH=\"$ARCHIVE_PRODUCTS_PATH\"" >> $BUILD_DIR/archive_paths.sh
echo "ARCHIVE_DSYMS_PATH=\"$ARCHIVE_DSYMS_PATH\"" >> $BUILD_DIR/archive_paths.sh
echo "INFOPLIST_PATH=\"$INFOPLIST_PATH\"" >> $BUILD_DIR/archive_paths.sh
然后在我的CI脚本中,我可以运行以下命令:
xcodebuild -alltargets -scheme [Scheme Name] -configuration [Config Name] clean archive
source build/archive_paths.sh
ARCHIVE_NAME=AppName-$APP_VERSION-$APP_BUILD.xcarchive
cp -r "$ARCHIVE_PATH" "$BUILD_DIR/$ARCHIVE_NAME"
答案 2 :(得分:9)
我刚解决了这个问题 - 只需将参数-archivePath
添加到你的xcode构建命令行,给出初步问题意味着:
xcodebuild -scheme myscheme archive
变成......
xcodebuild -scheme myscheme archive -archivePath Build/Archive
(注意:路径是相对的,我将构建输出到$PWD/Build
)
然后将.app文件夹放在:
中Build/Archive.xarchive/Products/Application
如果您的构建目标中已包含签名证书和配置文件,则可以使用以下命令创建IPA文件而无需重新签名:
xcrun -v -sdk iphoneos PackageApplication -v `pwd`'/Build/Archive.xarchive/Products/Application/my.app' -o `pwd`'/myapp.ipa'
(注意:xcrun不喜欢相对路径,因此pwd
)
-v args转储了许多有用的信息 - 这个命令无法正确签名并仍然以代码0退出,叹息!
如果您发现自己无法运行内置的.ipa,则可能是一个签名问题,您可以使用以下方式进行双重检查:
codesign --verify -vvvv myapp.app
如果它正确签名并且未经篡改输出,则会出现以下情况:
myapp.app: valid on disk
myapp.app: satisfies its Designated Requirement
如果没有,你会看到类似的东西:
Codesign check fails : /blahpath/myapp.app: a sealed resource is missing or invalid
file modified: /blahpath/ls-ios-develop.app/Assets.car
...这通常意味着您正在尝试使用中间输出目录而不是正确的存档。
答案 3 :(得分:2)
我目前的解决方案是重命名用户的现有档案文件夹,运行构建,并执行“查找”以复制我想要的档案,然后删除档案文件夹并重新命名旧文件夹,并使用代码在我的ruby构建脚本中像这样:
# Move the existing archives out of the way
system('mv ~/Library/Developer/Xcode/Archives ~/Library/Developer/Xcode/OldArchivesTemp')
# Build the .app, the .DSYM, and the .xcarchive
system("xcodebuild -scheme \"#{scheme}\" clean build archive CONFIGURATION_BUILD_DIR=\"#{build_destination_folder}\"")
# Find the xcarchive wherever it was placed and copy it where i want it
system("find ~/Library/Developer/Xcode/Archives -name *.xcarchive -exec cp -r {} \"#{build_destination_folder}\" \";\"")
# Delete the new archives folder with this new xcarchive
system('rm -rf ~/Library/Developer/Xcode/Archives')
# Put the old archives back
system('mv ~/Library/Developer/Xcode/OldArchivesTemp ~/Library/Developer/Xcode/Archives')
它有点hacky但我目前没有看到更好的解决方案。至少它会保留用户的“档案”文件夹及其所有预先存档的档案。
- 重要提示! -
我发现我找到存档的代码行和我想要的文件夹的cp没有正确复制存档中的符号链接,从而打破了应用程序中的代码签名。你需要用'mv'或维护符号链接的东西替换它。干杯!
答案 4 :(得分:2)
这是我为Jenkins CI系统提出的一些重击。在xcodebuild archive
命令完成后,应立即在脚本中运行这些命令。
BUILD_DIR="${WORKSPACE}/build"
XCODE_SCHEME="myscheme"
# Common path and partial filename
ARCHIVE_BASEPATH="${HOME}/Library/Developer/Xcode/Archives/$(date +%Y-%m-%d)/${XCODE_SCHEME}"
# Find the latest .xcarchive for the given scheme
NEW_ARCHIVE=$(ls -td "${ARCHIVE_BASEPATH}"* | head -n 1)
# Zip it up so non-Apple systems won't treat it as a dir
pushd "${NEW_ARCHIVE%/*}"
zip -r "${BUILD_DIR}/${NEW_ARCHIVE##*/}.zip" "${NEW_ARCHIVE##*/}"
popd
# Optional, disk cleanup
rm -rf "${NEW_ARCHIVE}"
BUILD_DIR用于收集工件,因此很容易使用诸如build/*.ipa,build/*.zip
答案 5 :(得分:0)
与其他人类似,但可能更简单一点,因为我尝试记录.xcarchive
文件的位置。 (我也不会移动档案文件夹,所以如果您同时进行多个构建,这将更有效。)
我的调用者构建脚本生成一个新的临时文件,并将其路径设置为名为XCARCHIVE_PATH_TMPFILE
的环境变量。这个环境变量在我的方案的归档后动作shell脚本中可用,然后该脚本将.xcarchive的路径写入该文件。然后构建脚本可以在调用xcodebuild archive
之后读取该文件。
动作后shell脚本
echo $ARCHIVE_PATH > "$XCARCHIVE_PATH_TMPFILE"
答案 6 :(得分:0)
在Xcode 4.6上,可以为要编译成xcarchive的方案指定一个构建后的操作:
echo "ARCHIVE_PATH=\"$ARCHIVE_PATH\"" > $PROJECT_DIR/archive_paths.sh
构建脚本可以用来检查运行xcodebuild后是否定义了$ ARCHIVE_PATH,如果是这种情况,输出xcarchive可以移动到指定的文件夹中。
如果项目中的目标数量很大,则此方法不易维护,因为每个目标都必须将相应的方案标记为“共享”并添加构建后操作。
为了解决这个问题,我创建了一个构建脚本,通过提取与当天目标名称匹配的最后一个构建,以编程方式生成存档路径。只要在计算机上没有运行相同目标名称的多个构建,此方法就可以正常工作(这可能是生成多个并发构建的生产环境中的问题)。
#!/bin/bash
#
# Script to archive an existing xcode project to a target location.
# The script checks for a post-build action that defines the $ARCHIVE_PATH as follows:
# echo "ARCHIVE_PATH=\"$ARCHIVE_PATH\"" > $PROJECT_DIR/archive_paths.sh
# If such post-build action does not exist or sourcing it doesn't define the $ARCHIVE_PATH
# variable, the script tries to generate it programmatically by finding the latest build
# in the expected archiving folder
#
post_build_script=archive_paths.sh
build_errors_file=build_errors.log
OUTPUT=output/
XCODEBUILD_CMD='/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild'
TARGET_SDK=iphoneos
function archive()
{
echo "Archiving target '$1'"
# Delete $post_build_script if it already exists as it should be generated by a
# post-build action
rm -f $post_build_script
# Use custom provisioning profile and code sign identity if specified, otherwise
# default to project settings
# Note: xcodebuild always returns 0 even if the build failed. We look for failure in
# the stderr output instead
if [[ ! -z "$2" ]] && [[ ! -z "$3" ]]; then
${XCODEBUILD_CMD} clean archive -scheme $1 -sdk "${TARGET_SDK}" \
"CODE_SIGN_IDENTITY=$3" "PROVISIONING_PROFILE=$2" 2>$build_errors_file
else
${XCODEBUILD_CMD} clean archive -scheme $1 -sdk "${TARGET_SDK}"
2>$build_errors_file
fi
errors=`grep -wc "The following build commands failed" $build_errors_file`
if [ "$errors" != "0" ]
then
echo "BUILD FAILED. Error Log:"
cat $build_errors_file
rm $build_errors_file
exit 1
fi
rm $build_errors_file
# Check if archive_paths.sh exists
if [ -f "$post_build_script" ]; then
source "$post_build_script"
if [ -z "$ARCHIVE_PATH" ]; then
echo "'$post_build_script' exists but ARCHIVE_PATH was not set.
Enabling auto-detection"
fi
fi
if [ -z "$ARCHIVE_PATH" ]; then
# This is the format of the xcarchive path:
# /Users/$USER/Library/Developer/Xcode/Archives/`date +%Y-%m-%d`/$1\
# `date +%d-%m-%Y\ %H.%M`.xcarchive
# In order to avoid mismatches with the hour/minute of creation of the archive and
# the current time, we list all archives with the correct target that have been
# built in the current day (this may fail if the build wraps around midnight) and
# fetch the correct file with a combination of ls and grep.
# This script can break only if there are multiple targets with exactly the same
# name running at the same time.
EXTRACTED_LINE=$(ls -lrt /Users/$USER/Library/Developer/Xcode/Archives/`date
+%Y-%m-%d`/ | grep $1\ `date +%d-%m-%Y` | tail -n 1)
if [ "$EXTRACTED_LINE" == "" ]; then
echo "Error: couldn't fetch archive path"
exit 1
fi
# ls -lrt prints lines with the following format
# drwxr-xr-x 5 mario 1306712193 170 25 Jul 17:17 ArchiveTest 25-07-2013
# 17.17.xcarchive
# We can split this line with the " " separator and take the latest bit:
# 17.17.xcarchive
FILE_NAME_SUFFIX=$(echo $EXTRACTED_LINE | awk '{split($0,a," "); print a[11]}')
if [ "$FILE_NAME_SUFFIX" == "" ]; then
echo "Error: couldn't fetch archive path"
exit 1
fi
# Finally, we can put everything together to generate the path to the xcarchive
ARCHIVE_PATH="/Users/$USER/Library/Developer/Xcode/Archives/`date
+%Y-%m-%d`/$1 `date +%d-%m-%Y` $FILE_NAME_SUFFIX/"
fi
# Create output folder if it doesn't already exist
mkdir -p "$OUTPUT"
# Move archived xcarchive build to designated output folder
mv -v "$ARCHIVE_PATH" "$OUTPUT"
}
# Check number of command line args
if [ $# -lt 1 ]; then
echo "Syntax: `basename $0` <target name> [/path/to/provisioning-profile]
[<code sign identity]"
exit 1
fi
if [ ! -z "$2" ]; then
PROVISIONING_PROFILE="$2"
fi
if [ ! -z "$3" ]; then
SIGN_PROVISIONING_PROFILE="$3"
else
if [ ! -z "$PROVISIONING_PROFILE" ]; then
SIGN_PROVISIONING_PROFILE=$(cat "$PROVISIONING_PROFILE" | egrep -a -o
'[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}')
fi
fi
archive "$1" "$PROVISIONING_PROFILE" "$SIGN_PROVISIONING_PROFILE"
可以在此处找到带有示例Xcode项目的完整源代码: