How do I deploy automated builds to TestFlight from an Xcode Bot? - ios

I spent a good amount of time formatting the mentioned blog with code, screenshots, and etc. that is too much effort to duplicate here on Stack Overflow. That said I figured the community would want some help in this arena (I struggled for a long time figuring it all out), so I posted this question and respective answer. If you still think that the intent of this post is nefarious, please comment as such and I'll delete it!
The question is: how do I configure my fancy new Xcode server with Bots to continuously integrate and send completed builds to my testers via test flight? To me, this seems like the holy grail of CI in the iOS world, so I spent a lot of time to figure it out.
The process involves some manual work that just doesn't seem to get done properly by the XCode server software in Mavericks, at least in the initial release. It took me a lot of time and even some scripting to figure it all out and make it work, and I'm happy to share the results.
For the sake of adding value to this question, I've posted the post-op script that you should run during the Archive process below. The link to my blog below provides step by step details should you need more information.
#!/bin/bash
#
# (Above line comes out when placing in Xcode scheme)
#
# Valid and working as of 10/29/2013
# Xcode 5.0.1, XCode Server
#
API_TOKEN="<Your TesFlight API Token>"
TEAM_TOKEN="<Your TestFlight Team Token>"
DISTRIBUTION_LISTS="<Comma separated TestFlight Distribution List Names for auto deploy>"
PROVISIONING_PROFILE="/Library/Server/Xcode/Data/ProvisioningProfiles/<your file name here>.mobileprovision"
#EXAMPLE:"/Library/Server/Xcode/Data/ProvisioningProfiles/DocLink_InHouse_2013.mobileprovision"
SIGNING_IDENTITY="<your provisioning profile name here>"
#EXAMPLE:"iPhone Distribution: Unwired Revolution, LLC."
# DO NOT EDIT BELOW HERE!
########################################
DSYM="/tmp/Archive.xcarchive/dSYMs/${PRODUCT_NAME}.app.dSYM"
IPA="/tmp/${PRODUCT_NAME}.ipa"
APP="/tmp/Archive.xcarchive/Products/Applications/${PRODUCT_NAME}.app"
# Clear out any old copies of the Archive
echo "Removing old Archive files from /tmp...";
/bin/rm -rf /tmp/Archive.xcarchive*
#Copy over the latest build the bot just created
echo "Copying latest Archive to /tmp/...";
LATESTBUILD=$(ls -1rt /Library/Server/Xcode/Data/BotRuns | tail -1)
/bin/cp -Rp "/Library/Server/Xcode/Data/BotRuns/${LATESTBUILD}/output/Archive.xcarchive" "/tmp/"
echo "Creating .ipa for ${PRODUCT_NAME}"
/bin/rm "${IPA}"
/usr/bin/xcrun -sdk iphoneos PackageApplication -v "${APP}" -o "${IPA}" --sign "${SIGNING_IDENTITY}" --embed "${PROVISIONING_PROFILE}"
echo "Done with IPA creation."
echo "Zipping .dSYM for ${PRODUCT_NAME}"
/bin/rm "${DSYM}.zip"
/usr/bin/zip -r "${DSYM}.zip" "${DSYM}"
echo "Created .dSYM for ${PRODUCT_NAME}"
echo "*** Uploading ${PRODUCT_NAME} to TestFlight ***"
/usr/bin/curl "http://testflightapp.com/api/builds.json" \
-F file=#"${IPA}" \
-F dsym=#"${DSYM}.zip" \
-F api_token="${API_TOKEN}" \
-F team_token="${TEAM_TOKEN}" \
-F distribution_lists="${DISTRIBUTION_LISTS}" \
-F notes="Build uploaded automatically from Xcode Server Bot."
echo "TestFlight upload finished!"
I hope all the time I spent on it will save the community collectively a lot more of theirs!
Here is the link: http://matt.vlasach.com/xcode-bots-hosted-git-repositories-and-automated-testflight-builds/

Here is a link to a post that outlines how to create an Xcode bot, connected to a 3rd party git repository, with automated deployment of builds to TestFlight:
http://matt.vlasach.com/xcode-bots-hosted-git-repositories-and-automated-testflight-builds/
Hope it helps! Please sound off with your comments or feedback.

Xcode 12
Use xcodebuild command-line tool
1. Create a Distribution Certificate
2. Create a Distribution Profile
3. Add "Post-Integration Script"
[Test Flight] Script
# Remove & Copy assets
rm -r ${XCS_SOURCE_DIR}/Archive
cp -R ${XCS_OUTPUT_DIR}/ ${XCS_SOURCE_DIR}/Archive
# Upload to TestFlight
IFS=$'\n'
ARCHIVE_PATH=$(find ${XCS_SOURCE_DIR}${PRODUCT_NAME} -name "BeBe.xcarchive")
IFS=$' '
IFS=$'\n'
PLIST_PATH=$(find ${XCS_SOURCE_DIR}${PRODUCT_NAME} -name "exportOptions.plist")
IFS=$' '
xcodebuild -exportArchive -archivePath $ARCHIVE_PATH -exportOptionsPlist $PLIST_PATH -exportPath $ARCHIVE_PATH
4. Add your "exportOption.plist" file in your project folder.
To export an ipa file, you need the"exportOption.plist" file.
exportOption.plist
<?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>method</key>
<string>app-store</string>
<key>destination</key>
<string>upload</string>
</dict>
</plist>
5. Run your Bot
6. Check your build

Related

Version and Build numbers not showing up while archiving the build in Xcode

I am archiving the build to be submitted to the App Store, but version and build not showing up. I have attached the screenshot for the same. After exporting the .ipa when I am submitting the app via Application Loader I am getting this error. "The Info.plist in the package must contain the CFBundleShortVersionString key." I have already added these details to info.plist file as below.
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1.1</string>
The info.plist file is also in the root folder. I have two separate targets for iPhone and iPad applications.
I found one script as below.
git=$(sh /etc/profile; which git)
git_release_version=$("$git" describe --tags --always --abbrev=0)
number_of_commits=$("$git" rev-list master | wc -l | tr -d ' ')
target_plist="$TARGET_BUILD_DIR/$INFOPLIST_PATH"
dsym_plist="$DWARF_DSYM_FOLDER_PATH/$DWARF_DSYM_FILE_NAME/Contents/Info
.plist"
for plist in "$target_plist" "$dsym_plist"; do
if [ -f "$plist" ]; then
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $number_of_commits"
"$plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString
${git_release_version#*v}" "$plist"
fi
done
I was able to solve the issue after removing this script. This script is for auto updating of version and build number. I do not know why this script was causing the issue.

MultiBranding the same source code without using xcode

I have an app and I’d like to brand it as 3 different apps with different UI.
I don’t want to do using Xcode directly.
So far I found so many solutions that says create different targets and drag and drop the image like that
But I intend to do this branding work outside of Xcode


Help me out !
Now we can build the code without opening the Xcode
Branding
(UI,Build settings and functional)
UI
App icon & other icons
iTunes Artwork
Build settings
App Name
Bundle Identifier
Provisioning profile
Code signing identity
Functional
Brand specific URLs(login,logout,resource-fetch etc...)
Using Terminal
Branding.sh
#Author: Durai Amuthan(h.duraiamuthan#gmail.com)
#This is to achieve multiple branding of an iOS app by configuring the variables below
#************ Configuring the brand starts ************
#Directory path where .xcworkspace or .xcodeproj exists
PathOfProjectDirectory=/Users/Shared/Jenkins/Documents/JenkinsTestNuu/New/
#Path where info.plist exists
PathOfInfoPlist=$PathOfProjectDirectory/XxYyZz
#Path to icons where new iTunesArtwork and application icon exixts
#Note: Make sure proper naming conventions of file has been followed
PathOfNewIcons=/Users/Shared/Jenkins/Documents/icons-two
#Path to asset resource where you have kept your application icon.
PathOfAppIconSet=$PathOfProjectDirectory/XxYyZz/Icon.xcassets/AppIcon.appiconset
#Path where do you want the .app file has to be kept
PathToApp=/Users/Shared/Jenkins/Documents/JenkinsTestNuu/app
#Path where do you want the .ipa file has to kept
PathToIpa=/Users/Shared/Jenkins/Documents/JenkinsTestNuu/ipa
#Cocoapods project or project that involves more than one modules are scheme based
isWorkspaceBased=true
#Path of the Project (.xcodeproj) - applicable for workspace(.xcworkspace) based project
PathofProjectFile=$PathOfProjectDirectory/XxYyZz.xcodeproj
#Path of the Workspace (.xcworkspace)
PathofWorkspaceFile=$PathOfProjectDirectory/XxYyZz.xcworkspace
#Name of the target - applicable only for non-workspace(.xcodeproj) based projects
Target=XxYyZz
#Scheme of the iOS app
Scheme=XxYyZz
#To ascertain Cocoapods has been used or not
isCocoaPodsBased=true
#Configuration of the app (Debug -(Development) or Release(Adhoc or Distribution))
Config=Release
#For giving access to signing idetity found in KeyChain
LoginKeychainPath=/Users/Shared/Jenkins/Library/Keychains/login.keychain
LoginKeyChainPassword=xxyyzz
#Name of the code signing identity.You can find the name in Keychain or xcode build setting
CodeSigningIdentity='iPhone Distribution: Xx Yy Zz Limited (3Z5MHUYJ2L)'
#Path of the provisioning profile
PathToMobileProvision=/Users/Shared/Jenkins/Desktop/BrandingTest.mobileprovision
#UUID value found inside Provisioning profile has to be given
#Do not forget to install provisiong profile in the system
ProvisioningProfileIdentity=6e6506e9-8233-4886-9084-zf21e8f8bbae
#Bundle identifier of the app
BundleIdentifier=com.xxyy.zz
#AppVersion of the app
AppVersion=2.2.2
#App Name
Appname=Two
#************ Configuring the brand ends ************
#** Creatting the build based on configuration starts **
cd $PathOfInfoPlist
echo "****************** Setting App Name ******************"
/usr/libexec/PlistBuddy -c "Set :CFBundleName $Appname" info.plist
/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName $Appname" info.plist
echo "app name has been set as $Appname"
cd $PathOfProjectDirectory
echo "****************** Setting AppVersion ******************"
/usr/bin/agvtool new-marketing-AppVersion $AppVersion
/usr/bin/agvtool new-AppVersion -all $AppVersion
echo "****************** Changing app icons & iTunes Artwork ******************"
cp -R $PathOfNewIcons/*.png $PathOfAppIconSet
echo "App icons has been changed at $PathOfNewIcons"
cp -R $PathOfNewIcons/iTunesArtwork#2x $PathOfProjectDirectory/XxYyZz
cp -R $PathOfNewIcons/iTunesArtwork $PathOfProjectDirectory/XxYyZz
echo "iTunesArtwork has been changed at $PathOfProjectDirectory"
#Unlock login keychain
security unlock-keychain -p $LoginKeyChainPassword $LoginKeychainPath
if $isCocoaPodsBased == 'true'
then
echo "****************** Installing Cocoapods **********************"
/usr/local/bin/pod install
echo "Cocoapods has been installed"
fi
echo "****************** Creating .app ******************"
if $isWorkspaceBased == 'true'
then
/usr/bin/xcodebuild -scheme $Scheme -workspace $PathofWorkspaceFile -configuration $Config clean build CONFIGURATION_BUILD_DIR=$PathToApp "CODE_SIGN_IDENTITY=$CodeSigningIdentity" "PRODUCT_BUNDLE_IDENTIFIER=$BundleIdentifier" "PROVISIONING_PROFILE=$ProvisioningProfileIdentity"
else
/usr/bin/xcodebuild -target $Target -project $PathofProjectFile -configuration $Config clean build CONFIGURATION_BUILD_DIR=$PathToApp "CODE_SIGN_IDENTITY=$CodeSigningIdentity" "PRODUCT_BUNDLE_IDENTIFIER=$BundleIdentifier" "PROVISIONING_PROFILE=$ProvisioningProfileIdentity"
fi
echo ".app has been generated at $PathToApp"
echo "****************** Creating .ipa *******************"
/usr/bin/xcrun -sdk iphoneos PackageApplication -v $PathToApp/XxYyZz.app -o $PathToIpa/$Appname.ipa --embed $PathToMobileProvision --sign "$CodeSigningIdentity"
echo "$Appname.ipa has been generated at $PathToIpa"
#** Creatting the build based on configuration ends **
The file is self-descriptive you can understand easily.
Just configure the values of variable in the file and call it like below
sh Branding.sh
FYI:
If you want some other icons also to be changed besides App Icon and iTunesArtwork
use cp command e.g
cp path/to/source path/to/destination
To know more info do cp man
With the above file you can do Branding for UI and Build Settings.
For functional branding , you have to keep
Brand specific URLs
Other inputs respective to a brand
in a separate plist file so that this things also can be changed according to respective brand while building the app
In coding side you can customise your application to read the values from plist like this
Function defintion:
func getPlistFile()->Dictionary<String,AnyObject>? {
var dictPlistFile:Dictionary<String,AnyObject>?
if let path = NSBundle.mainBundle().pathForResource("plistfile", ofType: "plist") {
if let dictValue = NSDictionary(contentsOfFile: path) as? Dictionary<String, AnyObject> {
dictPlistFile=dictValue
}
}
return dictPlistFile
}
Function calling:
var Value=getPlistFile()?["Key"]
You can change the values of the key according to brand using the PlistBuddy while building the app
Here is the syntax
/usr/libexec/PlistBuddy -c "Set :Key Value" plistfile.plist
Using Jenkins
We can effectively re-use the shell script here in jenkins
1.You have to parameterise all the variables in shell script in jenkins using Add Parameter... like in the below screenshot I have done for one variable like that you have to do it for all others
2.Choose Execute shell in the Build Step
3.Copy the script that is there in between Creating the build based on configuration starts and Creating the build based on configuration ends and paste it in Execute Shell
Note:
Resource Rules
There is a known bug Regarding ResourceRules of Xcode in some versions while building and packaging the app through non-xcode interface.
So it has to be run once to deactivate a validation for resource rules path in xcode.The resource rules path is deprecated feature and apple doesn't accept apps that comes with resource rules but if we build an app without using Xcode the validation error saying resource rules has not been found will arise to counter that we have to run the script only once.
xcode_fix_PackageApplicationResourceRules.sh
#!/bin/sh
# A script to patch xcrun PackageInstallation so that it doesn't use the deprecated --resource-rules
# See "Do not use the --resource-rules flag or ResourceRules.plist. They have been obsoleted and will be rejected."
# under https://developer.apple.com/library/mac/technotes/tn2206/_index.html#//apple_ref/doc/uid/DTS40007919-CH1-TNTAG205
# Reported as Apple bug #19384243
#
# should be run as a user who can modify the PackageApplication file
xcodedir=$1
function usage {
# FIXME we cannot parse args properly because 2 are optional...
echo "USAGE: $0 xcodedir"
echo " xcodedir: an install dir like /Application/Xcode6.1.1.app"
}
if [[ $# -ne 1 ]]; then
echo "ERROR: invalid number of arguments"
usage
exit -1
fi
pi="$xcodedir/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/PackageApplication"
piorig="$piOrig"
if [[ ! -f "$pi" ]]; then
echo "$pi file not found. Invalid argument ?"
usage
exit -1
fi
grep resource-rules "$pi"
if [[ $? -ne 0 ]]; then
echo "PackageApplication doesn't use resource-rules. Skipping"
exit 0
fi
if [[ -f "$piorig" ]]; then
echo "Backup file $piorig already exist. Aborting"
exit -1
fi
perl -p -i'Orig' -e 'BEGIN{undef $/;} s/,resource-rules(.*sign}).*ResourceRules.plist"/$1/smg' "$pi"
echo $?
Unlock keychain
Whenever you run Branding.sh in terminal it will prompt username and password as its accessing system keychain
Whenever you run the Job in jenkins you will get "User Interaction Is Not Allowed" error
so to tackle this you have to follow the below steps
Open the Keychain Access
Right click on the private key
Select "Get Info"
Select "Access Control" tab
Click "Allow all applications to access this item"
Click "Save Changes"
Enter your password
Provisioning profile
if you ever get "No Matching Provisioning Profile Found" make sure you have double clicked and installed it via Xcode.
The moment you install you'll see UUID.mobileprovision in ~/Library/MobileDevice/Provisioning Profiles/
This UUID is the value inside mobile provision that means the provisioning profile is installed.
I hope this helps you

xcodebuild is not compiling the project unless it is opened using Xcode atleast only once for cocoapods integrated project

I have a project with cocoa pods.
Here is the command that I use to build the project.
/usr/bin/xcodebuild -scheme Jenkins -workspace
/Users/Shared/Jenkins/Documents/Jenkins/Jenkins2/Jenkins.xcworkspace
-configuration Release clean build CONFIGURATION_BUILD_DIR=/Users/Shared/Jenkins/Documents/JenkinsTestNuu/app
'CODE_SIGN_IDENTITY=iPhone Distribution: XXXX yay (3G5FKTZJ2K)'
PRODUCT_BUNDLE_IDENTIFIER=com.XXXX.two
PROVISIONING_PROFILE=6e6506e9-8233-4886-9084-ce21e8f8bbae
The above script works good only if the project has been opened using Xcode atleast once after that Xcode can be closed no issues.
If the project has not been opened then If I run the script
the wheel is spinning below without any progress forever for example in the below image
if its opened once instead of the spinning wheel below texts would be shown below
=== CLEAN TARGET XWebView OF PROJECT Pods WITH CONFIGURATION Release ===
Check dependencies
Clean.Remove clean
/Users/Shared/Jenkins/Documents/JenkinsTestNuu/app/XWebView.framework.dSYM
builtin-rm -rf /Users/Shared/Jenkins/Documents/JenkinsTestNuu/app/XWebView.framework.dSYM
Clean.Remove clean
/Users/Shared/Jenkins/Library/Developer/Xcode/DerivedData/appanme-bqjwbjcqisegldeaonpytprisnig/Build/Intermediates/Pods.build/Release-iphoneos/XWebView.build
builtin-rm -rf /Users/Shared/Jenkins/Library/Developer/Xcode/DerivedData/appanme-bqjwbjcqisegldeaonpytprisnig/Build/Intermediates/Pods.build/Release-iphoneos/XWebView.build
Clean.Remove clean
/Users/Shared/Jenkins/Documents/JenkinsTestNuu/app/XWebView.framework
builtin-rm -rf /Users/Shared/Jenkins/Documents/JenkinsTestNuu/app/XWebView.framework
=== CLEAN TARGET Pods OF PROJECT Pods WITH CONFIGURATION Release ===
Check dependencies
etc...
The problem is not observed in any non-cocoapods project.
So what would be the cause and how to solve it ?
Why it happens ?
A quick diffMerge tool analysis between a project opened by Xcode Vs a same project not opened by Xcode so far
From this we can see lots of scheme related files being created once opened by Xcode inside .xcodeproj
so xcodebuild creates .app by using the scheme related meta data but as there is no scheme to build against its failing
How to solve this ?
As there is lack of meta data it couldn't build and so it requires Xcode to be opened so that the files get automactically created by Xcode and so there will be some schemes to build against.
But when you open the Xcode this Scheme related files gets created under xcuserdata which is for particular user. i.e each user gets their own file that saves state folders opened,last file opened etc...
Its not wise idea to keep this file with us.
The problem can be solved by checking the Shared Check box under Manage Schemes
This moves schemes out from under your individual xcuserdata into a shared folder that can be committed via source control and you can safely ignore xcuserdata and keep the shared folder in source control
Now we can build the code without opening the Xcode even for only one time.
Branding
(UI,Build settings and functional)
UI
App icon & other icons
iTunes Artwork
Build settings
App Name
Bundle Identifier
Provisioning profile
Code signing identity
Functional
Brand specific URLs(login,logout,resource-fetch etc...)
Using Terminal
Branding.sh
#Author: Durai Amuthan(h.duraiamuthan#gmail.com)
#This is to achieve multiple branding of an iOS app by configuring the variables below
#************ Configuring the brand starts ************
#Directory path where .xcworkspace or .xcodeproj exists
PathOfProjectDirectory=/Users/Shared/Jenkins/Documents/JenkinsTestNuu/New/
#Path where info.plist exists
PathOfInfoPlist=$PathOfProjectDirectory/XxYyZz
#Path to icons where new iTunesArtwork and application icon exixts
#Note: Make sure proper naming conventions of file has been followed
PathOfNewIcons=/Users/Shared/Jenkins/Documents/icons-two
#Path to asset resource where you have kept your application icon.
PathOfAppIconSet=$PathOfProjectDirectory/XxYyZz/Icon.xcassets/AppIcon.appiconset
#Path where do you want the .app file has to be kept
PathToApp=/Users/Shared/Jenkins/Documents/JenkinsTestNuu/app
#Path where do you want the .ipa file has to kept
PathToIpa=/Users/Shared/Jenkins/Documents/JenkinsTestNuu/ipa
#Cocoapods project or project that involves more than one modules are scheme based
isWorkspaceBased=true
#Path of the Project (.xcodeproj) - applicable for workspace(.xcworkspace) based project
PathofProjectFile=$PathOfProjectDirectory/XxYyZz.xcodeproj
#Path of the Workspace (.xcworkspace)
PathofWorkspaceFile=$PathOfProjectDirectory/XxYyZz.xcworkspace
#Name of the target - applicable only for non-workspace(.xcodeproj) based projects
Target=XxYyZz
#Scheme of the iOS app
Scheme=XxYyZz
#To ascertain Cocoapods has been used or not
isCocoaPodsBased=true
#Configuration of the app (Debug -(Development) or Release(Adhoc or Distribution))
Config=Release
#For giving access to signing idetity found in KeyChain
LoginKeychainPath=/Users/Shared/Jenkins/Library/Keychains/login.keychain
LoginKeyChainPassword=xxyyzz
#Name of the code signing identity.You can find the name in Keychain or xcode build setting
CodeSigningIdentity='iPhone Distribution: Xx Yy Zz Limited (3Z5MHUYJ2L)'
#Path of the provisioning profile
PathToMobileProvision=/Users/Shared/Jenkins/Desktop/BrandingTest.mobileprovision
#UUID value found inside Provisioning profile has to be given
#Do not forget to install provisiong profile in the system
ProvisioningProfileIdentity=6e6506e9-8233-4886-9084-zf21e8f8bbae
#Bundle identifier of the app
BundleIdentifier=com.xxyy.zz
#AppVersion of the app
AppVersion=2.2.2
#App Name
Appname=Two
#************ Configuring the brand ends ************
#** Creatting the build based on configuration starts **
cd $PathOfInfoPlist
echo "****************** Setting App Name ******************"
/usr/libexec/PlistBuddy -c "Set :CFBundleName $Appname" info.plist
/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName $Appname" info.plist
echo "app name has been set as $Appname"
cd $PathOfProjectDirectory
echo "****************** Setting AppVersion ******************"
/usr/bin/agvtool new-marketing-AppVersion $AppVersion
/usr/bin/agvtool new-AppVersion -all $AppVersion
echo "****************** Changing app icons & iTunes Artwork ******************"
cp -R $PathOfNewIcons/*.png $PathOfAppIconSet
echo "App icons has been changed at $PathOfNewIcons"
cp -R $PathOfNewIcons/iTunesArtwork#2x $PathOfProjectDirectory/XxYyZz
cp -R $PathOfNewIcons/iTunesArtwork $PathOfProjectDirectory/XxYyZz
echo "iTunesArtwork has been changed at $PathOfProjectDirectory"
#Unlock login keychain
security unlock-keychain -p $LoginKeyChainPassword $LoginKeychainPath
if $isCocoaPodsBased == 'true'
then
echo "****************** Installing Cocoapods **********************"
/usr/local/bin/pod install
echo "Cocoapods has been installed"
fi
echo "****************** Creating .app ******************"
if $isWorkspaceBased == 'true'
then
/usr/bin/xcodebuild -scheme $Scheme -workspace $PathofWorkspaceFile -configuration $Config clean build CONFIGURATION_BUILD_DIR=$PathToApp "CODE_SIGN_IDENTITY=$CodeSigningIdentity" "PRODUCT_BUNDLE_IDENTIFIER=$BundleIdentifier" "PROVISIONING_PROFILE=$ProvisioningProfileIdentity"
else
/usr/bin/xcodebuild -target $Target -project $PathofProjectFile -configuration $Config clean build CONFIGURATION_BUILD_DIR=$PathToApp "CODE_SIGN_IDENTITY=$CodeSigningIdentity" "PRODUCT_BUNDLE_IDENTIFIER=$BundleIdentifier" "PROVISIONING_PROFILE=$ProvisioningProfileIdentity"
fi
echo ".app has been generated at $PathToApp"
echo "****************** Creating .ipa *******************"
/usr/bin/xcrun -sdk iphoneos PackageApplication -v $PathToApp/XxYyZz.app -o $PathToIpa/$Appname.ipa --embed $PathToMobileProvision --sign "$CodeSigningIdentity"
echo "$Appname.ipa has been generated at $PathToIpa"
#** Creatting the build based on configuration ends **
The file is self-descriptive you can understand easily.
Just configure the values of variable in the file and call it like below
sh Branding.sh
FYI:
If you want some other icons also to be changed besides App Icon and iTunesArtwork
use cp command e.g
cp path/to/source path/to/destination
To know more info do cp man
With the above file you can do Branding for UI and Build Settings.
For functional branding , you have to keep
Brand specific URLs
Other inputs respective to a brand
in a separate plist file so that this things also can be changed according to respective brand while building the app
In coding side you can customise your application to read the values from plist like this
Function defintion:
func getPlistFile()->Dictionary<String,AnyObject>? {
var dictPlistFile:Dictionary<String,AnyObject>?
if let path = NSBundle.mainBundle().pathForResource("plistfile", ofType: "plist") {
if let dictValue = NSDictionary(contentsOfFile: path) as? Dictionary<String, AnyObject> {
dictPlistFile=dictValue
}
}
return dictPlistFile
}
Function calling:
var Value=getPlistFile()?["Key"]
You can change the values of the key according to brand using the PlistBuddy while building the app
Here is the syntax
/usr/libexec/PlistBuddy -c "Set :Key Value" plistfile.plist
Using Jenkins
We can effectively re-use the shell script here in jenkins
1.You have to parameterise all the variables in shell script in jenkins using Add Parameter... like in the below screenshot I have done for one variable like that you have to do it for all others
2.Choose Execute shell in the Build Step
3.Copy the script that is there in between Creating the build based on configuration starts and Creating the build based on configuration ends and paste it in Execute Shell
Note:
Resource Rules
There is a known bug Regarding ResourceRules of Xcode in some versions while building and packaging the app through non-xcode interface.
So it has to be run once to deactivate a validation for resource rules path in xcode.The resource rules path is deprecated feature and apple doesn't accept apps that comes with resource rules but if we build an app without using Xcode the validation error saying resource rules has not been found will arise to counter that we have to run the script only once.
xcode_fix_PackageApplicationResourceRules.sh
#!/bin/sh
# A script to patch xcrun PackageInstallation so that it doesn't use the deprecated --resource-rules
# See "Do not use the --resource-rules flag or ResourceRules.plist. They have been obsoleted and will be rejected."
# under https://developer.apple.com/library/mac/technotes/tn2206/_index.html#//apple_ref/doc/uid/DTS40007919-CH1-TNTAG205
# Reported as Apple bug #19384243
#
# should be run as a user who can modify the PackageApplication file
xcodedir=$1
function usage {
# FIXME we cannot parse args properly because 2 are optional...
echo "USAGE: $0 xcodedir"
echo " xcodedir: an install dir like /Application/Xcode6.1.1.app"
}
if [[ $# -ne 1 ]]; then
echo "ERROR: invalid number of arguments"
usage
exit -1
fi
pi="$xcodedir/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/PackageApplication"
piorig="$piOrig"
if [[ ! -f "$pi" ]]; then
echo "$pi file not found. Invalid argument ?"
usage
exit -1
fi
grep resource-rules "$pi"
if [[ $? -ne 0 ]]; then
echo "PackageApplication doesn't use resource-rules. Skipping"
exit 0
fi
if [[ -f "$piorig" ]]; then
echo "Backup file $piorig already exist. Aborting"
exit -1
fi
perl -p -i'Orig' -e 'BEGIN{undef $/;} s/,resource-rules(.*sign}).*ResourceRules.plist"/$1/smg' "$pi"
echo $?
Unlock keychain
Whenever you run Branding.sh in terminal it will prompt username and password as its accessing system keychain
Whenever you run the Job in jenkins you will get "User Interaction Is Not Allowed" error
so to tackle this you have to follow the below steps
Open the Keychain Access
Right click on the private key
Select "Get Info"
Select "Access Control" tab
Click "Allow all applications to access this item"
Click "Save Changes"
Enter your password
Provisioning profile
if you ever get "No Matching Provisioning Profile Found" make sure you have double clicked and installed it via Xcode.
The moment you install you'll see UUID.mobileprovision in ~/Library/MobileDevice/Provisioning Profiles/
This UUID is the value inside mobile provision that means the provisioning profile is installed.
I hope this helps you
You need to run pod install before building the project so that CocoaPods fetches the Pods specified in your Podfile within the Jenkins workspace.

Distributing iOS App on App Store and Enterprise

TLDR :
A. Issue in exporting app with Enterprise Cert
Error : wildcard app id cannot be used to create in house provisioning
profiles
B. Right approach to distribute app on Enterprise and AppStore
We have been distributing apps on Apple's AppStore for years, Enterprise is new addition.
– App has Watch App and supports iOS 8+.
What is done so far:
– Two different dev accounts and certificates.
– Separate provisioning profiles on each accounts
– Build Config and Scheme for Enterprise a AppStore
– using Scheme/Config to switch between settings like bundleId, etc.
– Successfully Archive Enterprise Application
I have NOT created separate info.plist or entitlements (Do I need to?)
Issue: When I try to export Enterprise Archive, I am getting error
wildcard app id cannot be used to create in house provisioning
profiles<
I do have proper provisioning profiles created. None of them are wildcard, except created by Xcode.
I have read this post, which says needs to create different targets. That is overhead of keeping both the targets in sync.
Question:
bool itIsPossible = Can this be achieved with Configuration/Schemes?
if (itIsPossible){
– What else I need to create separate entitlements etc?
}else{
– Do I have to create new target to support Enterprise App?
– Separate Target for Watch and Extension?
– What else I need to create separately Info.plist, entitlements etc?
}
Using Targets
New targets do create some overhead (new files must be added to all relevant targets). New targets allow to easily compartmentalize which file goes where, provide a platform for separate plist & config, Unit Tests, etc.
Remember that App Store executable and Enterprise executable are two different applications, with different certificates and signatures. (1)
Separate target recommendations (from an actual product)
Shared Entitlements
PROJECT > TARGETS > General > Team > pick separate teams there
< yourTarget >.xconfig (optional & handy)
.plist (most likely, but not required)(2)
(1) Same can be said about Apple Watch executables
(2) Separate plist allows for runtime magic: single code controlled by resources.
It is quite easy to do if you can create separate build script for each application. No need to have separate target.
Here is my build script:
# Created by Nguyen Tuan on 10/8/14.
#!/bin/sh
AP_NAME="$1"
echo "App name $AP_NAME"
FILE_NAME="$2"
echo "FILE_NAME $FILE_NAME"
SCHEME="$3"
echo "SCHEME $SCHEME"
PROVISIONING_NAME="$4"
echo "provisioning $PROVISIONING_NAME"
BUNDLE_ID="$5"
echo "BUNDLE_ID $BUNDLE_ID"
AP_ICON="$6"
echo "AP_ICON $AP_ICON"
PARENT_FOLDER="$7"
echo "PARENT_FOLDER $PARENT_FOLDER"
CONFIG="$8"
echo "CONFIG $CONFIG"
PROJECT_HOME_DIR="$9"
#Goto working folder
MY_PATH="`dirname \"$0\"`"
cd $MY_PATH
echo "build sh: This is the current working directory: $MY_PATH"
SCRIPT_FOLDER=$(basename "$MY_PATH")
MY_NAME=$(whoami)
echo "Script Folder $SCRIPT_FOLDER"
sudo sh sudo.sh
echo "Global PATH: \n$PATH"
#Go up to Project folder
cd ../../
rm -r -f build/$PARENT_FOLDER
PLIST=$PROJECT_HOME_DIR/Info.plist
echo "Please enter build number"
#BUILD_NUMBER=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "$PLIST")
#BUILD_NUMBER=$(expr $BUILD_NUMBER + 1)
BUILD_NUMBER=`git rev-list HEAD --count`
echo "Get provisioning file: UUID + name for $PROVISIONING_NAME"
if test -d ~/Library/MobileDevice/Provisioning\ Profiles/; then
ProfilesDir=~/Library/MobileDevice/Provisioning\ Profiles/
else
ProfilesDir=/Library/Developer/XcodeServer/ProvisioningProfiles/
fi
array=$(ls "$ProfilesDir")
provi=""
for i in $array; \
do output=$(/usr/libexec/PlistBuddy -c 'Print :Name' /dev/stdin <<< $(security cms -D -i "$ProfilesDir/${i%%/}") 2>&1); \
echo $output; \
if [ "$output" == "$PROVISIONING_NAME" ]; then provi=$(/usr/libexec/PlistBuddy -c 'Print :UUID' /dev/stdin <<< $(security cms -D -i "$ProfilesDir/${i%%/}") 2>&1); break; fi;\
done
#echo PROVISIONING_UUID=$provi >> provisioning.properties
echo "selected profile $provi"
/usr/libexec/Plistbuddy -c "Set CFBundleVersion $BUILD_NUMBER" "$PLIST"
/usr/libexec/Plistbuddy -c "Set CFBundleIdentifier $BUNDLE_ID" "$PLIST"
xcodebuild -alltargets -configuration "$CONFIG" clean
xcodebuild -scheme $SCHEME PRODUCT_BUNDLE_IDENTIFIER=$BUNDLE_ID ONLY_ACTIVE_ARCH=NO ARCHS="armv7 arm64" PROVISIONING_PROFILE=$provi PRODUCT_NAME="$AP_NAME" ASSETCATALOG_COMPILER_APPICON_NAME=$AP_ICON archive -archivePath "build/$PARENT_FOLDER/$FILE_NAME.xcarchive"
#xcodebuild -exportArchive -archivePath "build/$PARENT_FOLDER/$FILE_NAME.xcarchive" -exportPath "build/$PARENT_FOLDER"
#-exportOptionsPlist $PLIST
echo "export ipa file"
rm -r -f build/$PARENT_FOLDER/$FILE_NAME.ipa
sh $MY_PATH/create_ipa.sh build/$PARENT_FOLDER/$FILE_NAME.xcarchive build/$PARENT_FOLDER/$FILE_NAME.ipa
mv build/$PARENT_FOLDER/**You need to change this to your app name**/.ipa build/$PARENT_FOLDER/$FILE_NAME.ipa
rm -r -f $HOME/Dropbox/$FILE_NAME.ipa
cp build/$PARENT_FOLDER/$FILE_NAME.ipa $HOME/Dropbox/$FILE_NAME.ipa
rm -r -f "build/$CONFIG-iphoneos"
echo "copy xcarchive file into organizer"
sh $MY_PATH/copy_resource.sh build/$PARENT_FOLDER/$FILE_NAME.xcarchive $MY_NAME
And then create two build command, one for enterprise build and one for app store build, something like this:
AP_NAME="ABCD"
FILE_NAME="An App Name"
SCHEME="Scheme for enterprise build"
PROVISIONING_NAME="Expected provisioning profile, what is shown in XCode"
BUNDLE_ID="app bundle Id"
AP_ICON="custom icon if need?"
PARENT_FOLDER="the folder that will contains the build"
CONFIG="Release"
#Goto working folder
MY_PATH="`dirname \"$0\"`"
sh $MY_PATH/build.sh "$AP_NAME" "$FILE_NAME" "$SCHEME" "$PROVISIONING_NAME" "$BUNDLE_ID" "$AP_ICON" "$PARENT_FOLDER" "$CONFIG"
In case you need the copy_resource script:
path=$1
user=$2
echo $path
filename=$(basename "$path")
extension="${filename##*.}"
filename="${filename%.*}"
now=`date +%Y-%m-%d`
et=`date +%H:%M:%S`
PATH="/Users/$user/Library/Developer/Xcode/Archives/$now"
echo $PATH
/bin/mkdir -p $PATH
PATH=$PATH/$filename$et.$extension
/bin/mv $path $PATH
From now on, just run the command and you will see a build either in working folder or in Xcode Organizer
It seems like the provisioning profile is not set correctly.
The easiest way of doing this is to create an additional configuration.
Select the project in the navigator. Then duplicate the Release configuration and rename it to Enterprise Distribution or Enterprise Release.
Then select your target and go to Build Settings. There you can unfold the settings for Code Signing Identity, Provisioning Profile. You also need to use a different bundle identifier.
Check what configuration you use in the archive scheme too.
We had the error
Wildcard app id cannot be used to create in house provisioning
profiles
We solved it by manually adding a Distribution (In House) Provisioning Profile in the Apple Developer Portal...

Is there any automatic Testflight upload script on application archiving?

I found that Testflight is supporting application uploading through API call http://testflightapp.com/api/builds.format. It accepts application package, dsyms, application info and other.
So my question is next: Is there any automatic script for xcode which will upload build into Testflight after "archive" operation? Share the links, please.
SOLUTION IS HERE (Mac OS X 10.8):
1) Follow this manual and setup post-execution script
2) Remove Replace "echo" strings with next rule:
#!/bin/bash
#
# (Above line comes out when placing in Xcode scheme)
#
API_TOKEN="<YOUR-TESTFLIGHT-API-TOKEN>"
TEAM_TOKEN="<YOUR-TESTFLIGHT-TEAM-TOKEN>"
SIGNING_IDENTITY="iPhone Developer"
PROVISIONING_PROFILE="${HOME}/Library/MobileDevice/Provisioning Profiles/<YOUR-PROFILE-NAME>.mobileprovision"
LOG="/tmp/testflight.log"
GROWL="/usr/bin/terminal-notifier -title Xcode -message"
DATE=$( /bin/date +"%Y-%m-%d" )
ARCHIVE=$( /bin/ls -t "${HOME}/Library/Developer/Xcode/Archives/${DATE}" | /usr/bin/grep xcarchive | /usr/bin/sed -n 1p )
DSYM="${HOME}/Library/Developer/Xcode/Archives/${DATE}/${ARCHIVE}/dSYMs/${PRODUCT_NAME}.app.dSYM"
APP="${HOME}/Library/Developer/Xcode/Archives/${DATE}/${ARCHIVE}/Products/Applications/${PRODUCT_NAME}.app"
#/usr/bin/open -a /Applications/Utilities/Console.app $LOG
#echo -n "Creating .ipa for ${PRODUCT_NAME}... " > $LOG
${GROWL} "Creating .ipa for ${PRODUCT_NAME}"
/bin/rm "/tmp/${PRODUCT_NAME}.ipa"
/usr/bin/xcrun -sdk iphoneos PackageApplication -v "${APP}" -o "/tmp/${PRODUCT_NAME}.ipa" --sign "${SIGNING_IDENTITY}" --embed "${PROVISIONING_PROFILE}"
#echo "done." >> $LOG
${GROWL} "Created .ipa for ${PRODUCT_NAME}"
#echo -n "Zipping .dSYM for ${PRODUCT_NAME}..." >> $LOG
${GROWL} "Zipping .dSYM for ${PRODUCT_NAME}"
/bin/rm "/tmp/${PRODUCT_NAME}.dSYM.zip"
/usr/bin/zip -r "/tmp/${PRODUCT_NAME}.dSYM.zip" "${DSYM}"
#echo "done." >> $LOG
${GROWL} "Created .dSYM for ${PRODUCT_NAME}"
#echo -n "Uploading to TestFlight... " >> $LOG
${GROWL} "Uploading to TestFlight"
/usr/bin/curl "http://testflightapp.com/api/builds.json" \
-F file=#"/tmp/${PRODUCT_NAME}.ipa" \
-F dsym=#"/tmp/${PRODUCT_NAME}.dSYM.zip" \
-F api_token="${API_TOKEN}" \
-F team_token="${TEAM_TOKEN}" \
-F notes="Build uploaded automatically from Xcode."
#echo "done." >> $LOG
${GROWL} "Uploaded to TestFlight"
/usr/bin/open "https://testflightapp.com/dashboard/builds/"
3) Reveal provision profile in finder: go to Organazier/Devices/Provision profiles, then right mouse on your profile, and click "Reveal in finder". Copy profile name and paste to PROVISIONING_PROFILE variable instead of <YOUR-PROFILE-NAME>
4) Open terminal and install terminal-notifier:
sudo gem install terminal-notifier
5) You're ready :)
I've also created a ruby gem for this if you want to integrate this into rake tasks:
gem install testflight_upload
source on my github here
Here's a nice collection of utilities http://nomad-cli.com/
I ended up using Shenzen to automate builds and testflight deployments.
Here is one nice tutorial..may be useful for you:
http://developmentseed.org/blog/2011/sep/02/automating-development-uploads-testflight-xcode/
Shenzhen is discontinued, you can use pilot instead. It's a Ruby based tool to upload new builds and manage your beta testers. Under the hood it uses the iTunes Transporter and spaceship.

Resources