Genera xcarchive en una carpeta específica desde la línea de comando

Para propósitos de CI, necesito poder generar un XCARCHIVE y un archivo IPA en nuestra comstackción nocturna. El IPA es para nuestros probadores, que se debe firmar con nuestras claves ad-hoc, y XCARCHIVE se envía al cliente para que pueda importarlo a Xcode y enviarlo a la tienda de aplicaciones cuando estén contentos con él.

Generar el IPA es bastante simple con un poco de Google, sin embargo, cómo se genera el archivo .XCARCHIVE es lo que me elude. Lo más cerca que he encontrado es:

xcodebuild -scheme myscheme archive 

Sin embargo, esto almacena .xcarchive en alguna carpeta difícil de encontrar, por ejemplo:

 /Users/me/Library/Developer/Xcode/Archives/2011-12-14/MyApp 14-12-11 11.42 AM.xcarchive 

¿Hay alguna manera de controlar dónde se coloca el archivo, cómo se llama y cómo evitar tener que volver a comstackrlo? Supongo que el mejor resultado posible sería generar el xcarchive de la DSYM y la APLICACIÓN que se generan cuando haces una ‘construcción xcodebuild’ – ¿es esto posible?

Xcode 5 ahora es compatible con una opción -archivePath :

 xcodebuild -scheme myscheme archive -archivePath /path/to/AppName.xcarchive 

También puede exportar una IPA firmada desde el archivo que acaba de crear:

 xcodebuild -exportArchive -exportFormat IPA -exportProvisioningProfile my_profile_name -archivePath /path/to/AppName.xcarchive -exportPath /path/to/AppName.ipa 

Comenzando con Xcode 4 Preview 5, hay tres variables de entorno a las que se puede acceder en las acciones posteriores del archivo de esquema.

 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. 

Podrías mover / copiar el archivo aquí. Quería tener un poco más de control sobre el proceso en una secuencia de comandos de CI, así que guardé un archivo temporal que podría ser fácilmente fuente en mi script de CI que contenía estos valores.

 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 

Luego en mi script de CI puedo ejecutar lo siguiente:

 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" 

Acabo de resolver este: simplemente agregue el argumento -archivePath a su línea de comando de construcción xcode, dada la pregunta inicial que significaría:

 xcodebuild -scheme myscheme archive 

se convierte …

 xcodebuild -scheme myscheme archive -archivePath Build/Archive 

(Nota: las rutas son relativas, dejo mi comstackción a $PWD/Build )

Esto colocará su carpeta .app en:

 Build/Archive.xarchive/Products/Application 

Si su objective de comstackción ya tiene su certificado de firma y su perfil de aprovisionamiento, puede crear su archivo IPA sin volver a firmar con el siguiente comando:

 xcrun -v -sdk iphoneos PackageApplication -v `pwd`'/Build/Archive.xarchive/Products/Application/my.app' -o `pwd`'/myapp.ipa' 

(Nota: xcrun no le gusta los caminos relativos, por lo tanto, el pwd )

El archivo -v arroja mucha información útil – este comando puede no firmar correctamente y todavía salir con el código 0, suspiro!

Si descubre que no puede ejecutar el .ipa incorporado, es probable que sea un problema de firma que puede hacer doble control utilizando:

 codesign --verify -vvvv myapp.app 

Si está firmado correctamente y sin alteraciones con la salida tendrá esto en:

 myapp.app: valid on disk myapp.app: satisfies its Designated Requirement 

Si no, verás algo similar a esto:

 Codesign check fails : /blahpath/myapp.app: a sealed resource is missing or invalid file modified: /blahpath/ls-ios-develop.app/Assets.car 

… lo que generalmente significa que está intentando usar un directorio de salida intermedio en lugar del archivo correcto.

Mi solución actual es cambiar el nombre de la carpeta de archivos existente del usuario, ejecutar la construcción y hacer ‘buscar’ para copiar los archivos donde yo quiera, luego eliminar la carpeta de archivos y cambiar el nombre de la carpeta anterior tal como estaba, con un código como este en mi script de construcción de 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') 

Es un poco hacky pero no veo una mejor solución actualmente. Al menos preserva la carpeta ‘archivos’ del usuario y todos sus archivos preexistentes.

–¡Nota IMPORTANTE!–

Desde que descubrí que la línea de código donde encuentro el archivo y lo copio en la carpeta que quiero no copia los enlaces simbólicos dentro del archivo correctamente, rompiendo así la firma del código en la aplicación. Querrá reemplazar eso con un ‘mv’ o algo que mantenga enlaces simbólicos. ¡Aclamaciones!

Aquí hay un poco de bash que he creado para nuestro sistema Jenkins CI. Estos comandos deben ejecutarse en un script inmediatamente después de que xcodebuild archive comando 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}" 

El BUILD_DIR se usa para recolectar artefactos para que sea fácil archivarlos desde Jenkins con un glob como build/*.ipa,build/*.zip

Similar a los demás, pero tal vez un poco más simple ya que trato de registrar la .xcarchive archivo .xcarchive . (Tampoco muevo la carpeta de archivos, así que esto funcionará mejor si estás haciendo varias comstackciones al mismo tiempo).

La secuencia de comandos de comstackción de llamada genera un nuevo archivo de temp y establece su ruta a una variable de entorno llamada XCARCHIVE_PATH_TMPFILE . Esta variable de entorno está disponible en la secuencia de comandos del shell post-acción Archive de mi esquema, que luego escribe la ruta de .xcarchive a ese archivo. La secuencia de comandos de comstackción que luego puede leer ese archivo después de llamar a xcodebuild archive .

script de shell post-acción

 echo $ARCHIVE_PATH > "$XCARCHIVE_PATH_TMPFILE" 

En Xcode 4.6, es posible especificar una acción posterior a la comstackción para que el esquema se compile en xcarchive:

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

Se puede usar un script de comstackción para verificar si $ ARCHIVE_PATH se define después de ejecutar xcodebuild y, si este es el caso, el xcarchive de salida se puede mover a una carpeta designada.

Este método no es muy fácil de mantener si los objectives en el proyecto son un gran número, ya que para cada uno es necesario etiquetar el esquema correspondiente como ‘compartido’ y agregar la acción posterior a la construcción.

Para resolver este problema, he creado un script de comstackción que genera la ruta del archivo mediante progtwigción extrayendo la última comstackción que coincide con el nombre del objective en el día actual. Este método funciona de manera confiable siempre que no haya comstackciones múltiples con el mismo nombre de destino ejecutándose en la máquina (esto puede ser un problema en entornos de producción donde se ejecutan múltiples comstackciones simultáneas).

 #!/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`  [/path/to/provisioning-profile] [ 

El código fuente completo con un ejemplo de proyecto Xcode se puede encontrar aquí:

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