Создайте xcarchive в определенной папке из командной строки
Для целей CI мне нужно иметь возможность генерировать XCARCHIVE и файл IPA в нашей ночной сборке. IPA предназначен для наших тестировщиков, которые должны быть подписаны с нашими специальными ключами, а XCARCHIVE - отправить клиенту, чтобы они могли импортировать его в Xcode и отправить его в магазин приложений, когда они им довольны.
Создание IPA достаточно просто с небольшим количеством поисковых запросов, однако, как сгенерировать файл .XCARCHIVE, это ускользает от меня. Самое близкое, что я нашел:
xcodebuild -scheme myscheme archive
Однако это сохраняет .xcarchive в некоторой труднодоступной папке, например:
/Users/me/Library/Developer/Xcode/Archives/2011-12-14/MyApp 14-12-11 11.42 AM.xcarchive
Есть ли способ контролировать, где хранится архив, каково его имя и как избежать его повторной компиляции? Я думаю, что наилучшим возможным результатом было бы генерировать xcarchive из DSYM и APP, которые генерируются, когда вы создаете "сборку xcodebuild" - возможно ли это?
Ответы
Ответ 1
Мое текущее решение - переименовать существующую папку архивов пользователей, запустить сборку и сделать "найти", чтобы скопировать архивы, где я хочу, а затем удалить папку архивов и переименовать старую папку, как было, с кодом как это в моем ruby build script:
# 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')
Немного взломанный, но в настоящее время я не вижу лучшего решения. По крайней мере, он сохраняет папку "архивы" пользователя и все существующие ранее архивы.
- Важное примечание!
Я с тех пор узнал, что строка кода, где я нахожу архив, и cp его в папку, которую я хочу, не копирует символические ссылки внутри архива правильно, тем самым нарушая подписание кода в приложении. Вы захотите заменить это на "mv" или что-то, что поддерживает символические ссылки. Ура!
Ответ 2
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
Ответ 3
Начиная с версии Xcode 4 Preview 5 есть три переменные среды, которые доступны в пост-действиях архива схемы.
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 script, поэтому я сохранил временный файл, который можно было легко получить в моем CI script, который содержал эти значения.
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 script я могу запустить следующее:
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"
Ответ 4
Я только что решил это - просто добавьте аргумент -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 выгружают много полезной информации - эта команда может неправильно подписать и все же выйти с кодом 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
... что обычно означает, что вы пытаетесь использовать промежуточный выходной каталог, а не соответствующий архив.
Ответ 5
Вот немного bash, что я придумал для нашей системы Jenkins CI. Эти команды должны запускаться в script сразу после завершения команды 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 используется для сбора артефактов, чтобы легко архивировать их у Jenkins с помощью glob, например build/*.ipa,build/*.zip
Ответ 6
Подобно другим, но, возможно, немного проще, так как я пытаюсь записать расположение файла .xcarchive
. (Я также не перемещаю папку архивов, так что это будет работать лучше, если вы будете делать несколько сборок одновременно.)
My caller build script создает новый tempfile и устанавливает его путь к переменной среды с именем XCARCHIVE_PATH_TMPFILE
. Эта переменная среды доступна в моей схеме Archive post-action shell script, которая затем записывает путь .xcarchive к этому файлу. Строка script, которая может затем прочитать этот файл после вызова xcodebuild archive
.
оболочка после действия script
echo $ARCHIVE_PATH > "$XCARCHIVE_PATH_TMPFILE"
Ответ 7
В Xcode 4.6 можно указать действие post-build для схемы, которая будет скомпилирована в xcarchive:
echo "ARCHIVE_PATH=\"$ARCHIVE_PATH\"" > $PROJECT_DIR/archive_paths.sh
Конструкция script может использоваться для проверки того, определена ли функция $ARCHIVE_PATH после запуска xcodebuild, и если это так, выходной файл xcarchive можно перенести в назначенную папку.
Этот метод не очень удобен в обслуживании, если цели в проекте являются большим числом, так как для каждого из них необходимо пометить соответствующую схему как "общий" и добавить действие после сборки.
Чтобы решить эту проблему, я создал сборку script, которая программно генерирует путь к архиву, извлекая последнюю сборку, которая соответствует целевому имени в текущий день. Этот метод работает надежно, если не существует нескольких сборок с одним и тем же именем цели, запущенным на машине (это может быть проблемой в производственных средах, где выполняется несколько параллельных сборок).
#!/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 можно найти здесь:
https://github.com/bizz84/Xcode-xcarchive-command