Pour les besoins de CI, j'ai besoin de pouvoir générer un XCARCHIVE et un fichier IPA dans notre version nocturne. L'IPA est destiné à nos testeurs, à signer avec nos clés ad hoc, et le XCARCHIVE est à envoyer au client afin qu'il puisse l'importer dans Xcode et le soumettre à l'App Store quand il en est satisfait.
La génération de l'IPA est assez simple avec un peu de recherche sur Google, mais comment générer le fichier .XCARCHIVE est ce qui m'échappe. Le plus proche que j'ai trouvé est:
xcodebuild -scheme myscheme archive
Cependant, cela stocke le .xcarchive dans un dossier difficile à trouver, par exemple:
/Users/me/Library/Developer/Xcode/Archives/2011-12-14/MyApp 14-12-11 11.42 AM.xcarchive
Existe-t-il un moyen de contrôler où l'archive est placée, quel est son nom et comment éviter d'avoir à la recompiler? Je suppose que le meilleur résultat possible serait de générer le xcarchive à partir du DSYM et de l'APP qui sont générés lorsque vous effectuez une "construction de xcodebuild" - est-ce possible?
Ma solution actuelle consiste à renommer le dossier d'archives existant de l'utilisateur, à exécuter la génération et à faire une "recherche" pour copier les archives où je veux, puis à supprimer le dossier d'archives et à renommer l'ancien dossier tel qu'il était, avec un code comme celui-ci dans mon Ruby script de construction:
# 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')
C'est un peu hacky mais je ne vois pas de meilleure solution actuellement. Au moins, il conserve le dossier "archives" de l'utilisateur et toutes ses archives préexistantes.
--Note importante!--
Depuis, j'ai découvert que la ligne de code où je trouve l'archive et la cp dans le dossier que je veux ne copie pas correctement les liens symboliques à l'intérieur de l'archive, brisant ainsi la signature de code dans l'application. Vous voudrez remplacer cela par un "mv" ou quelque chose qui maintient des liens symboliques. À votre santé!
Xcode 5 prend désormais en charge un -archivePath
option:
xcodebuild -scheme myscheme archive -archivePath /path/to/AppName.xcarchive
Vous pouvez également désormais exporter une IPA signée à partir de l'archive que vous venez de créer:
xcodebuild -exportArchive -exportFormat IPA -exportProvisioningProfile my_profile_name -archivePath /path/to/AppName.xcarchive -exportPath /path/to/AppName.ipa
À partir de Xcode 4 Preview 5, trois variables d'environnement sont accessibles dans les post-actions de l'archive de schéma.
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.
Vous pouvez déplacer/copier l'archive ici. Je voulais avoir un peu plus de contrôle sur le processus dans un script CI, j'ai donc enregistré un fichier temporaire qui pourrait facilement être trouvé dans mon script CI qui contenait ces valeurs.
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
Ensuite, dans mon script CI, je peux exécuter ce qui suit:
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"
Je viens de résoudre celui-ci - ajoutez simplement l'argument -archivePath
à votre ligne de commande de construction xcode, étant donné la question initiale qui signifierait:
xcodebuild -scheme myscheme archive
devient ...
xcodebuild -scheme myscheme archive -archivePath Build/Archive
(Remarque: les chemins d'accès sont relatifs, je génère ma version sur $PWD/Build
)
Cela placera ensuite votre dossier .app dans:
Build/Archive.xarchive/Products/Application
Si votre cible de build contient déjà votre certificat de signature et votre profil d'approvisionnement, vous pouvez ensuite créer votre fichier IPA sans re-signer à l'aide de la commande suivante:
xcrun -v -sdk iphoneos PackageApplication -v `pwd`'/Build/Archive.xarchive/Products/Application/my.app' -o `pwd`'/myapp.ipa'
(Remarque: xcrun n'aime pas les chemins relatifs d'où le pwd
)
Le -v args dump beaucoup d'informations utiles - cette commande peut échouer à signer correctement et toujours quitter avec le code 0, soupir!
Si vous constatez que vous ne pouvez pas exécuter le .ipa intégré, il s'agit probablement d'un problème de signature que vous pouvez vérifier à l'aide de:
codesign --verify -vvvv myapp.app
S'il est signé correctement et non altéré, la sortie aura ceci dans:
myapp.app: valid on disk
myapp.app: satisfies its Designated Requirement
Sinon, vous verrez quelque chose de similaire à ceci:
Codesign check fails : /blahpath/myapp.app: a sealed resource is missing or invalid
file modified: /blahpath/ls-ios-develop.app/Assets.car
... ce qui signifie généralement que vous essayez d'utiliser un répertoire de sortie intermédiaire plutôt que l'archive appropriée.
Voici un peu de bash que j'ai trouvé pour notre système CI Jenkins. Ces commandes doivent être exécutées dans un script immédiatement après le xcodebuild archive
la commande se termine.
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}"
Le BUILD_DIR est utilisé pour collecter des artefacts afin qu'il soit facile de les archiver à partir de Jenkins avec un glob tel que build/*.ipa,build/*.Zip
Sur Xcode 4.6, il est possible de spécifier une action post-build pour le schéma à compiler dans un xcarchive:
echo "ARCHIVE_PATH=\"$ARCHIVE_PATH\"" > $PROJECT_DIR/archive_paths.sh
Un script de construction peut être utilisé pour vérifier si $ ARCHIVE_PATH est défini après l'exécution de xcodebuild et si c'est le cas, la sortie xcarchive peut être déplacée dans un dossier désigné.
Cette méthode n'est pas très maintenable si les cibles dans le projet sont un grand nombre, car pour chacune, il est nécessaire de marquer le schéma correspondant comme "partagé" et d'ajouter l'action post-construction.
Pour résoudre ce problème, j'ai créé un script de génération qui génère le chemin d'archivage par programme en extrayant la dernière génération qui correspond au nom cible du jour en cours. Cette méthode fonctionne de manière fiable tant qu'il n'y a pas plusieurs versions avec le même nom cible en cours d'exécution sur la machine (cela peut être un problème dans les environnements de production où plusieurs versions simultanées sont exécutées).
#!/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"
Le code source complet avec un exemple de projet Xcode peut être trouvé ici:
Similaire aux autres, mais peut-être un peu plus simple puisque j'essaie d'enregistrer le .xcarchive
emplacement du fichier. (Je ne déplace pas non plus le dossier d'archives, donc cela fonctionnera mieux si vous faites plusieurs builds en même temps.)
Mon script de génération d'appelant génère un nouveau fichier temporaire et définit son chemin vers une variable d'environnement nommée XCARCHIVE_PATH_TMPFILE
. Cette variable d'environnement est disponible dans le script Shell post-action Archive de mon schéma, qui écrit ensuite le chemin d'accès du fichier .xcarchive à ce fichier. Le script de génération qui peut ensuite lire ce fichier après avoir appelé xcodebuild archive
.
script Shell post-action
echo $ARCHIVE_PATH > "$XCARCHIVE_PATH_TMPFILE"