Создайте 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