Archiving iOS projects from command line using xcodebuild and xcrun
In one of my recent projects, I need to provided 16 different builds from a single iOS application code base. Although the different builds targeting different customers, having different application names, server addresses and build configurations have already been configured as different schemes in xCode, having to perform these builds manually using xCode is still time consuming and error-prone.
Generating IPA file from command line
Fortunately, cleaning, building, archiving and exporting an iOS project to IPA file can be done easily using the following xcodebuild commands (assuming xCode 5 is installed):
xcodebuild -project Reporter.xcodeproj -scheme “InternalTest” -configuration “Release Adhoc” clean
xcodebuild -archivePath “InternalTestRelease.xcarchive” -project Reporter.xcodeproj -sdk iphoneos -scheme “InternalTest” -configuration “Release Adhoc” archive
xcodebuild -exportArchive -exportFormat IPA -exportProvisioningProfile “My Release Profile” -archivePath “InternalTestRelease.xcarchive” -exportPath “InternalTestRelease.ipa”
After cleaning, the project is archived to a .xcarchive file, exported to an IPA file and signed using the given provisioning profile, ready to be distributed for internal testing.
Bug with xcodebuild
This seems easy. However, as with xCode (or many other Apple developer tools for that matter), xcodebuild comes with bugs, and sometimes hard-to-find ones. After a few round of testing, I realized that the generated signed IPA file would sometimes fail to be installed on the device. When attempting to install the IPA file, the iPhone configuration utility says “The executable was signed with invalid entitlements” with the following detailed messages in the console log:
Admin-iPhone installd[31]
Admin-iPhone installd[31]
Admin-iPhone installd[31]
Basically the IPA file was not signed properly and could not be installed. What made this very strange is that although all the provisioning profiles and signing identities were configured correctly, the signing issue still occurred intermittently – one attempt would produce an incorrectly signed IPA file while the next attempt would produce a correctly signed IPA file that can be installed on the device. Frustrated, I decided to investigate and found out the root cause of the issue.
First I checked if the correct provisioning profile was used to sign the IPA file by extracting the file as if it was a ZIP file and searching the .app folder for a file called embedded.mobileprovision. It was indeed the correct signing profile – even when the generated IPA file was corrupted.
Secondly, I compared the extracted files from the correctly signed IPA package and the corrupted IPA package to see the differences. The digital signatures for the signed components can be found in a file named CodeResources, an XML-formatted file located in the .app folder, and they look like below:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>files</key>
<dict>
<key>32x32_arrow.png</key>
<data>
P/XDWeYKpPpwSzLCFxSXV23inIQ=
</data>
<key>logo.jpg</key>
<data>
L+8Od1POJVPM7BFJPofhiR2rDso=
</data>
......
</dict>
</dict>
</plist>
After comparing the CodeResources file of the correct and corrupted IPA packages, I realized that most of the digital signatures were actually the same, with the only difference being the signature for the application executable file, e.g. TestApp. I checked the MD5 hashes of these two files and they were indeed different, explaining the difference in the signatures. This however did not yet explain the reason for the corrupted package, until I checked the MD5 hash of all the extracted files from both packages and realized that, although the generated core data model files (.mom and .omo) from the two packages were different, they both had the same digital signatures in CodeSignatures! This explained why the corrupted package failed the integrity check and could not be installed on the device. To verify this theory, I tried to overwrite the .omo and .mom files in the corrupted package with those from the correct IPA package while keeping the rest of the files the same. The modified IPA file could indeed be installed and run on the device. This showed that the incorrect digital signatures were indeed the issue.
But why is there such an issue? During my testing the corruption seemed to happen more often if xcodebuild is run repeatedly from command line to build one project after another. It does not happen that often if xcodebuild is run once in a while, or with sufficient delay between executions. Sounds kind of some caching issue, causing the program to use back the old digital signatures. The exact answer, of course, is only known by Apple.
The solution: xcrun
To fix this problem, we need to use xcrun, another tool with similar commands to build and export the project to an IPA file:
xcodebuild -project Reporter.xcodeproj -scheme “InternalTest” -configuration “Release Adhoc” clean
xcodebuild -project Reporter.xcodeproj -sdk iphoneos -scheme “InternalTest” -configuration “Release Adhoc”
xcrun -sdk iphoneos PackageApplication -v “Internaltest/TestApp.app” -o “InternalTestRelease.ipa” –sign “iPhone Distribution: My Company Pte Ltd (XCDEFV)”
A minor inconvenience is that xcrun requires the exact provisioning identity, e.g iPhone Distribution: My Company Pte Ltd (XCDEFV)) and the full path to the application to be signed. The project would therefore need to be built first using xcodebuild, with the build output path specified in the “Per-configuration Build Products Path” settings and passed to xcrun via the -v parameter.
However, at least with xcrun, I encountered no signing issues after repeated testings, and the generated IPA packages can always be installed on the device successfully.
Thanks for posting this info. I spent many frustrating hours before finding it.
Holy smokes! I think you just saved me hours of agony. Thank you for describing this issue.
Is there a way to get the provisioning identity name based off a provisioning profile? I'm in this same scenario except I have multiple signing keys
Hi,
Although the .mobileprovision profile file is a binary file, it contains readable XML text which describes the profile and determines certain properties for the plist file of the application being installed. To get the provisioning identity name from an adhoc release provisioning profile, open the file as text and look for the TeamIdentifier (which looks like BCD3JZ141M) and TeamName property (which looks like My Company Pte Ltd) towards the end of the readable XML part of the file, indicated by the < /plist > closing tag. From this you can form the signing identity name, something like "My Company Pte Ltd (BCD3JZ141M)". This process can be automated, for example, by usage of 'sed' from a bash shell script.
I do not know of a way to get the signing identity of a debug profile (e.g. something like iPhone Developer: John Smith…) just by parsing that file – it contains no such information in clear text. It may be embedded in the DeveloperCertificates data section if it's indeed contained in the profile file. However I believe there is no need to know this as most of the time building and archiving the IPA file should be done with an adhoc release profile, and not with a debug profile.
great awesome. Here's a bash script I made if anyone reading this needed it:
ORIGINAL_PROVISION="./appname.mobileprovision"
security cms -D -i $ORIGINAL_PROVISION > tmp.plist
TMP_FILE="./tmp.plist"
result=()
for var in TeamName TeamIdentifier ; do
val=$( /usr/libexec/PlistBuddy -c "Print $var" "$TMP_FILE" )
declare -x $var="$val"
for name in ${val[@]} ; do
if [[ "${name}" != "{" && "${name}" != "}" && "${name}" != "Array" ]] ; then
result+=("${name}")
fi
done
done
TEAM_NAME="${result[0]}"
TEAM_IDENTIFIER="${result[1]}"
echo $TEAM_NAME
echo $TEAM_IDENTIFIER
Great, thank you for sharing your Bash script to get the signing identity name too I believe it will help others with similar problem.