Создать xcarchive в определенную папку из командной строки - PullRequest
33 голосов
/ 04 января 2012

Для целей CI мне нужно иметь возможность генерировать файл XCARCHIVE и IPA в нашей ночной сборке. IPA предназначен для наших тестировщиков и должен быть подписан нашими специальными ключами, а XCARCHIVE должен отправить клиенту, чтобы они могли импортировать его в Xcode и отправить его в магазин приложений, когда им это понравится. 1001 *

Генерация IPA достаточно проста с небольшим поиском в Google, однако, как создать файл .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 - возможно ли это?

Ответы [ 7 ]

53 голосов
/ 03 января 2014

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
41 голосов
/ 27 января 2012

Начиная с 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, поэтому я сохранил временный файл, который можно было легко найти в моем скрипте 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"
9 голосов
/ 19 июня 2014

Я только что решил эту проблему - просто добавьте аргумент -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

... что обычно означает, что вы пытаетесь использовать промежуточный выходной каталог, а не нужный архив.

2 голосов
/ 12 июля 2013

Вот немного bash, которое я придумал для нашей системы 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 используется для сбора артефактов, поэтому их легко архивировать из Jenkins с помощью глобуса, например build/*.ipa,build/*.zip

2 голосов
/ 05 января 2012

Мое текущее решение - переименовать существующую папку архивов пользователя, запустить сборку и выполнить «поиск», чтобы скопировать архивы туда, куда я хочу, затем удалить папку архивов и переименовать старую папку, как было, с кодом вот так в моем скрипте сборки 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')

Это немного странно, но я не вижу лучшего решения в настоящее время. По крайней мере, он сохраняет папку «Архивы» пользователя и все его существующие архивы.

- Важное замечание! -

С тех пор я обнаружил, что строка кода, в которой я нахожу архив и помещаю его в нужную папку, неправильно копирует символические ссылки внутри архива, нарушая тем самым подписание кода в приложении. Вы захотите заменить это на «mv» или что-то, что поддерживает символические ссылки. Ура!

0 голосов
/ 29 июля 2013

В Xcode 4.6 можно указать действие после сборки для схемы, которая будет скомпилирована в xcarchive:

echo "ARCHIVE_PATH=\"$ARCHIVE_PATH\"" > $PROJECT_DIR/archive_paths.sh

С помощью сценария сборки можно проверить, определен ли $ ARCHIVE_PATH после запуска xcodebuild, и в этом случае выходной файл 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 можно найти здесь:

https://github.com/bizz84/Xcode-xcarchive-command

0 голосов
/ 20 ноября 2012

Аналогично другим, но, возможно, немного проще, так как я пытаюсь записать местоположение файла .xcarchive.(Я также не перемещаю папку архивов, так что это будет работать лучше, если вы выполняете несколько сборок одновременно.)

Мой скрипт сборки вызывающей программы генерирует новый временный файл и устанавливает его путь к средепеременная с именем XCARCHIVE_PATH_TMPFILE.Эта переменная окружения доступна в скрипте архива после действия моей схемы, который затем записывает путь к файлу .xcarchive.Сценарий сборки, который может затем прочитать этот файл после вызова xcodebuild archive.

сценарий оболочки после действия

echo $ARCHIVE_PATH > "$XCARCHIVE_PATH_TMPFILE"
...