Xcode: increment build number per configuration

In your current Xcode project, you might have different configurations and certain configurations might have different product bundle identifier, say you have a product id for staging, another for production, and another for development.

When you push your build for TestFlight for instance you have to update the build number or the version, if you have another app for staging say com.myapp.com.staging and you use agvtool or fastlane action increment_build_number which uses agvtool , the new number will be set for the whole configurations and you will find that your staging or production builds in TestFlight have no clear convention.

we can use this script as a fastlane lane .

desc("increase the current_project_version config based on configuration")
    lane :increase_build do |option|
        
        fastlane_require 'Xcodeproj'
        
        project = "../#{urProjectName.xcodeproj}"
        target = <#targetName#>
        buildConfiguration = <#release_configuration_name#> 
        CUSTOM_BUILD_NUMBER = ''
        
        project = Xcodeproj::Project.open(project)
        project.targets.each do |mtarget|
            if mtarget.name == target
                mtarget.build_configurations.each do |mbuild|
                    if mbuild.name == buildConfiguration
                        CUSTOM_BUILD_NUMBER = mbuild.build_settings['CURRENT_PROJECT_VERSION']
                        mbuild.build_settings['CURRENT_PROJECT_VERSION'] = CUSTOM_BUILD_NUMBER.to_i + 1
                    end
                end
            end
        end
        project.save()
    end

to get the build number for configuration you may use almost the same script like this

lane :get_build_number_for_configuraiton do |option|
        fastlane_require 'Xcodeproj'
        
        project = "../#{urProjectName.xcodeproj}"
        target = <#targetName#>
        buildConfiguration = <#release_configuration_name#> 
        CUSTOM_BUILD_NUMBER = ''

        project = Xcodeproj::Project.open(project)
        project.targets.each do |mtarget|
            if mtarget.name == target
                mtarget.build_configurations.each do |mbuild|
                    if mbuild.name == buildConfiguration
                        CUSTOM_BUILD_NUMBER = mbuild.build_settings['CURRENT_PROJECT_VERSION']
                    end
                end
            end
        end
        CUSTOM_BUILD_NUMBER
    end

In the info.plist file you need to set the value $(CURRENT_PROJECT_VERSION) for the key Bundle version

Versioning system in build settings should be set to Apple generic.

And make sure in the General tap the build number has a value so Xcode does not complain.

it can use some refactoring , but that should do it.

Enjoy.

Xcode configuration Part 1

In a non-trivial application, you probably have more than just a single environment for your project and you would find it difficult to manage the many environments and configurations manually in the code.
In this post we will tackle this issue using Xcode configuration files, and we will see how we can switch between configurations without manually modifying our codebase.

Use Case

You have an app that has different environments namely local, staging, and production. Each environment requires different configuration; the endpoints are different, the product app is different, as you want to have different app for each environment where each app has different app icon and name for example. Another ability you want, is that on staging / local builds you want to be able to use some debugging tools like Inspector or capturing network calls but not on App Store version for instance.

Setting up

  1. Go to your beloved IDE Xcode :). Create a new project or open an existent project. Since in the end we won’t to test our release App(s) you should have a valid App id (creating app ids and profiles is handled automatically by Xcode these days), so you should have access to an apple account with sufficient permissions to create certificates…
  2. Create a folder called Configurations to make it easier to spot your configuration files, in the configurations folder lets create configuration files by clicking right click and selecting new file which will open a window to select the type of the file, in the search box type config and you will see a file with Configuration Settings File, select it and click next , now name it something like Staging-Debug, keep doing this and create Staging-Release, Production-Debug, and Production-Release. “You can have as many configurations as you wish , we will stick with 4 , for this tutorial”.
  3. We will be having 2 products with different App Ids , you can name them the way you wish based on your app. for me they will be com.whitetorch.Configuragtions (Appstore/production version) and com.whitetorch.Configuragtions.staging for staging environment.
  4. To achieve that without create a target for each one, we will have to play around the product bundle identifier. In the configuration files create a new setting with name PRODUCT_BUNDLE_ID = <#the App id for configuration#> , e.g. for Staging configuration files it will be PRODUCT_BUNDLE_ID=com.whitetorch.Configuragtions.staging and for production configuration files something like PRODUCT_BUNDLE_ID=com.whitetorch.Configuragtions.
  5. Now head to Targets -> choose app target -> build settings and find Product bundle identifier setting in the Packaging section , you will find the bundle id specified explicitly but since we want it be configurable , change it to $(PRODUCT_BUNDLE_ID).
  6. Go to the Project (the blue icon with the project name) -> Info -> Configurations section, duplicate Debug and name it Staging-Debug, also duplicate Release and name it Staging-Release, and leave the original for production configurations but you can also rename them.
  7. Select each configuration and select the matching configuration file name from the drop down list a long the project name.

if you go back to the target->build settings -> product bundle identifier , you will see the correct bundle id is listed for each configuration. Also if you go to Signing and Capabilities tab , you will see that Xcode spectated both bundle identifier meaning you can actually use different teams 😀 which can be useful say you building a product for a client , you can only have the production id on the client apple account, and other stuff on your development account.

Cool!! Lengthy steps!!! !^_^.

Let’s actually use that!

Configure the schemes:

Select the only Scheme we have setup by Xcode and click on edit scheme , that will be our production scheme so we will leave the configurations as is , but make sure that “Run” has Debug build configuration, and archive has Release build configuration.

Click on Manage Schemes and rename the selected scheme to be Production App , to make it easier to distinguish.

go back to edit Scheme window and click on “Duplicate Scheme” this time change the build configuration for Run to be “Staging-Debug” and for Archive to be “Staging-Release”. In Manage Schemes window make sure the Shared checkbox is selected for both Schemes.

Test it on simulator

Finally some demoing, select the “Production App” scheme and run it on your simulator or on a device.
Select the “Staging App” Scheme and run again on same place , you should be seeing two app icons with same name which means We are on the right track :).


Now we have a proof of concept for our strategy that we can build on it, so let’s do some more.

change the app name

in each configuration file create a setting and call APP_NAME. and set it , e.g. APP_NAME = Staging app.

go to info.plist and add a new key , just select any row and press Enter, and type Bundle display name, and in the value field type $(APP_NAME), this means set the displayed name on springboard/search/settings app to the APP_NAME setting we provided earlier.

Use that in code to change View Color/Theme

As you saw before to use the settings in the Xcode level we can wrap it with $(setting_name), but we can’t do that alas in our code. and to achieve that we need another way.

  • Let’s create a new property list file and name it configuration.plist, inside it we create two new keys with Dictionary type. namely Staging and Production , under each key create a new key with String type and name it colorName. Now we have Staging and Production dictionaries where each has one key named colorName, which will be used to determine the view color of your choice.
  • Head to info.plist and create a new Key and name it ‘Environment’ of type String and in the value field type $(Environment) , now head to the configurations files xcconfig files and add new setting, name it ‘Environment’ and set a value for it matching the name we used in the configuration.plist file, so for Stage-Debug and Staging-Release.xcconfig we add Environment=Staging and so on.
  • Let’s create a utility file to be able to parse the configurations from configurations.plist to avoid redundancy.
  • Now head to the view you want to change its color and type
let colorName: String = AppConfigurations().get(key: AppConfigurationsKey.colorName)         
let color = UIColor.init(named: colorName) self.view.backgroundColor = color

AppConfigurations is a utility file , you can find it in the repo here.

make sure you add the color name in Assets.xcassets file.

This is meant just as an example , you can have whatever you want , you can add and endpoint url for instance , or maybe Some SDK key, etc…


Cool!!! Now we have a tool that can let us achieve many things.

I will stop right here , it’s too long post already. maybe it will be followed with another post soon, as am planning.

You can find the project with the custom configurations here
https://github.com/MoathOthman/ConfigurationsExample


Useful links and references

build settings reference: https://help.apple.com/xcode/mac/10.2/#/itcaec37c2a6

Configuration Settings https://help.apple.com/xcode/mac/10.2/#/dev745c5c974

https://pewpewthespells.com/blog/xcconfig_guide.html

https://nshipster.com/xcconfig/

Deploying through bitbucket pipeline

bitbucket pipelines is a great feature and it can facilitate a lot of work, I won’t say its better than Jenkins or other CI tools, but I like how simple it is.

Here I will be talking about deploying a PHP api codebase into a server, it could be your production code , staging, etc…

The problem I faced is that I wanted to work locally on my php project without worrying about deploying the code into the public server, though you can use netbeans syncing feature, but that means every change you make will be deployed directly without testing, also it would be hard to avoid conflicts if you are working with other developers on the same codebase. Another way to solve it, is to use git, where you push your code and then go to the server and pull your code there, but as you see here , you need to go to the server and pull your code manually.

So the idea here is that whenever I push some code into the master branch or certain branch(s), I want my code to be deployed into the server as fast as possible, without shutting the service for long time.

Bitbucket pipeline can help us tremendously here , as we can trigger the pipeline script(s) on certain triggers (manual, schedules, on commit ), check https://confluence.atlassian.com/bitbucket/configure-bitbucket-pipelines-yml-792298910.html for more info.

after the pipeline is triggered, a certain script will be executed, and there where the work is done.

the pipeline file is a YAML file (https://en.wikipedia.org/wiki/YAML), which is really convenient to use, before we start scripting we need to enable pipeline for our project, so go to settings -> Pipelines -> settings -> click on enable pipeline switch. You will find a button saying configure bitbucket-pipelines.yml, click it.

you will see a page asking you to choose a language template , I will be using php here.

you will see an editor where you can edit the yaml file and on the side there is pipelines templates sections where you can find already made scripts you can use, like deploying to Amazon s3, firebase and so on.

For me I am going to deploy my project through ssh, you may use SFTP here and there is an already made pipeline called SFTP-deploy that you can find in the pipelines templets, but if you have a non trivial code base , it will be slow to move all the files on each commit/file change!!.

So am not going to use any pipeline and stick with the default one as starter. To transfer the data over ssh , am going to use Rsync tool, which is the one used by netbeans to sync your files with the remote server quickly since whenever you make some changes like adding new file or deleting one , rsync can sync only the updated files and does not copy the whole codebase all over again as if you used SFTP-deploy or FTP-deploy pipelines.

you can read more about rsync here “https://en.wikipedia.org/wiki/Rsync” and here https://linux.die.net/man/1/rsync

ok the pipeline code


--- 
image: "php:7.2.13"
pipelines: 
  branches:
    master:
      - step:
          name: "deployment"
          deployment: "staging" 
          caches: 
            - composer
          script:
            - echo "Starting Staging deplyment from master"
            - apt-get update
            - apt-get -qq install sshpass
            - "apt-get update && apt-get install -y unzip"
            - "curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer"
            - "composer install"
            - sshpass -p $FTP_PASSWORD rsync -are "ssh" --delete ./* [email protected]$HOST:$HOST_PATH --exclude runtime/

So in the pipeline configuration file here we ask to download php docker image and use it (it might need to match the one on your server), you see there is branches and under it staging, this means that when new code is updated on the branch named “Staging” run next steps , and we have 1 steps here , the first which will install dependencies and move the code from the bitbucket CI into our server. Note that none of these commands is actually executing on our server.

note that we install sshpass tool , since where moving a the files here using ssh with password , and rsync will prompt you to ask for a password in the terminal , but the script running on the CI is not interactive so we use it to overcome that hurdle.

you see that we use something like $FTP_PASSWORD, these are called repository variables and you can set them from settings->pipelines->repository variables and the cool thing about it is that you can have secured variables for something like password, so you don’t have to put the password in a plain text in the repository.

--delete : is used to be able to delete files when deleted by the source , and --exclude runtime/ means we don’t want to sync this directory since its would be a place for things like logs, and cache and we don’t want to tamper with the web service logs.

so that’s basically it.

hope its been useful for you, took me days to figure it out !^_^.

disclaimer: this is tested on small/medium size monolithic projects only and not on production, so do your testing/research and be careful. I am just sharing my experience, but please let me know if you have suggestions or rectifications.