40

For the purposes of CI, I need to be able to generate an XCARCHIVE and an IPA file in our nightly build. The IPA is for our testers, to be signed with our ad-hoc keys, and the XCARCHIVE is to send to the client so that they can import it into Xcode and submit it to the app store when they're happy with it.

Generating the IPA is simple enough with a bit of googling, however how to generate the .XCARCHIVE file is what eludes me. The closest I've found is:

xcodebuild -scheme myscheme archive

However, this stores the .xcarchive in some hard-to-find folder, eg:

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

Is there some way to control where the archive is put, what its name is, and how to avoid having to re-compile it? I guess the best possible outcome would be to generate the xcarchive from the DSYM and APP that are generated when you do an 'xcodebuild build' - is this possible?

jscs
64k13 gold badges155 silver badges200 bronze badges
asked Jan 4, 2012 at 5:59

7 Answers 7

60

Xcode 5 now supports an -archivePath option:

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

You can also now export a signed IPA from the archive you just built:

xcodebuild -exportArchive -exportFormat IPA -exportProvisioningProfile my_profile_name -archivePath /path/to/AppName.xcarchive -exportPath /path/to/AppName.ipa
answered Jan 3, 2014 at 14:27
Sign up to request clarification or add additional context in comments.

4 Comments

Great! Didn't know xcodebuild has this feature. Going to be so useful.
Glad they added that argument...this is the correct answer, be sure to include the filename of the xcarchive and not just the path...Thanks!!!
Tried that in Xcode 6.1 and end up with an empty folder at -archivePath and the real archive in the default location.
-exportFormat and -exportProvisioningProfile are now deprecated in favor of -exportOptionsPlist, which annoyingly is harder to get a particular mobileprovision used with. See man xcodebuild and xcodebuild -help (note there are things covered in each that are not covered in the other.)
43

Starting with Xcode 4 Preview 5 there are three environment variables that are accessible in the scheme archive's post-actions.

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.

You could move/copy the archive in here. I wanted to have a little more control over the process in a CI script, so I saved a temporary file that could easily be sourced in my CI script that contained these values.

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

Then in my CI script I can run the following:

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"
Dov
16.3k14 gold badges80 silver badges183 bronze badges
answered Jan 26, 2012 at 20:31

6 Comments

Thank you thank you thank you! How on earth did you find this? I've combed the docs for days looking for something like this.
to build on @jemmons comment...where DID you find this? After days of combing through everyone's custom scripts I'd love to read some official documentation
This is the only mention I can find. Crappy documentation at its best. developer.apple.com/library/ios/releasenotes/developertools/…
I'm not sure why, but my "ARCHIVE_"paths are coming up empty. :( (XCode 5.0.2)
If I build within the IDE these parameters get filled. But if I build on the command line using xcodebuild then they don't get set. :/ doing [xcodebuild -sdk iphoneos -project "${PROJECT_NAME}" -scheme "myApp Adhoc" -configuration "Release (Adhoc) - QA" clean archive]
|
11

I have just solved this one - just add the argument -archivePath to your xcode build command line, given the initial question that would mean:

xcodebuild -scheme myscheme archive

becomes ...

xcodebuild -scheme myscheme archive -archivePath Build/Archive

(Note: paths are relative, I output my build to $PWD/Build)

This will then place your .app folder in:

Build/Archive.xarchive/Products/Application

If your build target already has your signing certificate and provisioning profile in it you can then create your IPA file without re-signing using the following command:

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

(Note: xcrun doesn't like relative paths hence the pwd)

The -v args dump lots of useful information - this command can fail to sign properly and still exit with code 0, sigh!

If you are finding that you can't run the built .ipa it's probably a signing issue that you can do a double check on using:

codesign --verify -vvvv myapp.app

If it's signed correctly and un-tampered with the output will have this in:

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

If not you will see something similar to this:

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

... which generally means you are trying to use an intermediate output directory rather than the proper archive.

answered Jun 19, 2014 at 15:29

1 Comment

xcrun PackageApplication is deprecated, I believe; xcodebuild -exportArchive after an xcodebuild archive seems to be preferred now (apparently was the case sometime around Xcode 6, and things started breaking in PackageApplication around Xcode 7). Sadly, the functionality sets are not identical; it seems hard to get a particular provisioning profile used if it wasn't already set up in the pbxproj when using -exportArchive.
2

My current solution is to rename the user's existing archives folder, run the build, and do a 'find' to copy the archives where i want, then delete the archives folder and rename the old folder back as it was, with code like this in my 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')

Its a bit hacky but i don't see a better solution currently. At least it preserves the user's 'archives' folder and all their pre-existing archives.

--Important note!--

I since found out that the line of code where i find the archive and cp it to the folder i want doesn't copy the symlinks inside the archive correctly, thus breaking the code signing in the app. You'll want to replace that with a 'mv' or something that maintains symlinks. Cheers!

answered Jan 5, 2012 at 3:32

1 Comment

This is a great find, but doesn't work for a CI system with lots of concurrent builds running at the same time. I'm going to try out this bit of bashery: export ARCHIVE_BASEPATH="${HOME}/Library/Developer/Xcode/Archives/$(date +%Y-%m-%d)/${SCHEME}" && \ ls -td "${ARCHIVE_BASEPATH}"* | \ head -n 1 Where SCHEME is the string name of the Xcode scheme being built (possibly containing spaces). This will still have a race condition if two different CI builds are currently building the same scheme.
2

Here's a bit of bash that I've come up with for our Jenkins CI system. These commands should be run in a script immediately after the xcodebuild archive command finishes.

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}"

The BUILD_DIR is used to collect artifacts so that it's easy to archive them from Jenkins with a glob such as build/*.ipa,build/*.zip

answered Jul 12, 2013 at 14:34

Comments

0

Similar to the others, but perhaps a little simpler since I try to record the .xcarchive file's location. (I also don't move the archives folder, so this will work better if you're doing multiple builds at the same time.)

My caller build script generates a new tempfile and sets its path to an environment variable named XCARCHIVE_PATH_TMPFILE. This environment variable is available in my scheme's Archive post-action shell script, which then that writes the .xcarchive's path to that file. The build script that can then read that file after it calls xcodebuild archive.

post-action shell script

echo $ARCHIVE_PATH > "$XCARCHIVE_PATH_TMPFILE"
answered Nov 20, 2012 at 17:26

Comments

0

On Xcode 4.6 it is possible to specify a post-build action for the scheme to be compiled into an xcarchive:

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

A build script can be used to check if $ARCHIVE_PATH is defined after running xcodebuild and if this is the case, the output xcarchive can be moved into a designated folder.

This method is not very maintainable if the targets in the project are a large number, as for each one it is necessary to tag the corresponding scheme as 'shared' and add the post-build action.

To address this problem, I have created a build script that generates the archive path programmatically by extracting the last build that matches the target name on the current day. This method works reliably as long as there aren't multiple builds with the same target name running on the machine (this may be a problem in production environments where multiple concurrent builds are run).

#!/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"

Full source code with an example Xcode project can be found here:

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

answered Jul 29, 2013 at 10:22

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.