MultiBranding the same source code without using xcode - ios

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

Related

Microsoft App Center build failed: Multiple commands produce 'appcenter-pre-build.sh'

Prelude
I am using App Center for CI of my application with default "Push hook", which rebuilds every time I push to the branch. I have a task to pass an API endpoint as an App Center variable, which is configured in Build Configurations.
Alongside, I have my appcenter-pre-build.sh script, which is placed in the same directory, as .xcworkspace file (as it is noted in official documentation). The script itself looks like this:
#!/usr/bin/env bash
echo "EXECUTING APPCENTER_PRE_BUILD SCRIPT"
if [ -z "$VERSION_CODE_SHIFT" ]
then
echo "You need define the VERSION_CODE_SHIFT variable in App Center"
exit
fi
if [ -z "$ENDPOINT" ]
then
echo "You need define the ENDPOINT variable in App Center"
exit
fi
PLIST_PATH="VideoApp/VideoApp/Info.plist"
VERSION_CODE=$((VERSION_CODE_SHIFT + APPCENTER_BUILD_ID))
APP_CENTER_CURRENT_PLATFORM="ios"
if [ "$APP_CENTER_CURRENT_PLATFORM" == "ios" ]
then
plutil -replace CFBundleVersion -string "$VERSION_CODE" $PLIST_PATH
echo "Updated version code in $PLIST_PATH to new value: $VERSION_CODE"
plutil -replace CFBundleShortVersionString -string "\${MARKETING_VERSION}.$VERSION_CODE" $PLIST_PATH
echo "Updated marketing version in $PLIST_PATH to new value: \${MARKETING_VERSION}.$VERSION_CODE"
plutil -replace HubEndpoint -string "$ENDPOINT" $PLIST_PATH
echo "Updated HubEndpoint in $PLIST_PATH to new value: $ENDPOINT"
fi
So, basically, I pull environment variables from App Center and modify my Info.plist and then use its properties in code to set the API endpoint. Also, as you can see, app's version is being modified in similar fashion.
The Issue
The App Center build eventually fails with the following error:
error: Multiple commands produce '/Users/runner/Library/Developer/Xcode/DerivedData/VideoApp-gpxrsqjtrulyrqamenreayeeatqj/Build/Intermediates.noindex/ArchiveIntermediates/Video-Community/InstallationBuildProductsLocation/Applications/PC365.app/appcenter-pre-build.sh':
1) Target 'Video-Community' (project 'VideoApp') has copy command from '/Users/runner/runners/2.168.2/work/1/s/VideoApp/VideoApp/appcenter-pre-build.sh' to '/Users/runner/Library/Developer/Xcode/DerivedData/VideoApp-gpxrsqjtrulyrqamenreayeeatqj/Build/Intermediates.noindex/ArchiveIntermediates/Video-Community/InstallationBuildProductsLocation/Applications/PC365.app
/appcenter-pre-build.sh'
2) Target 'Video-Community' (project 'VideoApp') has copy command from '/Users/runner/runners/2.168.2/work/1/s/VideoApp/appcenter-pre-build.sh' to '/Users/runner/Library/Developer/Xcode/DerivedData/VideoApp-gpxrsqjtrulyrqamenreayeeatqj/Build/Intermediates.noindex/ArchiveIntermediates/Video-Community/InstallationBuildProductsLocation/Applications/PC365.app
/appcenter-pre-build.sh'
warning: duplicate output file '/Users/runner/Library/Developer/Xcode/DerivedData/VideoApp-gpxrsqjtrulyrqamenreayeeatqj/Build/Intermediates.noindex/ArchiveIntermediates/Video-Community/InstallationBuildProductsLocation/Applications/PC365.app/appcenter-pre-build.sh' on task: CpResource /Users/runner/runners/2.168.2/work/1/s/VideoApp/appcenter-pre-build.sh /Users/runner/Library/Developer/Xcode/DerivedData/VideoApp-gpxrsqjtrulyrqamenreayeeatqj/Build/Intermediates.noindex/ArchiveIntermediates/Video-Community/InstallationBuildProductsLocation/Applications/PC365.app
/appcenter-pre-build.sh (in target 'Video-Community' from project 'VideoApp')
** ARCHIVE FAILED **
##[error]Error: /usr/bin/xcodebuild failed with return code: 65
So it is somehow produces duplicates of this script.
It seems very odd to me, because the script is not assigned to any target and created outside of Xcode.
I guess, the issue was about some kind of script caching. I have removed appcenter-pre-build.sh from the repository, pushed, builded in App Center (this time without errors), then added the script again, pushed and it just worked.

Unable to remote build xcode project

I have a jenkins server connecting to a remote mac mini through ssh to execute a shell script that has to build an IPA from a unity project.
When the shell script is executed locally on the mac mini everything goes fine. But when the shell script is ran from jenkins (with the exact same parameter and the same user) it fails codesigning the archive.
I will share with you the obfuscated shell script as well as the build log.
Thank you for your help in advance.
The shell script :
#!/bin/bash
# Consider directory paths initialized in parameter here
#
#
# Consider git cleaning / fetching commit here
#
#
# Consider environment / version and build name controls here
#
#
# Start Unity Build :
/Applications/Unity2017.4.10f1/Unity.app/Contents/MacOS/Unity -batchmode -quit -projectPath "$SOURCE_PATH" -executeMethod "BuildManager.BuildPlayer" -logFile "$BUILD_LOG_FILE" -buildEnvironment "$ENVIRONMENT" -buildPlatform "IOS" -buildPath "$TARGET_BUILD_DIR" -overrideVersion "$OVERRIDE_VERSION"
if [ ! -d "${TARGET_BUILD_DIR}/Unity-iPhone.xcodeproj" ]
then
echo "[ERR]Exporting unity project to Xcode failed."
exit 1
else
echo "Build successfull"
fi
#
#
# Consider initializing a param for the provisioning profile file path
#
#
# Consider initializing a param for the plist file path
cd $TARGET_BUILD_DIR
# archive generated xcode project
xcodebuild -scheme "Unity-iPhone" -archivePath "${DEPLOY_DIR_ROOT}/${BUILD_NAME}_${FILE_FORMAT_VERSION}/archive.xcarchive" -sdk iphoneos -configuration Release PROVISIONING_PROFILE="${PROVISIONING_PROFILE_PATH}" archive
if [ $? != 0 ]; then
echo "FAILED ARCHIVING XCODE PROJECT"
exit 1
fi
# export ipa from archive
xcodebuild -exportArchive -archivePath "${DEPLOY_DIR_ROOT}/${BUILD_NAME}_${FILE_FORMAT_VERSION}/archive.xcarchive" -exportOptionsPlist "${PLIST_PATH}" -exportPath "${DEPLOY_DIR_ROOT}/${BUILD_NAME}_${FILE_FORMAT_VERSION}"
if [ $? != 0 ]; then
echo "FAILED EXPORTING IPA FROM ARCHIVE"
exit 1
fi
#
# Section reserved for uploading the ipa to relevant remote storage
#
exit 0
So everything works like a charm (even the build can be installed on a device) when the shell script is ran locally from the terminal on the mac mini.
When it comes to run the shell script remotely through ssh it fails to codesign the archive. The user used over ssh is the same than the one used locally to run the script.
Here is the error :
CodeSign
/#######/Library/Developer/Xcode/DerivedData/Unity-iPhone-#########/Build/Intermediates.noindex/ArchiveIntermediates/Unity-iPhone/InstallationBuildProductsLocation/Applications/#########.app
(in target: Unity-iPhone) cd /#########/xcodeProjPath export
CODESIGN_ALLOCATE=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate
Signing Identity: "#########" Provisioning Profile: "iOS Team
Provisioning Profile: #########"
(#########)
/usr/bin/codesign --force --sign ######### --entitlements
/#########/Library/Developer/Xcode/DerivedData/Unity-iPhone-#########/Build/Intermediates.noindex/ArchiveIntermediates/Unity-iPhone/IntermediateBuildFilesPath/Unity-iPhone.build/Release-iphoneos/Unity-iPhone.build/#########.app.xcent
--timestamp=none /#########/Library/Developer/Xcode/DerivedData/Unity-iPhone-#########/Build/Intermediates.noindex/ArchiveIntermediates/Unity-iPhone/InstallationBuildProductsLocation/Applications/#########.app
/#########/Library/Developer/Xcode/DerivedData/Unity-iPhone-#########/Build/Intermediates.noindex/ArchiveIntermediates/Unity-iPhone/InstallationBuildProductsLocation/Applications/#########.app:
errSecInternalComponent
Command CodeSign failed with a nonzero exit code
** ARCHIVE FAILED **
I m kind of stuck right now since all my attempts didn't work at all ...
Thank you in advance for your help.
EDIT:
mac mini on macOS High Sierra Version 10.13.6 (17G65)
xcode Version 10.0 (10A255)
Ok so for all of you guys struggling around this tricky subject ( totally invisible if we don't know enough about macOS ) there is kind of security system that still makes the difference between a local user and a remote access to a user.
So the keychain handling your keys and certificates ( used by codesign to sign your build ) is not usable out of the box for the remote user. Its needs to be unlocked first !!!
To know about the available keychains on your system just type in on your terminal :
security list-keychains
You should see something like :
"/Users/'YOURUSER'/Library/Keychains/login.keychain-db"
"/Library/Keychains/System.keychain"
And you guessed it right there, you have to unlock the keychain of your user ! Juste run this :
security unlock-keychain -p 'USER_PASSWORD' 'PATH_TO_USER_KEYCHAIN'
And that's it.
N.B:
Please let me know if I understood something wrong about all this.

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...

Failed to generate release build of cordova ios app

I am using phonegap CLI 3.1 and XCode5. I am trying to generate the build for release mode through Phonegap CLI and Xcrun. I don't want to use Phonegap Build to upload the mobileprovision or whatever the process of them. I want to do it by xcrun to assign the mobileprovison to release build.
1) cordova build ios --release
Compiling app on platform "ios" via command
"/Applications/MAMP/htdocs/MyTest/MyTestApp/platforms/ios/cordova/build" --release
Platform "ios" compiled successfully.
2) sudo xcrun -sdk iphoneos PackageApplication -v "ios/build/emulator/MyTestApp.app" -o "/Users/mymac/Desktop/Testnew/MyTestApp.ipa" --sign "iPhone Distribution: NAME (TEAM_ID)" --embed "MyTestApp_Dis.mobileprovision"
Packaging application: 'ios/build/emulator/MyTestApp.app'
Arguments: embed=MyTestApp_Dis.mobileprovision verbose=1 output=/Users/mymac/Desktop/Testnew/MyTestApp.ipa sign=iPhone Distribution: NAME (TEAM_ID)
Environment variables:
HOME = /Users/mymac
SUDO_GID = 20
SDKROOT = /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS7.0.sdk
VERSIONER_PERL_PREFER_32_BIT = no
MAIL = /var/mail/root
SSH_AUTH_SOCK = /tmp/launch-zsBMC8/Listeners
LANG = en_US.UTF-8
USER = root
LOGNAME = root
__CF_USER_TEXT_ENCODING = 0x0:0:0
USERNAME = root
PATH = /usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/local/git/bin
SUDO_USER = mymac
SHELL = /bin/bash
TERM = xterm-256color
SUDO_COMMAND = /usr/bin/xcrun -sdk iphoneos PackageApplication -v ios/build/emulator/MyTestApp.app -o /Users/mymac/Desktop/Testnew/MyTestApp.ipa --sign iPhone Distribution: NAME (TEAM_ID) --embed MyTestApp_Dis.mobileprovision
SUDO_UID = 501
VERSIONER_PERL_VERSION = 5.12
Output directory: '/Users/mymac/Desktop/Testnew/MyTestApp.ipa'
Temporary Directory: '/tmp/W81FhZ9VAH' (will NOT be deleted on exit when verbose set)
+ /bin/cp -Rp ios/build/emulator/MyTestApp.app /tmp/W81FhZ9VAH/Payload
Program /bin/cp returned 0 : []
Checking original app
/usr/bin/codesign --verify -vvvv ios/build/emulator/MyTestApp.app
Program /usr/bin/codesign returned 1 : [ios/build/emulator/MyTestApp.app: code object is not signed at all
In architecture: i386
]
Codesign check fails : ios/build/emulator/MyTestApp.app: code object is not signed at all
In architecture: i386
Done checking the original app
Embedding 'MyTestApp_Dis.mobileprovision'
/bin/rm -rf /tmp/W81FhZ9VAH/Payload/MyTestApp.app/embedded.mobileprovision
Program /bin/rm returned 0 : []
/bin/cp -rp MyTestApp_Dis.mobileprovision /tmp/W81FhZ9VAH/Payload/MyTestApp.app/embedded.mobileprovision
Program /bin/cp returned 0 : []
/usr/bin/codesign -d --entitlements /tmp/W81FhZ9VAH/entitlements_rawixGWnKhi /tmp/W81FhZ9VAH/Payload/MyTestApp.app
Program /usr/bin/codesign returned 1 : [/tmp/W81FhZ9VAH/Payload/MyTestApp.app: code object is not signed at all
]
error: Failed to read entitlements from '/tmp/W81FhZ9VAH/Payload/MyTestApp.app'
Hmmh, I'm having a similar problem like Shashi.
When running 'cordova buld ios [--release]' from shell and then doing a 'xcrun ...' afterwards it works for me okay.
BUT: When running this sequence from within a script, I receive a "Codesign check fails ..." error too ...
If I insert (like) a "wait" cycle inside my script between the cordova and the xcrun call, it works.
So - to me - it seems, as if cordova returns to shell while it isn't completely finished (?)
Fact is if I code my script like
#!/bin/bash
cordova build ios --release
sleep 5
sh -c "xcrun ..."
it's working for me.
Question: Is it a bug in cordova/phonegap ???
So, finally I got everything to work okay ... :D
The problem of Jenkins complaining about a failed 'codesign ...' run is a MacOS (configuration) issue
The crucial thing is to allow the Jenkins access to the keychain of the system. The allowed access for the Login-shell of the Jenkins user is different from the Jenkins server process running under the Jenkins user account (!)
For now I realize this by running the unlock of the login.keychain within the Jenkins job before running my build script
like: in the Jenkins job for "execute shell"
security unlock-keychain -p password /Users/Shared/Jenkins/Library/Keychains/login.keychain
echo ##### building now ######################
./buildit.sh ios --release -v
This may not be the 100% nicest solution - but for now it works :P
See as well: [1]: Keychain won't unlock from Jenkins script unless user logged in
Meanwhile I found:
Fact is, that - when cordova exits and returns to shell - cordova related activities are NOT completed yet!
It takes a while after the cordova exit, for the 'platforms/ios/AppName/_CodeSignature/CodeResources' file to show up. This file obviously is essential for the 'codesign' which is started by xcrun command to succeed.
So I do in my script (which i call 'buildit.sh')
#!/bin/bash
[...]
cordova build ios --release
signaturefile="platforms/ios/build/device/$appname/_CodeSignature/CodeResources"
echo DEBUG:signatur file is $signaturefile
while [ ! -f $signaturefile ]
do
echo waiting
sleep 1
done
xcrun ...
Then the whole build/packaging process in one script succeeds.
However: Running the script from my ContinuousIntegration server Jenkins, I observe that this criteria may be essential, but not enough. From the CI I still get a
/usr/bin/codesign --verify -vvvv [...]
Program /usr/bin/codesign returned 1 : [...] code object is not signed at all
error!??
EDIT (05.12.2013): This is due to the fact that the Jenkins service couldn't access the keychain. E.g. doing in the Jenkins job an unlock of the keychain prior running the build script sorts it. (May not be the most elegant solution, but at least it prooves the problem not to be in the scripting :)
In order to skip the code signing you can perform a manual build from the console like this:
xcodebuild clean build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO
Use additionally the -configuration, -target and -sdk parameters in order to define your build settings.
To Disable Code Signing:
*Go to /Applications.
Right click on XCode and select 'Show Package Contents'.
Copy Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.1.sdk/SDKSettings.plist to your desktop. (Make sure to actually copy and paste. No drag and drop)
Open it and under DefaultProperties set CODE_SIGNING_REQUIRED to NO.
Copy it back and replace the original file.
Restart XCode.
Open your project.
In Project Navigator select your project and open Build Settings section of your porject (and not any particular target)
Under Code Signing find Code Signing Identity and for both Debug and Release modes set Any iOS SKD to Don't Code Sign.
Now you should be able to build your project without any errors.*
To make an IPA:
In 'Project Navigator' select Products
Right click on [NameOfYourProject].app and select 'Show in Finder'.
Create a folder and name it Payload
Move [NameOfYourProject].app to Payload.
Compress Payload and rename it to [NameOfYourProject].ipa

Resources